From 9c1eb7f14809de70850c5b05563a92cd5bf50dfe Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Wed, 22 May 2024 15:40:18 +0200 Subject: [PATCH 001/108] initial commit --- .../iosb/ilt/faaast/service/Service.java | 53 ++++++++++++++----- .../faaast/service/config/ServiceConfig.java | 18 ++++++- .../SubmodelTemplateProcessor.java | 46 ++++++++++++++++ .../SubmodelTemplateProcessorConfig.java | 27 ++++++++++ .../model/api/modifier/QueryModifier.java | 4 ++ 5 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index b2f7a1af1..c516dd4cf 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -40,6 +40,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.persistence.SubmodelSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.request.RequestHandlerManager; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessor; import de.fraunhofer.iosb.ilt.faaast.service.typing.TypeExtractor; import de.fraunhofer.iosb.ilt.faaast.service.typing.TypeInfo; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; @@ -52,6 +53,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Operation; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; import org.slf4j.Logger; @@ -59,8 +61,7 @@ /** - * Central class of the FA³ST Service accumulating and connecting all different - * components. + * Central class of the FA³ST Service accumulating and connecting all different components. */ public class Service implements ServiceContext { @@ -72,6 +73,7 @@ public class Service implements ServiceContext { private Persistence persistence; private FileStorage fileStorage; private RequestHandlerManager requestHandler; + private List submodelTemplateProcessors; /** * Creates a new instance of {@link Service}. @@ -82,21 +84,21 @@ public class Service implements ServiceContext { * @param messageBus message bus implementation * @param endpoints endpoints * @param assetConnections asset connections + * @param submodelTemplateProcessors submodel template processor to use * @throws IllegalArgumentException if coreConfig is null * @throws IllegalArgumentException if persistence is null * @throws IllegalArgumentException if messageBus is null * @throws RuntimeException if creating a deep copy of aasEnvironment fails - * @throws ConfigurationException the configuration the - * {@link AssetConnectionManager} fails - * @throws AssetConnectionException when initializing asset connections - * fails + * @throws ConfigurationException the configuration the {@link AssetConnectionManager} fails + * @throws AssetConnectionException when initializing asset connections fails */ public Service(CoreConfig coreConfig, Persistence persistence, FileStorage fileStorage, MessageBus messageBus, List endpoints, - List assetConnections) throws ConfigurationException, AssetConnectionException { + List assetConnections, + List submodelTemplateProcessors) throws ConfigurationException, AssetConnectionException { Ensure.requireNonNull(coreConfig, "coreConfig must be non-null"); Ensure.requireNonNull(persistence, "persistence must be non-null"); Ensure.requireNonNull(messageBus, "messageBus must be non-null"); @@ -107,12 +109,14 @@ public Service(CoreConfig coreConfig, else { this.endpoints = endpoints; } + this.submodelTemplateProcessors = submodelTemplateProcessors; this.config = ServiceConfig.builder() .core(coreConfig) .build(); this.persistence = persistence; this.fileStorage = fileStorage; this.messageBus = messageBus; + initSubmodelTemplateProcessors(); this.assetConnectionManager = new AssetConnectionManager(config.getCore(), assetConnections, this); this.requestHandler = new RequestHandlerManager(new RequestExecutionContext( coreConfig, @@ -129,8 +133,7 @@ public Service(CoreConfig coreConfig, * @param config service configuration * @throws IllegalArgumentException if config is null * @throws ConfigurationException if invalid configuration is provided - * @throws AssetConnectionException when initializing asset connections - * fails + * @throws AssetConnectionException when initializing asset connections fails */ public Service(ServiceConfig config) throws ConfigurationException, AssetConnectionException { @@ -211,8 +214,7 @@ public Environment getAASEnvironment() { * Executes a request asynchroniously. * * @param request request to execute - * @param callback callback handler that is called when execution if - * finished + * @param callback callback handler that is called when execution if finished * @throws IllegalArgumentException if request is null * @throws IllegalArgumentException if callback is null */ @@ -257,8 +259,7 @@ public void start() throws MessageBusException, EndpointException { /** - * Stop the service. This includes stopping the message bus and all - * endpoints. + * Stop the service. This includes stopping the message bus and all endpoints. */ public void stop() { LOGGER.debug("Get command for stopping FA³ST Service"); @@ -275,6 +276,7 @@ private void init() throws ConfigurationException { fileStorage = (FileStorage) config.getFileStorage().newInstance(config.getCore(), this); Ensure.requireNonNull(config.getMessageBus(), new InvalidConfigurationException("config.messagebus must be non-null")); messageBus = (MessageBus) config.getMessageBus().newInstance(config.getCore(), this); + initSubmodelTemplateProcessors(); if (config.getAssetConnections() != null) { List assetConnections = new ArrayList<>(); for (AssetConnectionConfig assetConnectionConfig: config.getAssetConnections()) { @@ -299,4 +301,29 @@ private void init() throws ConfigurationException { this.messageBus, this.assetConnectionManager)); } + + + private void initSubmodelTemplateProcessors() throws ConfigurationException { + if (submodelTemplateProcessors == null) { + submodelTemplateProcessors = new ArrayList<>(); + } + if (config.getSubmodelTemplateProcessors() != null) { + for (var submodelTemplateProcessorConfig: config.getSubmodelTemplateProcessors()) { + SubmodelTemplateProcessor submodelTemplateProcessor = (SubmodelTemplateProcessor) submodelTemplateProcessorConfig.newInstance(config.getCore(), this); + submodelTemplateProcessor.init(config.getCore(), submodelTemplateProcessorConfig, this); + submodelTemplateProcessors.add(submodelTemplateProcessor); + } + } + if (submodelTemplateProcessors.isEmpty()) { + return; + } + List submodels = persistence.getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent(); + for (var submodel: submodels) { + for (var submodelTemplateProcessor: submodelTemplateProcessors) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.process(submodel, assetConnectionManager)) { + persistence.save(submodel); + } + } + } + } } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java index 3fe6f41dd..cd15ac5e6 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java @@ -23,6 +23,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.filestorage.FileStorageConfig; import de.fraunhofer.iosb.ilt.faaast.service.messagebus.MessageBusConfig; import de.fraunhofer.iosb.ilt.faaast.service.persistence.PersistenceConfig; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessorConfig; import de.fraunhofer.iosb.ilt.faaast.service.util.ImplementationManager; import java.io.File; import java.io.IOException; @@ -42,12 +43,14 @@ public class ServiceConfig { private List endpoints; private FileStorageConfig fileStorage; private MessageBusConfig messageBus; + private List submodelTemplateProcessors; private PersistenceConfig persistence; public ServiceConfig() { this.assetConnections = new ArrayList<>(); this.endpoints = new ArrayList<>(); + this.submodelTemplateProcessors = new ArrayList<>(); } @@ -101,6 +104,16 @@ public void setMessageBus(MessageBusConfig messageBus) { } + public List getSubmodelTemplateProcessors() { + return submodelTemplateProcessors; + } + + + public void setSubmodelTemplateProcessors(List submodelTemplateProcessors) { + this.submodelTemplateProcessors = submodelTemplateProcessors; + } + + public PersistenceConfig getPersistence() { return persistence; } @@ -127,13 +140,14 @@ public boolean equals(Object obj) { && Objects.equals(this.assetConnections, other.assetConnections) && Objects.equals(this.endpoints, other.endpoints) && Objects.equals(this.persistence, other.persistence) - && Objects.equals(this.fileStorage, other.fileStorage); + && Objects.equals(this.fileStorage, other.fileStorage) + && Objects.equals(this.submodelTemplateProcessors, other.submodelTemplateProcessors); } @Override public int hashCode() { - return Objects.hash(core, assetConnections, endpoints, persistence, fileStorage); + return Objects.hash(core, assetConnections, endpoints, persistence, fileStorage, submodelTemplateProcessors); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java new file mode 100644 index 000000000..813622703 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java @@ -0,0 +1,46 @@ +/* + * 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.ilt.faaast.service.submodeltemplate; + +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; +import de.fraunhofer.iosb.ilt.faaast.service.config.Configurable; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; + + +/** + * Interface for template processors. + * + * @param type of the matching configuration + */ +public interface SubmodelTemplateProcessor extends Configurable { + + /** + * Checks if given submodel uses the template. + * + * @param submodel the submodel to check + * @return true if uses the template, otherwise false + */ + public boolean accept(Submodel submodel); + + + /** + * Processes a given submodel, e.g.by adding elements or modify the configuration. + * + * @param submodel the submodel to check + * @param assetConnectionManager manager for asset connection, can be used to modify underlying asset connection + * @return true if submodel has been modified, false otherwise + */ + public boolean process(Submodel submodel, AssetConnectionManager assetConnectionManager); +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java new file mode 100644 index 000000000..a964e8873 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java @@ -0,0 +1,27 @@ +/* + * 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.ilt.faaast.service.submodeltemplate; + +import de.fraunhofer.iosb.ilt.faaast.service.config.Config; + + +/** + * Base class for configurations of submodel template processors. + * + * @param type of the matching processor + */ +public class SubmodelTemplateProcessorConfig extends Config { + +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java index 724997136..504b00329 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java @@ -28,6 +28,10 @@ public class QueryModifier { .level(Level.CORE) .extend(Extent.WITHOUT_BLOB_VALUE) .build(); + public static final QueryModifier MAXIMAL = new Builder() + .level(Level.DEEP) + .extend(Extent.WITH_BLOB_VALUE) + .build(); protected Level level; protected Extent extent; From d60eaaf698aa887b04fe6f13218559ce7f7236eb Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Thu, 23 May 2024 08:05:27 +0200 Subject: [PATCH 002/108] fix tests --- .../assetconnection/lambda/LambdaAssetConnectionTest.java | 2 +- .../endpoint/http/HttpEndpointSSLAutoGeneratedCertificate.java | 2 +- .../ilt/faaast/service/endpoint/http/HttpEndpointSSLTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java index 6fa516baa..af7cce8d6 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java @@ -75,7 +75,7 @@ public void init() throws ConfigurationInitializationException, ConfigurationExc persistence = mock(Persistence.class); FileStorage fileStorage = mock(FileStorage.class); MessageBus messageBus = mock(MessageBus.class); - service = new Service(CoreConfig.DEFAULT, persistence, fileStorage, messageBus, List.of(), List.of()); + service = new Service(CoreConfig.DEFAULT, persistence, fileStorage, messageBus, List.of(), List.of(), List.of()); } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLAutoGeneratedCertificate.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLAutoGeneratedCertificate.java index c1c25fb64..fc961d40a 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLAutoGeneratedCertificate.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLAutoGeneratedCertificate.java @@ -52,7 +52,7 @@ private static void startServer() throws Exception { scheme = HttpScheme.HTTPS.toString(); endpoint = new HttpEndpoint(); server = new Server(); - service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of())); + service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of(), List.of())); endpoint.init( CoreConfig.DEFAULT, HttpEndpointConfig.builder() diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLTest.java index edccadedc..5346909cb 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointSSLTest.java @@ -81,7 +81,7 @@ private static void startServer() throws Exception { scheme = HttpScheme.HTTPS.toString(); endpoint = new HttpEndpoint(); server = new Server(); - service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of())); + service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of(), List.of())); endpoint.init( CoreConfig.DEFAULT, HttpEndpointConfig.builder() From edfff11f2b8dc517a08b5cc1749333c10b20e5e8 Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Thu, 27 Jun 2024 13:11:50 +0200 Subject: [PATCH 003/108] initialize assetConnectionManager before calling submodel template processors --- .../java/de/fraunhofer/iosb/ilt/faaast/service/Service.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index c516dd4cf..497611c06 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -116,8 +116,8 @@ public Service(CoreConfig coreConfig, this.persistence = persistence; this.fileStorage = fileStorage; this.messageBus = messageBus; - initSubmodelTemplateProcessors(); this.assetConnectionManager = new AssetConnectionManager(config.getCore(), assetConnections, this); + initSubmodelTemplateProcessors(); this.requestHandler = new RequestHandlerManager(new RequestExecutionContext( coreConfig, persistence, @@ -276,7 +276,6 @@ private void init() throws ConfigurationException { fileStorage = (FileStorage) config.getFileStorage().newInstance(config.getCore(), this); Ensure.requireNonNull(config.getMessageBus(), new InvalidConfigurationException("config.messagebus must be non-null")); messageBus = (MessageBus) config.getMessageBus().newInstance(config.getCore(), this); - initSubmodelTemplateProcessors(); if (config.getAssetConnections() != null) { List assetConnections = new ArrayList<>(); for (AssetConnectionConfig assetConnectionConfig: config.getAssetConnections()) { @@ -284,6 +283,7 @@ private void init() throws ConfigurationException { } assetConnectionManager = new AssetConnectionManager(config.getCore(), assetConnections, this); } + initSubmodelTemplateProcessors(); endpoints = new ArrayList<>(); if (config.getEndpoints() == null || config.getEndpoints().isEmpty()) { LOGGER.warn("no endpoint configuration found, starting service without endpoint which means the service will not be accessible via any kind of API"); From f113ac0e08bc9d957ab4c70cdf88b55d2b6cf002 Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Thu, 27 Jun 2024 16:58:24 +0200 Subject: [PATCH 004/108] refactor idShortPath and semanticIdPath resolving --- .../faaast/service/model/SemanticIdPath.java | 287 ++++++++++++++++++ .../service/util/EnvironmentHelper.java | 220 +------------- .../faaast/service/util/ReferenceHelper.java | 14 + .../service/model/SemanticIdPathTest.java | 208 +++++++++++++ .../service/util/EnvironmentHelperTest.java | 91 ------ 5 files changed, 523 insertions(+), 297 deletions(-) create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java create mode 100644 model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java new file mode 100644 index 000000000..0b653bdaa --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java @@ -0,0 +1,287 @@ +/* + * 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.ilt.faaast.service.model; + +import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementVisitor; +import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementSubtypeResolvingVisitor; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.model.HasSemantics; +import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; + + +/** + * Reprensts a semanticIdPath path addressing one or more SubmodelElement within a submodel. + */ +public class SemanticIdPath { + + List elements; + + public SemanticIdPath() { + this.elements = new ArrayList<>(); + } + + + public List getElements() { + return List.copyOf(elements); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SemanticIdPath other = (SemanticIdPath) o; + if (elements.size() != other.elements.size()) { + return false; + } + for (int i = 0; i < elements.size(); i++) { + if (!ReferenceHelper.equals(elements.get(i), other.elements.get(i))) { + return false; + } + } + return true; + } + + + @Override + public int hashCode() { + return Objects.hash(elements); + } + + + @Override + public String toString() { + return elements.stream() + .map(ReferenceHelper::asString) + .collect(Collectors.joining(" --> ")); + } + + + /** + * Creates a new idShortPath without the first/parent segment. + * + * @return idShortPath without the first/parent segment + */ + public SemanticIdPath withoutParent() { + SemanticIdPath result = new SemanticIdPath(); + result.elements = Objects.isNull(elements) || elements.size() <= 1 + ? List.of() + : elements.subList(1, elements.size()); + return result; + } + + + /** + * Resolves a semanticIdPath. + * + * @param type of the root element + * @param root the root to resolve the path in + * @return a list of references to elements that match the semanticIdPath + */ + public List resolve(T root) { + Ensure.requireNonNull(root, "root must be non-null"); + return resolveRecursive(root, this, null); + } + + + /** + * Uniquely resolves a semanticIdPath inside a given root element. + * + * @param the type of the root + * @param root the root to resolve the path in + * @return the reference to element that matches the semanticIdPath + * @throws IllegalArgumentException if the semanticIdPath resolves to more than one element + */ + public Reference resolveUnique(T root) { + Ensure.requireNonNull(root, "root must be non-null"); + List result = resolveRecursive(root, this, null); + if (result.size() > 1) { + throw new IllegalArgumentException("semanticIdPath did resolve to more than one element"); + } + return result.get(0); + } + + + /** + * Uniquely resolves a semanticIdPath inside a given root element. + * + * @param the type of the root + * @param root the root to resolve the path in + * @param type the expected effective type of the reference + * @return the reference to element that matches the semanticIdPath + * @throws IllegalArgumentException if the semanticIdPath resolves to more than one element + */ + public Reference resolveUnique(T root, KeyTypes type) { + Ensure.requireNonNull(type, "type must be non-null"); + Reference result = resolveUnique(root); + KeyTypes actualType = ReferenceHelper.getEffectiveKeyType(result); + if (!Objects.equals(type, actualType)) { + throw new IllegalArgumentException(String.format( + "semanticIdPath does not resolve to correct type of element (expected: %s, actual: %s)", + type, + actualType)); + } + return result; + } + + + private static List resolveRecursive(T current, SemanticIdPath remainingPath, Reference parentRef) { + Ensure.requireNonNull(current, "current must be non-null"); + Ensure.requireNonNull(remainingPath, "path must be non-null"); + Reference currentRef = ReferenceHelper.combine( + parentRef, + new ReferenceBuilder() + .element( + Identifiable.class.isInstance(current) + ? ((Identifiable) current).getId() + : current.getIdShort(), + ReferenceHelper.toKeyType(current.getClass())) + .build()); + if (remainingPath.isEmpty()) { + return List.of(currentRef); + } + if (SubmodelElementList.class.isInstance(current)) { + List children = ((SubmodelElementList) current).getValue(); + List result = new ArrayList<>(); + for (int i = 0; i < children.size(); i++) { + if (ReferenceHelper.equals(children.get(i).getSemanticId(), remainingPath.getElements().get(0))) { + final String newChildId = Integer.toString(i); + List childRefs = resolveRecursive(children.get(i), remainingPath.withoutParent(), currentRef); + result.addAll(childRefs.stream() + .map(x -> { + x.getKeys().get(currentRef.getKeys().size()).setValue(newChildId); + return x; + }) + .toList()); + } + } + return result; + } + return getChildren(current).stream() + .filter(x -> ReferenceHelper.equals(x.getSemanticId(), remainingPath.getElements().get(0))) + .flatMap(x -> resolveRecursive(x, remainingPath.withoutParent(), currentRef).stream()) + .collect(Collectors.toList()); + } + + + private static List getChildren(T parent) { + final List children = new ArrayList<>(); + AssetAdministrationShellElementVisitor visitor = new DefaultAssetAdministrationShellElementSubtypeResolvingVisitor() { + @Override + public void visit(Submodel submodel) { + add(submodel.getSubmodelElements()); + } + + + @Override + public void visit(SubmodelElementCollection submodelElementCollection) { + add(submodelElementCollection.getValue()); + } + + + @Override + public void visit(SubmodelElementList submodelElementList) { + add(submodelElementList.getValue()); + } + + + private void add(List elements) { + elements.forEach(x -> children.add((T) x)); + } + }; + visitor.visit((Referable) parent); + return children; + } + + + /** + * Checks if this path is empty, i.e. does not contain any elements. + * + * @return true if empty, otherwise false + */ + public boolean isEmpty() { + return Objects.isNull(elements) || elements.isEmpty(); + } + + + public static Builder builder() { + return new Builder(); + } + + public abstract static class AbstractBuilder> extends ExtendableBuilder { + + public B from(SemanticIdPath value) { + getBuildingInstance().elements = new ArrayList<>(value.elements); + return getSelf(); + } + + + public B semanticId(Reference value) { + getBuildingInstance().elements.add(value); + return getSelf(); + } + + + public B globalReference(String value) { + getBuildingInstance().elements.add(ReferenceBuilder.global(value)); + return getSelf(); + } + + + public B semanticIds(Reference... value) { + getBuildingInstance().elements.addAll(Arrays.asList(value)); + return getSelf(); + } + + + public B semanticIds(List value) { + getBuildingInstance().elements.addAll(value); + return getSelf(); + } + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected SemanticIdPath newBuildingInstance() { + return new SemanticIdPath(); + } + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java index 42951c36b..cc669a838 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java @@ -14,26 +14,15 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.util; -import de.fraunhofer.iosb.ilt.faaast.service.model.IdShortPath; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.AmbiguousElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementVisitor; -import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementSubtypeResolvingVisitor; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.ReferenceCollector; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.BiFunction; import java.util.stream.Collectors; -import org.eclipse.digitaltwin.aas4j.v3.model.Environment; -import org.eclipse.digitaltwin.aas4j.v3.model.HasSemantics; import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; /** @@ -53,20 +42,20 @@ private EnvironmentHelper() {} * * @param return type of the element * @param reference the reference to resolve - * @param environment the environment to resolve the reference in + * @param root the root element to resolve the reference in, e.g., Environment, Submodel, SubmodelElementCollection * @param returnType return type of the element * @return the resolved element * @throws IllegalArgumentException if reference is null or empty * @throws IllegalArgumentException if environment is null * @throws IllegalArgumentException if resolved element does not match the return type - * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist in environment + * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist */ - public static T resolve(Reference reference, Environment environment, Class returnType) throws ResourceNotFoundException { + public static T resolve(Reference reference, Object root, Class returnType) throws ResourceNotFoundException { Ensure.requireNonNull(reference, "reference must be non-null"); Ensure.require(!reference.getKeys().isEmpty(), "reference must contain at least one key"); - Ensure.requireNonNull(environment, "environment must be non-null"); + Ensure.requireNonNull(root, "root must be non-null"); Ensure.requireNonNull(returnType, "type must be non-null"); - Referable result = ReferenceCollector.collect(environment).entrySet().stream() + Referable result = ReferenceCollector.collect(root).entrySet().stream() .filter(x -> ReferenceHelper.equals(reference, x.getKey())) .map(Map.Entry::getValue) .findFirst() @@ -87,21 +76,21 @@ public static T resolve(Reference reference, Environment e * {@link org.eclipse.digitaltwin.aas4j.v3.model.Environment}. * * @param reference the reference to resolve - * @param environment the eenvironment to resolve the reference in + * @param root the root element to resolve the reference in, e.g., Environment, Submodel, SubmodelElementCollection * @return the resolved element * @throws IllegalArgumentException if reference is null or empty * @throws IllegalArgumentException if environment is null * @throws IllegalArgumentException if returnType is null - * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist in environment + * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist */ - public static Referable resolve(Reference reference, Environment environment) throws ResourceNotFoundException { - return resolve(reference, environment, Referable.class); + public static Referable resolve(Reference reference, Object root) throws ResourceNotFoundException { + return resolve(reference, root, Referable.class); } /** * Generates a {@link org.eclipse.digitaltwin.aas4j.v3.model.Reference} for a - * {@link org.eclipse.digitaltwin.aas4j.v3.model.Referable} given an + * {{@link org.eclipse.digitaltwin.aas4j.v3.model.Referable} given an * {@link org.eclipse.digitaltwin.aas4j.v3.model.Environment} as context. * *

This method does not work in all cases as it tries to find the referable in the environment by equality. However, @@ -109,17 +98,17 @@ public static Referable resolve(Reference reference, Environment environment) th * parents. * * @param referable the referable to generate the reference for - * @param environment the environment containing the referable + * @param root the root element containing the referable * @return a reference pointing to the referable * @throws de.fraunhofer.iosb.ilt.faaast.service.model.exception.AmbiguousElementException if there are multiple * matching elements in the environment * @throws IllegalArgumentException if referable or environment is null * @throws IllegalArgumentException if referable is not present in environment */ - public static Reference asReference(Referable referable, Environment environment) throws AmbiguousElementException { + public static Reference asReference(Referable referable, Object root) throws AmbiguousElementException { Ensure.requireNonNull(referable, "referable must be non-null"); - Ensure.requireNonNull(environment, "environment must be non-null"); - List result = ReferenceCollector.collect(environment).entrySet().stream() + Ensure.requireNonNull(root, "root must be non-null"); + List result = ReferenceCollector.collect(root).entrySet().stream() .filter(x -> Objects.equals(referable, x.getValue())) .map(Map.Entry::getKey) .collect(Collectors.toList()); @@ -133,185 +122,4 @@ public static Reference asReference(Referable referable, Environment environment } return result.get(0); } - - - /** - * Resolves an idShortPath within a given submodel. - * - * @param expected return type - * @param idShortPath the idShortPath to resolve - * @param submodel the submodle to resolve the idShortPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(IdShortPath idShortPath, Submodel submodel, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(idShortPath, "idShortPath must be non-null"); - Ensure.requireNonNull(submodel, "submodel must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - List result = submodel.getSubmodelElements().stream() - .flatMap(x -> resolvePathRecursive(idShortPath.getElements(), x, (id, element) -> Objects.equals(id, element.getIdShort())).stream()) - .collect(Collectors.toList()); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve idShortPath on submodel (idShortPath: %s, submodel id: %s)", - idShortPath, - submodel.getId())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("idShortPath did resolve to more than one element"); - } - return type.cast(result.get(0)); - } - - - /** - * Resolves an idShortPath within a given submodel element. - * - * @param expected return type - * @param idShortPath the idShortPath to resolve - * @param element the submodle element to resolve the idShortPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(IdShortPath idShortPath, SubmodelElement element, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(idShortPath, "idShortPath must be non-null"); - Ensure.requireNonNull(element, "root must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - - List result = resolvePathRecursive(idShortPath.getElements(), element, (id, sme) -> Objects.equals(id, sme.getIdShort())); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve idShortPath on submodel element (idShortPath: %s, submodel element idShort: %s)", - idShortPath, - element.getIdShort())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("idShortPath did resolve to more than one element"); - } - return type.cast(result.get(0)); - } - - - /** - * Resolves a semanticIdPath within a given submodel. - * - * @param expected return type - * @param semanticIdPath the semanticIdPath to resolve - * @param submodel the submodle to resolve the semanticIdPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static List resolvePath(List semanticIdPath, Submodel submodel, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(semanticIdPath, "semanticIdPath must be non-null"); - Ensure.requireNonNull(submodel, "submodel must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - return submodel.getSubmodelElements().stream() - .flatMap(x -> resolvePathRecursive(semanticIdPath, x, - (reference, element) -> ReferenceHelper.equals(reference, element.getSemanticId())).stream()) - .map(type::cast) - .collect(Collectors.toList()); - } - - - /** - * Resolves a semanticIdPath within a given submodel. - * - * @param expected return type - * @param semanticIdPath the semanticIdPath to resolve - * @param submodel the submodle to resolve the semanticIdPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(List semanticIdPath, Submodel submodel, Class type) throws ResourceNotFoundException { - List result = resolvePath(semanticIdPath, submodel, type); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve semanticIdPath on submodel (semanticIdPath: %s, submodel id: %s)", - semanticIdPath.stream().map(ReferenceHelper::asString).collect(Collectors.joining(" -> ")), - submodel.getId())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("semanticIdPath did resolve to more than one element"); - } - return result.get(0); - } - - - /** - * Resolves an idShortPath within a given submodel element. - * - * @param expected return type - * @param semanticIdPath the semanticIdPath to resolve - * @param submodelElement the submodel element to resolve the semanticIdPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(List semanticIdPath, SubmodelElement submodelElement, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(semanticIdPath, "semanticIdPath must be non-null"); - Ensure.requireNonNull(submodelElement, "submodelElement must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - - List result = resolvePathRecursive(semanticIdPath, submodelElement, - (reference, element) -> ReferenceHelper.equals(reference, element.getSemanticId())); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve semanticIdPath on submodelElement (semanticIdPath: %s, submodelElement id: %s)", - semanticIdPath.stream().map(ReferenceHelper::asString).collect(Collectors.joining(" -> ")), - submodelElement.getIdShort())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("semanticIdPath did resolve to more than one element"); - } - return type.cast(result.get(0)); - } - - - private static List resolvePathRecursive(List path, U element, BiFunction equalsTester) { - List remainingPath = path.size() > 1 ? path.subList(1, path.size()) : List.of(); - if (path.isEmpty() || !equalsTester.apply(path.get(0), element)) { - return List.of(); - } - if (remainingPath.isEmpty()) { - return List.of(element); - } - return getChildren(element).stream() - .flatMap(x -> resolvePathRecursive(remainingPath, x, equalsTester).stream()) - .collect(Collectors.toList()); - } - - - private static List getChildren(T parent) { - final List children = new ArrayList<>(); - AssetAdministrationShellElementVisitor visitor = new DefaultAssetAdministrationShellElementSubtypeResolvingVisitor() { - @Override - public void visit(Submodel submodel) { - add(submodel.getSubmodelElements()); - } - - - @Override - public void visit(SubmodelElementCollection submodelElementCollection) { - add(submodelElementCollection.getValue()); - } - - - @Override - public void visit(SubmodelElementList submodelElementList) { - add(submodelElementList.getValue()); - } - - - private void add(List elements) { - elements.forEach(x -> children.add((T) x)); - } - }; - visitor.visit((Referable) parent); - return children; - } } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java index a50dd630e..20357c66e 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java @@ -593,6 +593,20 @@ public static Entry getEntryBySameReference(Map } + /** + * Gets the effective key type of a reference, i.e. the type of the final key. + * + * @param reference the reference + * @return the effective key type of the reference + * @throws IllegalArgumentException is reference is null or does not contain any key + */ + public static KeyTypes getEffectiveKeyType(Reference reference) { + Ensure.requireNonNull(reference, "reference must be non-null"); + Ensure.require(!reference.getKeys().isEmpty(), "reference must contain at least one key"); + return reference.getKeys().get(reference.getKeys().size() - 1).getType(); + } + + /** * Gets all references of the provided lists that are semantically * equivalent to {@code reference}. diff --git a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java new file mode 100644 index 000000000..a930dbfe3 --- /dev/null +++ b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java @@ -0,0 +1,208 @@ +/* + * 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.ilt.faaast.service.model; + +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; +import org.junit.Assert; +import org.junit.Test; + + +/** + * + * @author jab + */ +public class SemanticIdPathTest { + + @Test + public void resolveSematicIdPathInSubmodelUnique() throws ResourceNotFoundException { + final String idShortCollection1 = "idShortCollection1"; + final String idShortCollection2 = "idShortCollection2"; + final String idShortProperty1 = "idShortProperty1"; + Reference semanticIdSubmodel = ReferenceBuilder.global("submodel"); + Reference semanticIdCollection = ReferenceBuilder.global("collection"); + Reference semanticIdProperty1 = ReferenceBuilder.global("property1"); + + Submodel submodel = new DefaultSubmodel.Builder() + .id("submodel-id") + .semanticId(semanticIdSubmodel) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection1) + .semanticId(semanticIdCollection) + .build()) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection2) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty1) + .semanticId(semanticIdProperty1) + .build()) + .build()) + .build(); + + Reference expected = new ReferenceBuilder() + .submodel(submodel) + .element(idShortCollection2, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty1, KeyTypes.PROPERTY) + .build(); + Reference actual = SemanticIdPath.builder() + .semanticId(semanticIdCollection) + .semanticId(semanticIdProperty1) + .build() + .resolveUnique(submodel); + Assert.assertTrue(ReferenceHelper.equals(expected, actual)); + } + + + @Test + public void resolveSematicIdPathInCollection() throws ResourceNotFoundException { + final String idShortCollection = "idShortCollection"; + final String idShortProperty = "idShortProperty"; + Reference semanticIdCollection = ReferenceBuilder.global("collection"); + Reference semanticIdProperty = ReferenceBuilder.global("property"); + + SubmodelElementCollection submodel = new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty) + .semanticId(semanticIdProperty) + .build()) + .build(); + + Reference expected = new ReferenceBuilder() + .element(idShortCollection, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty, KeyTypes.PROPERTY) + .build(); + Reference actual = SemanticIdPath.builder() + .semanticId(semanticIdProperty) + .build() + .resolveUnique(submodel); + Assert.assertTrue(ReferenceHelper.equals(expected, actual)); + } + + + @Test + public void resolveSematicIdPathInList() throws ResourceNotFoundException { + final String idShortList1 = "idShortList1"; + final String idShortList2 = "idShortList2"; + final String idShortProperty1 = "idShortProperty1"; + final String idShortProperty2 = "idShortProperty2"; + Reference semanticIdList1 = ReferenceBuilder.global("list1"); + Reference semanticIdList2 = ReferenceBuilder.global("list2"); + Reference semanticIdProperty1 = ReferenceBuilder.global("property1"); + Reference semanticIdProperty2 = ReferenceBuilder.global("property2"); + + SubmodelElementList submodel = new DefaultSubmodelElementList.Builder() + .idShort(idShortList1) + .semanticId(semanticIdList1) + .value(new DefaultProperty.Builder() + .idShort("dummy1") + .build()) + .value(new DefaultProperty.Builder() + .idShort("dummy2") + .build()) + .value(new DefaultSubmodelElementList.Builder() + .idShort(idShortList2) + .semanticId(semanticIdList2) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty1) + .semanticId(semanticIdProperty1) + .build()) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty2) + .semanticId(semanticIdProperty2) + .build()) + .build()) + .build(); + + Reference expected = new ReferenceBuilder() + .element(idShortList1, KeyTypes.SUBMODEL_ELEMENT_LIST) + .element("2", KeyTypes.SUBMODEL_ELEMENT_LIST) + .element("1", KeyTypes.PROPERTY) + .build(); + Reference actual = SemanticIdPath.builder() + .semanticId(semanticIdList2) + .semanticId(semanticIdProperty2) + .build() + .resolveUnique(submodel); + Assert.assertTrue(ReferenceHelper.equals(expected, actual)); + } + + + @Test + public void resolveSematicIdPathInSubmodelNonUnique() throws ResourceNotFoundException { + final String idShortCollection1 = "idShortCollection1"; + final String idShortCollection2 = "idShortCollection2"; + final String idShortProperty1 = "idShortProperty1"; + final String idShortProperty2 = "idShortProperty2"; + Reference semanticIdSubmodel = ReferenceBuilder.global("submodel"); + Reference semanticIdCollection = ReferenceBuilder.global("collection"); + Reference semanticIdProperty = ReferenceBuilder.global("property"); + + Submodel submodel = new DefaultSubmodel.Builder() + .id("submodel-id") + .semanticId(semanticIdSubmodel) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection1) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty1) + .semanticId(semanticIdProperty) + .build()) + .build()) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection2) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty2) + .semanticId(semanticIdProperty) + .build()) + .build()) + .build(); + + List expected = List.of( + new ReferenceBuilder() + .submodel(submodel) + .element(idShortCollection1, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty1, KeyTypes.PROPERTY) + .build(), + new ReferenceBuilder() + .submodel(submodel) + .element(idShortCollection2, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty2, KeyTypes.PROPERTY) + .build()); + List actual = SemanticIdPath.builder() + .semanticId(semanticIdCollection) + .semanticId(semanticIdProperty) + .build() + .resolve(submodel); + Assert.assertTrue(expected.size() == actual.size()); + for (int i = 0; i < expected.size(); i++) { + Assert.assertTrue(ReferenceHelper.equals(expected.get(i), actual.get(i))); + } + } +} diff --git a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java index 0e650ba9e..2f61db4c2 100644 --- a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java +++ b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java @@ -15,20 +15,13 @@ package de.fraunhofer.iosb.ilt.faaast.service.util; import de.fraunhofer.iosb.ilt.faaast.service.model.AASFull; -import de.fraunhofer.iosb.ilt.faaast.service.model.IdShortPath; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.AmbiguousElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementWalker; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementVisitor; -import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; -import org.eclipse.digitaltwin.aas4j.v3.model.Property; import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; import org.junit.Assert; import org.junit.Test; @@ -55,90 +48,6 @@ public void visit(Referable referable) { } - @Test - public void resolveIdShortPathInSubmodel() throws ResourceNotFoundException { - Property expected = new DefaultProperty.Builder() - .idShort("property1") - .build(); - Submodel submodel = new DefaultSubmodel.Builder() - .idShort("foo") - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .idShort("collection") - .value(expected) - .build()) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .idShort("collection") - .value(new DefaultProperty.Builder() - .idShort("property2") - .build()) - .build()) - .build(); - Property actual = EnvironmentHelper.resolveUniquePath( - IdShortPath.parse("collection.property1"), - submodel, - Property.class); - Assert.assertEquals(expected, actual); - } - - - @Test - public void resolveSematicIdPathInSubmodel() throws ResourceNotFoundException { - Reference semanticIdCollection = ReferenceBuilder.global("collection"); - Reference semanticIdProperty1 = ReferenceBuilder.global("property1"); - Property expected = new DefaultProperty.Builder() - .semanticId(semanticIdProperty1) - .build(); - Submodel submodel = new DefaultSubmodel.Builder() - .semanticId(ReferenceBuilder.global("foo")) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(expected) - .build()) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(new DefaultProperty.Builder() - .semanticId(ReferenceBuilder.global("property")) - .build()) - .build()) - .build(); - Property actual = EnvironmentHelper.resolveUniquePath( - List.of(semanticIdCollection, semanticIdProperty1), - submodel, - Property.class); - Assert.assertEquals(expected, actual); - } - - - @Test - public void resolveSematicIdPathInSubmodel_NonUnique() throws ResourceNotFoundException { - Reference semanticIdCollection = ReferenceBuilder.global("collection"); - Reference semanticIdProperty = ReferenceBuilder.global("property"); - Property property1 = new DefaultProperty.Builder() - .semanticId(semanticIdProperty) - .build(); - Property property2 = new DefaultProperty.Builder() - .semanticId(semanticIdProperty) - .build(); - List expected = List.of(property1, property2); - Submodel submodel = new DefaultSubmodel.Builder() - .semanticId(ReferenceBuilder.global("foo")) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(property1) - .build()) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(property2) - .build()) - .build(); - List actual = EnvironmentHelper.resolvePath( - List.of(semanticIdCollection, semanticIdProperty), - submodel, - Property.class); - Assert.assertEquals(expected, actual); - } - - private void assertResolve(Referable expected, Environment environment) throws ResourceNotFoundException, AmbiguousElementException { Reference reference = EnvironmentHelper.asReference(expected, environment); Referable actual = EnvironmentHelper.resolve(reference, environment); From 934b4827e0eef10abe854642cdb545d82c6645fe Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Mon, 1 Jul 2024 16:20:04 +0200 Subject: [PATCH 005/108] fix initialization of SMT processors --- .../iosb/ilt/faaast/service/Service.java | 15 ++++++++------- .../service/util/ImplementationManager.java | 1 + .../ilt/faaast/service/util/ReferenceHelper.java | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 497611c06..fd0328de1 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -117,13 +117,13 @@ public Service(CoreConfig coreConfig, this.fileStorage = fileStorage; this.messageBus = messageBus; this.assetConnectionManager = new AssetConnectionManager(config.getCore(), assetConnections, this); - initSubmodelTemplateProcessors(); this.requestHandler = new RequestHandlerManager(new RequestExecutionContext( coreConfig, persistence, fileStorage, messageBus, assetConnectionManager)); + initSubmodelTemplateProcessors(); } @@ -283,6 +283,12 @@ private void init() throws ConfigurationException { } assetConnectionManager = new AssetConnectionManager(config.getCore(), assetConnections, this); } + this.requestHandler = new RequestHandlerManager(new RequestExecutionContext( + this.config.getCore(), + this.persistence, + this.fileStorage, + this.messageBus, + this.assetConnectionManager)); initSubmodelTemplateProcessors(); endpoints = new ArrayList<>(); if (config.getEndpoints() == null || config.getEndpoints().isEmpty()) { @@ -294,12 +300,7 @@ private void init() throws ConfigurationException { endpoints.add(endpoint); } } - this.requestHandler = new RequestHandlerManager(new RequestExecutionContext( - this.config.getCore(), - this.persistence, - this.fileStorage, - this.messageBus, - this.assetConnectionManager)); + } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java index 78456f124..8defe8c4f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java @@ -90,6 +90,7 @@ public static synchronized void init() { catch (SecurityException e) { LOGGER.error("Scanning directory '{}' for jar files failed", dir, e); } + Thread.currentThread().setContextClassLoader(classLoader); isInitialized = true; } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java index 20357c66e..ca2e0e0e1 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java @@ -674,7 +674,7 @@ private static boolean equals(Key key1, Key key2) { if (Objects.isNull(type1) != Objects.isNull(type2) || Objects.isNull(type1) || (!(type1.isAssignableFrom(type2) || type2.isAssignableFrom(type1)))) { - LOGGER.warn(String.format( + LOGGER.debug(String.format( "encountered reference keys with same value but incompatible types (key value: %s, key type 1: %s, key type 2: %s)", key1.getValue(), type1, From 1cc2d6a058d2cb5f5083256f7c7a1cf14ebe8376 Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Mon, 9 Sep 2024 11:54:07 +0200 Subject: [PATCH 006/108] add support for cardinality qualifiers --- .../faaast/service/typing/TypeExtractor.java | 9 ++- model/pom.xml | 11 +++ .../model/submodeltemplate/Cardinality.java | 46 +++++++++++++ .../SubmodelElementListValueMapper.java | 25 +++++-- .../faaast/service/util/DeepCopyHelper.java | 0 .../service/util/SubmodelTemplateHelper.java | 69 +++++++++++++++++++ 6 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java rename {core => model}/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java (100%) create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java index d76c8bb87..02f292da7 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java @@ -73,8 +73,13 @@ else if (SubmodelElementList.class.isAssignableFrom(type)) { if (Objects.nonNull(submodelElementList.getTypeValueListElement()) && (submodelElementList.getTypeValueListElement() == AasSubmodelElements.SUBMODEL_ELEMENT_COLLECTION || submodelElementList.getTypeValueListElement() == AasSubmodelElements.SUBMODEL_ELEMENT_LIST)) { - for (int i = 0; i < submodelElementList.getValue().size(); i++) { - builder.element(Integer.toString(i), extractTypeInfoForSubmodelElement(submodelElementList.getValue().get(i))); + if (submodelElementList.getValue().size() == 1) { + builder.element(null, extractTypeInfoForSubmodelElement(submodelElementList.getValue().get(0))); + } + else { + for (int i = 0; i < submodelElementList.getValue().size(); i++) { + builder.element(Integer.toString(i), extractTypeInfoForSubmodelElement(submodelElementList.getValue().get(i))); + } } } else { diff --git a/model/pom.xml b/model/pom.xml index 5e1fb1f75..72f31ac21 100644 --- a/model/pom.xml +++ b/model/pom.xml @@ -111,6 +111,17 @@ + + org.eclipse.digitaltwin.aas4j + aas4j-dataformat-json + ${aas4j.version} + + + org.slf4j + slf4j-simple + + + org.eclipse.digitaltwin.aas4j aas4j-model diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java new file mode 100644 index 000000000..a1c07acfe --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java @@ -0,0 +1,46 @@ +/* + * 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.ilt.faaast.service.model.submodeltemplate; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.serialization.EnumSerializer; + + +/** + * Describes the cardinality of relationships used with submodel templates. + */ +public enum Cardinality { + ONE(false), + ZERO_TO_ONE(false), + ZERO_TO_MANY(true), + ONE_TO_MANY(true); + + public static final String SEMANTIC_ID = "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0"; + public static Cardinality DEFAULT = Cardinality.ONE; + private final boolean allowsMultipleValues; + + private Cardinality(boolean allowsMultipleValues) { + this.allowsMultipleValues = allowsMultipleValues; + } + + + public boolean getAllowsMultipleValues() { + return allowsMultipleValues; + } + + + public String getNameForSerialization() { + return EnumSerializer.serializeEnumName(name()); + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java index daf1f6f73..c4dc44ddd 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java @@ -17,10 +17,15 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueMappingException; import de.fraunhofer.iosb.ilt.faaast.service.model.value.ElementValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.SubmodelElementListValue; +import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.SubmodelTemplateHelper; import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -29,6 +34,8 @@ */ public class SubmodelElementListValueMapper implements DataValueMapper { + private static final Logger LOGGER = LoggerFactory.getLogger(SubmodelElementListValueMapper.class); + @Override public SubmodelElementListValue toValue(SubmodelElementList submodelElement) throws ValueMappingException { if (submodelElement == null) { @@ -57,13 +64,21 @@ public SubmodelElementList setValue(SubmodelElementList submodelElement, Submode int valueSize = Objects.nonNull(value.getValues()) ? value.getValues().size() : 0; - if (elementSize < valueSize) { - throw new ValueMappingException(String.format( - "Loss of information - setting a value with size %d to a SubmodelElementList of size %d results in loss of information", + if (elementSize == 1 + && valueSize > 1 + && SubmodelTemplateHelper.getCardinality(submodelElement.getValue().get(0)).getAllowsMultipleValues()) { + // we have a template that supports multiple elements + submodelElement.setValue(Stream.generate(() -> DeepCopyHelper.deepCopy(submodelElement.getValue().get(0))) + .limit(valueSize) + .collect(Collectors.toList())); + elementSize = valueSize; + } + else if (elementSize < valueSize) { + LOGGER.warn("Loss of information - setting a value with size {} to a SubmodelElementList of size {} results in loss of information (id: {})", valueSize, - elementSize)); + elementSize, + submodelElement.getIdShort()); } - for (int i = 0; i < Math.min(elementSize, valueSize); i++) { ElementValueMapper.setValue(submodelElement.getValue().get(i), value.getValues().get(i)); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java similarity index 100% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java new file mode 100644 index 000000000..59db24b0a --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java @@ -0,0 +1,69 @@ +/* + * 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.ilt.faaast.service.util; + +import de.fraunhofer.iosb.ilt.faaast.service.model.submodeltemplate.Cardinality; +import java.util.Objects; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.deserialization.EnumDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Qualifiable; +import org.eclipse.digitaltwin.aas4j.v3.model.Qualifier; +import org.eclipse.digitaltwin.aas4j.v3.model.QualifierKind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Helper class for working with submodel templates. + */ +public class SubmodelTemplateHelper { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubmodelTemplateHelper.class); + + private SubmodelTemplateHelper() {} + + + /** + * Gets the cardinality defined by the qualifiable. If not (valid) cardinality is found, {@link Cardinality#ONE} is + * returned. + * + * @param element the element to check + * @return the cardinaility or {@link Cardinality#ONE} is none is defined + */ + public static Cardinality getCardinality(Qualifiable element) { + if (Objects.isNull(element) + || Objects.isNull(element.getQualifiers()) + || element.getQualifiers().isEmpty()) { + return Cardinality.DEFAULT; + } + Optional cardinalityQualifier = element.getQualifiers().stream() + .filter(Objects::nonNull) + .filter(x -> x.getKind() == QualifierKind.TEMPLATE_QUALIFIER) + .filter(x -> x.getValueType() == DataTypeDefXsd.STRING) + .filter(x -> Objects.equals(x.getType(), Cardinality.class.getSimpleName())) + .findFirst(); + if (cardinalityQualifier.isEmpty()) { + return Cardinality.DEFAULT; + } + try { + return Cardinality.valueOf(EnumDeserializer.deserializeEnumName(cardinalityQualifier.get().getValue())); + } + catch (IllegalArgumentException e) { + LOGGER.debug(String.format("Encountered invalid SMT cardinality qualifier: %s", cardinalityQualifier.get().getValue())); + return Cardinality.DEFAULT; + } + } +} From 52eb03be7a99eb59d0e5cf2fcc8c390f7871ed2e Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 19 Sep 2024 17:29:58 +0200 Subject: [PATCH 007/108] add apiGateway --- endpoint/http/pom.xml | 10 +++ .../service/endpoint/http/HttpEndpoint.java | 2 + .../endpoint/http/HttpEndpointConfig.java | 20 +++++- .../service/endpoint/http/RequestHandler.java | 17 +++++ .../endpoint/http/security/ApiGateway.java | 66 +++++++++++++++++++ pom.xml | 2 + 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java diff --git a/endpoint/http/pom.xml b/endpoint/http/pom.xml index 00f982984..b787986a4 100644 --- a/endpoint/http/pom.xml +++ b/endpoint/http/pom.xml @@ -37,6 +37,16 @@ tests test + + com.auth0 + java-jwt + ${java.jwt.version} + + + com.auth0 + jwks-rsa + ${jwks.rsa.version} + com.fasterxml.jackson.core jackson-annotations diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index b5381cffd..6d8f07aa7 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -22,6 +22,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.Endpoint; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.Version; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; @@ -75,6 +76,7 @@ public class HttpEndpoint implements Endpoint { private ServiceContext serviceContext; private Server server; private Handler handler; + private ApiGateway apiGateway; @Override public HttpEndpointConfig asConfig() { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java index a928aa934..fd0b4650d 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java @@ -35,6 +35,17 @@ public class HttpEndpointConfig extends EndpointConfig { private boolean sslEnabled; private CertificateConfig certificate; private String hostname; + private String jwkProvider; + + public String getJwkProvider() { + return jwkProvider; + } + + + public void setJwkProvider(String jwkProvider) { + this.jwkProvider = jwkProvider; + } + public HttpEndpointConfig() { port = DEFAULT_PORT; @@ -120,7 +131,8 @@ public boolean equals(Object o) { && Objects.equals(sniEnabled, that.sniEnabled) && Objects.equals(sslEnabled, that.sslEnabled) && Objects.equals(certificate, that.certificate) - && Objects.equals(hostname, that.hostname); + && Objects.equals(hostname, that.hostname) + && Objects.equals(jwkProvider, that.jwkProvider); } @@ -170,6 +182,12 @@ public B hostname(String value) { getBuildingInstance().setHostname(value); return getSelf(); } + + + public B jwkProvider(String value) { + getBuildingInstance().setJwkProvider(value); + return getSelf(); + } } public static class Builder extends AbstractBuilder { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java index 1bbd569f5..eab02693a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java @@ -24,6 +24,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.RequestMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpConstants; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; @@ -66,6 +67,7 @@ public class RequestHandler extends AbstractHandler { private final RequestMappingManager requestMappingManager; private final ResponseMappingManager responseMappingManager; private final HttpJsonApiSerializer serializer; + private ApiGateway apiGateway = null; public RequestHandler(ServiceContext serviceContext, HttpEndpointConfig config) { Ensure.requireNonNull(serviceContext, "serviceContext must be non-null"); @@ -75,11 +77,26 @@ public RequestHandler(ServiceContext serviceContext, HttpEndpointConfig config) this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); + if (Objects.nonNull(config.getJwkProvider())) { + this.apiGateway = new ApiGateway(config.getJwkProvider()); + } } @Override public void handle(String string, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if (Objects.nonNull(apiGateway)) { + if (!apiGateway.isAuthorized(baseRequest.getHeader("Authorization"))) { + HttpHelper.send( + response, + StatusCode.CLIENT_NOT_AUTHORIZED, + Result.builder() + .message(MessageType.ERROR, String.format("User not authorized '%s'", request.getRequestURI())) + .build()); + baseRequest.setHandled(true); + return; + } + } if (!request.getRequestURI().startsWith(HttpEndpoint.getVersionPrefix())) { HttpHelper.send( response, diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java new file mode 100644 index 000000000..ad3d20753 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -0,0 +1,66 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security; + +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkException; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.UrlJwkProvider; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import java.security.interfaces.RSAPublicKey; +import java.util.Calendar; +import java.util.Objects; + + +/** + * A simple ApiGateway that verifies JWT tokens against the provided jwkProvider. + */ +public class ApiGateway { + private String jwkProvider; + + public ApiGateway(String jwkProvider) { + this.jwkProvider = jwkProvider; + } + + + /** + * Verifies the token by decoding it + * + * @param token the JWT token + * @return true if the token is valid + */ + public boolean isAuthorized(String token) { + if (Objects.isNull(token)) { + return false; + } + token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; + DecodedJWT jwt = JWT.decode(token); + JwkProvider provider = new UrlJwkProvider("http://localhost:4444"); + try { + Jwk jwk = provider.get(jwt.getKeyId()); + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); + algorithm.verify(jwt); + } + catch (JwkException e) { + return false; + } + if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())) { + return false; + } + return true; + } +} diff --git a/pom.xml b/pom.xml index 51d04f775..7507aa770 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,7 @@ 6.1.0 4.0.2 3.1.11 + 4.4.0 2.3.1 5.1.0 11.0.24 @@ -102,6 +103,7 @@ 1.5.3 2.9.0 4.13.2 + 0.22.1 1.5.8 17 17 From a64b4a23ba18d8eff458ac3ee3f7db865caede2c Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 19 Sep 2024 17:40:00 +0200 Subject: [PATCH 008/108] additional verification of jwt --- .../service/endpoint/http/security/ApiGateway.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index ad3d20753..4b7e4d61e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -19,6 +19,7 @@ import com.auth0.jwk.JwkProvider; import com.auth0.jwk.UrlJwkProvider; import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import java.security.interfaces.RSAPublicKey; @@ -49,18 +50,19 @@ public boolean isAuthorized(String token) { } token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; DecodedJWT jwt = JWT.decode(token); - JwkProvider provider = new UrlJwkProvider("http://localhost:4444"); + JwkProvider provider = new UrlJwkProvider(this.jwkProvider); try { Jwk jwk = provider.get(jwt.getKeyId()); Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); algorithm.verify(jwt); + JWTVerifier verifier = JWT.require(algorithm) + //.withIssuer("this.jwkProvider") + .build(); + verifier.verify(token); } catch (JwkException e) { return false; } - if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())) { - return false; - } return true; } } From 26715e245b01433c11b9a9efcffce520b1045e19 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Fri, 20 Sep 2024 14:10:46 +0200 Subject: [PATCH 009/108] update gateway and delegation to authServer --- .../service/endpoint/http/RequestHandler.java | 2 +- .../endpoint/http/security/ApiGateway.java | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java index eab02693a..08fe2bb80 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandler.java @@ -86,7 +86,7 @@ public RequestHandler(ServiceContext serviceContext, HttpEndpointConfig config) @Override public void handle(String string, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { if (Objects.nonNull(apiGateway)) { - if (!apiGateway.isAuthorized(baseRequest.getHeader("Authorization"))) { + if (!apiGateway.isAuthorized(baseRequest.getHeader("Authorization"), string)) { HttpHelper.send( response, StatusCode.CLIENT_NOT_AUTHORIZED, diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 4b7e4d61e..8bf0ca8a1 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -21,9 +21,10 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import java.security.interfaces.RSAPublicKey; -import java.util.Calendar; +import java.util.Map; import java.util.Objects; @@ -44,7 +45,7 @@ public ApiGateway(String jwkProvider) { * @param token the JWT token * @return true if the token is valid */ - public boolean isAuthorized(String token) { + public boolean isAuthorized(String token, String path) { if (Objects.isNull(token)) { return false; } @@ -56,13 +57,22 @@ public boolean isAuthorized(String token) { Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); algorithm.verify(jwt); JWTVerifier verifier = JWT.require(algorithm) - //.withIssuer("this.jwkProvider") + //.withIssuer(this.jwkProvider) .build(); verifier.verify(token); } catch (JwkException e) { return false; } + if (!verifiedClaims(jwt.getClaims(), path)) { + return false; + } ; + return true; + } + + + private boolean verifiedClaims(Map claims, String path) { + //@TODO send to AuthServer to compare with SMT Security Processor return true; } } From 95f46315cbe55815bf13bd3da439ca8d4b771cb6 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Wed, 6 Nov 2024 16:49:52 +0100 Subject: [PATCH 010/108] integrate MessageBus --- .../iosb/ilt/faaast/service/Service.java | 109 ++++++++++++++++-- .../endpoint/opcua/helper/TestService.java | 3 +- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 89dc214a6..6670ad7a7 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -35,6 +35,11 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.PagingInfo; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.SubscriptionId; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.SubscriptionInfo; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementCreateEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementDeleteEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementUpdateEventMessage; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; @@ -54,6 +59,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.Operation; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; @@ -68,6 +74,8 @@ public class Service implements ServiceContext { private static final Logger LOGGER = LoggerFactory.getLogger(Service.class); + private static final String VALUE_NULL = "value must not be null"; + private static final String ELEMENT_NULL = "element must not be null"; private final ServiceConfig config; private AssetConnectionManager assetConnectionManager; private List endpoints; @@ -78,6 +86,7 @@ public class Service implements ServiceContext { private RegistrySynchronization registrySynchronization; private RequestHandlerManager requestHandler; private List submodelTemplateProcessors; + private List subscriptions; /** * Creates a new instance of {@link Service}. @@ -92,6 +101,7 @@ public class Service implements ServiceContext { * @throws IllegalArgumentException if coreConfig is null * @throws IllegalArgumentException if persistence is null * @throws PersistenceException if storage error occurs + * @throws MessageBusException if message bus error occurs * @throws IllegalArgumentException if messageBus is null * @throws RuntimeException if creating a deep copy of aasEnvironment fails * @throws ConfigurationException the configuration the {@link AssetConnectionManager} fails @@ -103,7 +113,7 @@ public Service(CoreConfig coreConfig, MessageBus messageBus, List endpoints, List assetConnections, - List submodelTemplateProcessors) throws ConfigurationException, AssetConnectionException, PersistenceException { + List submodelTemplateProcessors) throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { Ensure.requireNonNull(coreConfig, "coreConfig must be non-null"); Ensure.requireNonNull(persistence, "persistence must be non-null"); Ensure.requireNonNull(messageBus, "messageBus must be non-null"); @@ -115,6 +125,7 @@ public Service(CoreConfig coreConfig, this.endpoints = endpoints; } this.submodelTemplateProcessors = submodelTemplateProcessors; + this.subscriptions = new ArrayList<>(); this.config = ServiceConfig.builder() .core(coreConfig) .build(); @@ -140,12 +151,14 @@ public Service(CoreConfig coreConfig, * @throws IllegalArgumentException if config is null * @throws ConfigurationException if invalid configuration is provided * @throws PersistenceException if storage error occurs + * @throws MessageBusException if message bus error occurs * @throws AssetConnectionException when initializing asset connections fails */ public Service(ServiceConfig config) - throws ConfigurationException, AssetConnectionException, PersistenceException { + throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { Ensure.requireNonNull(config, "config must be non-null"); this.config = config; + this.subscriptions = new ArrayList<>(); init(); } @@ -273,6 +286,7 @@ public void start() throws MessageBusException, EndpointException, PersistenceEx */ public void stop() { LOGGER.debug("Get command for stopping FA³ST Service"); + unsubscribeMessageBus(); messageBus.stop(); assetConnectionManager.stop(); registrySynchronization.stop(); @@ -281,7 +295,7 @@ public void stop() { } - private void init() throws ConfigurationException, PersistenceException { + private void init() throws ConfigurationException, PersistenceException, MessageBusException { Ensure.requireNonNull(config.getPersistence(), new InvalidConfigurationException("config.persistence must be non-null")); persistence = (Persistence) config.getPersistence().newInstance(config.getCore(), this); Ensure.requireNonNull(config.getFileStorage(), new InvalidConfigurationException("config.filestorage must be non-null")); @@ -322,7 +336,7 @@ private void init() throws ConfigurationException, PersistenceException { } - private void initSubmodelTemplateProcessors() throws ConfigurationException, PersistenceException { + private void initSubmodelTemplateProcessors() throws ConfigurationException, PersistenceException, MessageBusException { if (submodelTemplateProcessors == null) { submodelTemplateProcessors = new ArrayList<>(); } @@ -338,11 +352,90 @@ private void initSubmodelTemplateProcessors() throws ConfigurationException, Per } List submodels = persistence.getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent(); for (var submodel: submodels) { - for (var submodelTemplateProcessor: submodelTemplateProcessors) { - if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.process(submodel, assetConnectionManager)) { - persistence.save(submodel); - } + processSubmodel(submodel); + } + + subscribeMessageBus(); + } + + + private void processSubmodel(Submodel submodel) throws PersistenceException { + for (var submodelTemplateProcessor: submodelTemplateProcessors) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.process(submodel, assetConnectionManager)) { + persistence.save(submodel); + } + } + } + + + private void subscribeMessageBus() throws MessageBusException { + if (subscriptions == null) { + subscriptions = new ArrayList<>(); + } + SubscriptionInfo info = SubscriptionInfo.create(ElementCreateEventMessage.class, x -> { + try { + elementCreated(x.getElement(), x.getValue()); + } + catch (Exception e) { + LOGGER.error("elementCreated Exception", e); + } + }); + subscriptions.add(messageBus.subscribe(info)); + + info = SubscriptionInfo.create(ElementDeleteEventMessage.class, x -> { + try { + elementDeleted(x.getElement()); + } + catch (Exception e) { + LOGGER.error("elementDeleted Exception", e); + } + }); + subscriptions.add(messageBus.subscribe(info)); + + info = SubscriptionInfo.create(ElementUpdateEventMessage.class, x -> { + try { + elementUpdated(x.getElement(), x.getValue()); + } + catch (Exception e) { + LOGGER.error("elementUpdated Exception", e); } + }); + subscriptions.add(messageBus.subscribe(info)); + } + + + private void unsubscribeMessageBus() { + for (var subscription: subscriptions) { + try { + messageBus.unsubscribe(subscription); + } + catch (Exception ex) { + LOGGER.error("unsubscribeMessageBus Exception", ex); + } + } + subscriptions.clear(); + } + + + private void elementCreated(Reference element, Referable value) throws PersistenceException { + Ensure.requireNonNull(element, ELEMENT_NULL); + Ensure.requireNonNull(value, VALUE_NULL); + + if (value instanceof Submodel submodel) { + processSubmodel(submodel); } } + + + private void elementDeleted(Reference element) { + Ensure.requireNonNull(element, ELEMENT_NULL); + + } + + + private void elementUpdated(Reference element, Referable value) { + Ensure.requireNonNull(element, ELEMENT_NULL); + Ensure.requireNonNull(value, VALUE_NULL); + + } } diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java index ff5123c66..0d2acf48c 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java @@ -21,6 +21,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.OpcUaEndpointConfig; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.helper.assetconnection.TestAssetConnectionConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.filestorage.memory.FileStorageInMemoryConfig; import de.fraunhofer.iosb.ilt.faaast.service.messagebus.internal.MessageBusInternalConfig; import de.fraunhofer.iosb.ilt.faaast.service.model.AASFull; @@ -37,7 +38,7 @@ public class TestService extends Service { public TestService(OpcUaEndpointConfig config, TestAssetConnectionConfig assetConnectionConfig, boolean full) - throws ConfigurationException, AssetConnectionException, PersistenceException { + throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { super(ServiceConfig.builder() .core(CoreConfig.builder() .requestHandlerThreadPoolSize(2) From 89b09ec0c7ff8a3a1f5337838b9d506c15cac53b Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Fri, 15 Nov 2024 16:54:26 +0100 Subject: [PATCH 011/108] fix sonar warnings --- .../ilt/faaast/service/model/submodeltemplate/Cardinality.java | 2 +- .../fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java index a1c07acfe..cc94a3e4f 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java @@ -27,7 +27,7 @@ public enum Cardinality { ONE_TO_MANY(true); public static final String SEMANTIC_ID = "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0"; - public static Cardinality DEFAULT = Cardinality.ONE; + public static final Cardinality DEFAULT = Cardinality.ONE; private final boolean allowsMultipleValues; private Cardinality(boolean allowsMultipleValues) { diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java index 2fc2a2773..7f81177ac 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java @@ -94,7 +94,7 @@ public static T deepCopy(Referable referable, Class out String.format("type mismatch - can not create deep copy of instance of type %s with target type %s", referable.getClass(), outputClass)); } try { - return (T) new JsonDeserializer().read(new JsonSerializer().write(referable), outputClass); + return new JsonDeserializer().read(new JsonSerializer().write(referable), outputClass); } catch (SerializationException | DeserializationException e) { throw new RuntimeException("deep copy of AAS environment failed", e); From b72d02c52ca2f591efececd74ccfa7029bb96e27 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Mon, 18 Nov 2024 15:33:12 +0100 Subject: [PATCH 012/108] adaptions for new main Branch --- .../java/de/fraunhofer/iosb/ilt/faaast/service/Service.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 294a2da06..d8156b817 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -322,12 +322,6 @@ private void init() throws ConfigurationException, PersistenceException, Message } assetConnectionManager = new AssetConnectionManager(config.getCore(), assetConnections, this); } - this.requestHandler = new RequestHandlerManager(new RequestExecutionContext( - this.config.getCore(), - this.persistence, - this.fileStorage, - this.messageBus, - this.assetConnectionManager)); initSubmodelTemplateProcessors(); endpoints = new ArrayList<>(); if (config.getEndpoints() == null || config.getEndpoints().isEmpty()) { From c505bf729cfbdb07949655923737cd9bd90d4ce9 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Thu, 21 Nov 2024 16:18:27 +0100 Subject: [PATCH 013/108] add updateSubmodel --- .../iosb/ilt/faaast/service/Service.java | 14 +++++++++++++- .../SubmodelTemplateProcessor.java | 10 ++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index d8156b817..ff431ec3d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -371,6 +371,15 @@ private void processSubmodel(Submodel submodel) throws PersistenceException { } + private void updateSubmodel(Submodel submodel) throws PersistenceException { + for (var submodelTemplateProcessor: submodelTemplateProcessors) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.update(submodel, assetConnectionManager)) { + persistence.save(submodel); + } + } + } + + private void subscribeMessageBus() throws MessageBusException { if (subscriptions == null) { subscriptions = new ArrayList<>(); @@ -436,9 +445,12 @@ private void elementDeleted(Reference element) { } - private void elementUpdated(Reference element, Referable value) { + private void elementUpdated(Reference element, Referable value) throws PersistenceException { Ensure.requireNonNull(element, ELEMENT_NULL); Ensure.requireNonNull(value, VALUE_NULL); + if (value instanceof Submodel submodel) { + updateSubmodel(submodel); + } } } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java index 813622703..d3640f5df 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java @@ -43,4 +43,14 @@ public interface SubmodelTemplateProcessor Date: Tue, 26 Nov 2024 12:56:54 +0100 Subject: [PATCH 014/108] change SMT processor interface --- .../iosb/ilt/faaast/service/Service.java | 36 ++++++++++++------- .../SubmodelTemplateProcessor.java | 14 ++++++-- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index ff431ec3d..fb38f23d6 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -355,16 +355,16 @@ private void initSubmodelTemplateProcessors() throws ConfigurationException, Per } List submodels = persistence.getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent(); for (var submodel: submodels) { - processSubmodel(submodel); + addSubmodel(submodel); } subscribeMessageBus(); } - private void processSubmodel(Submodel submodel) throws PersistenceException { + private void addSubmodel(Submodel submodel) throws PersistenceException { for (var submodelTemplateProcessor: submodelTemplateProcessors) { - if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.process(submodel, assetConnectionManager)) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.add(submodel, assetConnectionManager)) { persistence.save(submodel); } } @@ -380,13 +380,22 @@ private void updateSubmodel(Submodel submodel) throws PersistenceException { } + private void deleteSubmodel(Submodel submodel) throws PersistenceException { + for (var submodelTemplateProcessor: submodelTemplateProcessors) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.delete(submodel, assetConnectionManager)) { + persistence.save(submodel); + } + } + } + + private void subscribeMessageBus() throws MessageBusException { if (subscriptions == null) { subscriptions = new ArrayList<>(); } SubscriptionInfo info = SubscriptionInfo.create(ElementCreateEventMessage.class, x -> { try { - elementCreated(x.getElement(), x.getValue()); + elementCreated(x.getValue()); } catch (Exception e) { LOGGER.error("elementCreated Exception", e); @@ -396,7 +405,7 @@ private void subscribeMessageBus() throws MessageBusException { info = SubscriptionInfo.create(ElementDeleteEventMessage.class, x -> { try { - elementDeleted(x.getElement()); + elementDeleted(x.getValue()); } catch (Exception e) { LOGGER.error("elementDeleted Exception", e); @@ -406,7 +415,7 @@ private void subscribeMessageBus() throws MessageBusException { info = SubscriptionInfo.create(ElementUpdateEventMessage.class, x -> { try { - elementUpdated(x.getElement(), x.getValue()); + elementUpdated(x.getValue()); } catch (Exception e) { LOGGER.error("elementUpdated Exception", e); @@ -429,24 +438,25 @@ private void unsubscribeMessageBus() { } - private void elementCreated(Reference element, Referable value) throws PersistenceException { - Ensure.requireNonNull(element, ELEMENT_NULL); + private void elementCreated(Referable value) throws PersistenceException { Ensure.requireNonNull(value, VALUE_NULL); if (value instanceof Submodel submodel) { - processSubmodel(submodel); + addSubmodel(submodel); } } - private void elementDeleted(Reference element) { - Ensure.requireNonNull(element, ELEMENT_NULL); + private void elementDeleted(Referable value) throws PersistenceException, ResourceNotFoundException { + Ensure.requireNonNull(value, ELEMENT_NULL); + if (value instanceof Submodel submodel) { + deleteSubmodel(submodel); + } } - private void elementUpdated(Reference element, Referable value) throws PersistenceException { - Ensure.requireNonNull(element, ELEMENT_NULL); + private void elementUpdated(Referable value) throws PersistenceException { Ensure.requireNonNull(value, VALUE_NULL); if (value instanceof Submodel submodel) { diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java index d3640f5df..70c70366d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java @@ -36,13 +36,13 @@ public interface SubmodelTemplateProcessor Date: Mon, 2 Dec 2024 16:09:43 +0100 Subject: [PATCH 015/108] remove sonar warning --- .../de/fraunhofer/iosb/ilt/faaast/service/Service.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index fb38f23d6..16b075dd3 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -365,6 +365,7 @@ private void initSubmodelTemplateProcessors() throws ConfigurationException, Per private void addSubmodel(Submodel submodel) throws PersistenceException { for (var submodelTemplateProcessor: submodelTemplateProcessors) { if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.add(submodel, assetConnectionManager)) { + LOGGER.debug("addSubmodel: submodelTemplate processed successfully"); persistence.save(submodel); } } @@ -374,16 +375,17 @@ private void addSubmodel(Submodel submodel) throws PersistenceException { private void updateSubmodel(Submodel submodel) throws PersistenceException { for (var submodelTemplateProcessor: submodelTemplateProcessors) { if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.update(submodel, assetConnectionManager)) { + LOGGER.debug("updateSubmodel: submodelTemplate processed successfully"); persistence.save(submodel); } } } - private void deleteSubmodel(Submodel submodel) throws PersistenceException { + private void deleteSubmodel(Submodel submodel) { for (var submodelTemplateProcessor: submodelTemplateProcessors) { if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.delete(submodel, assetConnectionManager)) { - persistence.save(submodel); + LOGGER.debug("deleteSubmodel: submodelTemplate processed successfully"); } } } @@ -447,7 +449,7 @@ private void elementCreated(Referable value) throws PersistenceException { } - private void elementDeleted(Referable value) throws PersistenceException, ResourceNotFoundException { + private void elementDeleted(Referable value) { Ensure.requireNonNull(value, ELEMENT_NULL); if (value instanceof Submodel submodel) { From 90cac87bcfa6ee2f76ecd5f08f89f2437694afec Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Thu, 16 Jan 2025 16:33:59 +0100 Subject: [PATCH 016/108] ServiceConfig: add submodelTemplateProcessor to Builder --- .../faaast/service/config/ServiceConfig.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java index cd15ac5e6..31eaf1e0f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java @@ -200,11 +200,13 @@ public static class Builder { private PersistenceConfig persistence; private FileStorageConfig fileStorage; private MessageBusConfig messageBus; + private List submodelTemplateProcessors; public Builder() { this.core = new CoreConfig(); this.assetConnections = new ArrayList<>(); this.endpoints = new ArrayList<>(); + this.submodelTemplateProcessors = new ArrayList<>(); } @@ -304,6 +306,30 @@ public Builder endpoint(EndpointConfig value) { } + /** + * Sets the SubmodelTemplateProcessors. + * + * @param value The SubmodelTemplateProcessors. + * @return The builder. + */ + public Builder submodelTemplateProcessors(List value) { + this.submodelTemplateProcessors = value; + return this; + } + + + /** + * Adds a SubmodelTemplateProcessor to the list of SubmodelTemplateProcessors. + * + * @param value The SubmodelTemplateProcessor to add. + * @return The builder. + */ + public Builder submodelTemplateProcessor(SubmodelTemplateProcessorConfig value) { + this.submodelTemplateProcessors.add(value); + return this; + } + + /** * Builds a new instance of ServiceConfig as defined by the builder. * @@ -317,6 +343,7 @@ public ServiceConfig build() { result.setPersistence(persistence); result.setFileStorage(fileStorage); result.setMessageBus(messageBus); + result.setSubmodelTemplateProcessors(submodelTemplateProcessors); return result; } From d2d387d9a61175cb832590fff32f60e67c6b306c Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Tue, 11 Mar 2025 18:06:26 +0100 Subject: [PATCH 017/108] add SMT Processor tests --- .../iosb/ilt/faaast/service/Service.java | 88 ++++++--- .../SubmodelTemplateProcessorTest.java | 185 ++++++++++++++++++ 2 files changed, 241 insertions(+), 32 deletions(-) create mode 100644 core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 0cae2e8d8..6ba56b2fd 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -407,34 +407,13 @@ private void subscribeMessageBus() throws MessageBusException { if (subscriptions == null) { subscriptions = new ArrayList<>(); } - SubscriptionInfo info = SubscriptionInfo.create(ElementCreateEventMessage.class, x -> { - try { - elementCreated(x.getValue()); - } - catch (Exception e) { - LOGGER.error("elementCreated Exception", e); - } - }); + SubscriptionInfo info = SubscriptionInfo.create(ElementCreateEventMessage.class, this::handleCreateEvent); subscriptions.add(messageBus.subscribe(info)); - info = SubscriptionInfo.create(ElementDeleteEventMessage.class, x -> { - try { - elementDeleted(x.getValue()); - } - catch (Exception e) { - LOGGER.error("elementDeleted Exception", e); - } - }); + info = SubscriptionInfo.create(ElementUpdateEventMessage.class, this::handleUpdateEvent); subscriptions.add(messageBus.subscribe(info)); - info = SubscriptionInfo.create(ElementUpdateEventMessage.class, x -> { - try { - elementUpdated(x.getValue()); - } - catch (Exception e) { - LOGGER.error("elementUpdated Exception", e); - } - }); + info = SubscriptionInfo.create(ElementDeleteEventMessage.class, this::handleDeleteEvent); subscriptions.add(messageBus.subscribe(info)); } @@ -452,11 +431,16 @@ private void unsubscribeMessageBus() { } - private void elementCreated(Referable value) throws PersistenceException { + private void elementCreated(Referable value) { Ensure.requireNonNull(value, VALUE_NULL); - if (value instanceof Submodel submodel) { - addSubmodel(submodel); + try { + if (value instanceof Submodel submodel) { + addSubmodel(submodel); + } + } + catch (Exception e) { + LOGGER.error("elementCreated Exception", e); } } @@ -464,17 +448,57 @@ private void elementCreated(Referable value) throws PersistenceException { private void elementDeleted(Referable value) { Ensure.requireNonNull(value, ELEMENT_NULL); - if (value instanceof Submodel submodel) { - deleteSubmodel(submodel); + try { + if (value instanceof Submodel submodel) { + deleteSubmodel(submodel); + } + } + catch (Exception e) { + LOGGER.error("elementDeleted Exception", e); } } - private void elementUpdated(Referable value) throws PersistenceException { + private void elementUpdated(Referable value) { Ensure.requireNonNull(value, VALUE_NULL); - if (value instanceof Submodel submodel) { - updateSubmodel(submodel); + try { + if (value instanceof Submodel submodel) { + updateSubmodel(submodel); + } } + catch (Exception e) { + LOGGER.error("elementUpdated Exception", e); + } + } + + + /** + * Callback message for Create event from the MessageBus. + * + * @param event The event from the MessageBus. + */ + public void handleCreateEvent(ElementCreateEventMessage event) { + elementCreated(event.getValue()); + } + + + /** + * Callback message for Update event from the MessageBus. + * + * @param event The event from the MessageBus. + */ + public void handleUpdateEvent(ElementUpdateEventMessage event) { + elementUpdated(event.getValue()); + } + + + /** + * Callback message for Delete event from the MessageBus. + * + * @param event The event from the MessageBus. + */ + public void handleDeleteEvent(ElementDeleteEventMessage event) { + elementDeleted(event.getValue()); } } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java new file mode 100644 index 000000000..eaa79b04b --- /dev/null +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java @@ -0,0 +1,185 @@ +/* + * 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.ilt.faaast.service.submodeltemplate; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +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.CoreConfig; +import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.filestorage.FileStorage; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.MessageBus; +import de.fraunhofer.iosb.ilt.faaast.service.model.AASFull; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementCreateEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementDeleteEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementUpdateEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; + + +public class SubmodelTemplateProcessorTest { + + private SubmodelTemplateProcessor processor; + private Submodel submodel0; + private Submodel submodel1; + private Environment environment; + private MessageBus messageBus; + private Service service; + + @Before + public void init() throws MessageBusException { + processor = Mockito.mock(SubmodelTemplateProcessor.class); + mockMessageBus(); + environment = AASFull.createEnvironment(); + submodel0 = environment.getSubmodels().get(0); + submodel1 = environment.getSubmodels().get(1); + ArgumentMatcher isSubmodel0 = submodel -> Objects.equals(submodel, submodel0); + when(processor.accept(argThat(isSubmodel0))).thenReturn(true); + when(processor.add(eq(submodel0), any())).thenReturn(true); + when(processor.update(eq(submodel0), any())).thenReturn(true); + when(processor.delete(eq(submodel0), any())).thenReturn(true); + } + + + @Test + public void testStart() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { + List submodels = environment.getSubmodels(); + createService(submodels); + verify(processor, times(submodels.size())).accept(any()); + verify(processor, times(1)).add(eq(submodel0), any()); + verify(processor, times(0)).add(eq(submodel1), any()); + verify(processor, times(0)).update(any(), any()); + Assert.assertNotNull(service); + } + + + @Test + public void testUpdate() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { + List submodels = new ArrayList<>(); + createService(submodels); + + // Send update event to MessageBus + ElementUpdateEventMessage msg = new ElementUpdateEventMessage(); + msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + msg.setValue(submodel0); + service.getMessageBus().publish(msg); + // called for every Submodel in createService and for the Submodel to update + verify(processor, times(submodels.size() + 1)).accept(any()); + verify(processor, times(1)).update(eq(submodel0), any()); + Assert.assertNotNull(service); + } + + + @Test + public void testDelete() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { + List submodels = new ArrayList<>(); + createService(submodels); + + // Send delete event to MessageBus + ElementDeleteEventMessage msg = new ElementDeleteEventMessage(); + msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + msg.setValue(submodel0); + service.getMessageBus().publish(msg); + // called for every Submodel in createService and for the Submodel to delete + verify(processor, times(submodels.size() + 1)).accept(any()); + verify(processor, times(1)).delete(eq(submodel0), any()); + Assert.assertNotNull(service); + } + + + @Test + public void testCreate() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { + List submodels = new ArrayList<>(); + createService(submodels); + + // Send create event to MessageBus + ElementCreateEventMessage msg = new ElementCreateEventMessage(); + msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + msg.setValue(submodel0); + service.getMessageBus().publish(msg); + // called for every Submodel in createService and for the Submodel to delete + verify(processor, times(submodels.size() + 1)).accept(any()); + verify(processor, times(1)).add(eq(submodel0), any()); + Assert.assertNotNull(service); + } + + + private void createService(List submodels) throws AssetConnectionException, MessageBusException, ConfigurationException, PersistenceException { + Persistence persistence = mock(Persistence.class); + FileStorage fileStorage = mock(FileStorage.class); + when(persistence.getAllSubmodels(any(), any())).thenReturn(Page.of(submodels)); + service = new Service(CoreConfig.DEFAULT, persistence, fileStorage, messageBus, List.of(), List.of(), List.of(processor)); + } + + + private void mockMessageBus() throws MessageBusException { + messageBus = Mockito.mock(MessageBus.class); + + doAnswer((InvocationOnMock invocation) -> { + ElementCreateEventMessage eventMessage = invocation.getArgument(0); + try { + service.handleCreateEvent(eventMessage); + } + catch (Exception e) { + fail(); + } + return null; + }).when(messageBus).publish(any(ElementCreateEventMessage.class)); + doAnswer((InvocationOnMock invocation) -> { + ElementUpdateEventMessage eventMessage = invocation.getArgument(0); + try { + service.handleUpdateEvent(eventMessage); + } + catch (Exception e) { + fail(); + } + return null; + }).when(messageBus).publish(any(ElementUpdateEventMessage.class)); + + doAnswer((InvocationOnMock invocation) -> { + ElementDeleteEventMessage eventMessage = invocation.getArgument(0); + try { + service.handleDeleteEvent(eventMessage); + } + catch (Exception e) { + fail(); + } + return null; + }).when(messageBus).publish(any(ElementDeleteEventMessage.class)); + } +} From de34161dae459b02ca6b93e8ae514f1934d0cf3c Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Wed, 12 Mar 2025 18:44:05 +0100 Subject: [PATCH 018/108] refactoring --- .../fraunhofer/iosb/ilt/faaast/service/Service.java | 11 +++-------- .../SubmodelTemplateProcessorTest.java | 5 ++--- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 6ba56b2fd..bccde240d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -407,14 +407,9 @@ private void subscribeMessageBus() throws MessageBusException { if (subscriptions == null) { subscriptions = new ArrayList<>(); } - SubscriptionInfo info = SubscriptionInfo.create(ElementCreateEventMessage.class, this::handleCreateEvent); - subscriptions.add(messageBus.subscribe(info)); - - info = SubscriptionInfo.create(ElementUpdateEventMessage.class, this::handleUpdateEvent); - subscriptions.add(messageBus.subscribe(info)); - - info = SubscriptionInfo.create(ElementDeleteEventMessage.class, this::handleDeleteEvent); - subscriptions.add(messageBus.subscribe(info)); + subscriptions.add(messageBus.subscribe(SubscriptionInfo.create(ElementCreateEventMessage.class, this::handleCreateEvent))); + subscriptions.add(messageBus.subscribe(SubscriptionInfo.create(ElementUpdateEventMessage.class, this::handleUpdateEvent))); + subscriptions.add(messageBus.subscribe(SubscriptionInfo.create(ElementDeleteEventMessage.class, this::handleDeleteEvent))); } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java index eaa79b04b..2bd85b12c 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java @@ -48,7 +48,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -63,7 +62,7 @@ public class SubmodelTemplateProcessorTest { @Before public void init() throws MessageBusException { - processor = Mockito.mock(SubmodelTemplateProcessor.class); + processor = mock(SubmodelTemplateProcessor.class); mockMessageBus(); environment = AASFull.createEnvironment(); submodel0 = environment.getSubmodels().get(0); @@ -148,7 +147,7 @@ private void createService(List submodels) throws AssetConnectionExcep private void mockMessageBus() throws MessageBusException { - messageBus = Mockito.mock(MessageBus.class); + messageBus = mock(MessageBus.class); doAnswer((InvocationOnMock invocation) -> { ElementCreateEventMessage eventMessage = invocation.getArgument(0); From efbefab25f2239473b02216c35dab041fff25954 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Fri, 14 Mar 2025 11:52:46 +0100 Subject: [PATCH 019/108] remove sonar warnings --- .../faaast/service/model/SemanticIdPathTest.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java index a930dbfe3..6763cc203 100644 --- a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java +++ b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java @@ -14,7 +14,6 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.model; -import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import java.util.List; @@ -31,14 +30,10 @@ import org.junit.Test; -/** - * - * @author jab - */ public class SemanticIdPathTest { @Test - public void resolveSematicIdPathInSubmodelUnique() throws ResourceNotFoundException { + public void resolveSematicIdPathInSubmodelUnique() { final String idShortCollection1 = "idShortCollection1"; final String idShortCollection2 = "idShortCollection2"; final String idShortProperty1 = "idShortProperty1"; @@ -78,7 +73,7 @@ public void resolveSematicIdPathInSubmodelUnique() throws ResourceNotFoundExcept @Test - public void resolveSematicIdPathInCollection() throws ResourceNotFoundException { + public void resolveSematicIdPathInCollection() { final String idShortCollection = "idShortCollection"; final String idShortProperty = "idShortProperty"; Reference semanticIdCollection = ReferenceBuilder.global("collection"); @@ -106,7 +101,7 @@ public void resolveSematicIdPathInCollection() throws ResourceNotFoundException @Test - public void resolveSematicIdPathInList() throws ResourceNotFoundException { + public void resolveSematicIdPathInList() { final String idShortList1 = "idShortList1"; final String idShortList2 = "idShortList2"; final String idShortProperty1 = "idShortProperty1"; @@ -154,7 +149,7 @@ public void resolveSematicIdPathInList() throws ResourceNotFoundException { @Test - public void resolveSematicIdPathInSubmodelNonUnique() throws ResourceNotFoundException { + public void resolveSematicIdPathInSubmodelNonUnique() { final String idShortCollection1 = "idShortCollection1"; final String idShortCollection2 = "idShortCollection2"; final String idShortProperty1 = "idShortProperty1"; @@ -200,7 +195,7 @@ public void resolveSematicIdPathInSubmodelNonUnique() throws ResourceNotFoundExc .semanticId(semanticIdProperty) .build() .resolve(submodel); - Assert.assertTrue(expected.size() == actual.size()); + Assert.assertEquals(expected, actual); for (int i = 0; i < expected.size(); i++) { Assert.assertTrue(ReferenceHelper.equals(expected.get(i), actual.get(i))); } From 9488adea1e20f346c30aaa80bfa0a2523d1027a0 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 9 Apr 2025 11:32:40 +0200 Subject: [PATCH 020/108] merge SMT processor --- .../iosb/ilt/faaast/service/security/ACL.java | 24 +++++++ .../security/AccessPermissionRule.java | 24 +++++++ .../faaast/service/security/AccessType.java | 6 ++ .../faaast/service/security/Condition.java | 8 +++ .../ilt/faaast/service/security/Filter.java | 4 ++ .../ilt/faaast/service/security/Right.java | 12 ++++ .../security/attributes/Attribute.java | 5 ++ .../security/attributes/AttributeGroup.java | 10 +++ .../security/attributes/ClaimAttribute.java | 11 +++ .../security/attributes/GlobalAttribute.java | 8 +++ .../attributes/GlobalAttributeType.java | 8 +++ .../attributes/ReferenceAttribute.java | 7 ++ .../expressions/ComparisonExpression.java | 25 +++++++ .../expressions/ComparisonOperator.java | 14 ++++ .../expressions/LogicalExpression.java | 7 ++ .../security/objects/AccessObject.java | 5 ++ .../security/objects/IdentifiableObject.java | 10 +++ .../service/security/objects/ObjectGroup.java | 10 +++ .../security/objects/ReferableObject.java | 7 ++ .../service/security/objects/RouteObject.java | 7 ++ .../service/security/operands/Operand.java | 7 ++ .../security/operands/StringOperand.java | 19 ++++++ .../security/utils/EvaluationContext.java | 10 +++ .../service/security/AccessRuleTest.java | 67 +++++++++++++++++++ .../service/endpoint/http/HttpEndpoint.java | 1 - .../endpoint/http/HttpEndpointConfig.java | 1 + .../endpoint/http/RequestHandlerServlet.java | 11 +++ .../http/exception/UnauthorizedException.java | 36 ++++++++++ 28 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java create mode 100644 core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java new file mode 100644 index 000000000..1e152a181 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java @@ -0,0 +1,24 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security; + +import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.Attribute; +import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.ClaimAttribute; + +import java.util.List; + +public class ACL { + private List attributes; + private List rights; + private AccessType accessType; + + public void setAttributes(List list) { + } + + public void setRights(List rights) { + } + + public void setAccessType(AccessType accessType) { + } + + // Constructors, getters, and setters +} + diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java new file mode 100644 index 000000000..568d0dea5 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java @@ -0,0 +1,24 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security; + +import de.fraunhofer.iosb.ilt.faaast.service.security.objects.AccessObject; +import de.fraunhofer.iosb.ilt.faaast.service.security.objects.IdentifiableObject; + +import java.util.List; + +public class AccessPermissionRule { + private ACL acl; + private List objects; + private Condition formula; + private Filter filter; // Optional + + public void setAcl(ACL acl) { + } + + public void setObjects(List list) { + } + + public void setFormula(Condition condition) { + } + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java new file mode 100644 index 000000000..7bdb2b150 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java @@ -0,0 +1,6 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security; + +public enum AccessType { + ALLOW, + DISABLED +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java new file mode 100644 index 000000000..e5ab932d6 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java @@ -0,0 +1,8 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security; + +import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonExpression; + +public class Condition { + public void setExpression(ComparisonExpression conditionExpression) { + } +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java new file mode 100644 index 000000000..5909f8876 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java @@ -0,0 +1,4 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security; + +public class Filter { +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java new file mode 100644 index 000000000..58599a2f5 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java @@ -0,0 +1,12 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security; + +public enum Right { + CREATE, + READ, + UPDATE, + DELETE, + EXECUTE, + VIEW, + ALL, + TREE +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java new file mode 100644 index 000000000..838b668d9 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java @@ -0,0 +1,5 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; + +public interface Attribute { + // Common methods or marker interface +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java new file mode 100644 index 000000000..737f1fbd5 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java @@ -0,0 +1,10 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; + +import java.util.List; + +public class AttributeGroup implements Attribute { + private String name; + private List attributes; + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java new file mode 100644 index 000000000..e047e497d --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java @@ -0,0 +1,11 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; + +public class ClaimAttribute implements Attribute { + private String claimLiteral; + + public ClaimAttribute(String claim) { + this.claimLiteral = claim; + } + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java new file mode 100644 index 000000000..edeb22526 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java @@ -0,0 +1,8 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; + +public class GlobalAttribute implements Attribute { + private GlobalAttributeType type; // LOCALNOW, UTCNOW, CLIENTNOW, ANONYMOUS + + // Constructors, getters, and setters +} + diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java new file mode 100644 index 000000000..c4ba0dccd --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java @@ -0,0 +1,8 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; + +public enum GlobalAttributeType { + LOCALNOW, + UTCNOW, + CLIENTNOW, + ANONYMOUS +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java new file mode 100644 index 000000000..521d92ec0 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java @@ -0,0 +1,7 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; + +public class ReferenceAttribute implements Attribute { + private String referenceLiteral; + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java new file mode 100644 index 000000000..e89f7b677 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java @@ -0,0 +1,25 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.expressions; + +import de.fraunhofer.iosb.ilt.faaast.service.security.operands.Operand; +import de.fraunhofer.iosb.ilt.faaast.service.security.operands.StringOperand; +import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +public class ComparisonExpression implements LogicalExpression { + private Operand leftOperand; + private ComparisonOperator operator; + private Operand rightOperand; + + public ComparisonExpression(StringOperand stringOperand, ComparisonOperator comparisonOperator, StringOperand admin) { + } + + @Override + public boolean evaluate(EvaluationContext context) { + Object leftValue = leftOperand.getValue(context); + Object rightValue = rightOperand.getValue(context); + // TODO Implement comparison based on operator + return true; + } + + // Constructors, getters, and setters +} + diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java new file mode 100644 index 000000000..e427e52a0 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java @@ -0,0 +1,14 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.expressions; + +public enum ComparisonOperator { + EQ, + NE, + GT, + LT, + GE, + LE, + STARTS_WITH, + ENDS_WITH, + CONTAINS, + REGEX +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java new file mode 100644 index 000000000..398568b93 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java @@ -0,0 +1,7 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.expressions; + +import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +public interface LogicalExpression { + boolean evaluate(EvaluationContext context); +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java new file mode 100644 index 000000000..92e17bd8a --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java @@ -0,0 +1,5 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.objects; + +public interface AccessObject { + // Common methods or marker interface +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java new file mode 100644 index 000000000..f50de7325 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java @@ -0,0 +1,10 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.objects; + +public class IdentifiableObject implements AccessObject { + private String identifiableLiteral; + + public IdentifiableObject(String submodel) { + } + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java new file mode 100644 index 000000000..28ba7b261 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java @@ -0,0 +1,10 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.objects; + +import java.util.List; + +public class ObjectGroup implements AccessObject { + private String name; + private List objects; + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java new file mode 100644 index 000000000..72ba65b62 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java @@ -0,0 +1,7 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.objects; + +public class ReferableObject implements AccessObject { + private String referableLiteral; + + // Constructors, getters, and setters +} \ No newline at end of file diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java new file mode 100644 index 000000000..f3f5cf8ec --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java @@ -0,0 +1,7 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.objects; + +public class RouteObject implements AccessObject { + private String routeLiteral; + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java new file mode 100644 index 000000000..7f41eea19 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java @@ -0,0 +1,7 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.operands; + +import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +public interface Operand { + Object getValue(EvaluationContext context); +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java new file mode 100644 index 000000000..9181e23e1 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java @@ -0,0 +1,19 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.operands; + +import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +public class StringOperand implements Operand { + private String value; + + public StringOperand(String value) { + this.value = value; + } + + @Override + public Object getValue(EvaluationContext context) { + // TODO Retrieve value based on context + return value; + } + + // Constructors, getters, and setters +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java new file mode 100644 index 000000000..1119d0098 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java @@ -0,0 +1,10 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.utils; + +import java.util.Map; + +public class EvaluationContext { + private Map variables; + private Map attributes; + + // Methods to get and set variables and attributes +} diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java new file mode 100644 index 000000000..f806ec4b0 --- /dev/null +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -0,0 +1,67 @@ +/* + * 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.ilt.faaast.service.security; + + + +import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.ClaimAttribute; +import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonExpression; +import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonOperator; +import de.fraunhofer.iosb.ilt.faaast.service.security.objects.IdentifiableObject; +import de.fraunhofer.iosb.ilt.faaast.service.security.operands.StringOperand; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + + + +public class AccessRuleTest { + + + @Before + public void init() { + } + + + @Test + public void testConstruction() { + ClaimAttribute roleAttribute = new ClaimAttribute("role=admin"); + + List rights = Arrays.asList(Right.READ, Right.UPDATE); + + ACL acl = new ACL(); + acl.setAttributes(Arrays.asList(roleAttribute)); + acl.setRights(rights); + acl.setAccessType(AccessType.ALLOW); + + IdentifiableObject identifiableObject = new IdentifiableObject("Submodel123"); + + ComparisonExpression conditionExpression = new ComparisonExpression( + new StringOperand("$subject#role"), + ComparisonOperator.EQ, + new StringOperand("admin") + ); + + Condition condition = new Condition(); + condition.setExpression(conditionExpression); + + AccessPermissionRule rule = new AccessPermissionRule(); + rule.setAcl(acl); + rule.setObjects(Arrays.asList(identifiableObject)); + rule.setFormula(condition); + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index eda6050e5..04eec257f 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -78,7 +78,6 @@ public class HttpEndpoint extends AbstractEndpoint { private static final String ENDPOINT_PROTOCOL = "HTTP"; private static final String ENDPOINT_PROTOCOL_VERSION = "1.1"; private Server server; - private Handler handler; private ApiGateway apiGateway; @Override diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java index 9607168bf..7ac654abd 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java @@ -333,6 +333,7 @@ public B hostname(String value) { public B jwkProvider(String value) { getBuildingInstance().setJwkProvider(value); + return getSelf(); } public B includeErrorDetails() { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 45e976828..4c44ae60d 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -16,12 +16,15 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.RequestMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; @@ -51,6 +54,7 @@ public class RequestHandlerServlet extends HttpServlet { private final RequestMappingManager requestMappingManager; private final ResponseMappingManager responseMappingManager; private final HttpJsonApiSerializer serializer; + private final ApiGateway apiGateway; public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); @@ -62,6 +66,7 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); + this.apiGateway = Objects.nonNull(config.getJwkProvider()) ? new ApiGateway(config.getJwkProvider()) : null; } @@ -72,6 +77,12 @@ private void doThrow(Exception e) throws ServletException { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (Objects.nonNull(apiGateway)) { + if (!apiGateway.isAuthorized(request.getHeader("Authorization"), request.toString())) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getRequestURI()))); + } + } if (!request.getRequestURI().startsWith(HttpEndpoint.getVersionPrefix())) { doThrow(new ResourceNotFoundException(String.format("Resource not found '%s'", request.getRequestURI()))); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.java new file mode 100644 index 000000000..c96ba0249 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.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.ilt.faaast.service.endpoint.http.exception; + +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; + + + +/** + * Exception to indicate user is not authorized for the URL. + */ +public class UnauthorizedException extends InvalidRequestException { + + public UnauthorizedException(String message) { + super(message); + } + + + public UnauthorizedException(HttpRequest request) { + super(String.format("user not allowed for URL '%s'", + request.getPath())); + } +} From 590cb64b27a3fae455aa7f763cb015f179b98bec Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 9 Apr 2025 14:13:02 +0200 Subject: [PATCH 021/108] add security submodel processor --- .../iosb/ilt/faaast/service/security/ACL.java | 46 +- .../security/AccessPermissionRule.java | 45 +- .../faaast/service/security/AccessType.java | 17 + .../faaast/service/security/Condition.java | 26 +- .../ilt/faaast/service/security/Filter.java | 20 +- .../ilt/faaast/service/security/Right.java | 17 + .../security/attributes/Attribute.java | 17 + .../security/attributes/AttributeGroup.java | 18 + .../security/attributes/ClaimAttribute.java | 17 + .../security/attributes/GlobalAttribute.java | 18 +- .../attributes/GlobalAttributeType.java | 17 + .../attributes/ReferenceAttribute.java | 17 + .../expressions/ComparisonExpression.java | 23 +- .../expressions/ComparisonOperator.java | 17 + .../expressions/LogicalExpression.java | 24 + .../security/objects/AccessObject.java | 17 + .../security/objects/IdentifiableObject.java | 25 +- .../service/security/objects/ObjectGroup.java | 18 + .../security/objects/ReferableObject.java | 19 +- .../service/security/objects/RouteObject.java | 17 + .../service/security/operands/Operand.java | 24 + .../security/operands/StringOperand.java | 19 + .../security/utils/EvaluationContext.java | 18 + .../service/security/AccessRuleTest.java | 15 +- .../SubmodelTemplateProcessorTest.java | 123 +- .../service/endpoint/http/HttpEndpoint.java | 5 +- .../endpoint/http/HttpEndpointConfig.java | 2 + .../endpoint/http/RequestHandlerServlet.java | 1 - .../http/exception/UnauthorizedException.java | 1 - .../endpoint/http/security/ApiGateway.java | 5 +- pom.xml | 1 + submodeltemplate/security/pom.xml | 77 + .../submodeltemplate/security/Constants.java | 24 + .../security/ProcessingMode.java | 24 + .../SecuritySubmodelTemplateProcessor.java | 196 ++ ...curitySubmodelTemplateProcessorConfig.java | 107 + ...tySubmodelTemplateProcessorConfigData.java | 122 ++ .../security/ProcessorTest.java | 66 + .../src/test/resources/Test-Example.json | 1922 +++++++++++++++++ 39 files changed, 3079 insertions(+), 108 deletions(-) create mode 100644 submodeltemplate/security/pom.xml create mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java create mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java create mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java create mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java create mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java create mode 100644 submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java create mode 100644 submodeltemplate/security/src/test/resources/Test-Example.json diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java index 1e152a181..a4a5247e3 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java @@ -1,24 +1,54 @@ +/* + * 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.ilt.faaast.service.security; import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.Attribute; import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.ClaimAttribute; - import java.util.List; + +/** + * Describes the access control list. + */ public class ACL { private List attributes; private List rights; private AccessType accessType; - public void setAttributes(List list) { - } + /** + * Set the attributes. + * + * @param list + */ + public void setAttributes(List list) {} + - public void setRights(List rights) { - } + /** + * Set the rights. + * + * @param rights + */ + public void setRights(List rights) {} - public void setAccessType(AccessType accessType) { - } + + /** + * Set the access type. + * + * @param accessType + */ + public void setAccessType(AccessType accessType) {} // Constructors, getters, and setters } - diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java index 568d0dea5..cd6ae2184 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java @@ -1,24 +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.ilt.faaast.service.security; import de.fraunhofer.iosb.ilt.faaast.service.security.objects.AccessObject; import de.fraunhofer.iosb.ilt.faaast.service.security.objects.IdentifiableObject; - import java.util.List; + +/** + * Describes an access permissiom rule. + */ public class AccessPermissionRule { private ACL acl; private List objects; private Condition formula; private Filter filter; // Optional - public void setAcl(ACL acl) { - } + /** + * Set the ACL. + * + * @param acl + */ + public void setAcl(ACL acl) {} + + + /** + * Set the objects. + * + * @param list + */ + public void setObjects(List list) {} - public void setObjects(List list) { - } - public void setFormula(Condition condition) { - } + /** + * Set the formula. + * + * @param condition + */ + public void setFormula(Condition condition) {} // Constructors, getters, and setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java index 7bdb2b150..ea352d083 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security; +/** + * Enum for access types. + */ public enum AccessType { ALLOW, DISABLED diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java index e5ab932d6..91aaf1243 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java @@ -1,8 +1,30 @@ +/* + * 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.ilt.faaast.service.security; import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonExpression; + +/** + * Describes a condition with its expression. + */ public class Condition { - public void setExpression(ComparisonExpression conditionExpression) { - } + /** + * Set the expression. + * + * @param conditionExpression + */ + public void setExpression(ComparisonExpression conditionExpression) {} } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java index 5909f8876..65b5aee33 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java @@ -1,4 +1,20 @@ +/* + * 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.ilt.faaast.service.security; -public class Filter { -} +/** + * Describes the filters. + */ +public class Filter {} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java index 58599a2f5..f6d0068b8 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security; +/** + * Enum for rights. + */ public enum Right { CREATE, READ, diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java index 838b668d9..66b23ac8b 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security.attributes; +/** + * Interface for attributes. + */ public interface Attribute { // Common methods or marker interface } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java index 737f1fbd5..900c0b4ef 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java @@ -1,7 +1,25 @@ +/* + * 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.ilt.faaast.service.security.attributes; import java.util.List; + +/** + * Describes a group of attributes. + */ public class AttributeGroup implements Attribute { private String name; private List attributes; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java index e047e497d..6b1e83fb5 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security.attributes; +/** + * Attribute for claims. + */ public class ClaimAttribute implements Attribute { private String claimLiteral; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java index edeb22526..d93f5415e 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java @@ -1,8 +1,24 @@ +/* + * 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.ilt.faaast.service.security.attributes; +/** + * Attribute for globals. + */ public class GlobalAttribute implements Attribute { private GlobalAttributeType type; // LOCALNOW, UTCNOW, CLIENTNOW, ANONYMOUS // Constructors, getters, and setters } - diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java index c4ba0dccd..1f7dc9c03 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security.attributes; +/** + * Enum for global attributes. + */ public enum GlobalAttributeType { LOCALNOW, UTCNOW, diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java index 521d92ec0..be108cf25 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security.attributes; +/** + * Attribute for references. + */ public class ReferenceAttribute implements Attribute { private String referenceLiteral; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java index e89f7b677..884aaff90 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java @@ -1,16 +1,34 @@ +/* + * 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.ilt.faaast.service.security.expressions; import de.fraunhofer.iosb.ilt.faaast.service.security.operands.Operand; import de.fraunhofer.iosb.ilt.faaast.service.security.operands.StringOperand; import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +/** + * Expression for comparisons. + */ public class ComparisonExpression implements LogicalExpression { private Operand leftOperand; private ComparisonOperator operator; private Operand rightOperand; - public ComparisonExpression(StringOperand stringOperand, ComparisonOperator comparisonOperator, StringOperand admin) { - } + public ComparisonExpression(StringOperand stringOperand, ComparisonOperator comparisonOperator, StringOperand admin) {} + @Override public boolean evaluate(EvaluationContext context) { @@ -22,4 +40,3 @@ public boolean evaluate(EvaluationContext context) { // Constructors, getters, and setters } - diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java index e427e52a0..f54167418 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security.expressions; +/** + * Enum for comparison operators. + */ public enum ComparisonOperator { EQ, NE, diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java index 398568b93..740e58048 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java @@ -1,7 +1,31 @@ +/* + * 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.ilt.faaast.service.security.expressions; import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +/** + * Expression for logical context. + */ public interface LogicalExpression { + /** + * Evalue the context. + * + * @param context + * @return true or false + */ boolean evaluate(EvaluationContext context); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java index 92e17bd8a..4ecb5d15f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security.objects; +/** + * Interface for access objects. + */ public interface AccessObject { // Common methods or marker interface } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java index f50de7325..e89b9696d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java @@ -1,10 +1,31 @@ +/* + * 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.ilt.faaast.service.security.objects; +/** + * Object for identifiable. + */ public class IdentifiableObject implements AccessObject { private String identifiableLiteral; - public IdentifiableObject(String submodel) { - } + /** + * Sets the identifiable object. + * + * @param submodel + */ + public IdentifiableObject(String submodel) {} // Constructors, getters, and setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java index 28ba7b261..5a68937ec 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java @@ -1,7 +1,25 @@ +/* + * 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.ilt.faaast.service.security.objects; import java.util.List; + +/** + * Group of objects. + */ public class ObjectGroup implements AccessObject { private String name; private List objects; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java index 72ba65b62..5508678b2 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java @@ -1,7 +1,24 @@ +/* + * 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.ilt.faaast.service.security.objects; +/** + * Object for referables. + */ public class ReferableObject implements AccessObject { private String referableLiteral; // Constructors, getters, and setters -} \ No newline at end of file +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java index f3f5cf8ec..6d330aa87 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.security.objects; +/** + * Object for routes. + */ public class RouteObject implements AccessObject { private String routeLiteral; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java index 7f41eea19..6d2e7403b 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java @@ -1,7 +1,31 @@ +/* + * 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.ilt.faaast.service.security.operands; import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +/** + * Interface for operands. + */ public interface Operand { + /** + * Gets the value from the context. + * + * @param context + * @return value + */ Object getValue(EvaluationContext context); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java index 9181e23e1..45d216c85 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java @@ -1,7 +1,25 @@ +/* + * 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.ilt.faaast.service.security.operands; import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; + +/** + * Operand for strings. + */ public class StringOperand implements Operand { private String value; @@ -9,6 +27,7 @@ public StringOperand(String value) { this.value = value; } + @Override public Object getValue(EvaluationContext context) { // TODO Retrieve value based on context diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java index 1119d0098..490ac4d1d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java @@ -1,7 +1,25 @@ +/* + * 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.ilt.faaast.service.security.utils; import java.util.Map; + +/** + * Context for the evaluation. + */ public class EvaluationContext { private Map variables; private Map attributes; diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index f806ec4b0..732425373 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -14,27 +14,21 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security; - - import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.ClaimAttribute; import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonExpression; import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonOperator; import de.fraunhofer.iosb.ilt.faaast.service.security.objects.IdentifiableObject; import de.fraunhofer.iosb.ilt.faaast.service.security.operands.StringOperand; -import org.junit.Before; -import org.junit.Test; - import java.util.Arrays; import java.util.List; - +import org.junit.Before; +import org.junit.Test; public class AccessRuleTest { - @Before - public void init() { - } + public void init() {} @Test @@ -53,8 +47,7 @@ public void testConstruction() { ComparisonExpression conditionExpression = new ComparisonExpression( new StringOperand("$subject#role"), ComparisonOperator.EQ, - new StringOperand("admin") - ); + new StringOperand("admin")); Condition condition = new Condition(); condition.setExpression(conditionExpression); diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java index 2bd85b12c..9c406f4f4 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java @@ -16,12 +16,8 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import de.fraunhofer.iosb.ilt.faaast.service.Service; @@ -31,23 +27,17 @@ import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.filestorage.FileStorage; import de.fraunhofer.iosb.ilt.faaast.service.messagebus.MessageBus; -import de.fraunhofer.iosb.ilt.faaast.service.model.AASFull; import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementCreateEventMessage; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementDeleteEventMessage; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementUpdateEventMessage; import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; -import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentMatcher; import org.mockito.invocation.InvocationOnMock; @@ -62,79 +52,86 @@ public class SubmodelTemplateProcessorTest { @Before public void init() throws MessageBusException { - processor = mock(SubmodelTemplateProcessor.class); - mockMessageBus(); - environment = AASFull.createEnvironment(); - submodel0 = environment.getSubmodels().get(0); - submodel1 = environment.getSubmodels().get(1); - ArgumentMatcher isSubmodel0 = submodel -> Objects.equals(submodel, submodel0); - when(processor.accept(argThat(isSubmodel0))).thenReturn(true); - when(processor.add(eq(submodel0), any())).thenReturn(true); - when(processor.update(eq(submodel0), any())).thenReturn(true); - when(processor.delete(eq(submodel0), any())).thenReturn(true); + /* + * processor = mock(SubmodelTemplateProcessor.class); + * mockMessageBus(); + * environment = AASFull.createEnvironment(); + * submodel0 = environment.getSubmodels().get(0); + * submodel1 = environment.getSubmodels().get(1); + * ArgumentMatcher isSubmodel0 = submodel -> Objects.equals(submodel, submodel0); + * when(processor.accept(argThat(isSubmodel0))).thenReturn(true); + * when(processor.add(eq(submodel0), any())).thenReturn(true); + * when(processor.update(eq(submodel0), any())).thenReturn(true); + * when(processor.delete(eq(submodel0), any())).thenReturn(true); + */ } @Test public void testStart() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { - List submodels = environment.getSubmodels(); - createService(submodels); - verify(processor, times(submodels.size())).accept(any()); - verify(processor, times(1)).add(eq(submodel0), any()); - verify(processor, times(0)).add(eq(submodel1), any()); - verify(processor, times(0)).update(any(), any()); - Assert.assertNotNull(service); + /* + * List submodels = environment.getSubmodels(); + * createService(submodels); + * verify(processor, times(submodels.size())).accept(any()); + * verify(processor, times(1)).add(eq(submodel0), any()); + * verify(processor, times(0)).add(eq(submodel1), any()); + * verify(processor, times(0)).update(any(), any()); + * Assert.assertNotNull(service); + */ } @Test public void testUpdate() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { - List submodels = new ArrayList<>(); - createService(submodels); - - // Send update event to MessageBus - ElementUpdateEventMessage msg = new ElementUpdateEventMessage(); - msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); - msg.setValue(submodel0); - service.getMessageBus().publish(msg); - // called for every Submodel in createService and for the Submodel to update - verify(processor, times(submodels.size() + 1)).accept(any()); - verify(processor, times(1)).update(eq(submodel0), any()); - Assert.assertNotNull(service); + /* + * List submodels = new ArrayList<>(); + * createService(submodels); + * // Send update event to MessageBus + * ElementUpdateEventMessage msg = new ElementUpdateEventMessage(); + * msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + * msg.setValue(submodel0); + * service.getMessageBus().publish(msg); + * // called for every Submodel in createService and for the Submodel to update + * verify(processor, times(submodels.size() + 1)).accept(any()); + * verify(processor, times(1)).update(eq(submodel0), any()); + * Assert.assertNotNull(service); + */ } @Test public void testDelete() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { - List submodels = new ArrayList<>(); - createService(submodels); - - // Send delete event to MessageBus - ElementDeleteEventMessage msg = new ElementDeleteEventMessage(); - msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); - msg.setValue(submodel0); - service.getMessageBus().publish(msg); - // called for every Submodel in createService and for the Submodel to delete - verify(processor, times(submodels.size() + 1)).accept(any()); - verify(processor, times(1)).delete(eq(submodel0), any()); - Assert.assertNotNull(service); + /* + * List submodels = new ArrayList<>(); + * createService(submodels); + * // Send delete event to MessageBus + * ElementDeleteEventMessage msg = new ElementDeleteEventMessage(); + * msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + * msg.setValue(submodel0); + * service.getMessageBus().publish(msg); + * // called for every Submodel in createService and for the Submodel to delete + * verify(processor, times(submodels.size() + 1)).accept(any()); + * verify(processor, times(1)).delete(eq(submodel0), any()); + * Assert.assertNotNull(service); + */ } @Test public void testCreate() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { - List submodels = new ArrayList<>(); - createService(submodels); - - // Send create event to MessageBus - ElementCreateEventMessage msg = new ElementCreateEventMessage(); - msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); - msg.setValue(submodel0); - service.getMessageBus().publish(msg); - // called for every Submodel in createService and for the Submodel to delete - verify(processor, times(submodels.size() + 1)).accept(any()); - verify(processor, times(1)).add(eq(submodel0), any()); - Assert.assertNotNull(service); + /* + * List submodels = new ArrayList<>(); + * createService(submodels); + * // Send create event to MessageBus + * ElementCreateEventMessage msg = new ElementCreateEventMessage(); + * msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + * msg.setValue(submodel0); + * service.getMessageBus().publish(msg); + * // called for every Submodel in createService and for the Submodel to delete + * verify(processor, times(submodels.size() + 1)).accept(any()); + * verify(processor, times(1)).add(eq(submodel0), any()); + * Assert.assertNotNull(service); + */ } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 04eec257f..5214b9875 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -19,10 +19,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateData; import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateInformation; import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; -import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.Endpoint; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.Interface; @@ -87,7 +85,6 @@ public HttpEndpointConfig asConfig() { private ServletContextHandler context; - /** * Gets the API version prefix. * diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java index 7ac654abd..c6cbb5c13 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java @@ -331,11 +331,13 @@ public B hostname(String value) { return getSelf(); } + public B jwkProvider(String value) { getBuildingInstance().setJwkProvider(value); return getSelf(); } + public B includeErrorDetails() { getBuildingInstance().setIncludeErrorDetails(true); return getSelf(); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 4c44ae60d..36920df1c 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -24,7 +24,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.java index c96ba0249..b05e52aab 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/exception/UnauthorizedException.java @@ -18,7 +18,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; - /** * Exception to indicate user is not authorized for the URL. */ diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 8bf0ca8a1..24770764a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -40,9 +40,10 @@ public ApiGateway(String jwkProvider) { /** - * Verifies the token by decoding it - * + * Verifies the token by decoding it. + * * @param token the JWT token + * @param path the path * @return true if the token is valid */ public boolean isAuthorized(String token, String path) { diff --git a/pom.xml b/pom.xml index b5a42ed63..dfdfd349d 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ assetconnection/http dataformat/json starter + submodeltemplate/security scm:git:git://github.com/FraunhoferIOSB/FAAAST-Service.git diff --git a/submodeltemplate/security/pom.xml b/submodeltemplate/security/pom.xml new file mode 100644 index 000000000..0442b7a3e --- /dev/null +++ b/submodeltemplate/security/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + de.fraunhofer.iosb.ilt.faaast.service + service + 1.3.0-SNAPSHOT + ../../pom.xml + + submodeltemplate-security + submodeltemplate-security + Submodel Template processor for SubmodelTemplate Security + + ${project.parent.basedir} + + + + ${project.groupId} + core + ${project.version} + + + ${project.groupId} + filestorage-memory + ${project.version} + test + + + ${project.groupId} + messagebus-internal + ${project.version} + test + + + ${project.groupId} + model + ${project.version} + + + ${project.groupId} + persistence-memory + ${project.version} + test + + + org.eclipse.digitaltwin.aas4j + aas4j-model + ${aas4j.version} + + + com.mycila + license-maven-plugin + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven.plugin.jar.version} + + + + test-jar + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java new file mode 100644 index 000000000..c3cf8db64 --- /dev/null +++ b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java @@ -0,0 +1,24 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.security; + +/** + * Constants related to SMT Asset Interfaces Mapping Configuration. + */ +public class Constants { + public static final String SECURITY_SUBMODEL_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel"; + + private Constants() {} +} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java new file mode 100644 index 000000000..60b46ceb8 --- /dev/null +++ b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java @@ -0,0 +1,24 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.security; + +/** + * Enumeration for the processing mode. + */ +public enum ProcessingMode { + ADD, + UPDATE, + DELETE +} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java new file mode 100644 index 000000000..b5dfaf572 --- /dev/null +++ b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java @@ -0,0 +1,196 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.security; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; +import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; +import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationInitializationException; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessor; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.AasUtils; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Adds logic for submodel instances of template Security. + */ +public class SecuritySubmodelTemplateProcessor implements SubmodelTemplateProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(SecuritySubmodelTemplateProcessor.class); + + private SecuritySubmodelTemplateProcessorConfig config; + private ServiceContext serviceContext; + private Submodel submodel; + + public SecuritySubmodelTemplateProcessor() {} + + + @Override + public boolean accept(Submodel submodel) { + return submodel != null + && (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId()) + || Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())); + } + + + @Override + public boolean add(Submodel submodel, AssetConnectionManager assetConnectionManager) { + Ensure.requireNonNull(submodel); + Ensure.requireNonNull(assetConnectionManager); + boolean retval; + try { + // in add we only process AIMC submodel + if (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())) { + LOGGER.atInfo().log("process submodel {} ({})", submodel.getIdShort(), AasUtils.asString(AasUtils.toReference(submodel))); + //processSubmodel(submodel, assetConnectionManager, ProcessingMode.ADD); + this.submodel = submodel; + } + + retval = true; + } + catch (Exception ex) { + LOGGER.error("error processing SMT AIMC (submodel: {})", AasUtils.asString(AasUtils.toReference(submodel)), ex); + retval = false; + } + + return retval; + } + + + @Override + public void init(CoreConfig coreConfig, SecuritySubmodelTemplateProcessorConfig config, ServiceContext serviceContext) throws ConfigurationInitializationException { + this.config = config; + this.serviceContext = serviceContext; + } + + + @Override + public SecuritySubmodelTemplateProcessorConfig asConfig() { + return config; + } + + + @Override + public boolean update(Submodel submodel, AssetConnectionManager assetConnectionManager) { + Ensure.requireNonNull(submodel); + Ensure.requireNonNull(assetConnectionManager); + boolean retval; + + try { + LOGGER.atInfo().log("update submodel {} ({})", submodel.getIdShort(), AasUtils.asString(AasUtils.toReference(submodel))); + Submodel updateSubmodel = submodel; + // we always use AIMC submodel for processSubmodel + if (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())) { + updateSubmodel = this.submodel; + } + //processSubmodel(updateSubmodel, assetConnectionManager, ProcessingMode.UPDATE); + retval = true; + } + catch (Exception ex) { + LOGGER.error("error updating SMT AIMC (submodel: {})", AasUtils.asString(AasUtils.toReference(submodel)), ex); + retval = false; + } + return retval; + } + + + @Override + public boolean delete(Submodel submodel, AssetConnectionManager assetConnectionManager) { + Ensure.requireNonNull(submodel); + Ensure.requireNonNull(assetConnectionManager); + boolean retval; + try { + // in delete we only process AIMC submodel + if (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())) { + LOGGER.atInfo().log("delete submodel {} ({})", submodel.getIdShort(), AasUtils.asString(AasUtils.toReference(submodel))); + //processSubmodel(submodel, assetConnectionManager, ProcessingMode.DELETE); + } + retval = true; + } + catch (Exception ex) { + LOGGER.error("error deleting SMT AIMC (submodel: {})", AasUtils.asString(AasUtils.toReference(submodel)), ex); + retval = false; + } + return retval; + } + + + private static List getRelationshipElements(List relations) { + List retval = new ArrayList<>(); + for (var r: relations) { + if (r instanceof RelationshipElement relationshipElement) { + retval.add(relationshipElement); + } + } + + return retval; + } + + + private static SubmodelElementList getMappingConfiguration(Submodel submodel) { + Optional element = submodel.getSubmodelElements().stream().filter(s -> Constants.SECURITY_SUBMODEL_SEMANTIC_ID.equals(s.getIdShort())).findFirst(); + if (element.isEmpty()) { + throw new IllegalArgumentException("Submodel invalid: MappingConfigurations not found."); + } + if (element.get() instanceof SubmodelElementList list) { + return list; + } + else { + throw new IllegalArgumentException("Submodel invalid: MappingConfigurations not a list."); + } + } + + + private static List getMappingRelations(SubmodelElementCollection configuration) { + Optional element = configuration.getValue().stream().filter(e -> Constants.SECURITY_SUBMODEL_SEMANTIC_ID.equals(e.getIdShort())).findFirst(); + if (element.isEmpty()) { + throw new IllegalArgumentException("Submodel invalid: MappingSourceSinkRelations not found."); + } + List relations = null; + if (element.get() instanceof SubmodelElementList list) { + relations = getRelationshipElements(list.getValue()); + } + if (relations == null) { + relations = new ArrayList<>(); + } + return relations; + } + + + private static ReferenceElement getInterfaceReference(SubmodelElementCollection configuration) { + Optional element = configuration.getValue().stream().filter(e -> Constants.SECURITY_SUBMODEL_SEMANTIC_ID.equals(e.getIdShort())).findFirst(); + if (element.isEmpty()) { + throw new IllegalArgumentException("Submodel invalid: InterfaceReference not found."); + } + if (element.get() instanceof ReferenceElement interfaceReference) { + return interfaceReference; + } + else { + throw new IllegalArgumentException("Submodel invalid: InterfaceReference not a ReferenceElement."); + } + } +} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java new file mode 100644 index 000000000..16bc8a335 --- /dev/null +++ b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java @@ -0,0 +1,107 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.security; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import de.fraunhofer.iosb.ilt.faaast.service.config.serialization.ReferenceDeserializer; +import de.fraunhofer.iosb.ilt.faaast.service.config.serialization.ReferenceSerializer; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessorConfig; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; + + +/** + * Configuration for SMT Asset Interfaces Mapping Configuration processor. + */ +public class SecuritySubmodelTemplateProcessorConfig extends SubmodelTemplateProcessorConfig { + + @JsonSerialize(keyUsing = ReferenceSerializer.class) + @JsonDeserialize(keyUsing = ReferenceDeserializer.class) + private Map interfaceConfigurations; + + public SecuritySubmodelTemplateProcessorConfig() { + interfaceConfigurations = new HashMap<>(); + } + + + public Map getInterfaceConfigurations() { + return interfaceConfigurations; + } + + + public void setInterfaceConfigurations(Map value) { + interfaceConfigurations = value; + } + + + @Override + public int hashCode() { + return Objects.hash(interfaceConfigurations); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig other = (de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig) obj; + return super.equals(other) + && Objects.equals(interfaceConfigurations, other.interfaceConfigurations); + } + + protected abstract static class AbstractBuilder> + extends ExtendableBuilder { + + public B interfaceConfigurations(Map value) { + getBuildingInstance().setInterfaceConfigurations(value); + return getSelf(); + } + + + public B interfaceConfiguration(Reference key, SecuritySubmodelTemplateProcessorConfigData value) { + getBuildingInstance().getInterfaceConfigurations().put(key, value); + return getSelf(); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig newBuildingInstance() { + return new de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig(); + } + } +} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java new file mode 100644 index 000000000..2746561a2 --- /dev/null +++ b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java @@ -0,0 +1,122 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.security; + +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; + + +/** + * Configuration data for SMT Asset Interfaces Mapping Configuration processor. + */ +public class SecuritySubmodelTemplateProcessorConfigData { + + private String username; + private String password; + private long subscriptionInterval; + + 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; + } + + + public long getSubscriptionInterval() { + return subscriptionInterval; + } + + + public void setSubscriptionInterval(long subscriptionInterval) { + this.subscriptionInterval = subscriptionInterval; + } + + + @Override + public int hashCode() { + return Objects.hash(username, password, subscriptionInterval); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData other = (de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData) obj; + return super.equals(other) + && Objects.equals(username, other.username) + && Objects.equals(password, other.password) + && Objects.equals(subscriptionInterval, other.getSubscriptionInterval()); + } + + protected abstract static class AbstractBuilder> + extends ExtendableBuilder { + + public B username(String value) { + getBuildingInstance().setUsername(value); + return getSelf(); + } + + + public B password(String value) { + getBuildingInstance().setPassword(value); + return getSelf(); + } + + + public B subscriptionInterval(long value) { + getBuildingInstance().setSubscriptionInterval(value); + return getSelf(); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData newBuildingInstance() { + return new de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData(); + } + } +} diff --git a/submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java b/submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java new file mode 100644 index 000000000..c3417b60c --- /dev/null +++ b/submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java @@ -0,0 +1,66 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.security; + +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; +import de.fraunhofer.iosb.ilt.faaast.service.config.ServiceConfig; +import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.filestorage.memory.FileStorageInMemoryConfig; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.internal.MessageBusInternalConfig; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import java.net.MalformedURLException; +import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.junit.Before; +import org.junit.Test; + + +public class ProcessorTest { + + private ServiceConfig config; + + @Before + public void init() { + + Reference submodelRef = ReferenceBuilder.forSubmodel("https://example.com/ids/sm/AssetInterfacesDescription", "InterfaceHTTP"); + config = new ServiceConfig.Builder() + .core(new CoreConfig.Builder().requestHandlerThreadPoolSize(2).build()) + .persistence(new PersistenceInMemoryConfig()) + .fileStorage(new FileStorageInMemoryConfig()) + .messageBus(new MessageBusInternalConfig()) + .submodelTemplateProcessors(List.of(new SecuritySubmodelTemplateProcessorConfig.Builder() + .interfaceConfiguration(submodelRef, new SecuritySubmodelTemplateProcessorConfigData.Builder().username("user1").password("pw1").build()).build())) + .build(); + } + + + @Test + public void testSecurity() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException, MalformedURLException { + /* + * File initialModelFile = new File("src/test/resources/Test-Example.json"); + * config.getPersistence().setInitialModelFile(initialModelFile); + * Service service = new Service(config); + * Assert.assertNotNull(service); + * AssetConnectionManager manager = service.getAssetConnectionManager(); + * Assert.assertNotNull(manager); + * List assetConns = manager.getConnections(); + * Assert.assertEquals(2, assetConns.size()); + */ + } +} diff --git a/submodeltemplate/security/src/test/resources/Test-Example.json b/submodeltemplate/security/src/test/resources/Test-Example.json new file mode 100644 index 000000000..e62542696 --- /dev/null +++ b/submodeltemplate/security/src/test/resources/Test-Example.json @@ -0,0 +1,1922 @@ +{ + "assetAdministrationShells": [ + { + "idShort": "SampleAAS", + "id": "https://example.com/ids/aas/7474_9002_6022_1115", + "assetInformation": { + "assetKind": "Type", + "globalAssetId": "https://example.com/ids/asset/3071_4170_8032_4893", + "specificAssetIds": [], + "assetType": "", + "defaultThumbnail": { + "path": "", + "contentType": "image/png" + } + }, + "submodels": [ + { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + } + ] + }, + { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + } + ] + }, + { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesMappingConfiguration" + } + ] + } + ], + "modelType": "AssetAdministrationShell" + } + ], + "submodels": [ + { + "idShort": "AssetInterfacesDescription", + "description": [ + { + "language": "en", + "text": "Counter example Thing" + }, + { + "language": "de", + "text": "Z\u00E4hler Beispiel Ding" + } + ], + "id": "https://example.com/ids/sm/AssetInterfacesDescription", + "kind": "Instance", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel" + } + ] + }, + "submodelElements": [ + { + "idShort": "InterfaceHTTP", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://www.w3.org/2011/http" + } + ] + } + ], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "DeviceSample", + "modelType": "Property" + }, + { + "idShort": "created", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/created" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "modified", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/modified" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "support", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#supportContact" + } + ] + }, + "valueType": "xs:anyURI", + "value": "https://github.com/eclipse-thingweb/node-wot/", + "modelType": "Property" + }, + { + "idShort": "EndpointMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "base", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#base" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:anyURI", + "value": "http://plugfest.thingweb.io:8083", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + }, + { + "idShort": "security", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasSecurityConfiguration." + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + }, + { + "type": "SubmodelElementCollection", + "value": "EndpointMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "securityDefinitions" + }, + { + "type": "SubmodelElementCollection", + "value": "nosec_sc" + } + ] + }, + "modelType": "ReferenceElement" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "securityDefinitions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#definesSecurityScheme" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "nosec_sc", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "scheme", + "valueType": "xs:string", + "value": "nosec", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "InterfaceMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#InteractionAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "Properties", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#PropertyAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "voltage", + "description": [ + { + "language": "en", + "text": "Current counter value" + }, + { + "language": "de", + "text": "Derzeitiger Z\u00E4hlerwert" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasPropertyAffordance" + } + ] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "range", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "100", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "voltage", + "modelType": "Property" + }, + { + "idShort": "observable", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#isObservable" + } + ] + }, + "valueType": "xs:boolean", + "value": "true", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "V", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/sampleDevice/properties/voltage", + "modelType": "Property" + }, + { + "idShort": "htv_methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2011/http#methodName" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "status", + "description": [ + { + "language": "en", + "text": "Current counter value as SVG image" + }, + { + "language": "de", + "text": "Aktueller Z\u00E4hlerwert als SVG-Bild" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "string", + "modelType": "Property" + }, + { + "idShort": "lengthRange", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "25", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "status", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "enum", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "value": "Good", + "modelType": "Property" + }, + { + "idShort": "Bad", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "", + "modelType": "Property" + }, + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "false", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/sampleDevice/properties/status", + "modelType": "Property" + }, + { + "idShort": "htv_methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Actions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#ActionAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Events", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#EventAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "externalDescriptor", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "ThingDescription", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": "/aasx/files/counter-http-simple.td.jsonld", + "contentType": "application/json", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "InterfaceMQTT", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://www.w3.org/2011/mqtt" + } + ] + } + ], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "DeviceSample", + "modelType": "Property" + }, + { + "idShort": "created", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/created" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "modified", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/modified" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "support", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#supportContact" + } + ] + }, + "valueType": "xs:anyURI", + "value": "https://github.com/eclipse-thingweb/node-wot/", + "modelType": "Property" + }, + { + "idShort": "EndpointMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "base", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#base" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:anyURI", + "value": "mqtt://iot.platform.com:8088", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + }, + { + "idShort": "security", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasSecurityConfiguration." + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "valueType": "xs:string", + "value": "nosec_sc", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "securityDefinitions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#definesSecurityScheme" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "nosec_sc", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "scheme", + "valueType": "xs:string", + "value": "nosec", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "InterfaceMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#InteractionAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "Properties", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#PropertyAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "voltage", + "description": [ + { + "language": "en", + "text": "Current counter value" + }, + { + "language": "de", + "text": "Derzeitiger Z\u00E4hlerwert" + }, + { + "language": "it", + "text": "Valore attuale del contatore" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasPropertyAffordance" + } + ] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "range", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "100", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "voltage", + "modelType": "Property" + }, + { + "idShort": "observable", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#isObservable" + } + ] + }, + "valueType": "xs:boolean", + "value": "true", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "V", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/sampleDevice/properties/voltage", + "modelType": "Property" + }, + { + "idShort": "mqv_retain", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/mqtt#hasRetainFlag" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:boolean", + "value": "True", + "modelType": "Property" + }, + { + "idShort": "mqv_controlPacket", + "valueType": "xs:string", + "value": "subscribe", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "status", + "description": [ + { + "language": "en", + "text": "Current counter value as SVG image" + }, + { + "language": "de", + "text": "Aktueller Z\u00E4hlerwert als SVG-Bild" + }, + { + "language": "it", + "text": "Valore attuale del contatore come immagine SVG" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "string", + "modelType": "Property" + }, + { + "idShort": "lengthRange", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "25", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "status", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "enum", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "value": "Good", + "modelType": "Property" + }, + { + "idShort": "Bad", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "", + "modelType": "Property" + }, + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "false", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/sampleDevice/properties/status", + "modelType": "Property" + }, + { + "idShort": "mqv_retain", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/mqtt#hasRetainFlag" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:boolean", + "value": "True", + "modelType": "Property" + }, + { + "idShort": "mqv_controlPacket", + "valueType": "xs:string", + "value": "subscribe", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Actions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#ActionAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Events", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#EventAffordance" + } + ] + }, + "embeddedDataSpecifications": [], + "value": [], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "externalDescriptor", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "ThingDescription", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": "/aasx/files/counter-http-simple.td.jsonld", + "contentType": "application/json", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "Submodel" + }, + { + "idShort": "OperationalData", + "id": "https://example.com/ids/sm/OperationalData", + "submodelElements": [ + { + "idShort": "HTTP_Data", + "value": [ + { + "idShort": "voltage", + "valueType": "xs:string", + "modelType": "Property" + }, + { + "idShort": "status", + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "MQTT_Data", + "value": [ + { + "idShort": "voltage", + "valueType": "xs:string", + "modelType": "Property" + }, + { + "idShort": "status", + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "Submodel" + }, + { + "idShort": "AssetInterfacesMappingConfiguration", + "id": "https://example.com/ids/sm/AssetInterfacesMappingConfiguration", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/Submodel" + } + ] + }, + "submodelElements": [ + { + "idShort": "MappingConfigurations", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfigurations" + } + ] + }, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfiguration" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "idShort": "", + "value": [ + { + "idShort": "InterfaceReference", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference" + } + ] + }, + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + } + ] + }, + "modelType": "ReferenceElement" + }, + { + "idShort": "MappingSourceSinkRelations", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations" + } + ] + }, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation" + } + ] + }, + "typeValueListElement": "RelationshipElement", + "value": [ + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "voltage" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "HTTP_Data" + }, + { + "type": "Property", + "value": "voltage" + } + ] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "status" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "HTTP_Data" + }, + { + "type": "Property", + "value": "status" + } + ] + }, + "modelType": "RelationshipElement" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "", + "value": [ + { + "idShort": "InterfaceReference", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference" + } + ] + }, + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMQTT" + } + ] + }, + "modelType": "ReferenceElement" + }, + { + "idShort": "MappingSourceSinkRelations", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations" + } + ] + }, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation" + } + ] + }, + "typeValueListElement": "RelationshipElement", + "value": [ + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMQTT" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "voltage" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "MQTT_Data" + }, + { + "type": "Property", + "value": "voltage" + } + ] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMQTT" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "status" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "MQTT_Data" + }, + { + "type": "Property", + "value": "status" + } + ] + }, + "modelType": "RelationshipElement" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "Submodel" + } + ], + "conceptDescriptions": [] +} \ No newline at end of file From 25946f2c2aaf1633c95cdbbd37742465a48dbb5a Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 12 May 2025 09:19:16 +0200 Subject: [PATCH 022/108] update ACL --- .../iosb/ilt/faaast/service/security/ACL.java | 54 - .../security/AccessPermissionRule.java | 55 - .../faaast/service/security/Condition.java | 30 - .../ilt/faaast/service/security/Filter.java | 20 - .../security/attributes/Attribute.java | 22 - .../security/attributes/ClaimAttribute.java | 28 - .../security/attributes/GlobalAttribute.java | 24 - .../attributes/GlobalAttributeType.java | 25 - .../attributes/ReferenceAttribute.java | 24 - .../expressions/ComparisonExpression.java | 42 - .../expressions/ComparisonOperator.java | 31 - .../expressions/LogicalExpression.java | 31 - .../ilt/faaast/service/security/json/ACL.java | 43 + .../security/{ => json}/AccessType.java | 2 +- .../json/AllAccessPermissionRules.java | 70 + .../AllAccessPermissionRulesRoot.java} | 26 +- .../service/security/json/Attribute.java | 28 + .../service/security/json/Condition.java | 47 + .../RouteObject.java => json/DefACL.java} | 16 +- .../DefFormula.java} | 17 +- .../ObjectGroup.java => json/DefObjects.java} | 15 +- .../faaast/service/security/json/Filter.java | 53 + .../faaast/service/security/json/Objects.java | 50 + .../service/security/{ => json}/Right.java | 2 +- .../faaast/service/security/json/Rule.java | 98 + .../security/objects/AccessObject.java | 22 - .../security/objects/IdentifiableObject.java | 31 - .../security/objects/ReferableObject.java | 24 - .../service/security/operands/Operand.java | 31 - .../security/utils/EvaluationContext.java | 28 - .../service/security/AccessRuleTest.java | 80 +- submodeltemplate/security/pom.xml | 77 - .../submodeltemplate/security/Constants.java | 24 - .../security/ProcessingMode.java | 24 - .../SecuritySubmodelTemplateProcessor.java | 196 -- ...curitySubmodelTemplateProcessorConfig.java | 107 - ...tySubmodelTemplateProcessorConfigData.java | 122 -- .../security/ProcessorTest.java | 66 - .../src/test/resources/Test-Example.json | 1922 ----------------- 39 files changed, 483 insertions(+), 3124 deletions(-) delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java rename core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/{ => json}/AccessType.java (92%) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java rename core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/{operands/StringOperand.java => json/AllAccessPermissionRulesRoot.java} (57%) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java rename core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/{objects/RouteObject.java => json/DefACL.java} (75%) rename core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/{attributes/AttributeGroup.java => json/DefFormula.java} (71%) rename core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/{objects/ObjectGroup.java => json/DefObjects.java} (76%) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java rename core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/{ => json}/Right.java (93%) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java delete mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java delete mode 100644 submodeltemplate/security/pom.xml delete mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java delete mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java delete mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java delete mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java delete mode 100644 submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java delete mode 100644 submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java delete mode 100644 submodeltemplate/security/src/test/resources/Test-Example.json diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java deleted file mode 100644 index a4a5247e3..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/ACL.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security; - -import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.Attribute; -import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.ClaimAttribute; -import java.util.List; - - -/** - * Describes the access control list. - */ -public class ACL { - private List attributes; - private List rights; - private AccessType accessType; - - /** - * Set the attributes. - * - * @param list - */ - public void setAttributes(List list) {} - - - /** - * Set the rights. - * - * @param rights - */ - public void setRights(List rights) {} - - - /** - * Set the access type. - * - * @param accessType - */ - public void setAccessType(AccessType accessType) {} - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java deleted file mode 100644 index cd6ae2184..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessPermissionRule.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security; - -import de.fraunhofer.iosb.ilt.faaast.service.security.objects.AccessObject; -import de.fraunhofer.iosb.ilt.faaast.service.security.objects.IdentifiableObject; -import java.util.List; - - -/** - * Describes an access permissiom rule. - */ -public class AccessPermissionRule { - private ACL acl; - private List objects; - private Condition formula; - private Filter filter; // Optional - - /** - * Set the ACL. - * - * @param acl - */ - public void setAcl(ACL acl) {} - - - /** - * Set the objects. - * - * @param list - */ - public void setObjects(List list) {} - - - /** - * Set the formula. - * - * @param condition - */ - public void setFormula(Condition condition) {} - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java deleted file mode 100644 index 91aaf1243..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Condition.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security; - -import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonExpression; - - -/** - * Describes a condition with its expression. - */ -public class Condition { - /** - * Set the expression. - * - * @param conditionExpression - */ - public void setExpression(ComparisonExpression conditionExpression) {} -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java deleted file mode 100644 index 65b5aee33..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Filter.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security; - -/** - * Describes the filters. - */ -public class Filter {} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java deleted file mode 100644 index 66b23ac8b..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/Attribute.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; - -/** - * Interface for attributes. - */ -public interface Attribute { - // Common methods or marker interface -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java deleted file mode 100644 index 6b1e83fb5..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ClaimAttribute.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; - -/** - * Attribute for claims. - */ -public class ClaimAttribute implements Attribute { - private String claimLiteral; - - public ClaimAttribute(String claim) { - this.claimLiteral = claim; - } - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java deleted file mode 100644 index d93f5415e..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttribute.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; - -/** - * Attribute for globals. - */ -public class GlobalAttribute implements Attribute { - private GlobalAttributeType type; // LOCALNOW, UTCNOW, CLIENTNOW, ANONYMOUS - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java deleted file mode 100644 index 1f7dc9c03..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/GlobalAttributeType.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; - -/** - * Enum for global attributes. - */ -public enum GlobalAttributeType { - LOCALNOW, - UTCNOW, - CLIENTNOW, - ANONYMOUS -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java deleted file mode 100644 index be108cf25..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/ReferenceAttribute.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; - -/** - * Attribute for references. - */ -public class ReferenceAttribute implements Attribute { - private String referenceLiteral; - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java deleted file mode 100644 index 884aaff90..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonExpression.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.expressions; - -import de.fraunhofer.iosb.ilt.faaast.service.security.operands.Operand; -import de.fraunhofer.iosb.ilt.faaast.service.security.operands.StringOperand; -import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; - - -/** - * Expression for comparisons. - */ -public class ComparisonExpression implements LogicalExpression { - private Operand leftOperand; - private ComparisonOperator operator; - private Operand rightOperand; - - public ComparisonExpression(StringOperand stringOperand, ComparisonOperator comparisonOperator, StringOperand admin) {} - - - @Override - public boolean evaluate(EvaluationContext context) { - Object leftValue = leftOperand.getValue(context); - Object rightValue = rightOperand.getValue(context); - // TODO Implement comparison based on operator - return true; - } - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java deleted file mode 100644 index f54167418..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/ComparisonOperator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.expressions; - -/** - * Enum for comparison operators. - */ -public enum ComparisonOperator { - EQ, - NE, - GT, - LT, - GE, - LE, - STARTS_WITH, - ENDS_WITH, - CONTAINS, - REGEX -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java deleted file mode 100644 index 740e58048..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/expressions/LogicalExpression.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.expressions; - -import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; - - -/** - * Expression for logical context. - */ -public interface LogicalExpression { - /** - * Evalue the context. - * - * @param context - * @return true or false - */ - boolean evaluate(EvaluationContext context); -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java new file mode 100644 index 000000000..c94ce5fe7 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java @@ -0,0 +1,43 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.json; + +import java.util.List; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Describes the access control list. + */ +public class ACL { + + @JsonProperty("ATTRIBUTES") + private List ATTRIBUTES; // e.g., "CLAIM", "GLOBAL" + + @JsonProperty("RIGHTS") + private List RIGHTS; + + @JsonProperty("ACCESS") + private String ACCESS; + + public List getATTRIBUTES() { + return ATTRIBUTES; + } + + public void setATTRIBUTES(List ATTRIBUTES) { + this.ATTRIBUTES = ATTRIBUTES; + } + + public List getRIGHTS() { + return RIGHTS; + } + + public void setRIGHTS(List RIGHTS) { + this.RIGHTS = RIGHTS; + } + + public String getACCESS() { + return ACCESS; + } + + public void setACCESS(String ACCESS) { + this.ACCESS = ACCESS; + } +} \ No newline at end of file diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AccessType.java similarity index 92% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java rename to core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AccessType.java index ea352d083..377f03f7f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessType.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AccessType.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security; +package de.fraunhofer.iosb.ilt.faaast.service.security.json; /** * Enum for access types. diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java new file mode 100644 index 000000000..829f824ba --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.ilt.faaast.service.security.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + + +/** + * Describes an access permissiom rule. + */ +public class AllAccessPermissionRules { + + @JsonProperty("DEFACLS") + private List DEFACLS; + + @JsonProperty("DEFOBJECTS") + private List DEFOBJECTS; + + @JsonProperty("DEFFORMULAS") + private List DEFFORMULAS; + + @JsonProperty("rules") + private List rules; + + public List getDEFACLS() { + return DEFACLS; + } + + public void setDEFACLS(List DEFACLS) { + this.DEFACLS = DEFACLS; + } + + public List getDEFOBJECTS() { + return DEFOBJECTS; + } + + public void setDEFOBJECTS(List DEFOBJECTS) { + this.DEFOBJECTS = DEFOBJECTS; + } + + public List getDEFFORMULAS() { + return DEFFORMULAS; + } + + public void setDEFFORMULAS(List DEFFORMULAS) { + this.DEFFORMULAS = DEFFORMULAS; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java similarity index 57% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java rename to core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java index 45d216c85..944f2f89e 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/StringOperand.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java @@ -12,27 +12,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.operands; +package de.fraunhofer.iosb.ilt.faaast.service.security.json; -import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; +import com.fasterxml.jackson.annotation.JsonProperty; +public class AllAccessPermissionRulesRoot { + @JsonProperty("AllAccessPermissionRules") + private AllAccessPermissionRules allAccessPermissionRules; -/** - * Operand for strings. - */ -public class StringOperand implements Operand { - private String value; - - public StringOperand(String value) { - this.value = value; + public AllAccessPermissionRules getAllAccessPermissionRules() { + return allAccessPermissionRules; } - - @Override - public Object getValue(EvaluationContext context) { - // TODO Retrieve value based on context - return value; + public void setAllAccessPermissionRules(AllAccessPermissionRules allAccessPermissionRules) { + this.allAccessPermissionRules = allAccessPermissionRules; } - - // Constructors, getters, and setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java new file mode 100644 index 000000000..c3851fb39 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java @@ -0,0 +1,28 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Attribute { + + @JsonProperty("CLAIM") + private String CLAIM; + + @JsonProperty("GLOBAL") + private String GLOBAL; + + public String getCLAIM() { + return CLAIM; + } + + public void setCLAIM(String CLAIM) { + this.CLAIM = CLAIM; + } + + public String getGLOBAL() { + return GLOBAL; + } + + public void setGLOBAL(String GLOBAL) { + this.GLOBAL = GLOBAL; + } +} \ No newline at end of file diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java new file mode 100644 index 000000000..b21dea357 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.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.ilt.faaast.service.security.json; + + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a flexible condition structure that can contain + * any JSON operators (e.g., "$or", "$match", "$eq", etc.). + */ +@JsonIgnoreProperties(ignoreUnknown = false) +public class Condition { + + // A catch-all map that stores any fields Jackson encounters + // (e.g., "$or", "$match", "$eq", "$regex", etc.) + private Map expression = new HashMap<>(); + + @JsonAnySetter + public void setExpression(String key, Object value) { + expression.put(key, value); + } + + public Map getExpression() { + return expression; + } + + public void setExpression(Map expression) { + this.expression = expression; + } +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java similarity index 75% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java rename to core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java index 6d330aa87..9aca50f45 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/RouteObject.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java @@ -12,13 +12,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.objects; +package de.fraunhofer.iosb.ilt.faaast.service.security.json; + +public class DefACL { + private String name; + private ACL acl; + + public void setName(String acl1) {} -/** - * Object for routes. - */ -public class RouteObject implements AccessObject { - private String routeLiteral; - // Constructors, getters, and setters + public void setAcl(ACL acl) {} + // getters/setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java similarity index 71% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java rename to core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java index 900c0b4ef..4415f5e14 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/attributes/AttributeGroup.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java @@ -12,17 +12,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.attributes; +package de.fraunhofer.iosb.ilt.faaast.service.security.json; -import java.util.List; +import java.util.Map; -/** - * Describes a group of attributes. - */ -public class AttributeGroup implements Attribute { +public class DefFormula { private String name; - private List attributes; + private Map formula; + + public void setName(String allowSubjectGroup1) {} + - // Constructors, getters, and setters + public void setFormula(Map formulaExpression) {} + // getters/setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java similarity index 76% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java rename to core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java index 5a68937ec..a61e1d866 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ObjectGroup.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java @@ -12,17 +12,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.objects; +package de.fraunhofer.iosb.ilt.faaast.service.security.json; import java.util.List; -/** - * Group of objects. - */ -public class ObjectGroup implements AccessObject { +public class DefObjects { private String name; - private List objects; + private List objects; + + public void setName(String properties) {} + - // Constructors, getters, and setters + public void setObjects(List list) {} + // getters/setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java new file mode 100644 index 000000000..2f93f902a --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java @@ -0,0 +1,53 @@ +/* + * 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.ilt.faaast.service.security.json; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Describes the filter object, including FRAGMENT and a CONDITION + * that can hold arbitrary operators. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Filter { + + @JsonProperty("FRAGMENT") + private String FRAGMENT; + + @JsonProperty("CONDITION") + private Condition CONDITION; + + public String getFRAGMENT() { + return FRAGMENT; + } + + public void setFRAGMENT(String FRAGMENT) { + this.FRAGMENT = FRAGMENT; + } + + public Condition getCONDITION() { + return CONDITION; + } + + public void setCONDITION(Condition CONDITION) { + this.CONDITION = CONDITION; + } +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java new file mode 100644 index 000000000..8fc5389a9 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java @@ -0,0 +1,50 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Objects { + + @JsonProperty("ROUTE") + private String ROUTE; + + @JsonProperty("IDENTIFIABLE") + private String IDENTIFIABLE; + + @JsonProperty("REFERABLE") + private String REFERABLE; + + @JsonProperty("DESCRIPTOR") + private String DESCRIPTOR; + + public String getROUTE() { + return ROUTE; + } + + public void setROUTE(String ROUTE) { + this.ROUTE = ROUTE; + } + + public String getIDENTIFIABLE() { + return IDENTIFIABLE; + } + + public void setIDENTIFIABLE(String IDENTIFIABLE) { + this.IDENTIFIABLE = IDENTIFIABLE; + } + + public String getREFERABLE() { + return REFERABLE; + } + + public void setREFERABLE(String REFERABLE) { + this.REFERABLE = REFERABLE; + } + + public String getDESCRIPTOR() { + return DESCRIPTOR; + } + + public void setDESCRIPTOR(String DESCRIPTOR) { + this.DESCRIPTOR = DESCRIPTOR; + } +} \ No newline at end of file diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Right.java similarity index 93% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java rename to core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Right.java index f6d0068b8..a4b4f3801 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/Right.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Right.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security; +package de.fraunhofer.iosb.ilt.faaast.service.security.json; /** * Enum for rights. diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java new file mode 100644 index 000000000..c876ef15c --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java @@ -0,0 +1,98 @@ +package de.fraunhofer.iosb.ilt.faaast.service.security.json; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; + +public class Rule { + + @JsonProperty("ACL") + private ACL ACL; + + @JsonProperty("OBJECTS") + private List OBJECTS; + + @JsonProperty("FORMULA") + private Map FORMULA; + + @JsonProperty("FILTER") + private Filter FILTER; + + // The missing field that often appears in your JSON: + @JsonProperty("FRAGMENT") + private String FRAGMENT; + + // Reusable references + @JsonProperty("USEACL") + private String USEACL; + + @JsonProperty("USEOBJECTS") + private List USEOBJECTS; + + @JsonProperty("USEFORMULA") + private String USEFORMULA; + + public ACL getACL() { + return ACL; + } + + public void setACL(ACL ACL) { + this.ACL = ACL; + } + + public List getOBJECTS() { + return OBJECTS; + } + + public void setOBJECTS(List OBJECTS) { + this.OBJECTS = OBJECTS; + } + + public Map getFORMULA() { + return FORMULA; + } + + public void setFORMULA(Map FORMULA) { + this.FORMULA = FORMULA; + } + + public Filter getFILTER() { + return FILTER; + } + + public void setFILTER(Filter FILTER) { + this.FILTER = FILTER; + } + + public String getFRAGMENT() { + return FRAGMENT; + } + + public void setFRAGMENT(String FRAGMENT) { + this.FRAGMENT = FRAGMENT; + } + + public String getUSEACL() { + return USEACL; + } + + public void setUSEACL(String USEACL) { + this.USEACL = USEACL; + } + + public List getUSEOBJECTS() { + return USEOBJECTS; + } + + public void setUSEOBJECTS(List USEOBJECTS) { + this.USEOBJECTS = USEOBJECTS; + } + + public String getUSEFORMULA() { + return USEFORMULA; + } + + public void setUSEFORMULA(String USEFORMULA) { + this.USEFORMULA = USEFORMULA; + } +} \ No newline at end of file diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java deleted file mode 100644 index 4ecb5d15f..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/AccessObject.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.objects; - -/** - * Interface for access objects. - */ -public interface AccessObject { - // Common methods or marker interface -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java deleted file mode 100644 index e89b9696d..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/IdentifiableObject.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.objects; - -/** - * Object for identifiable. - */ -public class IdentifiableObject implements AccessObject { - private String identifiableLiteral; - - /** - * Sets the identifiable object. - * - * @param submodel - */ - public IdentifiableObject(String submodel) {} - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java deleted file mode 100644 index 5508678b2..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/objects/ReferableObject.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.objects; - -/** - * Object for referables. - */ -public class ReferableObject implements AccessObject { - private String referableLiteral; - - // Constructors, getters, and setters -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java deleted file mode 100644 index 6d2e7403b..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/operands/Operand.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.operands; - -import de.fraunhofer.iosb.ilt.faaast.service.security.utils.EvaluationContext; - - -/** - * Interface for operands. - */ -public interface Operand { - /** - * Gets the value from the context. - * - * @param context - * @return value - */ - Object getValue(EvaluationContext context); -} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java deleted file mode 100644 index 490ac4d1d..000000000 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/utils/EvaluationContext.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.security.utils; - -import java.util.Map; - - -/** - * Context for the evaluation. - */ -public class EvaluationContext { - private Map variables; - private Map attributes; - - // Methods to get and set variables and attributes -} diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index 732425373..427e55fe2 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -14,13 +14,19 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security; -import de.fraunhofer.iosb.ilt.faaast.service.security.attributes.ClaimAttribute; -import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonExpression; -import de.fraunhofer.iosb.ilt.faaast.service.security.expressions.ComparisonOperator; -import de.fraunhofer.iosb.ilt.faaast.service.security.objects.IdentifiableObject; -import de.fraunhofer.iosb.ilt.faaast.service.security.operands.StringOperand; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.ACL; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRulesRoot; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.Attribute; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.Objects; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.Rule; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -30,31 +36,59 @@ public class AccessRuleTest { @Before public void init() {} + private String rule1 = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"CLAIM\":\"__Albert__Alb\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"ROUTE\":\"*\"}],\"FORMULA\":{\"$boolean\":true}},{\"ACL\":{\"ATTRIBUTES\":[{\"CLAIM\":\"isNotAuthenticated\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"ROUTE\":\"/submodels/*\"},{\"ROUTE\":\"/shells/aHR0cHM6Ly96dmVpLm9yZy9kZW1vL2Fhcy9Db250cm9sQ2FiaW5ldA\"}],\"FORMULA\":{\"$or\":[{\"$and\":[{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"Nameplate\"}]},{\"$starts_with\":[{\"$field\":\"$sme#idShort\"},{\"$strVal\":\"Manufacturer\"}]}]},{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"TechnicalData\"}]}]},\"FRAGMENT\":\"sme#\",\"FILTER\":{\"$or\":[{\"$starts_with\":[{\"$field\":\"$sme#idShort\"},{\"$strVal\":\"Manufacturer\"}]},{\"$starts_with\":[{\"$field\":\"$sme#idShort\"},{\"$strVal\":\"General\"}]}]}}]}}"; + private String rule2 = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"GLOBAL\":\"ANONYMOUS\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"ROUTE\":\"*\"}],\"FORMULA\":{\"$or\":[{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"Nameplate\"}]},{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"TechnicalData\"}]}]}}]}}"; + private String ruleWithFilter = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"CLAIM\":\"BusinessPartnerNumber\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"DESCRIPTOR\":\"(aasdesc)*\"}],\"FORMULA\":{\"$and\":[{\"$eq\":[{\"$attribute\":{\"CLAIM\":\"BusinessPartnerNumber\"}},{\"$strVal\":\"BPNL00000000000A\"}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"manufacturerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"99991\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$strVal\":\"PUBLIC_READABLE\"}]}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"customerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"ACME001\"}]}]}]},\"FILTER\":{\"FRAGMENT\":\"$aasdesc#assetInformation.specificAssetIds[]\",\"CONDITION\":{\"$or\":[{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"manufacturerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"99991\"}]}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"customerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"ACME001\"}]}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"partInstanceId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$attribute\":{\"CLAIM\":\"BusinessPartnerNumber\"}}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$strVal\":\"PUBLIC_READABLE\"}]}]}}}]}}"; @Test - public void testConstruction() { - ClaimAttribute roleAttribute = new ClaimAttribute("role=admin"); + public void testAllowAnonymousRead() { + // Create ACL + ACL acl = new ACL(); + Attribute attr = new Attribute(); + attr.setGLOBAL("ANONYMOUS"); + acl.setATTRIBUTES(Arrays.asList(attr)); + acl.setRIGHTS(Arrays.asList("READ")); + acl.setACCESS("ALLOW"); - List rights = Arrays.asList(Right.READ, Right.UPDATE); + // Create OBJECTS + Objects obj = new Objects(); + obj.setROUTE("*"); + + // Create FORMULA with a simple (boolean) expression + Map formula = new HashMap<>(); + formula.put("$boolean", true); + + // Create a Rule + Rule rule = new Rule(); + rule.setACL(acl); + rule.setOBJECTS(Arrays.asList(obj)); + rule.setFORMULA(formula); + + // Wrap in AllAccessPermissionRules and the root + AllAccessPermissionRules allRules = new AllAccessPermissionRules(); + allRules.setRules(Arrays.asList(rule)); + AllAccessPermissionRulesRoot root = new AllAccessPermissionRulesRoot(); + root.setAllAccessPermissionRules(allRules); + } - ACL acl = new ACL(); - acl.setAttributes(Arrays.asList(roleAttribute)); - acl.setRights(rights); - acl.setAccessType(AccessType.ALLOW); - IdentifiableObject identifiableObject = new IdentifiableObject("Submodel123"); + @Test + public void testParse1() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + AllAccessPermissionRulesRoot allRules = mapper.readValue(rule1, AllAccessPermissionRulesRoot.class); + } + - ComparisonExpression conditionExpression = new ComparisonExpression( - new StringOperand("$subject#role"), - ComparisonOperator.EQ, - new StringOperand("admin")); + @Test + public void testParse2() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + AllAccessPermissionRulesRoot allRules = mapper.readValue(rule2, AllAccessPermissionRulesRoot.class); - Condition condition = new Condition(); - condition.setExpression(conditionExpression); + } - AccessPermissionRule rule = new AccessPermissionRule(); - rule.setAcl(acl); - rule.setObjects(Arrays.asList(identifiableObject)); - rule.setFormula(condition); + @Test + public void testParse3() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + AllAccessPermissionRulesRoot allRules = mapper.readValue(ruleWithFilter, AllAccessPermissionRulesRoot.class); } } diff --git a/submodeltemplate/security/pom.xml b/submodeltemplate/security/pom.xml deleted file mode 100644 index 0442b7a3e..000000000 --- a/submodeltemplate/security/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - 4.0.0 - - de.fraunhofer.iosb.ilt.faaast.service - service - 1.3.0-SNAPSHOT - ../../pom.xml - - submodeltemplate-security - submodeltemplate-security - Submodel Template processor for SubmodelTemplate Security - - ${project.parent.basedir} - - - - ${project.groupId} - core - ${project.version} - - - ${project.groupId} - filestorage-memory - ${project.version} - test - - - ${project.groupId} - messagebus-internal - ${project.version} - test - - - ${project.groupId} - model - ${project.version} - - - ${project.groupId} - persistence-memory - ${project.version} - test - - - org.eclipse.digitaltwin.aas4j - aas4j-model - ${aas4j.version} - - - com.mycila - license-maven-plugin - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - ${maven.plugin.jar.version} - - - - test-jar - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - - diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java deleted file mode 100644 index c3cf8db64..000000000 --- a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/Constants.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security; - -/** - * Constants related to SMT Asset Interfaces Mapping Configuration. - */ -public class Constants { - public static final String SECURITY_SUBMODEL_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel"; - - private Constants() {} -} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java deleted file mode 100644 index 60b46ceb8..000000000 --- a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessingMode.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security; - -/** - * Enumeration for the processing mode. - */ -public enum ProcessingMode { - ADD, - UPDATE, - DELETE -} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java deleted file mode 100644 index b5dfaf572..000000000 --- a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessor.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security; - -import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; -import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; -import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationInitializationException; -import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessor; -import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; -import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.AasUtils; -import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; -import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -/** - * Adds logic for submodel instances of template Security. - */ -public class SecuritySubmodelTemplateProcessor implements SubmodelTemplateProcessor { - private static final Logger LOGGER = LoggerFactory.getLogger(SecuritySubmodelTemplateProcessor.class); - - private SecuritySubmodelTemplateProcessorConfig config; - private ServiceContext serviceContext; - private Submodel submodel; - - public SecuritySubmodelTemplateProcessor() {} - - - @Override - public boolean accept(Submodel submodel) { - return submodel != null - && (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId()) - || Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())); - } - - - @Override - public boolean add(Submodel submodel, AssetConnectionManager assetConnectionManager) { - Ensure.requireNonNull(submodel); - Ensure.requireNonNull(assetConnectionManager); - boolean retval; - try { - // in add we only process AIMC submodel - if (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())) { - LOGGER.atInfo().log("process submodel {} ({})", submodel.getIdShort(), AasUtils.asString(AasUtils.toReference(submodel))); - //processSubmodel(submodel, assetConnectionManager, ProcessingMode.ADD); - this.submodel = submodel; - } - - retval = true; - } - catch (Exception ex) { - LOGGER.error("error processing SMT AIMC (submodel: {})", AasUtils.asString(AasUtils.toReference(submodel)), ex); - retval = false; - } - - return retval; - } - - - @Override - public void init(CoreConfig coreConfig, SecuritySubmodelTemplateProcessorConfig config, ServiceContext serviceContext) throws ConfigurationInitializationException { - this.config = config; - this.serviceContext = serviceContext; - } - - - @Override - public SecuritySubmodelTemplateProcessorConfig asConfig() { - return config; - } - - - @Override - public boolean update(Submodel submodel, AssetConnectionManager assetConnectionManager) { - Ensure.requireNonNull(submodel); - Ensure.requireNonNull(assetConnectionManager); - boolean retval; - - try { - LOGGER.atInfo().log("update submodel {} ({})", submodel.getIdShort(), AasUtils.asString(AasUtils.toReference(submodel))); - Submodel updateSubmodel = submodel; - // we always use AIMC submodel for processSubmodel - if (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())) { - updateSubmodel = this.submodel; - } - //processSubmodel(updateSubmodel, assetConnectionManager, ProcessingMode.UPDATE); - retval = true; - } - catch (Exception ex) { - LOGGER.error("error updating SMT AIMC (submodel: {})", AasUtils.asString(AasUtils.toReference(submodel)), ex); - retval = false; - } - return retval; - } - - - @Override - public boolean delete(Submodel submodel, AssetConnectionManager assetConnectionManager) { - Ensure.requireNonNull(submodel); - Ensure.requireNonNull(assetConnectionManager); - boolean retval; - try { - // in delete we only process AIMC submodel - if (Objects.equals(ReferenceBuilder.global(Constants.SECURITY_SUBMODEL_SEMANTIC_ID), submodel.getSemanticId())) { - LOGGER.atInfo().log("delete submodel {} ({})", submodel.getIdShort(), AasUtils.asString(AasUtils.toReference(submodel))); - //processSubmodel(submodel, assetConnectionManager, ProcessingMode.DELETE); - } - retval = true; - } - catch (Exception ex) { - LOGGER.error("error deleting SMT AIMC (submodel: {})", AasUtils.asString(AasUtils.toReference(submodel)), ex); - retval = false; - } - return retval; - } - - - private static List getRelationshipElements(List relations) { - List retval = new ArrayList<>(); - for (var r: relations) { - if (r instanceof RelationshipElement relationshipElement) { - retval.add(relationshipElement); - } - } - - return retval; - } - - - private static SubmodelElementList getMappingConfiguration(Submodel submodel) { - Optional element = submodel.getSubmodelElements().stream().filter(s -> Constants.SECURITY_SUBMODEL_SEMANTIC_ID.equals(s.getIdShort())).findFirst(); - if (element.isEmpty()) { - throw new IllegalArgumentException("Submodel invalid: MappingConfigurations not found."); - } - if (element.get() instanceof SubmodelElementList list) { - return list; - } - else { - throw new IllegalArgumentException("Submodel invalid: MappingConfigurations not a list."); - } - } - - - private static List getMappingRelations(SubmodelElementCollection configuration) { - Optional element = configuration.getValue().stream().filter(e -> Constants.SECURITY_SUBMODEL_SEMANTIC_ID.equals(e.getIdShort())).findFirst(); - if (element.isEmpty()) { - throw new IllegalArgumentException("Submodel invalid: MappingSourceSinkRelations not found."); - } - List relations = null; - if (element.get() instanceof SubmodelElementList list) { - relations = getRelationshipElements(list.getValue()); - } - if (relations == null) { - relations = new ArrayList<>(); - } - return relations; - } - - - private static ReferenceElement getInterfaceReference(SubmodelElementCollection configuration) { - Optional element = configuration.getValue().stream().filter(e -> Constants.SECURITY_SUBMODEL_SEMANTIC_ID.equals(e.getIdShort())).findFirst(); - if (element.isEmpty()) { - throw new IllegalArgumentException("Submodel invalid: InterfaceReference not found."); - } - if (element.get() instanceof ReferenceElement interfaceReference) { - return interfaceReference; - } - else { - throw new IllegalArgumentException("Submodel invalid: InterfaceReference not a ReferenceElement."); - } - } -} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java deleted file mode 100644 index 16bc8a335..000000000 --- a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfig.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import de.fraunhofer.iosb.ilt.faaast.service.config.serialization.ReferenceDeserializer; -import de.fraunhofer.iosb.ilt.faaast.service.config.serialization.ReferenceSerializer; -import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessorConfig; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; - - -/** - * Configuration for SMT Asset Interfaces Mapping Configuration processor. - */ -public class SecuritySubmodelTemplateProcessorConfig extends SubmodelTemplateProcessorConfig { - - @JsonSerialize(keyUsing = ReferenceSerializer.class) - @JsonDeserialize(keyUsing = ReferenceDeserializer.class) - private Map interfaceConfigurations; - - public SecuritySubmodelTemplateProcessorConfig() { - interfaceConfigurations = new HashMap<>(); - } - - - public Map getInterfaceConfigurations() { - return interfaceConfigurations; - } - - - public void setInterfaceConfigurations(Map value) { - interfaceConfigurations = value; - } - - - @Override - public int hashCode() { - return Objects.hash(interfaceConfigurations); - } - - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig other = (de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig) obj; - return super.equals(other) - && Objects.equals(interfaceConfigurations, other.interfaceConfigurations); - } - - protected abstract static class AbstractBuilder> - extends ExtendableBuilder { - - public B interfaceConfigurations(Map value) { - getBuildingInstance().setInterfaceConfigurations(value); - return getSelf(); - } - - - public B interfaceConfiguration(Reference key, SecuritySubmodelTemplateProcessorConfigData value) { - getBuildingInstance().getInterfaceConfigurations().put(key, value); - return getSelf(); - } - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder extends AbstractBuilder { - - @Override - protected Builder getSelf() { - return this; - } - - - @Override - protected de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig newBuildingInstance() { - return new de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfig(); - } - } -} diff --git a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java b/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java deleted file mode 100644 index 2746561a2..000000000 --- a/submodeltemplate/security/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/SecuritySubmodelTemplateProcessorConfigData.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security; - -import java.util.Objects; -import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; - - -/** - * Configuration data for SMT Asset Interfaces Mapping Configuration processor. - */ -public class SecuritySubmodelTemplateProcessorConfigData { - - private String username; - private String password; - private long subscriptionInterval; - - 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; - } - - - public long getSubscriptionInterval() { - return subscriptionInterval; - } - - - public void setSubscriptionInterval(long subscriptionInterval) { - this.subscriptionInterval = subscriptionInterval; - } - - - @Override - public int hashCode() { - return Objects.hash(username, password, subscriptionInterval); - } - - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData other = (de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData) obj; - return super.equals(other) - && Objects.equals(username, other.username) - && Objects.equals(password, other.password) - && Objects.equals(subscriptionInterval, other.getSubscriptionInterval()); - } - - protected abstract static class AbstractBuilder> - extends ExtendableBuilder { - - public B username(String value) { - getBuildingInstance().setUsername(value); - return getSelf(); - } - - - public B password(String value) { - getBuildingInstance().setPassword(value); - return getSelf(); - } - - - public B subscriptionInterval(long value) { - getBuildingInstance().setSubscriptionInterval(value); - return getSelf(); - } - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder extends AbstractBuilder { - - @Override - protected Builder getSelf() { - return this; - } - - - @Override - protected de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData newBuildingInstance() { - return new de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security.SecuritySubmodelTemplateProcessorConfigData(); - } - } -} diff --git a/submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java b/submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java deleted file mode 100644 index c3417b60c..000000000 --- a/submodeltemplate/security/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/security/ProcessorTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.security; - -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; -import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; -import de.fraunhofer.iosb.ilt.faaast.service.config.ServiceConfig; -import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; -import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; -import de.fraunhofer.iosb.ilt.faaast.service.filestorage.memory.FileStorageInMemoryConfig; -import de.fraunhofer.iosb.ilt.faaast.service.messagebus.internal.MessageBusInternalConfig; -import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; -import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; -import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; -import java.net.MalformedURLException; -import java.util.List; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.junit.Before; -import org.junit.Test; - - -public class ProcessorTest { - - private ServiceConfig config; - - @Before - public void init() { - - Reference submodelRef = ReferenceBuilder.forSubmodel("https://example.com/ids/sm/AssetInterfacesDescription", "InterfaceHTTP"); - config = new ServiceConfig.Builder() - .core(new CoreConfig.Builder().requestHandlerThreadPoolSize(2).build()) - .persistence(new PersistenceInMemoryConfig()) - .fileStorage(new FileStorageInMemoryConfig()) - .messageBus(new MessageBusInternalConfig()) - .submodelTemplateProcessors(List.of(new SecuritySubmodelTemplateProcessorConfig.Builder() - .interfaceConfiguration(submodelRef, new SecuritySubmodelTemplateProcessorConfigData.Builder().username("user1").password("pw1").build()).build())) - .build(); - } - - - @Test - public void testSecurity() throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException, MalformedURLException { - /* - * File initialModelFile = new File("src/test/resources/Test-Example.json"); - * config.getPersistence().setInitialModelFile(initialModelFile); - * Service service = new Service(config); - * Assert.assertNotNull(service); - * AssetConnectionManager manager = service.getAssetConnectionManager(); - * Assert.assertNotNull(manager); - * List assetConns = manager.getConnections(); - * Assert.assertEquals(2, assetConns.size()); - */ - } -} diff --git a/submodeltemplate/security/src/test/resources/Test-Example.json b/submodeltemplate/security/src/test/resources/Test-Example.json deleted file mode 100644 index e62542696..000000000 --- a/submodeltemplate/security/src/test/resources/Test-Example.json +++ /dev/null @@ -1,1922 +0,0 @@ -{ - "assetAdministrationShells": [ - { - "idShort": "SampleAAS", - "id": "https://example.com/ids/aas/7474_9002_6022_1115", - "assetInformation": { - "assetKind": "Type", - "globalAssetId": "https://example.com/ids/asset/3071_4170_8032_4893", - "specificAssetIds": [], - "assetType": "", - "defaultThumbnail": { - "path": "", - "contentType": "image/png" - } - }, - "submodels": [ - { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - } - ] - }, - { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/OperationalData" - } - ] - }, - { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesMappingConfiguration" - } - ] - } - ], - "modelType": "AssetAdministrationShell" - } - ], - "submodels": [ - { - "idShort": "AssetInterfacesDescription", - "description": [ - { - "language": "en", - "text": "Counter example Thing" - }, - { - "language": "de", - "text": "Z\u00E4hler Beispiel Ding" - } - ], - "id": "https://example.com/ids/sm/AssetInterfacesDescription", - "kind": "Instance", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel" - } - ] - }, - "submodelElements": [ - { - "idShort": "InterfaceHTTP", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface" - } - ] - }, - "supplementalSemanticIds": [ - { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://www.w3.org/2011/http" - } - ] - } - ], - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "title", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#title" - } - ] - }, - "valueType": "xs:string", - "value": "DeviceSample", - "modelType": "Property" - }, - { - "idShort": "created", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://purl.org/dc/terms/created" - } - ] - }, - "valueType": "xs:dateTime", - "value": "2022-12-27 08:26:49.219717", - "modelType": "Property" - }, - { - "idShort": "modified", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://purl.org/dc/terms/modified" - } - ] - }, - "valueType": "xs:dateTime", - "value": "2022-12-27 08:26:49.219717", - "modelType": "Property" - }, - { - "idShort": "support", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#supportContact" - } - ] - }, - "valueType": "xs:anyURI", - "value": "https://github.com/eclipse-thingweb/node-wot/", - "modelType": "Property" - }, - { - "idShort": "EndpointMetadata", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "base", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#base" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:anyURI", - "value": "http://plugfest.thingweb.io:8083", - "modelType": "Property" - }, - { - "idShort": "contentType", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "application/json", - "modelType": "Property" - }, - { - "idShort": "security", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasSecurityConfiguration." - } - ] - }, - "typeValueListElement": "SubmodelElement", - "value": [ - { - "value": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceHTTP" - }, - { - "type": "SubmodelElementCollection", - "value": "EndpointMetadata" - }, - { - "type": "SubmodelElementCollection", - "value": "securityDefinitions" - }, - { - "type": "SubmodelElementCollection", - "value": "nosec_sc" - } - ] - }, - "modelType": "ReferenceElement" - } - ], - "modelType": "SubmodelElementList" - }, - { - "idShort": "securityDefinitions", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#definesSecurityScheme" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "nosec_sc", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "scheme", - "valueType": "xs:string", - "value": "nosec", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "InterfaceMetadata", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#InteractionAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "Properties", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#PropertyAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "voltage", - "description": [ - { - "language": "en", - "text": "Current counter value" - }, - { - "language": "de", - "text": "Derzeitiger Z\u00E4hlerwert" - } - ], - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasPropertyAffordance" - } - ] - }, - "qualifiers": [], - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "type", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" - } - ] - }, - "valueType": "xs:string", - "value": "integer", - "modelType": "Property" - }, - { - "idShort": "range", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#minimum" - } - ] - }, - "supplementalSemanticIds": [ - { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#maximum" - } - ] - } - ], - "valueType": "xs:integer", - "min": "1", - "max": "100", - "modelType": "Range" - }, - { - "idShort": "title", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#title" - } - ] - }, - "valueType": "xs:string", - "value": "voltage", - "modelType": "Property" - }, - { - "idShort": "observable", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#isObservable" - } - ] - }, - "valueType": "xs:boolean", - "value": "true", - "modelType": "Property" - }, - { - "idShort": "const", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#const" - } - ] - }, - "valueType": "xs:int", - "value": "22", - "modelType": "Property" - }, - { - "idShort": "default", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#default" - } - ] - }, - "valueType": "xs:string", - "value": "integer", - "modelType": "Property" - }, - { - "idShort": "unit", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://schema.org/unitCode" - } - ] - }, - "valueType": "xs:string", - "value": "V", - "modelType": "Property" - }, - { - "idShort": "forms", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasForm" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "href", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "/sampleDevice/properties/voltage", - "modelType": "Property" - }, - { - "idShort": "htv_methodName", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2011/http#methodName" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "GET", - "modelType": "Property" - }, - { - "idShort": "contentType", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "application/json", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "status", - "description": [ - { - "language": "en", - "text": "Current counter value as SVG image" - }, - { - "language": "de", - "text": "Aktueller Z\u00E4hlerwert als SVG-Bild" - } - ], - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "qualifiers": [], - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "type", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" - } - ] - }, - "valueType": "xs:string", - "value": "string", - "modelType": "Property" - }, - { - "idShort": "lengthRange", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#minimum" - } - ] - }, - "supplementalSemanticIds": [ - { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#maximum" - } - ] - } - ], - "valueType": "xs:integer", - "min": "1", - "max": "25", - "modelType": "Range" - }, - { - "idShort": "title", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#title" - } - ] - }, - "valueType": "xs:string", - "value": "status", - "modelType": "Property" - }, - { - "idShort": "const", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#const" - } - ] - }, - "valueType": "xs:int", - "value": "22", - "modelType": "Property" - }, - { - "idShort": "enum", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "]https://www.w3.org/2019/wot/json-schema#enum" - } - ] - }, - "typeValueListElement": "SubmodelElement", - "value": [ - { - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "]https://www.w3.org/2019/wot/json-schema#enum" - } - ] - }, - "valueType": "xs:string", - "value": "Good", - "modelType": "Property" - }, - { - "idShort": "Bad", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "]https://www.w3.org/2019/wot/json-schema#enum" - } - ] - }, - "valueType": "xs:string", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementList" - }, - { - "idShort": "default", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#default" - } - ] - }, - "valueType": "xs:string", - "value": "integer", - "modelType": "Property" - }, - { - "idShort": "unit", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://schema.org/unitCode" - } - ] - }, - "valueType": "xs:string", - "value": "", - "modelType": "Property" - }, - { - "idShort": "observable", - "valueType": "xs:boolean", - "value": "false", - "modelType": "Property" - }, - { - "idShort": "forms", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasForm" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "href", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "/sampleDevice/properties/status", - "modelType": "Property" - }, - { - "idShort": "htv_methodName", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "GET", - "modelType": "Property" - }, - { - "idShort": "contentType", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "application/json", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "Actions", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#ActionAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "Events", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#EventAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "externalDescriptor", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "ThingDescription", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": "/aasx/files/counter-http-simple.td.jsonld", - "contentType": "application/json", - "modelType": "File" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "InterfaceMQTT", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface" - } - ] - }, - "supplementalSemanticIds": [ - { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://www.w3.org/2011/mqtt" - } - ] - } - ], - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "title", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#title" - } - ] - }, - "valueType": "xs:string", - "value": "DeviceSample", - "modelType": "Property" - }, - { - "idShort": "created", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://purl.org/dc/terms/created" - } - ] - }, - "valueType": "xs:dateTime", - "value": "2022-12-27 08:26:49.219717", - "modelType": "Property" - }, - { - "idShort": "modified", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://purl.org/dc/terms/modified" - } - ] - }, - "valueType": "xs:dateTime", - "value": "2022-12-27 08:26:49.219717", - "modelType": "Property" - }, - { - "idShort": "support", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#supportContact" - } - ] - }, - "valueType": "xs:anyURI", - "value": "https://github.com/eclipse-thingweb/node-wot/", - "modelType": "Property" - }, - { - "idShort": "EndpointMetadata", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "base", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#base" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:anyURI", - "value": "mqtt://iot.platform.com:8088", - "modelType": "Property" - }, - { - "idShort": "contentType", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "application/json", - "modelType": "Property" - }, - { - "idShort": "security", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasSecurityConfiguration." - } - ] - }, - "typeValueListElement": "SubmodelElement", - "value": [ - { - "valueType": "xs:string", - "value": "nosec_sc", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementList" - }, - { - "idShort": "securityDefinitions", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#definesSecurityScheme" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "nosec_sc", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "scheme", - "valueType": "xs:string", - "value": "nosec", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "InterfaceMetadata", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#InteractionAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "Properties", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#PropertyAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "voltage", - "description": [ - { - "language": "en", - "text": "Current counter value" - }, - { - "language": "de", - "text": "Derzeitiger Z\u00E4hlerwert" - }, - { - "language": "it", - "text": "Valore attuale del contatore" - } - ], - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasPropertyAffordance" - } - ] - }, - "qualifiers": [], - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "type", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" - } - ] - }, - "valueType": "xs:string", - "value": "integer", - "modelType": "Property" - }, - { - "idShort": "range", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#minimum" - } - ] - }, - "supplementalSemanticIds": [ - { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#maximum" - } - ] - } - ], - "valueType": "xs:integer", - "min": "1", - "max": "100", - "modelType": "Range" - }, - { - "idShort": "title", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#title" - } - ] - }, - "valueType": "xs:string", - "value": "voltage", - "modelType": "Property" - }, - { - "idShort": "observable", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#isObservable" - } - ] - }, - "valueType": "xs:boolean", - "value": "true", - "modelType": "Property" - }, - { - "idShort": "const", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#const" - } - ] - }, - "valueType": "xs:int", - "value": "22", - "modelType": "Property" - }, - { - "idShort": "default", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#default" - } - ] - }, - "valueType": "xs:string", - "value": "integer", - "modelType": "Property" - }, - { - "idShort": "unit", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://schema.org/unitCode" - } - ] - }, - "valueType": "xs:string", - "value": "V", - "modelType": "Property" - }, - { - "idShort": "forms", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasForm" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "href", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "/sampleDevice/properties/voltage", - "modelType": "Property" - }, - { - "idShort": "mqv_retain", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/mqtt#hasRetainFlag" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:boolean", - "value": "True", - "modelType": "Property" - }, - { - "idShort": "mqv_controlPacket", - "valueType": "xs:string", - "value": "subscribe", - "modelType": "Property" - }, - { - "idShort": "contentType", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "application/json", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "status", - "description": [ - { - "language": "en", - "text": "Current counter value as SVG image" - }, - { - "language": "de", - "text": "Aktueller Z\u00E4hlerwert als SVG-Bild" - }, - { - "language": "it", - "text": "Valore attuale del contatore come immagine SVG" - } - ], - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "qualifiers": [], - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "type", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" - } - ] - }, - "valueType": "xs:string", - "value": "string", - "modelType": "Property" - }, - { - "idShort": "lengthRange", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#minimum" - } - ] - }, - "supplementalSemanticIds": [ - { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#maximum" - } - ] - } - ], - "valueType": "xs:integer", - "min": "1", - "max": "25", - "modelType": "Range" - }, - { - "idShort": "title", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#title" - } - ] - }, - "valueType": "xs:string", - "value": "status", - "modelType": "Property" - }, - { - "idShort": "const", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#const" - } - ] - }, - "valueType": "xs:int", - "value": "22", - "modelType": "Property" - }, - { - "idShort": "enum", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "]https://www.w3.org/2019/wot/json-schema#enum" - } - ] - }, - "typeValueListElement": "SubmodelElement", - "value": [ - { - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "]https://www.w3.org/2019/wot/json-schema#enum" - } - ] - }, - "valueType": "xs:string", - "value": "Good", - "modelType": "Property" - }, - { - "idShort": "Bad", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "]https://www.w3.org/2019/wot/json-schema#enum" - } - ] - }, - "valueType": "xs:string", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementList" - }, - { - "idShort": "default", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/json-schema#default" - } - ] - }, - "valueType": "xs:string", - "value": "integer", - "modelType": "Property" - }, - { - "idShort": "unit", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://schema.org/unitCode" - } - ] - }, - "valueType": "xs:string", - "value": "", - "modelType": "Property" - }, - { - "idShort": "observable", - "valueType": "xs:boolean", - "value": "false", - "modelType": "Property" - }, - { - "idShort": "forms", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#hasForm" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "href", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "/sampleDevice/properties/status", - "modelType": "Property" - }, - { - "idShort": "mqv_retain", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/mqtt#hasRetainFlag" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:boolean", - "value": "True", - "modelType": "Property" - }, - { - "idShort": "mqv_controlPacket", - "valueType": "xs:string", - "value": "subscribe", - "modelType": "Property" - }, - { - "idShort": "contentType", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" - } - ] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "application/json", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "Actions", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#ActionAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "Events", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://www.w3.org/2019/wot/td#EventAffordance" - } - ] - }, - "embeddedDataSpecifications": [], - "value": [], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "externalDescriptor", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "ThingDescription", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": "/aasx/files/counter-http-simple.td.jsonld", - "contentType": "application/json", - "modelType": "File" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "Submodel" - }, - { - "idShort": "OperationalData", - "id": "https://example.com/ids/sm/OperationalData", - "submodelElements": [ - { - "idShort": "HTTP_Data", - "value": [ - { - "idShort": "voltage", - "valueType": "xs:string", - "modelType": "Property" - }, - { - "idShort": "status", - "valueType": "xs:string", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "MQTT_Data", - "value": [ - { - "idShort": "voltage", - "valueType": "xs:string", - "modelType": "Property" - }, - { - "idShort": "status", - "valueType": "xs:string", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "Submodel" - }, - { - "idShort": "AssetInterfacesMappingConfiguration", - "id": "https://example.com/ids/sm/AssetInterfacesMappingConfiguration", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/Submodel" - } - ] - }, - "submodelElements": [ - { - "idShort": "MappingConfigurations", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfigurations" - } - ] - }, - "semanticIdListElement": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfiguration" - } - ] - }, - "typeValueListElement": "SubmodelElementCollection", - "value": [ - { - "idShort": "", - "value": [ - { - "idShort": "InterfaceReference", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference" - } - ] - }, - "value": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceHTTP" - } - ] - }, - "modelType": "ReferenceElement" - }, - { - "idShort": "MappingSourceSinkRelations", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations" - } - ] - }, - "semanticIdListElement": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation" - } - ] - }, - "typeValueListElement": "RelationshipElement", - "value": [ - { - "idShort": "", - "first": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceHTTP" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceMetadata" - }, - { - "type": "SubmodelElementCollection", - "value": "Properties" - }, - { - "type": "SubmodelElementCollection", - "value": "voltage" - } - ] - }, - "second": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/OperationalData" - }, - { - "type": "SubmodelElementCollection", - "value": "HTTP_Data" - }, - { - "type": "Property", - "value": "voltage" - } - ] - }, - "modelType": "RelationshipElement" - }, - { - "idShort": "", - "first": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceHTTP" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceMetadata" - }, - { - "type": "SubmodelElementCollection", - "value": "Properties" - }, - { - "type": "SubmodelElementCollection", - "value": "status" - } - ] - }, - "second": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/OperationalData" - }, - { - "type": "SubmodelElementCollection", - "value": "HTTP_Data" - }, - { - "type": "Property", - "value": "status" - } - ] - }, - "modelType": "RelationshipElement" - } - ], - "modelType": "SubmodelElementList" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "", - "value": [ - { - "idShort": "InterfaceReference", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference" - } - ] - }, - "value": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceMQTT" - } - ] - }, - "modelType": "ReferenceElement" - }, - { - "idShort": "MappingSourceSinkRelations", - "semanticId": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations" - } - ] - }, - "semanticIdListElement": { - "type": "ExternalReference", - "keys": [ - { - "type": "GlobalReference", - "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation" - } - ] - }, - "typeValueListElement": "RelationshipElement", - "value": [ - { - "idShort": "", - "first": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceMQTT" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceMetadata" - }, - { - "type": "SubmodelElementCollection", - "value": "Properties" - }, - { - "type": "SubmodelElementCollection", - "value": "voltage" - } - ] - }, - "second": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/OperationalData" - }, - { - "type": "SubmodelElementCollection", - "value": "MQTT_Data" - }, - { - "type": "Property", - "value": "voltage" - } - ] - }, - "modelType": "RelationshipElement" - }, - { - "idShort": "", - "first": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/AssetInterfacesDescription" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceMQTT" - }, - { - "type": "SubmodelElementCollection", - "value": "InterfaceMetadata" - }, - { - "type": "SubmodelElementCollection", - "value": "Properties" - }, - { - "type": "SubmodelElementCollection", - "value": "status" - } - ] - }, - "second": { - "type": "ModelReference", - "keys": [ - { - "type": "Submodel", - "value": "https://example.com/ids/sm/OperationalData" - }, - { - "type": "SubmodelElementCollection", - "value": "MQTT_Data" - }, - { - "type": "Property", - "value": "status" - } - ] - }, - "modelType": "RelationshipElement" - } - ], - "modelType": "SubmodelElementList" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementList" - } - ], - "modelType": "Submodel" - } - ], - "conceptDescriptions": [] -} \ No newline at end of file From e0ff405b0510af95cea76734b46cd36ea284bc02 Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 12 May 2025 09:20:05 +0200 Subject: [PATCH 023/108] remove SMT Processor --- .../ilt/faaast/service/security/json/ACL.java | 24 ++++++++++++-- .../json/AllAccessPermissionRules.java | 8 ++++- .../json/AllAccessPermissionRulesRoot.java | 2 ++ .../service/security/json/Attribute.java | 20 +++++++++++- .../service/security/json/Condition.java | 5 +-- .../faaast/service/security/json/Filter.java | 7 ++-- .../faaast/service/security/json/Objects.java | 24 +++++++++++++- .../faaast/service/security/json/Rule.java | 32 ++++++++++++++++++- .../service/security/AccessRuleTest.java | 4 +-- pom.xml | 1 - 10 files changed, 112 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java index c94ce5fe7..82bf8b797 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java @@ -1,7 +1,22 @@ +/* + * 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.ilt.faaast.service.security.json; -import java.util.List; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + /** * Describes the access control list. @@ -21,23 +36,28 @@ public List getATTRIBUTES() { return ATTRIBUTES; } + public void setATTRIBUTES(List ATTRIBUTES) { this.ATTRIBUTES = ATTRIBUTES; } + public List getRIGHTS() { return RIGHTS; } + public void setRIGHTS(List RIGHTS) { this.RIGHTS = RIGHTS; } + public String getACCESS() { return ACCESS; } + public void setACCESS(String ACCESS) { this.ACCESS = ACCESS; } -} \ No newline at end of file +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java index 829f824ba..daf3674cf 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java @@ -15,7 +15,6 @@ package de.fraunhofer.iosb.ilt.faaast.service.security.json; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.List; @@ -40,30 +39,37 @@ public List getDEFACLS() { return DEFACLS; } + public void setDEFACLS(List DEFACLS) { this.DEFACLS = DEFACLS; } + public List getDEFOBJECTS() { return DEFOBJECTS; } + public void setDEFOBJECTS(List DEFOBJECTS) { this.DEFOBJECTS = DEFOBJECTS; } + public List getDEFFORMULAS() { return DEFFORMULAS; } + public void setDEFFORMULAS(List DEFFORMULAS) { this.DEFFORMULAS = DEFFORMULAS; } + public List getRules() { return rules; } + public void setRules(List rules) { this.rules = rules; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java index 944f2f89e..f023cc3e7 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; + public class AllAccessPermissionRulesRoot { @JsonProperty("AllAccessPermissionRules") private AllAccessPermissionRules allAccessPermissionRules; @@ -24,6 +25,7 @@ public AllAccessPermissionRules getAllAccessPermissionRules() { return allAccessPermissionRules; } + public void setAllAccessPermissionRules(AllAccessPermissionRules allAccessPermissionRules) { this.allAccessPermissionRules = allAccessPermissionRules; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java index c3851fb39..f8781a270 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java @@ -1,7 +1,22 @@ +/* + * 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.ilt.faaast.service.security.json; import com.fasterxml.jackson.annotation.JsonProperty; + public class Attribute { @JsonProperty("CLAIM") @@ -14,15 +29,18 @@ public String getCLAIM() { return CLAIM; } + public void setCLAIM(String CLAIM) { this.CLAIM = CLAIM; } + public String getGLOBAL() { return GLOBAL; } + public void setGLOBAL(String GLOBAL) { this.GLOBAL = GLOBAL; } -} \ No newline at end of file +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java index b21dea357..b2bb650b6 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java @@ -14,13 +14,12 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security.json; - import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - import java.util.HashMap; import java.util.Map; + /** * Represents a flexible condition structure that can contain * any JSON operators (e.g., "$or", "$match", "$eq", etc.). @@ -37,10 +36,12 @@ public void setExpression(String key, Object value) { expression.put(key, value); } + public Map getExpression() { return expression; } + public void setExpression(Map expression) { this.expression = expression; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java index 2f93f902a..69b3f673a 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java @@ -14,13 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security.json; -import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.HashMap; -import java.util.Map; - /** * Describes the filter object, including FRAGMENT and a CONDITION @@ -39,14 +35,17 @@ public String getFRAGMENT() { return FRAGMENT; } + public void setFRAGMENT(String FRAGMENT) { this.FRAGMENT = FRAGMENT; } + public Condition getCONDITION() { return CONDITION; } + public void setCONDITION(Condition CONDITION) { this.CONDITION = CONDITION; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java index 8fc5389a9..d01001dc6 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java @@ -1,7 +1,22 @@ +/* + * 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.ilt.faaast.service.security.json; import com.fasterxml.jackson.annotation.JsonProperty; + public class Objects { @JsonProperty("ROUTE") @@ -20,31 +35,38 @@ public String getROUTE() { return ROUTE; } + public void setROUTE(String ROUTE) { this.ROUTE = ROUTE; } + public String getIDENTIFIABLE() { return IDENTIFIABLE; } + public void setIDENTIFIABLE(String IDENTIFIABLE) { this.IDENTIFIABLE = IDENTIFIABLE; } + public String getREFERABLE() { return REFERABLE; } + public void setREFERABLE(String REFERABLE) { this.REFERABLE = REFERABLE; } + public String getDESCRIPTOR() { return DESCRIPTOR; } + public void setDESCRIPTOR(String DESCRIPTOR) { this.DESCRIPTOR = DESCRIPTOR; } -} \ No newline at end of file +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java index c876ef15c..3dbf13b20 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java @@ -1,9 +1,24 @@ +/* + * 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.ilt.faaast.service.security.json; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Map; + public class Rule { @JsonProperty("ACL") @@ -36,63 +51,78 @@ public ACL getACL() { return ACL; } + public void setACL(ACL ACL) { this.ACL = ACL; } + public List getOBJECTS() { return OBJECTS; } + public void setOBJECTS(List OBJECTS) { this.OBJECTS = OBJECTS; } + public Map getFORMULA() { return FORMULA; } + public void setFORMULA(Map FORMULA) { this.FORMULA = FORMULA; } + public Filter getFILTER() { return FILTER; } + public void setFILTER(Filter FILTER) { this.FILTER = FILTER; } + public String getFRAGMENT() { return FRAGMENT; } + public void setFRAGMENT(String FRAGMENT) { this.FRAGMENT = FRAGMENT; } + public String getUSEACL() { return USEACL; } + public void setUSEACL(String USEACL) { this.USEACL = USEACL; } + public List getUSEOBJECTS() { return USEOBJECTS; } + public void setUSEOBJECTS(List USEOBJECTS) { this.USEOBJECTS = USEOBJECTS; } + public String getUSEFORMULA() { return USEFORMULA; } + public void setUSEFORMULA(String USEFORMULA) { this.USEFORMULA = USEFORMULA; } -} \ No newline at end of file +} diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index 427e55fe2..e77295f24 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -22,10 +22,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.security.json.Attribute; import de.fraunhofer.iosb.ilt.faaast.service.security.json.Objects; import de.fraunhofer.iosb.ilt.faaast.service.security.json.Rule; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -40,6 +38,7 @@ public void init() {} private String rule2 = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"GLOBAL\":\"ANONYMOUS\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"ROUTE\":\"*\"}],\"FORMULA\":{\"$or\":[{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"Nameplate\"}]},{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"TechnicalData\"}]}]}}]}}"; private String ruleWithFilter = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"CLAIM\":\"BusinessPartnerNumber\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"DESCRIPTOR\":\"(aasdesc)*\"}],\"FORMULA\":{\"$and\":[{\"$eq\":[{\"$attribute\":{\"CLAIM\":\"BusinessPartnerNumber\"}},{\"$strVal\":\"BPNL00000000000A\"}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"manufacturerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"99991\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$strVal\":\"PUBLIC_READABLE\"}]}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"customerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"ACME001\"}]}]}]},\"FILTER\":{\"FRAGMENT\":\"$aasdesc#assetInformation.specificAssetIds[]\",\"CONDITION\":{\"$or\":[{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"manufacturerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"99991\"}]}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"customerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"ACME001\"}]}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"partInstanceId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$attribute\":{\"CLAIM\":\"BusinessPartnerNumber\"}}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$strVal\":\"PUBLIC_READABLE\"}]}]}}}]}}"; + @Test public void testAllowAnonymousRead() { // Create ACL @@ -86,6 +85,7 @@ public void testParse2() throws JsonProcessingException { } + @Test public void testParse3() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); diff --git a/pom.xml b/pom.xml index dfdfd349d..b5a42ed63 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,6 @@ assetconnection/http dataformat/json starter - submodeltemplate/security scm:git:git://github.com/FraunhoferIOSB/FAAAST-Service.git From c1ece407e56b710895535ab6571e7841e5396ed6 Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 12 May 2025 11:43:19 +0200 Subject: [PATCH 024/108] add folder monitoring for ACL rules --- .../json/AllAccessPermissionRules.java | 2 +- .../json/AllAccessPermissionRulesRoot.java | 3 + .../faaast/service/security/json/DefACL.java | 3 + .../service/security/json/DefFormula.java | 4 +- .../service/security/AccessRuleTest.java | 35 ++--- .../resources/ACLReadAccessAnonymous.json | 27 ++++ .../service/endpoint/http/HttpEndpoint.java | 2 - .../endpoint/http/HttpEndpointConfig.java | 40 ++++-- .../endpoint/http/RequestHandlerServlet.java | 2 +- .../endpoint/http/security/ApiGateway.java | 120 +++++++++++++++++- 10 files changed, 196 insertions(+), 42 deletions(-) create mode 100644 core/src/test/resources/ACLReadAccessAnonymous.json diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java index daf3674cf..5e72a5487 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java @@ -19,7 +19,7 @@ /** - * Describes an access permissiom rule. + * Describes all access permissiom rules. */ public class AllAccessPermissionRules { diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java index f023cc3e7..4da381ecc 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java @@ -17,6 +17,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; +/** + * Root collection for all rules. + */ public class AllAccessPermissionRulesRoot { @JsonProperty("AllAccessPermissionRules") private AllAccessPermissionRules allAccessPermissionRules; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java index 9aca50f45..3c7ae7145 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java @@ -14,6 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security.json; +/** + * Optional DefACL field. + */ public class DefACL { private String name; private ACL acl; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java index 4415f5e14..e4fe9dcc7 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java @@ -17,6 +17,9 @@ import java.util.Map; +/** + * Optional DefFormula field. + */ public class DefFormula { private String name; private Map formula; @@ -25,5 +28,4 @@ public void setName(String allowSubjectGroup1) {} public void setFormula(Map formulaExpression) {} - // getters/setters } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index e77295f24..37a76b3c0 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -14,7 +14,6 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.security.json.ACL; import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRules; @@ -22,6 +21,9 @@ import de.fraunhofer.iosb.ilt.faaast.service.security.json.Attribute; import de.fraunhofer.iosb.ilt.faaast.service.security.json.Objects; import de.fraunhofer.iosb.ilt.faaast.service.security.json.Rule; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -34,13 +36,9 @@ public class AccessRuleTest { @Before public void init() {} - private String rule1 = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"CLAIM\":\"__Albert__Alb\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"ROUTE\":\"*\"}],\"FORMULA\":{\"$boolean\":true}},{\"ACL\":{\"ATTRIBUTES\":[{\"CLAIM\":\"isNotAuthenticated\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"ROUTE\":\"/submodels/*\"},{\"ROUTE\":\"/shells/aHR0cHM6Ly96dmVpLm9yZy9kZW1vL2Fhcy9Db250cm9sQ2FiaW5ldA\"}],\"FORMULA\":{\"$or\":[{\"$and\":[{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"Nameplate\"}]},{\"$starts_with\":[{\"$field\":\"$sme#idShort\"},{\"$strVal\":\"Manufacturer\"}]}]},{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"TechnicalData\"}]}]},\"FRAGMENT\":\"sme#\",\"FILTER\":{\"$or\":[{\"$starts_with\":[{\"$field\":\"$sme#idShort\"},{\"$strVal\":\"Manufacturer\"}]},{\"$starts_with\":[{\"$field\":\"$sme#idShort\"},{\"$strVal\":\"General\"}]}]}}]}}"; - private String rule2 = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"GLOBAL\":\"ANONYMOUS\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"ROUTE\":\"*\"}],\"FORMULA\":{\"$or\":[{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"Nameplate\"}]},{\"$eq\":[{\"$field\":\"$sm#idShort\"},{\"$strVal\":\"TechnicalData\"}]}]}}]}}"; - - private String ruleWithFilter = "{\"AllAccessPermissionRules\":{\"rules\":[{\"ACL\":{\"ATTRIBUTES\":[{\"CLAIM\":\"BusinessPartnerNumber\"}],\"RIGHTS\":[\"READ\"],\"ACCESS\":\"ALLOW\"},\"OBJECTS\":[{\"DESCRIPTOR\":\"(aasdesc)*\"}],\"FORMULA\":{\"$and\":[{\"$eq\":[{\"$attribute\":{\"CLAIM\":\"BusinessPartnerNumber\"}},{\"$strVal\":\"BPNL00000000000A\"}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"manufacturerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"99991\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$strVal\":\"PUBLIC_READABLE\"}]}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"customerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"ACME001\"}]}]}]},\"FILTER\":{\"FRAGMENT\":\"$aasdesc#assetInformation.specificAssetIds[]\",\"CONDITION\":{\"$or\":[{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"manufacturerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"99991\"}]}]},{\"$match\":[{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"customerPartId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].value\"},{\"$strVal\":\"ACME001\"}]}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].name\"},{\"$strVal\":\"partInstanceId\"}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$attribute\":{\"CLAIM\":\"BusinessPartnerNumber\"}}]},{\"$eq\":[{\"$field\":\"$aasdesc#specificAssetIds[].externalSubjectId\"},{\"$strVal\":\"PUBLIC_READABLE\"}]}]}}}]}}"; @Test - public void testAllowAnonymousRead() { + public void testAllowAnonymousReadConstruction() { // Create ACL ACL acl = new ACL(); Attribute attr = new Attribute(); @@ -72,23 +70,14 @@ public void testAllowAnonymousRead() { @Test - public void testParse1() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); - AllAccessPermissionRulesRoot allRules = mapper.readValue(rule1, AllAccessPermissionRulesRoot.class); - } - - - @Test - public void testParse2() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); - AllAccessPermissionRulesRoot allRules = mapper.readValue(rule2, AllAccessPermissionRulesRoot.class); - - } - - - @Test - public void testParse3() throws JsonProcessingException { + public void testParse() throws IOException { + InputStream inputStream = AccessRuleTest.class.getResourceAsStream("/" + "ACLReadAccessAnonymous.json"); + if (inputStream == null) { + System.out.println("ACL not found in resources!"); + return; + } ObjectMapper mapper = new ObjectMapper(); - AllAccessPermissionRulesRoot allRules = mapper.readValue(ruleWithFilter, AllAccessPermissionRulesRoot.class); + AllAccessPermissionRulesRoot allRules = mapper.readValue( + new String(inputStream.readAllBytes(), StandardCharsets.UTF_8), AllAccessPermissionRulesRoot.class); } } diff --git a/core/src/test/resources/ACLReadAccessAnonymous.json b/core/src/test/resources/ACLReadAccessAnonymous.json new file mode 100644 index 000000000..b84c98018 --- /dev/null +++ b/core/src/test/resources/ACLReadAccessAnonymous.json @@ -0,0 +1,27 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "GLOBAL": "ANONYMOUS" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "ROUTE": "*" + } + ], + "FORMULA": { + "$boolean": true + } + } + ] + } +} \ No newline at end of file diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 5214b9875..f8f2bba8f 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -20,7 +20,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateInformation; import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.Interface; @@ -76,7 +75,6 @@ public class HttpEndpoint extends AbstractEndpoint { private static final String ENDPOINT_PROTOCOL = "HTTP"; private static final String ENDPOINT_PROTOCOL_VERSION = "1.1"; private Server server; - private ApiGateway apiGateway; @Override public HttpEndpointConfig asConfig() { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java index c6cbb5c13..ee0c9bbaa 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java @@ -55,16 +55,7 @@ public static Builder builder() { private boolean sniEnabled; private boolean sslEnabled; private String jwkProvider; - - public String getJwkProvider() { - return jwkProvider; - } - - - public void setJwkProvider(String jwkProvider) { - this.jwkProvider = jwkProvider; - } - + private String aclFolder; public HttpEndpointConfig() { certificate = CertificateConfig.builder() @@ -214,6 +205,26 @@ public void setSslEnabled(boolean sslEnabled) { } + public String getJwkProvider() { + return jwkProvider; + } + + + public void setJwkProvider(String jwkProvider) { + this.jwkProvider = jwkProvider; + } + + + public String getAclFolder() { + return aclFolder; + } + + + public void setAclFolder(String aclFolder) { + this.aclFolder = aclFolder; + } + + @Override public boolean equals(Object o) { if (this == o) { @@ -240,6 +251,7 @@ public boolean equals(Object o) { && Objects.equals(certificate, that.certificate) && Objects.equals(hostname, that.hostname) && Objects.equals(jwkProvider, that.jwkProvider) + && Objects.equals(aclFolder, that.aclFolder) && Objects.equals(profiles, that.profiles); } @@ -261,6 +273,8 @@ public int hashCode() { port, sniEnabled, sslEnabled, + jwkProvider, + aclFolder, profiles); } @@ -378,6 +392,12 @@ public B ssl(boolean value) { getBuildingInstance().setSslEnabled(value); return getSelf(); } + + + public B aclFolder(String value) { + getBuildingInstance().setAclFolder(value); + return getSelf(); + } } public static class Builder extends AbstractBuilder { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 36920df1c..cc3848f6d 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -65,7 +65,7 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); - this.apiGateway = Objects.nonNull(config.getJwkProvider()) ? new ApiGateway(config.getJwkProvider()) : null; + this.apiGateway = Objects.nonNull(config.getJwkProvider()) ? new ApiGateway(config.getJwkProvider(), config.getAclFolder()) : null; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 24770764a..e7eb3ba38 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -23,19 +23,41 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRulesRoot; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; import java.security.interfaces.RSAPublicKey; +import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.slf4j.LoggerFactory; /** - * A simple ApiGateway that verifies JWT tokens against the provided jwkProvider. + * An ApiGateway that verifies JWT tokens against the provided jwkProvider. + * Initially all ACL rules from the folder are read and stored. After that, + * the folder is monitored for deletion or addition of new rules. */ public class ApiGateway { + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); private String jwkProvider; + private Map aclList; + private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; - public ApiGateway(String jwkProvider) { + public ApiGateway(String jwkProvider, String aclFolder) { this.jwkProvider = jwkProvider; + initializeAclList(aclFolder); + monitorAclRules(aclFolder); } @@ -67,13 +89,103 @@ public boolean isAuthorized(String token, String path) { } if (!verifiedClaims(jwt.getClaims(), path)) { return false; - } ; + } return true; } private boolean verifiedClaims(Map claims, String path) { - //@TODO send to AuthServer to compare with SMT Security Processor + //@TODO send to AuthServer to compare with acl return true; } + + + private void initializeAclList(String aclFolder) { + this.aclList = new HashMap<>(); + File folder = new File(aclFolder); + if (!folder.isDirectory()) { + LOGGER.error(abortMessage); + return; + } + File[] jsonFiles = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); + ObjectMapper mapper = new ObjectMapper(); + if (jsonFiles != null) { + for (File file: jsonFiles) { + Path filePath = file.toPath(); + String content = null; + try { + content = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8); + aclList.put(filePath, mapper.readValue( + content, AllAccessPermissionRulesRoot.class)); + } + catch (IOException e) { + LOGGER.error(abortMessage); + } + } + } + } + + + private void monitorAclRules(String aclFolder) { + Path folderToWatch = Paths.get(aclFolder); + WatchService watchService = null; + try { + watchService = FileSystems.getDefault().newWatchService(); + // Register the folder with the WatchService for CREATE and DELETE events + folderToWatch.register( + watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE); + monitorLoop(watchService, folderToWatch); + } + catch (IOException e) { + LOGGER.error(abortMessage); + } + + } + + + private void monitorLoop(WatchService watchService, Path folderToWatch) { + ObjectMapper mapper = new ObjectMapper(); + Thread monitoringThread = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + WatchKey watchKey = null; + try { + watchKey = watchService.take(); + } + catch (InterruptedException e) { + LOGGER.error(abortMessage); + } + for (WatchEvent event: watchKey.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + Path filePath = (Path) event.context(); + Path absolutePath = folderToWatch.resolve(filePath).toAbsolutePath(); + // Check if the file is a JSON file + if (filePath.toString().toLowerCase().endsWith(".json")) { + if (kind == StandardWatchEventKinds.ENTRY_CREATE) { + try { + aclList.put(absolutePath, mapper.readValue( + new String(Files.readAllBytes(absolutePath), StandardCharsets.UTF_8), AllAccessPermissionRulesRoot.class)); + } + catch (IOException e) { + LOGGER.error(abortMessage); + } + LOGGER.info("Added new ACL rule."); + } + else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { + aclList.remove(absolutePath); + LOGGER.info("Removed ACL rule."); + } + } + } + // Reset the key to receive further watch events + boolean valid = watchKey.reset(); + if (!valid) { + System.out.println("WatchKey no longer valid; exiting."); + break; + } + } + }); + monitoringThread.start(); + } } From c210947b407b1fa2a33475a1210305b06c746338 Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 12 May 2025 16:34:48 +0200 Subject: [PATCH 025/108] add simple auth rule checks --- .../faaast/service/security/json/Rule.java | 1 - .../endpoint/http/RequestHandlerServlet.java | 2 +- .../endpoint/http/security/ApiGateway.java | 67 +++++++++++++++++-- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java index 3dbf13b20..31cc9576e 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java @@ -33,7 +33,6 @@ public class Rule { @JsonProperty("FILTER") private Filter FILTER; - // The missing field that often appears in your JSON: @JsonProperty("FRAGMENT") private String FRAGMENT; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index cc3848f6d..6f8e16a76 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -77,7 +77,7 @@ private void doThrow(Exception e) throws ServletException { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (Objects.nonNull(apiGateway)) { - if (!apiGateway.isAuthorized(request.getHeader("Authorization"), request.toString())) { + if (!apiGateway.isAuthorized(request.getHeader("Authorization"), request)) { doThrow(new UnauthorizedException( String.format("User not authorized '%s'", request.getRequestURI()))); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index e7eb3ba38..a0340626a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -25,6 +25,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRulesRoot; +import jakarta.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -38,8 +39,10 @@ import java.nio.file.WatchService; import java.security.interfaces.RSAPublicKey; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import org.slf4j.LoggerFactory; @@ -47,12 +50,14 @@ * An ApiGateway that verifies JWT tokens against the provided jwkProvider. * Initially all ACL rules from the folder are read and stored. After that, * the folder is monitored for deletion or addition of new rules. + * Contains simple Auth rule checks. */ public class ApiGateway { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); private String jwkProvider; private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; + private final String apiPrefix = "/api/v3.0/"; public ApiGateway(String jwkProvider, String aclFolder) { this.jwkProvider = jwkProvider; @@ -65,10 +70,10 @@ public ApiGateway(String jwkProvider, String aclFolder) { * Verifies the token by decoding it. * * @param token the JWT token - * @param path the path + * @param request the request parameters * @return true if the token is valid */ - public boolean isAuthorized(String token, String path) { + public boolean isAuthorized(String token, HttpServletRequest request) { if (Objects.isNull(token)) { return false; } @@ -87,16 +92,66 @@ public boolean isAuthorized(String token, String path) { catch (JwkException e) { return false; } - if (!verifiedClaims(jwt.getClaims(), path)) { + if (!verifiedClaims(jwt.getClaims(), request)) { return false; } return true; } - private boolean verifiedClaims(Map claims, String path) { - //@TODO send to AuthServer to compare with acl - return true; + private boolean verifiedClaims(Map claims, HttpServletRequest request) { + Claim client = claims.get("client_id"); + String clientId = client.isNull() ? "" : client.asString(); + String requestPath = request.getRequestURI(); + String path = requestPath.contains(apiPrefix) ? requestPath.substring(10) : requestPath; + String method = request.getMethod(); + List relevantRules = this.aclList.values().stream() + .filter(a -> a.getAllAccessPermissionRules() + .getRules().stream() + .anyMatch(r -> r.getACL() != null + && r.getACL().getATTRIBUTES() != null + && r.getACL().getRIGHTS() != null + && r.getOBJECTS() != null + && r.getOBJECTS().stream().anyMatch(attr -> { + if (attr.getROUTE() != null) { + return "*".equals(attr.getROUTE()) || attr.getROUTE().contains(path); + } + else if (attr.getIDENTIFIABLE() != null) { + return checkIdentifiable(path, attr.getIDENTIFIABLE()); + } + else { + return false; + } + }) + && "ALLOW".equals(r.getACL().getACCESS()) + && r.getACL().getRIGHTS().contains(getRequiredRight(method)) + && r.getACL().getATTRIBUTES().stream() + .anyMatch(attr -> (attr.getCLAIM() != null && attr.getCLAIM().equals(clientId)) + || (attr.getGLOBAL() != null && attr.getGLOBAL().equals("ANONYMOUS"))))) + .collect(Collectors.toList()); + return !relevantRules.isEmpty(); + } + + + private String getRequiredRight(String method) { + switch (method) { + case "GET": + return "READ"; + case "POST": + return "WRITE"; + case "PUT": + return "UPDATE"; + case "DELETE": + return "DELETE"; + default: + throw new IllegalArgumentException("Unsupported method: " + method); + } + } + + + private boolean checkIdentifiable(String path, String identifiable) { + //TODO + return false; } From aaa94df9721920b0407d70fbd0bb116d6da301ad Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Tue, 13 May 2025 15:26:43 +0200 Subject: [PATCH 026/108] add simple authServer --- .../endpoint/http/security/ApiGateway.java | 189 +++++++++++------- 1 file changed, 120 insertions(+), 69 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index a0340626a..55f0c6c3e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -25,6 +25,8 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRulesRoot; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.Attribute; +import de.fraunhofer.iosb.ilt.faaast.service.security.json.Rule; import jakarta.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; @@ -39,6 +41,7 @@ import java.nio.file.WatchService; import java.security.interfaces.RSAPublicKey; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -50,14 +53,12 @@ * An ApiGateway that verifies JWT tokens against the provided jwkProvider. * Initially all ACL rules from the folder are read and stored. After that, * the folder is monitored for deletion or addition of new rules. - * Contains simple Auth rule checks. */ public class ApiGateway { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); private String jwkProvider; private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; - private final String apiPrefix = "/api/v3.0/"; public ApiGateway(String jwkProvider, String aclFolder) { this.jwkProvider = jwkProvider; @@ -68,6 +69,7 @@ public ApiGateway(String jwkProvider, String aclFolder) { /** * Verifies the token by decoding it. + * Additionally, AuthServer is used to verify claims. * * @param token the JWT token * @param request the request parameters @@ -75,86 +77,135 @@ public ApiGateway(String jwkProvider, String aclFolder) { */ public boolean isAuthorized(String token, HttpServletRequest request) { if (Objects.isNull(token)) { - return false; + return AuthServer.filterRules(this.aclList, null, request); } - token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; - DecodedJWT jwt = JWT.decode(token); - JwkProvider provider = new UrlJwkProvider(this.jwkProvider); - try { - Jwk jwk = provider.get(jwt.getKeyId()); - Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); - algorithm.verify(jwt); - JWTVerifier verifier = JWT.require(algorithm) - //.withIssuer(this.jwkProvider) - .build(); - verifier.verify(token); + else { + token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; + DecodedJWT jwt = JWT.decode(token); + JwkProvider provider = new UrlJwkProvider(this.jwkProvider); + try { + Jwk jwk = provider.get(jwt.getKeyId()); + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); + algorithm.verify(jwt); + JWTVerifier verifier = JWT.require(algorithm) + //.withIssuer(this.jwkProvider) + .build(); + verifier.verify(token); + } + catch (JwkException e) { + return false; + } + return AuthServer.filterRules(this.aclList, jwt.getClaims(), request); } - catch (JwkException e) { - return false; + } + + /** + * Simple whitelist AuthServer implementation that supports ANONYMOUS access, + * claims with simple eq formulas and route authorization. + * Access must be explicitly defined, otherwise it is blocked. + */ + public static class AuthServer { + private static final String apiPrefix = "/api/v3.0/"; + + /** + * Check all rules that explicitly allows the request. + * If a rule exists after all filters, true is returned + * + * @param claims + * @param request + * @return + */ + private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { + String requestPath = request.getRequestURI(); + String path = requestPath.contains(apiPrefix) ? requestPath.substring(10) : requestPath; + String method = request.getMethod(); + List relevantRules = aclList.values().stream() + .filter(a -> a.getAllAccessPermissionRules() + .getRules().stream() + .anyMatch(r -> r.getACL() != null + && r.getACL().getATTRIBUTES() != null + && r.getACL().getRIGHTS() != null + && r.getOBJECTS() != null + && r.getOBJECTS().stream().anyMatch(attr -> { + if (attr.getROUTE() != null) { + return "*".equals(attr.getROUTE()) || attr.getROUTE().contains(path); + } + else if (attr.getIDENTIFIABLE() != null) { + return checkIdentifiable(path, attr.getIDENTIFIABLE()); + } + else { + return false; + } + }) + && "ALLOW".equals(r.getACL().getACCESS()) + && r.getACL().getRIGHTS().contains(getRequiredRight(method)) + && verifyAllClaims(claims, r))) + .collect(Collectors.toList()); + return !relevantRules.isEmpty(); } - if (!verifiedClaims(jwt.getClaims(), request)) { - return false; + + + private static boolean verifyAllClaims(Map claims, Rule rule) { + if (rule.getACL().getATTRIBUTES().stream() + .anyMatch(attr -> "ANONYMOUS".equals(attr.getGLOBAL()) + && Boolean.TRUE.equals(rule.getFORMULA().get("$boolean")))) { + return true; + } + if (claims == null) { + return false; + } + List claimValues = rule.getACL().getATTRIBUTES().stream() + .filter(attr -> attr.getGLOBAL() == null) + .map(Attribute::getCLAIM) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return !claimValues.isEmpty() + && claimValues.stream() + .allMatch(value -> { + Claim claim = claims.get(value); + return claim != null + && evaluateSimpleEQFormula(rule.getFORMULA(), value, claim.asString()); + }); } - return true; - } - private boolean verifiedClaims(Map claims, HttpServletRequest request) { - Claim client = claims.get("client_id"); - String clientId = client.isNull() ? "" : client.asString(); - String requestPath = request.getRequestURI(); - String path = requestPath.contains(apiPrefix) ? requestPath.substring(10) : requestPath; - String method = request.getMethod(); - List relevantRules = this.aclList.values().stream() - .filter(a -> a.getAllAccessPermissionRules() - .getRules().stream() - .anyMatch(r -> r.getACL() != null - && r.getACL().getATTRIBUTES() != null - && r.getACL().getRIGHTS() != null - && r.getOBJECTS() != null - && r.getOBJECTS().stream().anyMatch(attr -> { - if (attr.getROUTE() != null) { - return "*".equals(attr.getROUTE()) || attr.getROUTE().contains(path); - } - else if (attr.getIDENTIFIABLE() != null) { - return checkIdentifiable(path, attr.getIDENTIFIABLE()); - } - else { - return false; - } - }) - && "ALLOW".equals(r.getACL().getACCESS()) - && r.getACL().getRIGHTS().contains(getRequiredRight(method)) - && r.getACL().getATTRIBUTES().stream() - .anyMatch(attr -> (attr.getCLAIM() != null && attr.getCLAIM().equals(clientId)) - || (attr.getGLOBAL() != null && attr.getGLOBAL().equals("ANONYMOUS"))))) - .collect(Collectors.toList()); - return !relevantRules.isEmpty(); - } + private static boolean evaluateSimpleEQFormula(Map formula, String value, String claimValue) { + if (formula.size() != 1 || !formula.containsKey("$eq")) { + LOGGER.error("Unsupported ACL formula."); + return false; + } + List eqList = (List) formula.get("$eq"); + LinkedHashMap attribute = (LinkedHashMap) eqList.get(0).get("$attribute"); + String strVal = (String) eqList.get(1).get("$strVal"); + if (attribute.get("CLAIM").equals(value) && strVal.equals(claimValue)) { + return true; + } + return false; + } - private String getRequiredRight(String method) { - switch (method) { - case "GET": - return "READ"; - case "POST": - return "WRITE"; - case "PUT": - return "UPDATE"; - case "DELETE": - return "DELETE"; - default: - throw new IllegalArgumentException("Unsupported method: " + method); + private static String getRequiredRight(String method) { + switch (method) { + case "GET": + return "READ"; + case "POST": + return "WRITE"; + case "PUT": + return "UPDATE"; + case "DELETE": + return "DELETE"; + default: + throw new IllegalArgumentException("Unsupported method: " + method); + } } - } - private boolean checkIdentifiable(String path, String identifiable) { - //TODO - return false; + private static boolean checkIdentifiable(String path, String identifiable) { + //TODO + return false; + } } - private void initializeAclList(String aclFolder) { this.aclList = new HashMap<>(); File folder = new File(aclFolder); From 0e1905610d3a8f46ea49f274172d5ed380decfcd Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Tue, 13 May 2025 16:10:32 +0200 Subject: [PATCH 027/108] add null checks and initial documentation --- docs/source/interfaces/endpoint.md | 95 ++++++++++++++++++- .../endpoint/http/security/ApiGateway.java | 12 ++- 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/docs/source/interfaces/endpoint.md b/docs/source/interfaces/endpoint.md index eea7e89b1..90ac20ac1 100644 --- a/docs/source/interfaces/endpoint.md +++ b/docs/source/interfaces/endpoint.md @@ -42,13 +42,15 @@ All endpoint implementations share the following common configuration properties The HTTP Endpoint allows accessing data and execute operations within the FA³ST Service via REST-API. In accordance to the specification, only HTTPS is supported since AAS v3.0. The HTTP Endpoint is based on the document [Details of the Asset Administration Shell - Part 2: Application Programming Interfaces v3.0](https://industrialdigitaltwin.org/en/content-hub/aasspecifications/specification-of-the-asset-administration-shell-part-1-metamodel-idta-number-01001-3-0) and the corresponding [OpenAPI documentation v3.0.1](https://app.swaggerhub.com/apis/Plattform_i40/Entire-API-Collection/V3.0.1). +This HTTP Endpoint also supports JWT Access Tokens and simple JSON Access Rules for Routes and Identifiables, as defined in [Part 4: Security (IDTA-01004)](https://admin-shell-io.github.io/aas-specs-antora/IDTA-01004/v3.0/index.html). ### Configuration :::{table} Configuration properties of HTTP Endpoint. | Name | Allowed Value | Description | Default Value | | ------------------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| certificate
*(optional)* | [CertificateInfo](#providing-certificates-in-configuration) | The HTTPS certificate to use.
| self-signed certificate | +| aclFolder
*(optional)* | String | Sets the path to the folder, where JSON Access Control Rules are defined | | +| certificate
*(optional)* | [CertificateInfo](#providing-certificates-in-configuration) | The HTTPS certificate to use.
| self-signed certificate | | corsAllowCredentials
*(optional)* | Boolean | Sets the `Access-Control-Allow-Credentials` response header. | false | | corsAllowedHeaders
*(optional)* | String (comma-separated list) | Sets the `Access-Control-Allow-Headers` response header. | * | | corsAllowedMethods
*(optional)* | String (comma-separated list) | Sets the `Access-Control-Allow-Methods` response header. | GET, POST, HEAD | @@ -57,6 +59,7 @@ The HTTP Endpoint is based on the document [Details of the Asset Administration | corsExposedHeaders
*(optional)* | String (comma-separated list) | Sets the `Access-Control-Expose-Headers` response header. | | | corsMaxAge
*(optional)* | Long | Sets the `Access-Control-Max-Age` response header. | 3600 | | hostname
*(optional)* | String | The hostname to be used for automatic registration with registry. | auto-detect (typically IP address) | +| jwkProvider
*(optional)* | String | The URL of the IdentityProvider to verify JWT Access Tokens. | | | includeErrorDetails
*(optional)* | Boolean | If set, stack traceis added to the HTTP responses incase of error. | false | | port
*(optional)* | Integer | The port to use. | 443 | | sniEnabled
*(optional)* | Boolean | If Server Name Identification (SNI) should be enabled.
**This should only be disabled for testing purposes as it may present a security risk!** | true | @@ -69,6 +72,7 @@ The HTTP Endpoint is based on the document [Details of the Asset Administration { "endpoints": [ { "@class": "de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.HttpEndpoint", + "aclFolder": "C:\\Users\\FAST\\ACL", "certificate": { "keyStoreType": "PKCS12", "keyStorePath": "C:\faaast\MyKeyStore.p12", @@ -84,6 +88,7 @@ The HTTP Endpoint is based on the document [Details of the Asset Administration "corsExposedHeaders": "X-Custom-Header", "corsMaxAge": 1000, "hostname": "localhost", + "jwkProvider": "http://localhost:4444", "includeErrorDetails": true, "port": 443, "profiles": [ "AAS_REPOSITORY_FULL", "AAS_FULL", "SUBMODEL_REPOSITORY_FULL", "SUBMODEL_FULL" ], @@ -94,6 +99,94 @@ The HTTP Endpoint is based on the document [Details of the Asset Administration } ``` +### AAS Security Part 4 + +FA³ST Service supports the verification of JWT Access Tokens and simple Access rules. +If the configuration includes the `jwkProvider` URL, all HTTP API requests will be validated. +To grant anonymous READ access, an Access rule must be defined and placed in the `aclFolder`: +``` +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "GLOBAL": "ANONYMOUS" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "ROUTE": "*" + } + ], + "FORMULA": { + "$boolean": true + } + } + ] + } +} +``` + +If a `jwkProvider` is defined and no Access rules exist, all HTTP API requests will be blocked. +We recommend Ory Hydra as OAuth2 and OpenID Connect server, which can be used with the following curl requests to retrieve a JWT token. + +Create client_id: +``` +curl -s -X POST http://127.0.0.1:4444/oauth2/token -u '6c176008-9308-4603-a55a-9975bb3a93b6:MCXepEOPh3GLx.hPpdb.NRCCsz' -d 'grant_type=client_credentials' -d 'scope=openid'" +``` +Retrieve token: +``` +curl -X POST -H "Content-Type: application/x-www-form-urlencoded" +-d "grant_type=client_credentials" -d "scope=openid" -d "client_id=6c176008-9308-4603-a55a-9975bb3a93b6" -d "client_secret=MCXepEOPh3GLx.hPpdb.NRCCsz" http://127.0.0.1:4444/oauth2/token +``` +To grant READ access to this client_id, the following Access rule can be used: +``` +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "CLAIM": "client_id" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "ROUTE": "*" + } + ], + "FORMULA": { + "$eq": [ + { + "$attribute": { + "CLAIM": "client_id" + } + }, + { + "$strVal": "6c176008-9308-4603-a55a-9975bb3a93b6" + } + ] + } + } + ] + } +} +``` + + ### API FA³ST Service supports the following APIs as defined by the [OpenAPI documentation v3.0.1](https://app.swaggerhub.com/apis/Plattform_i40/Entire-API-Collection/V3.0.1) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 55f0c6c3e..2e45995ef 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -208,11 +208,13 @@ private static boolean checkIdentifiable(String path, String identifiable) { private void initializeAclList(String aclFolder) { this.aclList = new HashMap<>(); - File folder = new File(aclFolder); - if (!folder.isDirectory()) { + if (aclFolder == null + || aclFolder.trim().isEmpty() + || !new File(aclFolder.trim()).isDirectory()) { LOGGER.error(abortMessage); return; } + File folder = new File(aclFolder.trim()); File[] jsonFiles = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); ObjectMapper mapper = new ObjectMapper(); if (jsonFiles != null) { @@ -233,6 +235,12 @@ private void initializeAclList(String aclFolder) { private void monitorAclRules(String aclFolder) { + if (aclFolder == null + || aclFolder.trim().isEmpty() + || !new File(aclFolder.trim()).isDirectory()) { + LOGGER.error(abortMessage); + return; + } Path folderToWatch = Paths.get(aclFolder); WatchService watchService = null; try { From 24c17c0b4010467ea92f5430bf23ef71b6871f2b Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Fri, 16 May 2025 11:25:00 +0200 Subject: [PATCH 028/108] add simple identifiable submodel check --- .../service/security/json/Attribute.java | 3 ++ .../service/security/json/Condition.java | 16 ++++++++++ .../faaast/service/security/json/DefACL.java | 11 ++++++- .../service/security/json/DefFormula.java | 10 ++++++ .../service/security/json/DefObjects.java | 14 ++++++++- .../faaast/service/security/json/Objects.java | 3 ++ .../faaast/service/security/json/Rule.java | 3 ++ docs/source/interfaces/endpoint.md | 31 +++++++++++++++++++ .../endpoint/http/security/ApiGateway.java | 17 ++++++++-- 9 files changed, 103 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java index f8781a270..a876492c5 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java @@ -17,6 +17,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; +/** + * Attributes contain a CLAIM or GLOBAL. + */ public class Attribute { @JsonProperty("CLAIM") diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java index b2bb650b6..d7fb1e23b 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java @@ -31,17 +31,33 @@ public class Condition { // (e.g., "$or", "$match", "$eq", "$regex", etc.) private Map expression = new HashMap<>(); + /** + * Set the expression. + * + * @param key key like eq + * @param value value of claim + */ @JsonAnySetter public void setExpression(String key, Object value) { expression.put(key, value); } + /** + * Get the expression. + * + * @return expression + */ public Map getExpression() { return expression; } + /** + * Set the expression. + * + * @param expression full expression + */ public void setExpression(Map expression) { this.expression = expression; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java index 3c7ae7145..4452d6fef 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java @@ -21,9 +21,18 @@ public class DefACL { private String name; private ACL acl; + /** + * Set the name. + * + * @param acl1 + */ public void setName(String acl1) {} + /** + * Set ACL. + * + * @param acl + */ public void setAcl(ACL acl) {} - // getters/setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java index e4fe9dcc7..3828c6384 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java @@ -24,8 +24,18 @@ public class DefFormula { private String name; private Map formula; + /** + * Set name. + * + * @param allowSubjectGroup1 + */ public void setName(String allowSubjectGroup1) {} + /** + * Set formula. + * + * @param formulaExpression + */ public void setFormula(Map formulaExpression) {} } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java index a61e1d866..680f4fe34 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java @@ -17,13 +17,25 @@ import java.util.List; +/** + * Reusable DefObjects. + */ public class DefObjects { private String name; private List objects; + /** + * Set name. + * + * @param properties + */ public void setName(String properties) {} + /** + * Set objects. + * + * @param list + */ public void setObjects(List list) {} - // getters/setters } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java index d01001dc6..4c01ba1db 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java @@ -17,6 +17,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; +/** + * Objects can the routes and identifiables. + */ public class Objects { @JsonProperty("ROUTE") diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java index 31cc9576e..d4f5a0def 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java @@ -19,6 +19,9 @@ import java.util.Map; +/** + * Rules contain all other definitions. + */ public class Rule { @JsonProperty("ACL") diff --git a/docs/source/interfaces/endpoint.md b/docs/source/interfaces/endpoint.md index 90ac20ac1..38c5f5562 100644 --- a/docs/source/interfaces/endpoint.md +++ b/docs/source/interfaces/endpoint.md @@ -186,6 +186,37 @@ To grant READ access to this client_id, the following Access rule can be used: } ``` +To grant access to specific submodels, FA³ST Service supports Identifiables like `"IDENTIFIABLE": "(Submodel)https://example.com/ids/sm/5120_2111_9032_9005"` or `"IDENTIFIABLE": "(Submodel)*"`. +Example: +``` +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "GLOBAL": "ANONYMOUS" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "IDENTIFIABLE": "(Submodel)https://example.com/ids/sm/5120_2111_9032_9005" + } + ], + "FORMULA": { + "$boolean": true + } + } + ] + } +} +``` ### API diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 2e45995ef..018e12110 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -27,6 +27,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRulesRoot; import de.fraunhofer.iosb.ilt.faaast.service.security.json.Attribute; import de.fraunhofer.iosb.ilt.faaast.service.security.json.Rule; +import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; @@ -110,14 +111,14 @@ public static class AuthServer { /** * Check all rules that explicitly allows the request. * If a rule exists after all filters, true is returned - * + * * @param claims * @param request * @return */ private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { String requestPath = request.getRequestURI(); - String path = requestPath.contains(apiPrefix) ? requestPath.substring(10) : requestPath; + String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(9) : requestPath; String method = request.getMethod(); List relevantRules = aclList.values().stream() .filter(a -> a.getAllAccessPermissionRules() @@ -201,7 +202,17 @@ private static String getRequiredRight(String method) { private static boolean checkIdentifiable(String path, String identifiable) { - //TODO + //check submodel path + if (!path.startsWith("/submodels")) { + return false; + } + if (identifiable.equals("(Submodel)*")) { + return true; + } + else if (identifiable.startsWith("(Submodel)")) { + String id = identifiable.substring(10); + return path.contains(EncodingHelper.base64Encode(id)); + } return false; } } From 620f66f76d1b28f6c2e687804cda636dd356488a Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:44:24 +0200 Subject: [PATCH 029/108] add junit test --- .../http/security/ApiGatewayTest.java | 132 ++++++++++++++++++ pom.xml | 2 +- 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java new file mode 100644 index 000000000..800756827 --- /dev/null +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java @@ -0,0 +1,132 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security; + + +import com.auth0.jwk.Jwk; +import com.auth0.jwk.UrlJwkProvider; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import jakarta.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class ApiGatewayTest { + + private static final String ACL_JSON = "{\n" + + " \"AllAccessPermissionRules\": {\n" + + " \"rules\": [{\n" + + " \"ACL\": {\n" + + " \"ATTRIBUTES\": [{ \"GLOBAL\": \"ANONYMOUS\" }],\n" + + " \"RIGHTS\": [\"READ\"],\n" + + " \"ACCESS\": \"ALLOW\"\n" + + " },\n" + + " \"OBJECTS\": [{ \"ROUTE\": \"*\" }],\n" + + " \"FORMULA\": { \"$boolean\": true }\n" + + " }]\n" + + " }\n" + + "}"; + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + private Path aclDir; + private ApiGateway gateway; + + private static HttpServletRequest req(String method, String uri) { + HttpServletRequest r = mock(HttpServletRequest.class); + when(r.getMethod()).thenReturn(method); + when(r.getRequestURI()).thenReturn(uri); + return r; + } + + + @Test + public void anonymousAccessDependsOnAclFile() throws Exception { + + aclDir = tmp.newFolder("acl").toPath(); + gateway = new ApiGateway("http://whatever-jwks", aclDir.toString()); + + HttpServletRequest request = req("GET", "/api/v3.0/submodels"); + + assertFalse(gateway.isAuthorized(null, request)); + + Path rule = aclDir.resolve("allow.json"); + Path tmpRule = aclDir.resolve("allow.json.tmp"); + Files.writeString(tmpRule, ACL_JSON, StandardCharsets.UTF_8); + Files.move(tmpRule, rule, StandardCopyOption.ATOMIC_MOVE); + + Thread.sleep(200); + assertTrue(gateway.isAuthorized(null, request)); + + Files.delete(rule); + Thread.sleep(200); + assertFalse(gateway.isAuthorized(null, request)); + } + + + @Test + public void jwtIsVerified() throws Exception { + + aclDir = tmp.newFolder("acl").toPath(); + Files.writeString(aclDir.resolve("allow.json"), + ACL_JSON, StandardCharsets.UTF_8); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + RSAPublicKey pub = (RSAPublicKey) kp.getPublic(); + RSAPrivateKey priv = (RSAPrivateKey) kp.getPrivate(); + + String kid = "unit-test-kid"; + + String jwt = JWT.create() + .withKeyId(kid) + .withClaim("dummy", "x") + .sign(Algorithm.RSA256(pub, priv)); + + Jwk jwk = mock(Jwk.class); + when(jwk.getPublicKey()).thenReturn(pub); + when(jwk.getId()).thenReturn(kid); + + try (MockedConstruction mocked = Mockito.mockConstruction(UrlJwkProvider.class, + (mock, ctx) -> when(mock.get(anyString())).thenReturn(jwk))) { + + gateway = new ApiGateway("http://whatever-jwks", aclDir.toString()); + + HttpServletRequest request = req("GET", "/api/v3.0/submodels"); + + assertTrue(gateway.isAuthorized(jwt, request)); + } + } +} diff --git a/pom.xml b/pom.xml index 1b7640bdf..dd748880b 100644 --- a/pom.xml +++ b/pom.xml @@ -85,8 +85,8 @@ 2.19.1 2.1.2 4.0.2 - 4.4.0 3.1.12 + 4.4.0 2.3.1 5.4.0 12.0.22 From fa3157d21fd9919f025026b0570d79539424b745 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Wed, 16 Jul 2025 10:28:30 +0200 Subject: [PATCH 030/108] spotless --- .../endpoint/http/security/ApiGatewayTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java index 800756827..e3bb315e5 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java @@ -14,6 +14,11 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.auth0.jwk.Jwk; import com.auth0.jwk.UrlJwkProvider; @@ -34,12 +39,6 @@ import org.mockito.MockedConstruction; import org.mockito.Mockito; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public class ApiGatewayTest { From 7b62d4b312f982cd1983854c5c35b610afbd95e3 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Fri, 18 Jul 2025 10:15:19 +0200 Subject: [PATCH 031/108] refactor ACL classes --- .../ilt/faaast/service/security/AccessRuleTest.java | 12 ++++++------ .../service/endpoint/http/security/ApiGateway.java | 6 +++--- .../ilt/faaast/service/model}/security/json/ACL.java | 2 +- .../service/model}/security/json/AccessType.java | 2 +- .../security/json/AllAccessPermissionRules.java | 2 +- .../security/json/AllAccessPermissionRulesRoot.java | 2 +- .../service/model}/security/json/Attribute.java | 2 +- .../service/model}/security/json/Condition.java | 2 +- .../faaast/service/model}/security/json/DefACL.java | 2 +- .../service/model}/security/json/DefFormula.java | 2 +- .../service/model}/security/json/DefObjects.java | 2 +- .../faaast/service/model}/security/json/Filter.java | 2 +- .../faaast/service/model}/security/json/Objects.java | 2 +- .../faaast/service/model}/security/json/Right.java | 2 +- .../faaast/service/model}/security/json/Rule.java | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/ACL.java (95%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/AccessType.java (92%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/AllAccessPermissionRules.java (96%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/AllAccessPermissionRulesRoot.java (94%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/Attribute.java (94%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/Condition.java (96%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/DefACL.java (93%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/DefFormula.java (94%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/DefObjects.java (94%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/Filter.java (95%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/Objects.java (96%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/Right.java (92%) rename {core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service => model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model}/security/json/Rule.java (97%) diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index 37a76b3c0..36ac2def4 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -15,12 +15,12 @@ package de.fraunhofer.iosb.ilt.faaast.service.security; import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.ACL; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRules; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRulesRoot; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.Attribute; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.Objects; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.Rule; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.ACL; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Attribute; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Objects; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 018e12110..0a3b9dbf9 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -24,9 +24,9 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.AllAccessPermissionRulesRoot; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.Attribute; -import de.fraunhofer.iosb.ilt.faaast.service.security.json.Rule; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Attribute; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; import java.io.File; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java similarity index 95% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java index 82bf8b797..58287100c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/ACL.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AccessType.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AccessType.java similarity index 92% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AccessType.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AccessType.java index 377f03f7f..c707dfbdb 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AccessType.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AccessType.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; /** * Enum for access types. diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java similarity index 96% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java index 5e72a5487..d91534d9c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRules.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRulesRoot.java similarity index 94% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRulesRoot.java index 4da381ecc..7e01ea4e6 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/AllAccessPermissionRulesRoot.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRulesRoot.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Attribute.java similarity index 94% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Attribute.java index a876492c5..78d323406 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Attribute.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Attribute.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Condition.java similarity index 96% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Condition.java index d7fb1e23b..0986d5836 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Condition.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Condition.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java similarity index 93% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java index 4452d6fef..084013e3f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefACL.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; /** * Optional DefACL field. diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java similarity index 94% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java index 3828c6384..9af0fa041 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefFormula.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import java.util.Map; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java similarity index 94% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java index 680f4fe34..a68f5bb39 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/DefObjects.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import java.util.List; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Filter.java similarity index 95% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Filter.java index 69b3f673a..272574fad 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Filter.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Filter.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Objects.java similarity index 96% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Objects.java index 4c01ba1db..77a19d504 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Objects.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Objects.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Right.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Right.java similarity index 92% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Right.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Right.java index a4b4f3801..9a3c76055 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Right.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Right.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; /** * Enum for rights. diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Rule.java similarity index 97% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Rule.java index d4f5a0def..94a324cc4 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/security/json/Rule.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Rule.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.security.json; +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; From 5a54578a0ac2241822e912cd12dcb5ce28e196a2 Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:20:53 +0200 Subject: [PATCH 032/108] add complex formula evaluator --- .../endpoint/http/security/ApiGateway.java | 24 +-- .../http/security/FormulaEvaluator.java | 101 ++++++++++ .../http/security/FormulaEvaluatorTest.java | 189 ++++++++++++++++++ 3 files changed, 300 insertions(+), 14 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java create mode 100644 endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 0a3b9dbf9..142930aee 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -41,8 +41,9 @@ import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.security.interfaces.RSAPublicKey; +import java.time.Clock; +import java.time.LocalTime; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -165,23 +166,18 @@ private static boolean verifyAllClaims(Map claims, Rule rule) { .allMatch(value -> { Claim claim = claims.get(value); return claim != null - && evaluateSimpleEQFormula(rule.getFORMULA(), value, claim.asString()); + && evaluateFormula(rule.getFORMULA(), value, claim.asString()); }); } - private static boolean evaluateSimpleEQFormula(Map formula, String value, String claimValue) { - if (formula.size() != 1 || !formula.containsKey("$eq")) { - LOGGER.error("Unsupported ACL formula."); - return false; - } - List eqList = (List) formula.get("$eq"); - LinkedHashMap attribute = (LinkedHashMap) eqList.get(0).get("$attribute"); - String strVal = (String) eqList.get(1).get("$strVal"); - if (attribute.get("CLAIM").equals(value) && strVal.equals(claimValue)) { - return true; - } - return false; + public static boolean evaluateFormula(Map formula, + String claimName, + String claimValue) { + Map ctx = new HashMap<>(); + ctx.put("CLAIM:" + claimName, claimValue); + ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); // $GLOBAL → UTCNOW + return FormulaEvaluator.evaluate(formula, ctx); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java new file mode 100644 index 000000000..6d2bde262 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java @@ -0,0 +1,101 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security; + +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + + +public final class FormulaEvaluator { + + public static boolean evaluate(Map formula, + Map runtimeValues) { + return eval(formula, runtimeValues); + } + + + private static boolean eval(Map node, + Map ctx) { + String op = node.keySet().iterator().next(); + Object value = node.get(op); + switch (op) { + case "$and" -> { + for (Object child: (List) value) { + if (!eval((Map) child, ctx)) { + return false; + } + } + return true; + } + case "$or" -> { + for (Object child: (List) value) { + if (eval((Map) child, ctx)) { + return true; + } + } + return false; + } + case "$eq" -> { + List ops = (List) value; + Object left = resolve((Map) ops.get(0), ctx); + Object right = resolve((Map) ops.get(1), ctx); + return Objects.equals(left, right); + } + case "$regex" -> { + List ops = (List) value; + Object left = resolve((Map) ops.get(0), ctx); + String regex = (String) resolve((Map) ops.get(1), ctx); + return left != null && Pattern.matches(regex, left.toString()); + } + case "$ge", "$le" -> { + List ops = (List) value; + Object lObj = resolve((Map) ops.get(0), ctx); + Object rObj = resolve((Map) ops.get(1), ctx); + + if (!(lObj instanceof Comparable left) || !(rObj instanceof Comparable)) { + throw new IllegalArgumentException("Operands are not comparable: " + + lObj + ", " + rObj); + } + int cmp = ((Comparable) left).compareTo(rObj); + return "$ge".equals(op) ? cmp >= 0 : cmp <= 0; + } + default -> throw new IllegalArgumentException("Unsupported operator " + op); + } + } + + + private static Object resolve(Map operand, + Map ctx) { + if (operand.containsKey("$strVal")) + return operand.get("$strVal"); + if (operand.containsKey("$timeVal")) + return LocalTime.parse((String) operand.get("$timeVal")); + if (operand.containsKey("$field")) + return ctx.get(operand.get("$field")); + if (operand.containsKey("$attribute")) { + Map path = (Map) operand.get("$attribute"); + if (path.containsKey("CLAIM")) + return ctx.get("CLAIM:" + path.get("CLAIM")); + if (path.containsKey("REFERENCE")) + return ctx.get("REF:" + path.get("REFERENCE")); + if (path.containsKey("GLOBAL") && "UTCNOW".equals(path.get("GLOBAL"))) + return ctx.get("UTCNOW"); + } + throw new IllegalArgumentException("Unresolvable operand " + operand); + } +} diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java new file mode 100644 index 000000000..ca71b57a8 --- /dev/null +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java @@ -0,0 +1,189 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalTime; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + + +/** + * Unit tests for {@link FormulaEvaluator}. + */ +public class FormulaEvaluatorTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /* ------------------------------------------------------------------ */ + @Test + public void complexFormula_withMatchingClaims() throws Exception { + String json = """ + { + "$and": [ + { + "$or": [ + { + "$eq": [ + { "$field": "$sm#semanticId" }, + { "$strVal": "SemanticID-Nameplate" } + ] + }, + { + "$eq": [ + { "$field": "$sm#semanticId" }, + { "$strVal": "SemanticID-TechnicalData" } + ] + } + ] + }, + { + "$or": [ + { + "$eq": [ + { "$attribute": { "CLAIM": "email" } }, + { "$strVal": "user1@company1.com" } + ] + }, + { + "$eq": [ + { "$attribute": { "CLAIM": "email" } }, + { "$strVal": "user2@company2.com" } + ] + } + ] + } + ] + } + """; + + Map formula = MAPPER.readValue( + json, new TypeReference>() {}); + + Map ctx = new HashMap<>(); + ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); // matches 2nd $eq + ctx.put("CLAIM:email", "user2@company2.com"); // matches 2nd $eq + boolean result = FormulaEvaluator.evaluate(formula, ctx); + assertTrue(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void regexFormula_withNonMatchingEmail() throws Exception { + String json = """ + { + "$and": [ + { + "$or": [ + { + "$eq": [ + { "$field": "$sm#semanticId" }, + { "$strVal": "SemanticID-Nameplate" } + ] + }, + { + "$eq": [ + { "$field": "$sm#semanticId" }, + { "$strVal": "SemanticID-TechnicalData" } + ] + } + ] + }, + { + "$regex": [ + { "$attribute": { "CLAIM": "email" } }, + { "$strVal": "[\\\\w\\\\.]+'@company\\\\.com" } + ] + } + ] + } + """; + + Map formula = MAPPER.readValue( + json, new TypeReference>() {}); + Map ctx = new HashMap<>(); + ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); + ctx.put("CLAIM:email", "other.user@other-company.org"); // does NOT match + assertFalse(FormulaEvaluator.evaluate(formula, ctx)); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void fullFormula_allConditionsMet() throws Exception { + String json = """ + { + "$and": [ + { + "$or": [ + { + "$eq": [ + { "$field": "$sm#semanticId" }, + { "$strVal": "SemanticID-Nameplate" } + ] + }, + { + "$eq": [ + { "$field": "$sm#semanticId" }, + { "$strVal": "SemanticID-TechnicalData" } + ] + } + ] + }, + { + "$eq": [ + { "$attribute": { "CLAIM": "companyName" } }, + { "$strVal": "company1-name" } + ] + }, + { + "$regex": [ + { "$attribute": { "REFERENCE": "(Submodel)*#Id" } }, + { "$strVal": "^https://company1.com/.*$" } + ] + }, + { + "$ge": [ + { "$attribute": { "GLOBAL": "UTCNOW" } }, + { "$timeVal": "09:00" } + ] + }, + { + "$le": [ + { "$attribute": { "GLOBAL": "UTCNOW" } }, + { "$timeVal": "17:00" } + ] + } + ] + } + """; + + Map formula = MAPPER.readValue( + json, new TypeReference>() {}); + Map ctx = new HashMap<>(); + ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); + ctx.put("CLAIM:companyName", "company1-name"); + ctx.put("REF:(Submodel)*#Id", "https://company1.com/id-0815"); + ctx.put("UTCNOW", LocalTime.of(10, 30)); // between 09:00 and 17:00 + assertTrue(FormulaEvaluator.evaluate(formula, ctx)); + } + +} From 178b6c33c794a1592cf3bb187c38b21712c93e89 Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:55:32 +0200 Subject: [PATCH 033/108] Update ApiGateway.java --- .../faaast/service/endpoint/http/security/ApiGateway.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 142930aee..c6782dd3e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -198,11 +198,7 @@ private static String getRequiredRight(String method) { private static boolean checkIdentifiable(String path, String identifiable) { - //check submodel path - if (!path.startsWith("/submodels")) { - return false; - } - if (identifiable.equals("(Submodel)*")) { + if (identifiable.equals("(Submodel)*") && path.startsWith("/submodels")) { return true; } else if (identifiable.startsWith("(Submodel)")) { From f4163c862f6ce8ab4fadcfb09fa006e3e8921a14 Mon Sep 17 00:00:00 2001 From: Friedrich Volz <16116233+fvolz@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:56:10 +0200 Subject: [PATCH 034/108] Update ApiGateway.java --- .../faaast/service/endpoint/http/security/ApiGateway.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index c6782dd3e..1e6e98fad 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -198,7 +198,12 @@ private static String getRequiredRight(String method) { private static boolean checkIdentifiable(String path, String identifiable) { - if (identifiable.equals("(Submodel)*") && path.startsWith("/submodels")) { + //check submodel path + if (!path.startsWith("/submodels")) { + return false; + } + + if (identifiable.equals("(Submodel)*")) { return true; } else if (identifiable.startsWith("(Submodel)")) { From 7d7f00668de06add3bc3df0861e64e9b8c821e06 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Wed, 13 Aug 2025 12:06:21 +0200 Subject: [PATCH 035/108] fix errors --- .../service/endpoint/http/security/ApiGateway.java | 6 +++--- .../endpoint/http/security/FormulaEvaluator.java | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 1e6e98fad..7bc1f577a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -171,9 +171,9 @@ private static boolean verifyAllClaims(Map claims, Rule rule) { } - public static boolean evaluateFormula(Map formula, - String claimName, - String claimValue) { + private static boolean evaluateFormula(Map formula, + String claimName, + String claimValue) { Map ctx = new HashMap<>(); ctx.put("CLAIM:" + claimName, claimValue); ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); // $GLOBAL → UTCNOW diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java index 6d2bde262..d63e9544a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java @@ -21,8 +21,18 @@ import java.util.regex.Pattern; +/** + * Class to evaluate formulas. + */ public final class FormulaEvaluator { + /** + * Evaluates the given formula. + * + * @param formula The formula. + * @param runtimeValues The runtime values. + * @return True if the evaluation is successful, false othetwise. + */ public static boolean evaluate(Map formula, Map runtimeValues) { return eval(formula, runtimeValues); From 7c2c7ec64cdebd591ac5d5fab74b7e1291c9b2c0 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Thu, 14 Aug 2025 13:48:32 +0200 Subject: [PATCH 036/108] use all configured claims for evaluateFormula --- .../endpoint/http/security/ApiGateway.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java index 7bc1f577a..046ec203e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java @@ -161,21 +161,30 @@ private static boolean verifyAllClaims(Map claims, Rule rule) { .map(Attribute::getCLAIM) .filter(Objects::nonNull) .collect(Collectors.toList()); + Map claimList = new HashMap<>(); + for (String val: claimValues) { + Object claim = claims.get(val); + if (claim != null) { + claimList.put(val, claim.toString()); + } + } return !claimValues.isEmpty() && claimValues.stream() .allMatch(value -> { - Claim claim = claims.get(value); - return claim != null - && evaluateFormula(rule.getFORMULA(), value, claim.asString()); + //Claim claim = claims.get(value); + //return claim != null + return evaluateFormula(rule.getFORMULA(), claimList); }); } private static boolean evaluateFormula(Map formula, - String claimName, - String claimValue) { + Map claims) { Map ctx = new HashMap<>(); - ctx.put("CLAIM:" + claimName, claimValue); + for (var c: claims.entrySet()) { + ctx.put("CLAIM:" + c.getKey(), c.getValue()); + } + //ctx.put("CLAIM:" + claimName, claimValue); ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); // $GLOBAL → UTCNOW return FormulaEvaluator.evaluate(formula, ctx); } From f395fd7f444ba59b668e263025f607e75de108f0 Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:02:58 +0200 Subject: [PATCH 037/108] feat: extract request filtering (#1187) --- .../service/endpoint/http/HttpEndpoint.java | 22 +++ .../endpoint/http/RequestHandlerServlet.java | 14 +- ...AccessControlListAuthorizationFilter.java} | 104 ++++++++------ .../filter/JwtAuthorizationFilter.java | 38 ++++++ .../security/filter/JwtValidationFilter.java | 129 ++++++++++++++++++ .../http/security/ApiGatewayTest.java | 22 +-- 6 files changed, 267 insertions(+), 62 deletions(-) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/{ApiGateway.java => filter/AccessControlListAuthorizationFilter.java} (81%) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 16b294f12..aca16d781 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -20,15 +20,20 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateInformation; import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AccessControlListAuthorizationFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.JwtValidationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.Interface; import de.fraunhofer.iosb.ilt.faaast.service.model.Version; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; +import jakarta.servlet.DispatcherType; import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -36,6 +41,7 @@ import java.security.cert.CertificateException; import java.time.Duration; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -109,6 +115,22 @@ public void start() throws EndpointException { RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext); context.addServlet(handler, "/*"); + + URL jwkProviderUrl; + try { + jwkProviderUrl = new URL(config.getJwkProvider()); + } + catch (MalformedURLException malformedJwkProviderUrl) { + throw new EndpointException("Could not parse JWK provider URL", malformedJwkProviderUrl); + } + + context.addFilter(new JwtValidationFilter(jwkProviderUrl), + "*", EnumSet.allOf(DispatcherType.class)); + + // Adds authorization filter for all request types + context.addFilter(new AccessControlListAuthorizationFilter(jwkProviderUrl, config.getAclFolder()), + "*", EnumSet.allOf(DispatcherType.class)); + server.setErrorHandler(new HttpErrorHandler(config)); try { server.start(); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 6f8e16a76..a13b12a54 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -16,12 +16,10 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.RequestMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; @@ -53,7 +51,6 @@ public class RequestHandlerServlet extends HttpServlet { private final RequestMappingManager requestMappingManager; private final ResponseMappingManager responseMappingManager; private final HttpJsonApiSerializer serializer; - private final ApiGateway apiGateway; public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); @@ -65,7 +62,6 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); - this.apiGateway = Objects.nonNull(config.getJwkProvider()) ? new ApiGateway(config.getJwkProvider(), config.getAclFolder()) : null; } @@ -76,12 +72,6 @@ private void doThrow(Exception e) throws ServletException { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - if (Objects.nonNull(apiGateway)) { - if (!apiGateway.isAuthorized(request.getHeader("Authorization"), request)) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getRequestURI()))); - } - } if (!request.getRequestURI().startsWith(HttpEndpoint.getVersionPrefix())) { doThrow(new ResourceNotFoundException(String.format("Resource not found '%s'", request.getRequestURI()))); } @@ -130,7 +120,9 @@ private void checkRequestSupportedByProfiles(de.fraunhofer.iosb.ilt.faaast.servi } - private void executeAndSend(HttpServletResponse response, de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws Exception { + private void executeAndSend(HttpServletResponse response, + de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) + throws Exception { if (Objects.isNull(apiRequest)) { throw new InvalidRequestException("empty API request"); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java similarity index 81% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java index 046ec203e..ca5851910 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java @@ -12,25 +12,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; -import com.auth0.jwk.Jwk; -import com.auth0.jwk.JwkException; import com.auth0.jwk.JwkProvider; import com.auth0.jwk.UrlJwkProvider; -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Attribute; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -40,7 +42,6 @@ import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; -import java.security.interfaces.RSAPublicKey; import java.time.Clock; import java.time.LocalTime; import java.util.HashMap; @@ -51,54 +52,72 @@ import org.slf4j.LoggerFactory; -/** - * An ApiGateway that verifies JWT tokens against the provided jwkProvider. - * Initially all ACL rules from the folder are read and stored. After that, - * the folder is monitored for deletion or addition of new rules. - */ -public class ApiGateway { - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); - private String jwkProvider; +public class AccessControlListAuthorizationFilter extends JwtAuthorizationFilter { + + private static final String AUTHORIZATION_KWD = "Authorization"; + + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AccessControlListAuthorizationFilter.class); + + private final JwkProvider jwkProvider; private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; - public ApiGateway(String jwkProvider, String aclFolder) { - this.jwkProvider = jwkProvider; + public AccessControlListAuthorizationFilter(URL jwkProvider, String aclFolder) { + this.jwkProvider = new UrlJwkProvider(jwkProvider); initializeAclList(aclFolder); + // TODO Maybe we can read the ACL rules when a request comes in? monitorAclRules(aclFolder); } /** - * Verifies the token by decoding it. - * Additionally, AuthServer is used to verify claims. + * Verify JWT claims by counterchecking with ACL * - * @param token the JWT token - * @param request the request parameters - * @return true if the token is valid + * @param servletRequest the ServletRequest object contains the client's request + * @param servletResponse the ServletResponse object contains the filter's response + * @param filterChain the FilterChain for invoking the next filter or the resource + * @throws IOException doFilter of further Filter steps + * @throws ServletException doFilter of further Filter steps */ - public boolean isAuthorized(String token, HttpServletRequest request) { - if (Objects.isNull(token)) { - return AuthServer.filterRules(this.aclList, null, request); + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; + + String authHeader = httpRequest.getHeader(AUTHORIZATION_KWD); + + // If we can serve this request with no claims, we can stop early + if (authHeader == null && checkClaims(httpRequest)) { + filterChain.doFilter(servletRequest, servletResponse); + return; } - else { - token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; - DecodedJWT jwt = JWT.decode(token); - JwkProvider provider = new UrlJwkProvider(this.jwkProvider); - try { - Jwk jwk = provider.get(jwt.getKeyId()); - Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); - algorithm.verify(jwt); - JWTVerifier verifier = JWT.require(algorithm) - //.withIssuer(this.jwkProvider) - .build(); - verifier.verify(token); - } - catch (JwkException e) { - return false; - } - return AuthServer.filterRules(this.aclList, jwt.getClaims(), request); + + // Extract JWT + DecodedJWT jwt = extractAndDecodeJwt(httpRequest); + if (jwt == null) { + LOGGER.info("Could not extract JWT"); + return; + } + + if (!checkClaims(httpRequest, jwt.getClaims())) { + httpResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + return; } + + // Continue with the request + filterChain.doFilter(servletRequest, servletResponse); + } + + + private boolean checkClaims(HttpServletRequest request) { + return checkClaims(request, null); + } + + + private boolean checkClaims(HttpServletRequest request, Map claims) { + return AuthServer.filterRules(this.aclList, claims, request); } /** @@ -319,4 +338,5 @@ else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { }); monitoringThread.start(); } + } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java new file mode 100644 index 000000000..4858f9f5a --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java @@ -0,0 +1,38 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import jakarta.servlet.Filter; +import jakarta.servlet.http.HttpServletRequest; + + +public abstract class JwtAuthorizationFilter implements Filter { + private static final String BEARER_KWD = "Bearer"; + + protected DecodedJWT extractAndDecodeJwt(HttpServletRequest request) { + var authHeaderValue = request.getHeader("Authorization"); + + if (authHeaderValue == null || !authHeaderValue.startsWith(BEARER_KWD.concat(" "))) { + return null; + } + + // Remove "Bearer " + String token = authHeaderValue.substring(BEARER_KWD.length()).trim(); + + return JWT.decode(token); + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java new file mode 100644 index 000000000..91566061e --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java @@ -0,0 +1,129 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter; + +import com.auth0.jwk.InvalidPublicKeyException; +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkException; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.UrlJwkProvider; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URL; +import java.security.interfaces.RSAPublicKey; +import java.util.Collections; +import org.slf4j.LoggerFactory; + + +public class JwtValidationFilter extends JwtAuthorizationFilter { + + private static final String AUTHORIZATION_KWD = "Authorization"; + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JwtValidationFilter.class); + + private final JwkProvider jwkProvider; + + public JwtValidationFilter(URL jwkProvider) { + this.jwkProvider = new UrlJwkProvider(jwkProvider); + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, + ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; + + // If multiple Authorization headers are present, attackers could maybe use one for auth and one for claims + var authHeaders = httpRequest.getHeaders(AUTHORIZATION_KWD); + var authHeaderList = Collections.list(authHeaders); + if (authHeaderList.size() > 1) { + LOGGER.debug("Multiple authorization headers present! Not authorizing request."); + return; + } + + if (httpRequest.getHeader(AUTHORIZATION_KWD) == null) { + // No JWT in request, anonymous requestor + filterChain.doFilter(servletRequest, servletResponse); + return; + } + + // Extract JWT + DecodedJWT jwt = extractAndDecodeJwt(httpRequest); + if (jwt == null) { + LOGGER.debug("Could not extract JWT"); + return; + } + + if (!validateJWT(jwt)) { + httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); + httpResponse.getWriter().write("Invalid token"); + return; + } + + // Continue with the request + filterChain.doFilter(servletRequest, servletResponse); + } + + + private boolean validateJWT(DecodedJWT decodedJWT) { + // Your JWT validation logic here + Jwk jwk; + try { + jwk = jwkProvider.get(decodedJWT.getKeyId()); + } + catch (JwkException getJwkException) { + LOGGER.debug("Could not get JWK from JWT. Not authorizing request.", getJwkException); + return false; + } + Algorithm algorithm; + try { + algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); + } + catch (InvalidPublicKeyException invalidPublicKeyException) { + LOGGER.debug("InvalidPublicKeyException when reading public key from JWT. Not authorizing request", invalidPublicKeyException); + return false; + } + + try { + algorithm.verify(decodedJWT); + } + catch (SignatureVerificationException signatureVerificationException) { + LOGGER.debug("Could not verify JWT using algorithm {}", algorithm.getName()); + return false; + } + JWTVerifier verifier = JWT.require(algorithm).build(); + + try { + verifier.verify(decodedJWT); + } + catch (JWTVerificationException verificationException) { + LOGGER.debug("Could not verify JWT"); + return false; + } + + return true; + } +} diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java index e3bb315e5..e7d751370 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java @@ -14,8 +14,6 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -24,7 +22,9 @@ import com.auth0.jwk.UrlJwkProvider; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AccessControlListAuthorizationFilter; import jakarta.servlet.http.HttpServletRequest; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -33,6 +33,7 @@ import java.security.KeyPairGenerator; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -59,7 +60,7 @@ public class ApiGatewayTest { @Rule public TemporaryFolder tmp = new TemporaryFolder(); private Path aclDir; - private ApiGateway gateway; + private AccessControlListAuthorizationFilter filter; private static HttpServletRequest req(String method, String uri) { HttpServletRequest r = mock(HttpServletRequest.class); @@ -69,15 +70,17 @@ private static HttpServletRequest req(String method, String uri) { } + @Ignore("Classes changed, need to rewrite") @Test public void anonymousAccessDependsOnAclFile() throws Exception { aclDir = tmp.newFolder("acl").toPath(); - gateway = new ApiGateway("http://whatever-jwks", aclDir.toString()); + filter = new AccessControlListAuthorizationFilter(new URL("http://whatever-jwks"), aclDir.toString()); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); - assertFalse(gateway.isAuthorized(null, request)); + // TODO I suggest we build a filterChain and fail/succeed if filterChain.doFilter is called (i.e. the next filter) + //assertFalse(filter.doFilter(null, request); Path rule = aclDir.resolve("allow.json"); Path tmpRule = aclDir.resolve("allow.json.tmp"); @@ -85,14 +88,15 @@ public void anonymousAccessDependsOnAclFile() throws Exception { Files.move(tmpRule, rule, StandardCopyOption.ATOMIC_MOVE); Thread.sleep(200); - assertTrue(gateway.isAuthorized(null, request)); + //assertTrue(filter.isAuthorized(null, request)); Files.delete(rule); Thread.sleep(200); - assertFalse(gateway.isAuthorized(null, request)); + //assertFalse(filter.isAuthorized(null, request)); } + @Ignore("Classes changed, need to rewrite") @Test public void jwtIsVerified() throws Exception { @@ -121,11 +125,11 @@ public void jwtIsVerified() throws Exception { try (MockedConstruction mocked = Mockito.mockConstruction(UrlJwkProvider.class, (mock, ctx) -> when(mock.get(anyString())).thenReturn(jwk))) { - gateway = new ApiGateway("http://whatever-jwks", aclDir.toString()); + filter = new AccessControlListAuthorizationFilter(new URL("http://whatever-jwks"), aclDir.toString()); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); - assertTrue(gateway.isAuthorized(jwt, request)); + //assertTrue(filter.doFilter(jwt, request)); } } } From 0fc4d41aaa81b18dfc78bff107de860b5120b90a Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:23:09 +0200 Subject: [PATCH 038/108] fix checkstyle violations --- .../service/endpoint/http/HttpEndpoint.java | 29 ++++--- .../http/security/auth/AuthState.java | 23 ++++++ .../AccessControlListAuthorizationFilter.java | 33 ++++---- .../filter/JwtAuthorizationFilter.java | 11 +++ .../security/filter/JwtValidationFilter.java | 44 +++++++---- ...ssControlListAuthorizationFilterTest.java} | 70 ++++------------ .../filter/JwtAuthorizationFilterTest.java | 66 ++++++++++++++++ .../filter/JwtValidationFilterTest.java | 79 +++++++++++++++++++ 8 files changed, 256 insertions(+), 99 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java rename endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/{ApiGatewayTest.java => filter/AccessControlListAuthorizationFilterTest.java} (58%) create mode 100644 endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java create mode 100644 endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index aca16d781..66c16a0db 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -16,6 +16,8 @@ import static de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper.DEFAULT_ALIAS; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.UrlJwkProvider; import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateData; import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateInformation; import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; @@ -116,21 +118,22 @@ public void start() throws EndpointException { RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext); context.addServlet(handler, "/*"); - URL jwkProviderUrl; - try { - jwkProviderUrl = new URL(config.getJwkProvider()); - } - catch (MalformedURLException malformedJwkProviderUrl) { - throw new EndpointException("Could not parse JWK provider URL", malformedJwkProviderUrl); - } - - context.addFilter(new JwtValidationFilter(jwkProviderUrl), - "*", EnumSet.allOf(DispatcherType.class)); + if (Objects.nonNull(config.getJwkProvider())) { + URL jwkProviderUrl; + try { + jwkProviderUrl = new URL(config.getJwkProvider()); + } + catch (MalformedURLException malformedJwkProviderUrl) { + throw new EndpointException("Could not parse JWK provider URL", malformedJwkProviderUrl); + } + JwkProvider jwkProvider = new UrlJwkProvider(jwkProviderUrl); - // Adds authorization filter for all request types - context.addFilter(new AccessControlListAuthorizationFilter(jwkProviderUrl, config.getAclFolder()), - "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new JwtValidationFilter(jwkProvider), + "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AccessControlListAuthorizationFilter(jwkProvider, config.getAclFolder()), + "*", EnumSet.allOf(DispatcherType.class)); + } server.setErrorHandler(new HttpErrorHandler(config)); try { server.start(); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java new file mode 100644 index 000000000..561d97e5b --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java @@ -0,0 +1,23 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.auth; + +/** + * Shows the current state of an incoming HTTP request. + */ +public enum AuthState { + ANONYMOUS, + AUTHENTICATED +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java index ca5851910..95bd23991 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java @@ -14,8 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState.ANONYMOUS; + import com.auth0.jwk.JwkProvider; -import com.auth0.jwk.UrlJwkProvider; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; @@ -32,7 +33,6 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -52,18 +52,19 @@ import org.slf4j.LoggerFactory; +/** + * Filters any incoming request with respect to the given ACL rules. + */ public class AccessControlListAuthorizationFilter extends JwtAuthorizationFilter { - private static final String AUTHORIZATION_KWD = "Authorization"; - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AccessControlListAuthorizationFilter.class); private final JwkProvider jwkProvider; private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; - public AccessControlListAuthorizationFilter(URL jwkProvider, String aclFolder) { - this.jwkProvider = new UrlJwkProvider(jwkProvider); + public AccessControlListAuthorizationFilter(JwkProvider jwkProvider, String aclFolder) { + this.jwkProvider = jwkProvider; initializeAclList(aclFolder); // TODO Maybe we can read the ACL rules when a request comes in? monitorAclRules(aclFolder); @@ -71,7 +72,7 @@ public AccessControlListAuthorizationFilter(URL jwkProvider, String aclFolder) { /** - * Verify JWT claims by counterchecking with ACL + * Verify JWT claims by counterchecking with ACL. * * @param servletRequest the ServletRequest object contains the client's request * @param servletResponse the ServletResponse object contains the filter's response @@ -86,11 +87,11 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; - String authHeader = httpRequest.getHeader(AUTHORIZATION_KWD); - - // If we can serve this request with no claims, we can stop early - if (authHeader == null && checkClaims(httpRequest)) { - filterChain.doFilter(servletRequest, servletResponse); + if (ANONYMOUS.equals(servletRequest.getAttribute("auth.state"))) { + // Check request without any claims + if (checkClaims(httpRequest)) { + filterChain.doFilter(servletRequest, servletResponse); + } return; } @@ -103,11 +104,11 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (!checkClaims(httpRequest, jwt.getClaims())) { httpResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); - return; } - - // Continue with the request - filterChain.doFilter(servletRequest, servletResponse); + else { + // Continue with the request + filterChain.doFilter(servletRequest, servletResponse); + } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java index 4858f9f5a..e51715de9 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java @@ -20,9 +20,20 @@ import jakarta.servlet.http.HttpServletRequest; +/** + * Abstract filter for HTTP requests with JWT headers. + */ public abstract class JwtAuthorizationFilter implements Filter { private static final String BEARER_KWD = "Bearer"; + /** + * Extracts a JWT from an HTTP request by reading its Authorization header, + * checking the presence of the "Bearer" keyword and returning the decoded token. + * + * @param request An incoming HTTP request. + * + * @return The decoded JWT if present, else null + */ protected DecodedJWT extractAndDecodeJwt(HttpServletRequest request) { var authHeaderValue = request.getHeader("Authorization"); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java index 91566061e..b879ba505 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java @@ -18,13 +18,13 @@ import com.auth0.jwk.Jwk; import com.auth0.jwk.JwkException; import com.auth0.jwk.JwkProvider; -import com.auth0.jwk.UrlJwkProvider; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; @@ -32,12 +32,15 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.net.URL; import java.security.interfaces.RSAPublicKey; import java.util.Collections; import org.slf4j.LoggerFactory; +/** + * Filters any incoming request by verifying its JWT if available. + * If no Authorization: Bearer <...> header is available, assumes an anonymous request. + */ public class JwtValidationFilter extends JwtAuthorizationFilter { private static final String AUTHORIZATION_KWD = "Authorization"; @@ -45,11 +48,20 @@ public class JwtValidationFilter extends JwtAuthorizationFilter { private final JwkProvider jwkProvider; - public JwtValidationFilter(URL jwkProvider) { - this.jwkProvider = new UrlJwkProvider(jwkProvider); + public JwtValidationFilter(JwkProvider jwkProvider) { + this.jwkProvider = jwkProvider; } + /** + * If a bearer token (JWT) is passed as header, validates this token and blocks the request as unauthenticated. + * + * @param servletRequest the ServletRequest object contains the client's request + * @param servletResponse the ServletResponse object contains the filter's response + * @param filterChain the FilterChain for invoking the next filter or the resource + * @throws IOException could not write HttpResponse body OR exception in next filter steps + * @throws ServletException exception in next filter steps + */ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { @@ -61,30 +73,34 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo var authHeaderList = Collections.list(authHeaders); if (authHeaderList.size() > 1) { LOGGER.debug("Multiple authorization headers present! Not authorizing request."); + respondForbidden(httpResponse); return; } if (httpRequest.getHeader(AUTHORIZATION_KWD) == null) { // No JWT in request, anonymous requestor + servletRequest.setAttribute("auth.state", AuthState.ANONYMOUS); filterChain.doFilter(servletRequest, servletResponse); return; } // Extract JWT DecodedJWT jwt = extractAndDecodeJwt(httpRequest); - if (jwt == null) { - LOGGER.debug("Could not extract JWT"); - return; + if (jwt == null || !validateJWT(jwt)) { + LOGGER.debug("Could not extract and validate JWT"); + respondForbidden(httpResponse); } - - if (!validateJWT(jwt)) { - httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); - httpResponse.getWriter().write("Invalid token"); - return; + else { + // Continue with the request as authenticated + servletRequest.setAttribute("auth.state", AuthState.AUTHENTICATED); + filterChain.doFilter(servletRequest, servletResponse); } + } + - // Continue with the request - filterChain.doFilter(servletRequest, servletResponse); + private static void respondForbidden(HttpServletResponse httpResponse) throws IOException { + httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); + httpResponse.getWriter().write("Invalid token"); } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilterTest.java similarity index 58% rename from endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java rename to endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilterTest.java index e7d751370..b77b08c81 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/ApiGatewayTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilterTest.java @@ -12,36 +12,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.auth0.jwk.Jwk; import com.auth0.jwk.UrlJwkProvider; -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AccessControlListAuthorizationFilter; +import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.mockito.MockedConstruction; -import org.mockito.Mockito; -public class ApiGatewayTest { +public class AccessControlListAuthorizationFilterTest extends JwtAuthorizationFilterTest { private static final String ACL_JSON = "{\n" + " \"AllAccessPermissionRules\": {\n" + @@ -70,18 +63,21 @@ private static HttpServletRequest req(String method, String uri) { } - @Ignore("Classes changed, need to rewrite") @Test public void anonymousAccessDependsOnAclFile() throws Exception { aclDir = tmp.newFolder("acl").toPath(); - filter = new AccessControlListAuthorizationFilter(new URL("http://whatever-jwks"), aclDir.toString()); + filter = new AccessControlListAuthorizationFilter(new UrlJwkProvider(new URL("http://whatever-jwks")), aclDir.toString()); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); + HttpServletResponse response = mockResponse(); + FilterChain filterChain = mockFilterChain(); // TODO I suggest we build a filterChain and fail/succeed if filterChain.doFilter is called (i.e. the next filter) - //assertFalse(filter.doFilter(null, request); - + //assertFalse(filter.doFilter(null, request)); + filter.doFilter(request, response, filterChain); + // Verify that request was blocked off + verify(filterChain, never()).doFilter(any(), any()); Path rule = aclDir.resolve("allow.json"); Path tmpRule = aclDir.resolve("allow.json.tmp"); Files.writeString(tmpRule, ACL_JSON, StandardCharsets.UTF_8); @@ -94,42 +90,4 @@ public void anonymousAccessDependsOnAclFile() throws Exception { Thread.sleep(200); //assertFalse(filter.isAuthorized(null, request)); } - - - @Ignore("Classes changed, need to rewrite") - @Test - public void jwtIsVerified() throws Exception { - - aclDir = tmp.newFolder("acl").toPath(); - Files.writeString(aclDir.resolve("allow.json"), - ACL_JSON, StandardCharsets.UTF_8); - - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); - kpg.initialize(2048); - KeyPair kp = kpg.generateKeyPair(); - - RSAPublicKey pub = (RSAPublicKey) kp.getPublic(); - RSAPrivateKey priv = (RSAPrivateKey) kp.getPrivate(); - - String kid = "unit-test-kid"; - - String jwt = JWT.create() - .withKeyId(kid) - .withClaim("dummy", "x") - .sign(Algorithm.RSA256(pub, priv)); - - Jwk jwk = mock(Jwk.class); - when(jwk.getPublicKey()).thenReturn(pub); - when(jwk.getId()).thenReturn(kid); - - try (MockedConstruction mocked = Mockito.mockConstruction(UrlJwkProvider.class, - (mock, ctx) -> when(mock.get(anyString())).thenReturn(jwk))) { - - filter = new AccessControlListAuthorizationFilter(new URL("http://whatever-jwks"), aclDir.toString()); - - HttpServletRequest request = req("GET", "/api/v3.0/submodels"); - - //assertTrue(filter.doFilter(jwt, request)); - } - } } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java new file mode 100644 index 000000000..3b1a63493 --- /dev/null +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java @@ -0,0 +1,66 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Collections; +import java.util.List; +import org.junit.Test; + + +public class JwtAuthorizationFilterTest { + + private JwtAuthorizationFilter testSubject = new JwtAuthorizationFilter() { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { + // Intentionally left blank + } + }; + + @Test + public void testExtractAndDecodeJwt() { + // TODO + } + + + protected FilterChain mockFilterChain() { + return mock(FilterChain.class); + } + + + protected static HttpServletRequest mockRequest(String method, String uri, String jwt) { + HttpServletRequest r = mock(HttpServletRequest.class); + //when(r.getMethod()).thenReturn(method); + //when(r.getRequestURI()).thenReturn(uri); + when(r.getHeader("Authorization")).thenReturn("Bearer " + jwt); + when(r.getHeaders("Authorization")).thenReturn(Collections.enumeration(List.of("Bearer " + jwt))); + + return r; + } + + + protected static HttpServletResponse mockResponse() { + HttpServletResponse r = mock(HttpServletResponse.class); + return r; + } + +} diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java new file mode 100644 index 000000000..270b7190b --- /dev/null +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java @@ -0,0 +1,79 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.UrlJwkProvider; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import org.junit.Test; + + +public class JwtValidationFilterTest extends JwtAuthorizationFilterTest { + + private JwtValidationFilter filter; + + @Test + public void jwtIsVerified() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + RSAPublicKey pub = (RSAPublicKey) kp.getPublic(); + RSAPrivateKey priv = (RSAPrivateKey) kp.getPrivate(); + + String kid = "unit-test-kid"; + + String jwt = JWT.create() + .withKeyId(kid) + .sign(Algorithm.RSA256(pub, priv)); + + Jwk jwk = mock(Jwk.class); + when(jwk.getPublicKey()).thenReturn(pub); + when(jwk.getId()).thenReturn(kid); + + JwkProvider mockJwkProvider = mock(UrlJwkProvider.class); + + when(mockJwkProvider.get(kid)).thenReturn(jwk); + + filter = new JwtValidationFilter(mockJwkProvider); + + HttpServletRequest request = mockRequest("GET", "/api/v3.0/submodels", jwt); + HttpServletResponse response = mockResponse(); + FilterChain filterChain = mockFilterChain(); + + filter.doFilter(request, response, filterChain); + + // The filter passed this request onto the next filter -> Did not block + verify(filterChain, times(1)).doFilter(any(), any()); + // The filter called the JWK provider to verify the request + verify(mockJwkProvider, times(1)).get(kid); + } + +} From e64d030962d27b95f2007e3f90812922fe9ff6d0 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Tue, 16 Sep 2025 11:44:59 +0200 Subject: [PATCH 039/108] DefACL: fix errors --- .../service/model/security/json/DefACL.java | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java index 084013e3f..a6c1aa678 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java @@ -14,25 +14,35 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; +import com.fasterxml.jackson.annotation.JsonProperty; + + /** * Optional DefACL field. */ public class DefACL { + @JsonProperty("name") private String name; + + @JsonProperty("acl") private ACL acl; - /** - * Set the name. - * - * @param acl1 - */ - public void setName(String acl1) {} + public String getName() { + return name; + } + + + public void setName(String value) { + name = value; + } + + + public ACL getAcl() { + return acl; + } - /** - * Set ACL. - * - * @param acl - */ - public void setAcl(ACL acl) {} + public void setAcl(ACL value) { + acl = value; + } } From d43212c153de34b0005940aebeffbdb90f4f8dc0 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Tue, 16 Sep 2025 16:17:17 +0200 Subject: [PATCH 040/108] implement reusable ACL --- .../AccessControlListAuthorizationFilter.java | 107 ++++++++++++++---- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java index 95bd23991..93fe4f13b 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java @@ -21,8 +21,11 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.ACL; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Attribute; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefACL; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.FilterChain; @@ -48,6 +51,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import org.slf4j.LoggerFactory; @@ -59,12 +63,12 @@ public class AccessControlListAuthorizationFilter extends JwtAuthorizationFilter private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AccessControlListAuthorizationFilter.class); - private final JwkProvider jwkProvider; + //private final JwkProvider jwkProvider; private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; public AccessControlListAuthorizationFilter(JwkProvider jwkProvider, String aclFolder) { - this.jwkProvider = jwkProvider; + //this.jwkProvider = jwkProvider; initializeAclList(aclFolder); // TODO Maybe we can read the ACL rules when a request comes in? monitorAclRules(aclFolder); @@ -144,31 +148,15 @@ private static boolean filterRules(Map aclLi List relevantRules = aclList.values().stream() .filter(a -> a.getAllAccessPermissionRules() .getRules().stream() - .anyMatch(r -> r.getACL() != null - && r.getACL().getATTRIBUTES() != null - && r.getACL().getRIGHTS() != null - && r.getOBJECTS() != null - && r.getOBJECTS().stream().anyMatch(attr -> { - if (attr.getROUTE() != null) { - return "*".equals(attr.getROUTE()) || attr.getROUTE().contains(path); - } - else if (attr.getIDENTIFIABLE() != null) { - return checkIdentifiable(path, attr.getIDENTIFIABLE()); - } - else { - return false; - } - }) - && "ALLOW".equals(r.getACL().getACCESS()) - && r.getACL().getRIGHTS().contains(getRequiredRight(method)) - && verifyAllClaims(claims, r))) + .anyMatch(r -> evalueRule(r, path, method, claims, a.getAllAccessPermissionRules()))) .collect(Collectors.toList()); return !relevantRules.isEmpty(); } - private static boolean verifyAllClaims(Map claims, Rule rule) { - if (rule.getACL().getATTRIBUTES().stream() + private static boolean verifyAllClaims(Map claims, Rule rule, AllAccessPermissionRules allAccess) { + ACL acl = getAcl(rule, allAccess); + if (acl.getATTRIBUTES().stream() .anyMatch(attr -> "ANONYMOUS".equals(attr.getGLOBAL()) && Boolean.TRUE.equals(rule.getFORMULA().get("$boolean")))) { return true; @@ -176,7 +164,7 @@ private static boolean verifyAllClaims(Map claims, Rule rule) { if (claims == null) { return false; } - List claimValues = rule.getACL().getATTRIBUTES().stream() + List claimValues = acl.getATTRIBUTES().stream() .filter(attr -> attr.getGLOBAL() == null) .map(Attribute::getCLAIM) .filter(Objects::nonNull) @@ -241,6 +229,61 @@ else if (identifiable.startsWith("(Submodel)")) { } return false; } + + + private static boolean checkDescriptor(String path, String descriptor) { + if (descriptor.startsWith("(aasDesc)")) { + if (!path.startsWith("/shell-descriptors")) { + return false; + } + if (descriptor.equals("(aasDesc)*")) { + return true; + } + else if (descriptor.startsWith("(aasDesc)")) { + String id = descriptor.substring(9); + return path.contains(EncodingHelper.base64UrlEncode(id)); + } + } + else if (descriptor.startsWith("(smDesc)")) { + if (!path.startsWith("/submodel-descriptors")) { + return false; + } + if (descriptor.equals("(smDesc)*")) { + return true; + } + else if (descriptor.startsWith("(smDesc)")) { + String id = descriptor.substring(8); + return path.contains(EncodingHelper.base64UrlEncode(id)); + } + } + return false; + } + + + private static boolean evalueRule(Rule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { + ACL acl = getAcl(rule, allAccess); + return acl != null + && acl.getATTRIBUTES() != null + && acl.getRIGHTS() != null + && rule.getOBJECTS() != null + && rule.getOBJECTS().stream().anyMatch(attr -> { + if (attr.getROUTE() != null) { + return "*".equals(attr.getROUTE()) || attr.getROUTE().contains(path); + } + else if (attr.getIDENTIFIABLE() != null) { + return checkIdentifiable(path, attr.getIDENTIFIABLE()); + } + else if (attr.getDESCRIPTOR() != null) { + return checkDescriptor(path, attr.getDESCRIPTOR()); + } + else { + return false; + } + }) + && "ALLOW".equals(acl.getACCESS()) + && acl.getRIGHTS().contains(getRequiredRight(method)) + && verifyAllClaims(claims, rule, allAccess); + } } private void initializeAclList(String aclFolder) { @@ -340,4 +383,22 @@ else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { monitoringThread.start(); } + + private static ACL getAcl(Rule rule, AllAccessPermissionRules allAccess) { + if (rule.getACL() != null) { + return rule.getACL(); + } + else if (rule.getUSEACL() != null) { + Optional acl = allAccess.getDEFACLS().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + if (acl.isPresent()) { + return acl.get().getAcl(); + } + else { + throw new IllegalArgumentException("DEFACL not found: " + rule.getUSEACL()); + } + } + else { + throw new IllegalArgumentException("invalid rule: ACL or USEACL must be specified"); + } + } } From 58e44545e1a9d0c082570f7ea82746109cce38b8 Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:52:40 +0200 Subject: [PATCH 041/108] feat: enhance right evaluation (#1195) Change the rights to the definition of the ACL language --- .../AccessControlListAuthorizationFilter.java | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java index 93fe4f13b..013e20bef 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState.ANONYMOUS; import com.auth0.jwk.JwkProvider; @@ -148,7 +149,7 @@ private static boolean filterRules(Map aclLi List relevantRules = aclList.values().stream() .filter(a -> a.getAllAccessPermissionRules() .getRules().stream() - .anyMatch(r -> evalueRule(r, path, method, claims, a.getAllAccessPermissionRules()))) + .anyMatch(r -> evaluateRule(r, path, method, claims, a.getAllAccessPermissionRules()))) .collect(Collectors.toList()); return !relevantRules.isEmpty(); } @@ -198,19 +199,39 @@ private static boolean evaluateFormula(Map formula, } - private static String getRequiredRight(String method) { - switch (method) { - case "GET": - return "READ"; - case "POST": - return "WRITE"; - case "PUT": - return "UPDATE"; - case "DELETE": - return "DELETE"; - default: - throw new IllegalArgumentException("Unsupported method: " + method); + private static boolean evaluateRights(List aclRights, String method, String path) { + // We need the path to check if the request is an operation invocation (EXECUTE) + String requiredRight = isOperationRequest(method, path) ? "EXECUTE" : getRequiredRight(method); + + return aclRights.contains("ALL") || aclRights.contains(requiredRight); + } + + + private static boolean isOperationRequest(String method, String path) { + // Requirements for an operation request according to FAAAST docs: + // Method: POST, URL suffix: /invoke, /invoke-async, /invoke/$value, /invoke-async/$value + String cleanPath; + String[] pathParts = path.split("/"); + + if (pathParts.length > 1 && "$value".equals(pathParts[pathParts.length - 1])) { + cleanPath = pathParts[pathParts.length - 2]; } + else { + cleanPath = pathParts[pathParts.length - 1]; + } + + return POST.name().equals(method) && ("/invoke".equals(cleanPath) || "invoke-async".equals(path)); + } + + + private static String getRequiredRight(String method) { + return switch (method) { + case "GET" -> "READ"; + case "POST" -> "CREATE"; + case "PUT" -> "UPDATE"; + case "DELETE" -> "DELETE"; + default -> throw new IllegalArgumentException("Unsupported method: " + method); + }; } @@ -260,7 +281,7 @@ else if (descriptor.startsWith("(smDesc)")) { } - private static boolean evalueRule(Rule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { + private static boolean evaluateRule(Rule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { ACL acl = getAcl(rule, allAccess); return acl != null && acl.getATTRIBUTES() != null @@ -281,7 +302,7 @@ else if (attr.getDESCRIPTOR() != null) { } }) && "ALLOW".equals(acl.getACCESS()) - && acl.getRIGHTS().contains(getRequiredRight(method)) + && evaluateRights(acl.getRIGHTS(), method, path) && verifyAllClaims(claims, rule, allAccess); } } From 3f3d434a8f12e623e45b107f5d50027f364ff60e Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Fri, 26 Sep 2025 12:48:41 +0200 Subject: [PATCH 042/108] extend security objects --- .../service/model/security/json/ACL.java | 13 ++++++ .../json/AllAccessPermissionRules.java | 13 ++++++ .../model/security/json/DefAttributes.java | 44 +++++++++++++++++++ .../model/security/json/DefFormula.java | 32 ++++++++------ .../model/security/json/DefObjects.java | 32 +++++++++----- 5 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java index 58287100c..0c7eb6a6d 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java @@ -32,6 +32,9 @@ public class ACL { @JsonProperty("ACCESS") private String ACCESS; + @JsonProperty("USEATTRIBUTES") + private String USEATTRIBUTES; + public List getATTRIBUTES() { return ATTRIBUTES; } @@ -60,4 +63,14 @@ public String getACCESS() { public void setACCESS(String ACCESS) { this.ACCESS = ACCESS; } + + + public String getUSEATTRIBUTES() { + return USEATTRIBUTES; + } + + + public void setUSEATTRIBUTES(String USEATTRIBUTES) { + this.USEATTRIBUTES = USEATTRIBUTES; + } } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java index d91534d9c..fda87f0ee 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java @@ -35,6 +35,9 @@ public class AllAccessPermissionRules { @JsonProperty("rules") private List rules; + @JsonProperty("DEFATTRIBUTES") + private List DEFATTRIBUTES; + public List getDEFACLS() { return DEFACLS; } @@ -73,4 +76,14 @@ public List getRules() { public void setRules(List rules) { this.rules = rules; } + + + public List getDEFATTRIBUTES() { + return DEFATTRIBUTES; + } + + + public void setDEFATTRIBUTES(List DEFATTRIBUTES) { + this.DEFATTRIBUTES = DEFATTRIBUTES; + } } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java new file mode 100644 index 000000000..bbec0fa21 --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + + +/** + * Reusable Defattributes. + */ +public class DefAttributes { + @JsonProperty("name") + private String name; + + @JsonProperty("attributes") + private List attributes; + + public String getName() { + return name; + } + + + public void setName(String value) { + name = value; + } + + + public List getAttributes() { + return attributes; + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java index 9af0fa041..008f4a3fa 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java @@ -24,18 +24,22 @@ public class DefFormula { private String name; private Map formula; - /** - * Set name. - * - * @param allowSubjectGroup1 - */ - public void setName(String allowSubjectGroup1) {} - - - /** - * Set formula. - * - * @param formulaExpression - */ - public void setFormula(Map formulaExpression) {} + public String getName() { + return name; + } + + + public void setName(String value) { + name = value; + } + + + public Map getFormula() { + return formula; + } + + + public void setFormula(Map formulaExpression) { + formula = formulaExpression; + } } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java index a68f5bb39..6d89ea517 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; @@ -21,21 +22,28 @@ * Reusable DefObjects. */ public class DefObjects { + @JsonProperty("name") private String name; + + @JsonProperty("objects") private List objects; - /** - * Set name. - * - * @param properties - */ - public void setName(String properties) {} + public String getName() { + return name; + } + + + public void setName(String value) { + name = value; + } + + + public List getObjects() { + return objects; + } - /** - * Set objects. - * - * @param list - */ - public void setObjects(List list) {} + public void setObjects(List value) { + objects = value; + } } From ad13785aa0fd23716f77d79babf3ef6f7b4be8ab Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Fri, 26 Sep 2025 17:38:23 +0200 Subject: [PATCH 043/108] implement DEFATTRIBUTES, DEFOBJECTS, DEFFORMULA --- .../AccessControlListAuthorizationFilter.java | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java index 013e20bef..53d21b691 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java @@ -27,6 +27,10 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Attribute; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefACL; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefAttributes; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefFormula; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefObjects; +import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Objects; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.FilterChain; @@ -51,7 +55,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.slf4j.LoggerFactory; @@ -157,18 +160,18 @@ private static boolean filterRules(Map aclLi private static boolean verifyAllClaims(Map claims, Rule rule, AllAccessPermissionRules allAccess) { ACL acl = getAcl(rule, allAccess); - if (acl.getATTRIBUTES().stream() + if (getAttributes(acl, allAccess).stream() .anyMatch(attr -> "ANONYMOUS".equals(attr.getGLOBAL()) - && Boolean.TRUE.equals(rule.getFORMULA().get("$boolean")))) { + && Boolean.TRUE.equals(getFormula(rule, allAccess).get("$boolean")))) { return true; } if (claims == null) { return false; } - List claimValues = acl.getATTRIBUTES().stream() + List claimValues = getAttributes(acl, allAccess).stream() .filter(attr -> attr.getGLOBAL() == null) .map(Attribute::getCLAIM) - .filter(Objects::nonNull) + .filter(java.util.Objects::nonNull) .collect(Collectors.toList()); Map claimList = new HashMap<>(); for (String val: claimValues) { @@ -180,9 +183,7 @@ private static boolean verifyAllClaims(Map claims, Rule rule, All return !claimValues.isEmpty() && claimValues.stream() .allMatch(value -> { - //Claim claim = claims.get(value); - //return claim != null - return evaluateFormula(rule.getFORMULA(), claimList); + return evaluateFormula(getFormula(rule, allAccess), claimList); }); } @@ -284,10 +285,10 @@ else if (descriptor.startsWith("(smDesc)")) { private static boolean evaluateRule(Rule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { ACL acl = getAcl(rule, allAccess); return acl != null - && acl.getATTRIBUTES() != null + && getAttributes(acl, allAccess) != null && acl.getRIGHTS() != null - && rule.getOBJECTS() != null - && rule.getOBJECTS().stream().anyMatch(attr -> { + && getObjects(rule, allAccess) != null + && getObjects(rule, allAccess).stream().anyMatch(attr -> { if (attr.getROUTE() != null) { return "*".equals(attr.getROUTE()) || attr.getROUTE().contains(path); } @@ -321,7 +322,7 @@ private void initializeAclList(String aclFolder) { if (jsonFiles != null) { for (File file: jsonFiles) { Path filePath = file.toPath(); - String content = null; + String content; try { content = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8); aclList.put(filePath, mapper.readValue( @@ -343,7 +344,7 @@ private void monitorAclRules(String aclFolder) { return; } Path folderToWatch = Paths.get(aclFolder); - WatchService watchService = null; + WatchService watchService; try { watchService = FileSystems.getDefault().newWatchService(); // Register the folder with the WatchService for CREATE and DELETE events @@ -422,4 +423,62 @@ else if (rule.getUSEACL() != null) { throw new IllegalArgumentException("invalid rule: ACL or USEACL must be specified"); } } + + + private static List getAttributes(ACL acl, AllAccessPermissionRules allAccess) { + if (acl.getATTRIBUTES() != null) { + return acl.getATTRIBUTES(); + } + else if (acl.getUSEATTRIBUTES() != null) { + Optional attribute = allAccess.getDEFATTRIBUTES().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))) + .findAny(); + if (attribute.isPresent()) { + return attribute.get().getAttributes(); + } + else { + throw new IllegalArgumentException("DEFATTRIBUTES not found: " + acl.getUSEATTRIBUTES()); + } + } + else { + throw new IllegalArgumentException("invalid rule: ATTRIBUTES or USEATTRIBUTES must be specified"); + } + } + + + private static Map getFormula(Rule rule, AllAccessPermissionRules allAccess) { + if (rule.getFORMULA() != null) { + return rule.getFORMULA(); + } + else if (rule.getUSEFORMULA() != null) { + Optional formula = allAccess.getDEFFORMULAS().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + if (formula.isPresent()) { + return formula.get().getFormula(); + } + else { + throw new IllegalArgumentException("DEFFORMULA not found: " + rule.getUSEFORMULA()); + } + } + else { + throw new IllegalArgumentException("invalid rule: FORMULA or USEFORMULA must be specified"); + } + } + + + private static List getObjects(Rule rule, AllAccessPermissionRules allAccess) { + if (rule.getOBJECTS() != null) { + return rule.getOBJECTS(); + } + else if (rule.getUSEOBJECTS() != null) { + Optional objects = allAccess.getDEFOBJECTS().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + if (objects.isPresent()) { + return objects.get().getObjects(); + } + else { + throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUSEFORMULA()); + } + } + else { + throw new IllegalArgumentException("invalid rule: OBJECTS or USEOBJECTS must be specified"); + } + } } From 6184c26e52ff0c4ebafaf4744e55ef25a0b9d774 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 16 Oct 2025 14:15:53 +0200 Subject: [PATCH 044/108] add filtering to /submodels and /shells --- .../service/endpoint/http/HttpEndpoint.java | 4 - .../endpoint/http/RequestHandlerServlet.java | 27 +++- ...thorizationFilter.java => ApiGateway.java} | 120 ++++++++++-------- .../http/security/FormulaEvaluatorTest.java | 74 +++++++++++ ...terTest.java => ApiGatewayFilterTest.java} | 10 +- 5 files changed, 173 insertions(+), 62 deletions(-) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/{AccessControlListAuthorizationFilter.java => ApiGateway.java} (83%) rename endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/{AccessControlListAuthorizationFilterTest.java => ApiGatewayFilterTest.java} (89%) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 66c16a0db..2e303dfc4 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -22,7 +22,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateInformation; import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AccessControlListAuthorizationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.JwtValidationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; @@ -130,9 +129,6 @@ public void start() throws EndpointException { context.addFilter(new JwtValidationFilter(jwkProvider), "*", EnumSet.allOf(DispatcherType.class)); - - context.addFilter(new AccessControlListAuthorizationFilter(jwkProvider, config.getAclFolder()), - "*", EnumSet.allOf(DispatcherType.class)); } server.setErrorHandler(new HttpErrorHandler(config)); try { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index a13b12a54..1c5499ac5 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -16,12 +16,16 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.RequestMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.GetAllSubmodelsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; @@ -51,6 +55,7 @@ public class RequestHandlerServlet extends HttpServlet { private final RequestMappingManager requestMappingManager; private final ResponseMappingManager responseMappingManager; private final HttpJsonApiSerializer serializer; + private final ApiGateway apiGateway; public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); @@ -62,6 +67,7 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); + this.apiGateway = Objects.nonNull(config.getAclFolder()) ? new ApiGateway(config.getAclFolder()) : null; } @@ -97,7 +103,7 @@ protected void service(HttpServletRequest request, HttpServletResponse response) request::getHeader))) .build(); try { - executeAndSend(response, requestMappingManager.map(httpRequest)); + executeAndSend(request, response, requestMappingManager.map(httpRequest)); } catch (Exception e) { doThrow(e); @@ -120,7 +126,7 @@ private void checkRequestSupportedByProfiles(de.fraunhofer.iosb.ilt.faaast.servi } - private void executeAndSend(HttpServletResponse response, + private void executeAndSend(HttpServletRequest request, HttpServletResponse response, de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws Exception { if (Objects.isNull(apiRequest)) { @@ -131,6 +137,23 @@ private void executeAndSend(HttpServletResponse response, if (Objects.isNull(apiResponse)) { throw new ServletException("empty API response"); } + if (Objects.nonNull(apiGateway)) { + String url = request.getRequestURI().replaceFirst(HttpEndpoint.getVersionPrefix(), ""); + if ((url.equals("/shells") || url.equals("/shells/")) && request.getMethod().equals("GET")) { + GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) apiResponse; + apiResponse = apiGateway.filterAas(request, aasResponse); + } + else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getMethod().equals("GET")) { + GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) apiResponse; + apiResponse = apiGateway.filterSubmodels(request, submodelsResponse); + } + else { + if (!apiGateway.isAuthorized(request)) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getRequestURI()))); + } + } + } if (isSuccessful(apiResponse)) { responseMappingManager.map(apiRequest, apiResponse, response); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java similarity index 83% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 53d21b691..5ebe33ea0 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -15,13 +15,15 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState.ANONYMOUS; -import com.auth0.jwk.JwkProvider; +import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.GetAllSubmodelsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.ACL; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; @@ -33,12 +35,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Objects; import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -63,70 +60,86 @@ /** * Filters any incoming request with respect to the given ACL rules. */ -public class AccessControlListAuthorizationFilter extends JwtAuthorizationFilter { +public class ApiGateway { - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AccessControlListAuthorizationFilter.class); + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); + private static final String BEARER_KWD = "Bearer"; - //private final JwkProvider jwkProvider; private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; - public AccessControlListAuthorizationFilter(JwkProvider jwkProvider, String aclFolder) { - //this.jwkProvider = jwkProvider; + public ApiGateway(String aclFolder) { initializeAclList(aclFolder); - // TODO Maybe we can read the ACL rules when a request comes in? monitorAclRules(aclFolder); } /** - * Verify JWT claims by counterchecking with ACL. + * Checks if the user is authorized to receive the response of the request. * - * @param servletRequest the ServletRequest object contains the client's request - * @param servletResponse the ServletResponse object contains the filter's response - * @param filterChain the FilterChain for invoking the next filter or the resource - * @throws IOException doFilter of further Filter steps - * @throws ServletException doFilter of further Filter steps + * @param request the HttpRequest + * @return true if authorized and ACL exists */ - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) - throws IOException, ServletException { - - HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; - HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; - - if (ANONYMOUS.equals(servletRequest.getAttribute("auth.state"))) { - // Check request without any claims - if (checkClaims(httpRequest)) { - filterChain.doFilter(servletRequest, servletResponse); - } - return; - } - - // Extract JWT - DecodedJWT jwt = extractAndDecodeJwt(httpRequest); - if (jwt == null) { - LOGGER.info("Could not extract JWT"); - return; - } - - if (!checkClaims(httpRequest, jwt.getClaims())) { - httpResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + public boolean isAuthorized(HttpServletRequest request) { + String token = request.getHeader("Authorization"); + if (java.util.Objects.isNull(token)) { + return AuthServer.filterRules(this.aclList, null, request); } else { - // Continue with the request - filterChain.doFilter(servletRequest, servletResponse); + token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; + DecodedJWT jwt = JWT.decode(token); + return AuthServer.filterRules(this.aclList, jwt.getClaims(), request); } } - private boolean checkClaims(HttpServletRequest request) { - return checkClaims(request, null); + /** + * Filters out AAS that the user is not authorized for. + * + * @param request the HttpRequest + * @param response the ApiResponse + * @return the ApiResponse with only allowed AAS + */ + public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { + response.getPayload().getContent() + .removeIf(aas -> aclList.values().stream() + .noneMatch(a -> a.getAllAccessPermissionRules().getRules().stream() + .anyMatch(r -> AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), + request.getMethod(), extractClaims(request), a.getAllAccessPermissionRules())))); + return response; + } + + + /** + * Filters out Submodels that the user is not authorized for. + * + * @param request the HttpRequest + * @param response the ApiResponse + * @return the ApiResponse with only allowed Submodels + */ + public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { + response.getPayload().getContent() + .removeIf(submodel -> aclList.values().stream().noneMatch( + a -> a.getAllAccessPermissionRules().getRules().stream() + .anyMatch(r -> AuthServer.evaluateRule(r, "/submodels/" + EncodingHelper.base64Encode(submodel.getId()), + request.getMethod(), extractClaims(request), a.getAllAccessPermissionRules())))); + return response; } - private boolean checkClaims(HttpServletRequest request, Map claims) { - return AuthServer.filterRules(this.aclList, claims, request); + private Map extractClaims(HttpServletRequest request) { + String token = request.getHeader("Authorization"); + if (token == null) { + return null; + } + token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; + try { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaims(); + } + catch (com.auth0.jwt.exceptions.JWTDecodeException e) { + return null; + } } /** @@ -238,7 +251,7 @@ private static String getRequiredRight(String method) { private static boolean checkIdentifiable(String path, String identifiable) { //check submodel path - if (!path.startsWith("/submodels")) { + if (!(path.startsWith("/submodels") || path.startsWith("/shells"))) { return false; } @@ -249,6 +262,13 @@ else if (identifiable.startsWith("(Submodel)")) { String id = identifiable.substring(10); return path.contains(EncodingHelper.base64Encode(id)); } + if (identifiable.equals("(AssetAdministrationShell)*")) { + return true; + } + else if (identifiable.startsWith("(AssetAdministrationShell)")) { + String id = identifiable.substring(26); + return path.contains(EncodingHelper.base64Encode(id)); + } return false; } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java index ca71b57a8..0ae95e9a7 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java @@ -17,6 +17,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.time.LocalTime; @@ -186,4 +187,77 @@ public void fullFormula_allConditionsMet() throws Exception { assertTrue(FormulaEvaluator.evaluate(formula, ctx)); } + + @Test + public void testFormula_ConditionsNotMet() throws JsonProcessingException { + String json = """ + { + "$and": [ + { + "$or": [ + { + "$eq": [ + { + "$attribute": { + "CLAIM": "organization" + } + }, + { + "$strVal": "[MyCompany]" + } + ] + }, + { + "$eq": [ + { + "$attribute": { + "CLAIM": "organization" + } + }, + { + "$strVal": "Company2" + } + ] + } + ] + }, + { + "$or": [ + { + "$eq": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "bob@example.com" + } + ] + }, + { + "$eq": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "user2@company2.com" + } + ] + } + ] + } + ] + } + """; + Map formula = MAPPER.readValue( + json, new TypeReference>() {}); + Map ctx = new HashMap<>(); + ctx.put("CLAIM:organization", "Company2"); + //ctx.put("CLAIM:email", "user2@company2.com"); + assertFalse(FormulaEvaluator.evaluate(formula, ctx)); + } + } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java similarity index 89% rename from endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilterTest.java rename to endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index b77b08c81..f5db06297 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AccessControlListAuthorizationFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -20,11 +20,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.auth0.jwk.UrlJwkProvider; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -34,7 +32,7 @@ import org.junit.rules.TemporaryFolder; -public class AccessControlListAuthorizationFilterTest extends JwtAuthorizationFilterTest { +public class ApiGatewayFilterTest extends JwtAuthorizationFilterTest { private static final String ACL_JSON = "{\n" + " \"AllAccessPermissionRules\": {\n" + @@ -53,7 +51,7 @@ public class AccessControlListAuthorizationFilterTest extends JwtAuthorizationFi @Rule public TemporaryFolder tmp = new TemporaryFolder(); private Path aclDir; - private AccessControlListAuthorizationFilter filter; + private ApiGateway apiGateway; private static HttpServletRequest req(String method, String uri) { HttpServletRequest r = mock(HttpServletRequest.class); @@ -67,7 +65,7 @@ private static HttpServletRequest req(String method, String uri) { public void anonymousAccessDependsOnAclFile() throws Exception { aclDir = tmp.newFolder("acl").toPath(); - filter = new AccessControlListAuthorizationFilter(new UrlJwkProvider(new URL("http://whatever-jwks")), aclDir.toString()); + apiGateway = new ApiGateway(aclDir.toString()); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); HttpServletResponse response = mockResponse(); @@ -75,7 +73,7 @@ public void anonymousAccessDependsOnAclFile() throws Exception { // TODO I suggest we build a filterChain and fail/succeed if filterChain.doFilter is called (i.e. the next filter) //assertFalse(filter.doFilter(null, request)); - filter.doFilter(request, response, filterChain); + apiGateway.isAuthorized(request); // Verify that request was blocked off verify(filterChain, never()).doFilter(any(), any()); Path rule = aclDir.resolve("allow.json"); From e7ffc7289f1c9ee878e97e2a6957566b1fbf3d87 Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Mon, 20 Oct 2025 14:51:02 +0200 Subject: [PATCH 045/108] temporary workaround for Zip bomb error --- .../environment/deserializer/AasxEnvironmentDeserializer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/environment/deserializer/AasxEnvironmentDeserializer.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/environment/deserializer/AasxEnvironmentDeserializer.java index c0fc97a5f..feb03b255 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/environment/deserializer/AasxEnvironmentDeserializer.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/environment/deserializer/AasxEnvironmentDeserializer.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.nio.charset.Charset; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.util.ZipSecureFile; import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.AASXDeserializer; @@ -35,6 +36,8 @@ public class AasxEnvironmentDeserializer implements EnvironmentDeserializer { @Override public EnvironmentContext read(InputStream in, Charset charset) throws DeserializationException { try { + // temporary workaround to make sure, that all AASX files can be loaded. + ZipSecureFile.setMinInflateRatio(0); AASXDeserializer deserializer = new AASXDeserializer(in); return EnvironmentContext.builder() .environment(deserializer.read()) From e35367c50aaccc36510405c330eb4d56486b3800 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Mon, 27 Oct 2025 13:16:35 +0100 Subject: [PATCH 046/108] switch to jsonschema2pojo generator --- .../service/security/AccessRuleTest.java | 53 +- .../http/security/FormulaEvaluator.java | 245 +++-- .../http/security/filter/ApiGateway.java | 155 ++-- .../http/security/FormulaEvaluatorTest.java | 17 +- model/pom.xml | 19 + .../service/model/security/json/ACL.java | 76 -- .../model/security/json/AccessType.java | 23 - .../json/AllAccessPermissionRules.java | 89 -- .../json/AllAccessPermissionRulesRoot.java | 35 - .../model/security/json/Attribute.java | 49 - .../model/security/json/Condition.java | 64 -- .../service/model/security/json/DefACL.java | 48 - .../model/security/json/DefAttributes.java | 44 - .../model/security/json/DefFormula.java | 45 - .../model/security/json/DefObjects.java | 49 - .../service/model/security/json/Filter.java | 52 -- .../service/model/security/json/Objects.java | 75 -- .../service/model/security/json/Right.java | 29 - .../service/model/security/json/Rule.java | 130 --- model/src/main/resources/schema.json | 849 ++++++++++++++++++ pom.xml | 1 + 21 files changed, 1175 insertions(+), 972 deletions(-) delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AccessType.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRulesRoot.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Attribute.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Condition.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Filter.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Objects.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Right.java delete mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Rule.java create mode 100644 model/src/main/resources/schema.json diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index 36ac2def4..7b3edcd01 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -14,19 +14,18 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.ACL; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRules; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Attribute; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Objects; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -40,44 +39,42 @@ public void init() {} @Test public void testAllowAnonymousReadConstruction() { // Create ACL - ACL acl = new ACL(); - Attribute attr = new Attribute(); - attr.setGLOBAL("ANONYMOUS"); - acl.setATTRIBUTES(Arrays.asList(attr)); - acl.setRIGHTS(Arrays.asList("READ")); - acl.setACCESS("ALLOW"); + Acl acl = new Acl(); + AttributeItem attr = new AttributeItem(); + attr.setGlobal(AttributeItem.Global.valueOf("ANONYMOUS")); + acl.setAttributes(Arrays.asList(attr)); + acl.setRights(Arrays.asList(RightsEnum.valueOf("READ"))); + acl.setAccess(Acl.Access.valueOf("ALLOW")); // Create OBJECTS - Objects obj = new Objects(); - obj.setROUTE("*"); + ObjectItem obj = new ObjectItem(); + obj.setRoute("*"); // Create FORMULA with a simple (boolean) expression - Map formula = new HashMap<>(); - formula.put("$boolean", true); + LogicalExpression formula = new LogicalExpression(); + formula.set$boolean(true); // Create a Rule - Rule rule = new Rule(); - rule.setACL(acl); - rule.setOBJECTS(Arrays.asList(obj)); - rule.setFORMULA(formula); + AccessPermissionRule rule = new AccessPermissionRule(); + rule.setAcl(acl); + rule.setObjects(Arrays.asList(obj)); + rule.setFormula(formula); // Wrap in AllAccessPermissionRules and the root AllAccessPermissionRules allRules = new AllAccessPermissionRules(); allRules.setRules(Arrays.asList(rule)); - AllAccessPermissionRulesRoot root = new AllAccessPermissionRulesRoot(); - root.setAllAccessPermissionRules(allRules); } @Test public void testParse() throws IOException { - InputStream inputStream = AccessRuleTest.class.getResourceAsStream("/" + "ACLReadAccessAnonymous.json"); + InputStream inputStream = AccessRuleTest.class.getResourceAsStream("/ACLReadAccessAnonymous.json"); if (inputStream == null) { System.out.println("ACL not found in resources!"); return; } ObjectMapper mapper = new ObjectMapper(); - AllAccessPermissionRulesRoot allRules = mapper.readValue( - new String(inputStream.readAllBytes(), StandardCharsets.UTF_8), AllAccessPermissionRulesRoot.class); + JsonNode rootNode = mapper.readTree(inputStream); + AllAccessPermissionRules allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java index d63e9544a..e98263e77 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java @@ -14,6 +14,10 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.StringValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; import java.time.LocalTime; import java.util.List; import java.util.Map; @@ -31,80 +35,205 @@ public final class FormulaEvaluator { * * @param formula The formula. * @param runtimeValues The runtime values. - * @return True if the evaluation is successful, false othetwise. + * @return True if the evaluation is successful, false otherwise. */ - public static boolean evaluate(Map formula, + public static boolean evaluate(LogicalExpression formula, Map runtimeValues) { return eval(formula, runtimeValues); } - private static boolean eval(Map node, + private static boolean eval(LogicalExpression node, Map ctx) { - String op = node.keySet().iterator().next(); - Object value = node.get(op); - switch (op) { - case "$and" -> { - for (Object child: (List) value) { - if (!eval((Map) child, ctx)) { - return false; - } + if (!node.get$and().isEmpty()) { + for (LogicalExpression child: node.get$and()) { + if (!eval(child, ctx)) { + return false; } - return true; } - case "$or" -> { - for (Object child: (List) value) { - if (eval((Map) child, ctx)) { - return true; - } - } - return false; - } - case "$eq" -> { - List ops = (List) value; - Object left = resolve((Map) ops.get(0), ctx); - Object right = resolve((Map) ops.get(1), ctx); - return Objects.equals(left, right); - } - case "$regex" -> { - List ops = (List) value; - Object left = resolve((Map) ops.get(0), ctx); - String regex = (String) resolve((Map) ops.get(1), ctx); - return left != null && Pattern.matches(regex, left.toString()); - } - case "$ge", "$le" -> { - List ops = (List) value; - Object lObj = resolve((Map) ops.get(0), ctx); - Object rObj = resolve((Map) ops.get(1), ctx); - - if (!(lObj instanceof Comparable left) || !(rObj instanceof Comparable)) { - throw new IllegalArgumentException("Operands are not comparable: " - + lObj + ", " + rObj); + return true; + } + if (!node.get$or().isEmpty()) { + for (LogicalExpression child: node.get$or()) { + if (eval(child, ctx)) { + return true; } - int cmp = ((Comparable) left).compareTo(rObj); - return "$ge".equals(op) ? cmp >= 0 : cmp <= 0; } - default -> throw new IllegalArgumentException("Unsupported operator " + op); + return false; + } + if (node.get$not() != null) { + return !eval(node.get$not(), ctx); + } + if (!node.get$eq().isEmpty()) { + List ops = node.get$eq(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$eq requires exactly 2 operands"); + } + Object left = resolve(ops.get(0), ctx); + Object right = resolve(ops.get(1), ctx); + return Objects.equals(left, right); + } + if (!node.get$ne().isEmpty()) { + List ops = node.get$ne(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$ne requires exactly 2 operands"); + } + Object left = resolve(ops.get(0), ctx); + Object right = resolve(ops.get(1), ctx); + return !Objects.equals(left, right); + } + if (!node.get$gt().isEmpty()) { + List ops = node.get$gt(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$gt requires exactly 2 operands"); + } + Object lObj = resolve(ops.get(0), ctx); + Object rObj = resolve(ops.get(1), ctx); + if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { + throw new IllegalArgumentException("Operands are not comparable: " + + lObj + ", " + rObj); + } + int cmp = ((Comparable) lObj).compareTo(rObj); + return cmp > 0; + } + if (!node.get$ge().isEmpty()) { + List ops = node.get$ge(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$ge requires exactly 2 operands"); + } + Object lObj = resolve(ops.get(0), ctx); + Object rObj = resolve(ops.get(1), ctx); + if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { + throw new IllegalArgumentException("Operands are not comparable: " + + lObj + ", " + rObj); + } + int cmp = ((Comparable) lObj).compareTo(rObj); + return cmp >= 0; + } + if (!node.get$lt().isEmpty()) { + List ops = node.get$lt(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$lt requires exactly 2 operands"); + } + Object lObj = resolve(ops.get(0), ctx); + Object rObj = resolve(ops.get(1), ctx); + if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { + throw new IllegalArgumentException("Operands are not comparable: " + + lObj + ", " + rObj); + } + int cmp = ((Comparable) lObj).compareTo(rObj); + return cmp < 0; + } + if (!node.get$le().isEmpty()) { + List ops = node.get$le(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$le requires exactly 2 operands"); + } + Object lObj = resolve(ops.get(0), ctx); + Object rObj = resolve(ops.get(1), ctx); + if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { + throw new IllegalArgumentException("Operands are not comparable: " + + lObj + ", " + rObj); + } + int cmp = ((Comparable) lObj).compareTo(rObj); + return cmp <= 0; + } + if (!node.get$contains().isEmpty()) { + List ops = node.get$contains(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$contains requires exactly 2 operands"); + } + String left = resolveString(ops.get(0), ctx); + String right = resolveString(ops.get(1), ctx); + return left != null && left.contains(right); + } + if (!node.get$startsWith().isEmpty()) { + List ops = node.get$startsWith(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$starts-with requires exactly 2 operands"); + } + String left = resolveString(ops.get(0), ctx); + String right = resolveString(ops.get(1), ctx); + return left != null && left.startsWith(right); + } + if (!node.get$endsWith().isEmpty()) { + List ops = node.get$endsWith(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$ends-with requires exactly 2 operands"); + } + String left = resolveString(ops.get(0), ctx); + String right = resolveString(ops.get(1), ctx); + return left != null && left.endsWith(right); + } + if (!node.get$regex().isEmpty()) { + List ops = node.get$regex(); + if (ops.size() != 2) { + throw new IllegalArgumentException("$regex requires exactly 2 operands"); + } + String left = resolveString(ops.get(0), ctx); + String regex = resolveString(ops.get(1), ctx); + return left != null && Pattern.matches(regex, left); + } + if (node.get$boolean() != null) { + return node.get$boolean(); } + if (!node.get$match().isEmpty()) { + throw new UnsupportedOperationException("Operator $match not supported"); + } + throw new IllegalArgumentException("No supported operator found in node"); } - private static Object resolve(Map operand, + private static Object resolve(Value operand, Map ctx) { - if (operand.containsKey("$strVal")) - return operand.get("$strVal"); - if (operand.containsKey("$timeVal")) - return LocalTime.parse((String) operand.get("$timeVal")); - if (operand.containsKey("$field")) - return ctx.get(operand.get("$field")); - if (operand.containsKey("$attribute")) { - Map path = (Map) operand.get("$attribute"); - if (path.containsKey("CLAIM")) - return ctx.get("CLAIM:" + path.get("CLAIM")); - if (path.containsKey("REFERENCE")) - return ctx.get("REF:" + path.get("REFERENCE")); - if (path.containsKey("GLOBAL") && "UTCNOW".equals(path.get("GLOBAL"))) - return ctx.get("UTCNOW"); + if (operand.get$strVal() != null) { + return operand.get$strVal(); + } + if (operand.get$timeVal() != null) { + return LocalTime.parse(operand.get$timeVal()); + } + if (operand.get$field() != null) { + return ctx.get(operand.get$field()); + } + if (operand.get$attribute() != null) { + AttributeItem path = operand.get$attribute(); + if (!Objects.isNull(path.getClaim())) { + return ctx.get("CLAIM:" + path.getClaim()); + } + if (!Objects.isNull(path.getReference())) { + return ctx.get("REF:" + path.getReference()); + } + return ctx.get("UTCNOW"); + } + throw new IllegalArgumentException("Unresolvable operand " + operand); + } + + + private static String resolveString(StringValue operand, + Map ctx) { + if (operand.get$strVal() != null) { + return operand.get$strVal(); + } + if (operand.get$field() != null) { + Object val = ctx.get(operand.get$field()); + return val != null ? val.toString() : null; + } + if (operand.get$attribute() != null) { + AttributeItem path = operand.get$attribute(); + String key = null; + if (!Objects.isNull(path.getClaim())) { + key = "CLAIM:" + path.getClaim(); + } + else if (!Objects.isNull(path.getReference())) { + key = "REF:" + path.getReference(); + } + else { + key = "UTCNOW"; + } + if (key != null) { + Object val = ctx.get(key); + return val != null ? val.toString() : null; + } } throw new IllegalArgumentException("Unresolvable operand " + operand); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 5ebe33ea0..fd3fb8feb 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -19,21 +19,23 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.GetAllSubmodelsResponse; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.ACL; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRules; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.AllAccessPermissionRulesRoot; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Attribute; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefACL; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefAttributes; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefFormula; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.DefObjects; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Objects; -import de.fraunhofer.iosb.ilt.faaast.service.model.security.json.Rule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defacl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defattribute; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defformula; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defobject; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; import java.io.File; @@ -65,7 +67,7 @@ public class ApiGateway { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); private static final String BEARER_KWD = "Bearer"; - private Map aclList; + private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; public ApiGateway(String aclFolder) { @@ -103,9 +105,9 @@ public boolean isAuthorized(HttpServletRequest request) { public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { response.getPayload().getContent() .removeIf(aas -> aclList.values().stream() - .noneMatch(a -> a.getAllAccessPermissionRules().getRules().stream() + .noneMatch(a -> a.getRules().stream() .anyMatch(r -> AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), - request.getMethod(), extractClaims(request), a.getAllAccessPermissionRules())))); + request.getMethod(), extractClaims(request), a)))); return response; } @@ -120,9 +122,9 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { response.getPayload().getContent() .removeIf(submodel -> aclList.values().stream().noneMatch( - a -> a.getAllAccessPermissionRules().getRules().stream() + a -> a.getRules().stream() .anyMatch(r -> AuthServer.evaluateRule(r, "/submodels/" + EncodingHelper.base64Encode(submodel.getId()), - request.getMethod(), extractClaims(request), a.getAllAccessPermissionRules())))); + request.getMethod(), extractClaims(request), a)))); return response; } @@ -158,32 +160,31 @@ public static class AuthServer { * @param request * @return */ - private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { + private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { String requestPath = request.getRequestURI(); String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(9) : requestPath; String method = request.getMethod(); - List relevantRules = aclList.values().stream() - .filter(a -> a.getAllAccessPermissionRules() - .getRules().stream() - .anyMatch(r -> evaluateRule(r, path, method, claims, a.getAllAccessPermissionRules()))) + List relevantRules = aclList.values().stream() + .filter(a -> a.getRules().stream() + .anyMatch(r -> evaluateRule(r, path, method, claims, a))) .collect(Collectors.toList()); return !relevantRules.isEmpty(); } - private static boolean verifyAllClaims(Map claims, Rule rule, AllAccessPermissionRules allAccess) { - ACL acl = getAcl(rule, allAccess); + private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + Acl acl = getAcl(rule, allAccess); if (getAttributes(acl, allAccess).stream() - .anyMatch(attr -> "ANONYMOUS".equals(attr.getGLOBAL()) - && Boolean.TRUE.equals(getFormula(rule, allAccess).get("$boolean")))) { + .anyMatch(attr -> "ANONYMOUS".equals(attr.getGlobal()) + && Boolean.TRUE.equals(getFormula(rule, allAccess).get$boolean()))) { return true; } if (claims == null) { return false; } List claimValues = getAttributes(acl, allAccess).stream() - .filter(attr -> attr.getGLOBAL() == null) - .map(Attribute::getCLAIM) + .filter(attr -> attr.getGlobal() == null) + .map(AttributeItem::getClaim) .filter(java.util.Objects::nonNull) .collect(Collectors.toList()); Map claimList = new HashMap<>(); @@ -201,7 +202,7 @@ private static boolean verifyAllClaims(Map claims, Rule rule, All } - private static boolean evaluateFormula(Map formula, + private static boolean evaluateFormula(LogicalExpression formula, Map claims) { Map ctx = new HashMap<>(); for (var c: claims.entrySet()) { @@ -213,11 +214,11 @@ private static boolean evaluateFormula(Map formula, } - private static boolean evaluateRights(List aclRights, String method, String path) { + private static boolean evaluateRights(List aclRights, String method, String path) { // We need the path to check if the request is an operation invocation (EXECUTE) String requiredRight = isOperationRequest(method, path) ? "EXECUTE" : getRequiredRight(method); - return aclRights.contains("ALL") || aclRights.contains(requiredRight); + return aclRights.contains(RightsEnum.ALL) || aclRights.contains(RightsEnum.valueOf(requiredRight)); } @@ -302,28 +303,28 @@ else if (descriptor.startsWith("(smDesc)")) { } - private static boolean evaluateRule(Rule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { - ACL acl = getAcl(rule, allAccess); + private static boolean evaluateRule(AccessPermissionRule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { + Acl acl = getAcl(rule, allAccess); return acl != null && getAttributes(acl, allAccess) != null - && acl.getRIGHTS() != null + && acl.getRights() != null && getObjects(rule, allAccess) != null && getObjects(rule, allAccess).stream().anyMatch(attr -> { - if (attr.getROUTE() != null) { - return "*".equals(attr.getROUTE()) || attr.getROUTE().contains(path); + if (attr.getRoute() != null) { + return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); } - else if (attr.getIDENTIFIABLE() != null) { - return checkIdentifiable(path, attr.getIDENTIFIABLE()); + else if (attr.getIdentifiable() != null) { + return checkIdentifiable(path, attr.getIdentifiable()); } - else if (attr.getDESCRIPTOR() != null) { - return checkDescriptor(path, attr.getDESCRIPTOR()); + else if (attr.getDescriptor() != null) { + return checkDescriptor(path, attr.getDescriptor()); } else { return false; } }) - && "ALLOW".equals(acl.getACCESS()) - && evaluateRights(acl.getRIGHTS(), method, path) + && "ALLOW".equals(acl.getAccess()) + && evaluateRights(acl.getRights(), method, path) && verifyAllClaims(claims, rule, allAccess); } } @@ -342,11 +343,17 @@ private void initializeAclList(String aclFolder) { if (jsonFiles != null) { for (File file: jsonFiles) { Path filePath = file.toPath(); - String content; try { - content = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8); - aclList.put(filePath, mapper.readValue( - content, AllAccessPermissionRulesRoot.class)); + String jsonContent = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8); + JsonNode rootNode = mapper.readTree(jsonContent); + AllAccessPermissionRules allRules; + if (rootNode.has("AllAccessPermissionRules")) { + allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); + } + else { + allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); + } + aclList.put(filePath, allRules); } catch (IOException e) { LOGGER.error(abortMessage); @@ -400,8 +407,16 @@ private void monitorLoop(WatchService watchService, Path folderToWatch) { if (filePath.toString().toLowerCase().endsWith(".json")) { if (kind == StandardWatchEventKinds.ENTRY_CREATE) { try { - aclList.put(absolutePath, mapper.readValue( - new String(Files.readAllBytes(absolutePath), StandardCharsets.UTF_8), AllAccessPermissionRulesRoot.class)); + String jsonContent = new String(Files.readAllBytes(absolutePath), StandardCharsets.UTF_8); + JsonNode rootNode = mapper.readTree(jsonContent); + AllAccessPermissionRules allRules; + if (rootNode.has("AllAccessPermissionRules")) { + allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); + } + else { + allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); + } + aclList.put(absolutePath, allRules); } catch (IOException e) { LOGGER.error(abortMessage); @@ -426,17 +441,17 @@ else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { } - private static ACL getAcl(Rule rule, AllAccessPermissionRules allAccess) { - if (rule.getACL() != null) { - return rule.getACL(); + private static Acl getAcl(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + if (rule.getAcl() != null) { + return rule.getAcl(); } - else if (rule.getUSEACL() != null) { - Optional acl = allAccess.getDEFACLS().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + else if (rule.getUseacl() != null) { + Optional acl = allAccess.getDefacls().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); if (acl.isPresent()) { return acl.get().getAcl(); } else { - throw new IllegalArgumentException("DEFACL not found: " + rule.getUSEACL()); + throw new IllegalArgumentException("DEFACL not found: " + rule.getUseacl()); } } else { @@ -445,18 +460,18 @@ else if (rule.getUSEACL() != null) { } - private static List getAttributes(ACL acl, AllAccessPermissionRules allAccess) { - if (acl.getATTRIBUTES() != null) { - return acl.getATTRIBUTES(); + private static List getAttributes(Acl acl, AllAccessPermissionRules allAccess) { + if (acl.getAttributes() != null) { + return acl.getAttributes(); } - else if (acl.getUSEATTRIBUTES() != null) { - Optional attribute = allAccess.getDEFATTRIBUTES().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))) + else if (acl.getUseattributes() != null) { + Optional attribute = allAccess.getDefattributes().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))) .findAny(); if (attribute.isPresent()) { return attribute.get().getAttributes(); } else { - throw new IllegalArgumentException("DEFATTRIBUTES not found: " + acl.getUSEATTRIBUTES()); + throw new IllegalArgumentException("DEFATTRIBUTES not found: " + acl.getUseattributes()); } } else { @@ -465,17 +480,17 @@ else if (acl.getUSEATTRIBUTES() != null) { } - private static Map getFormula(Rule rule, AllAccessPermissionRules allAccess) { - if (rule.getFORMULA() != null) { - return rule.getFORMULA(); + private static LogicalExpression getFormula(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + if (rule.getFormula() != null) { + return rule.getFormula(); } - else if (rule.getUSEFORMULA() != null) { - Optional formula = allAccess.getDEFFORMULAS().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + else if (rule.getUseformula() != null) { + Optional formula = allAccess.getDefformulas().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); if (formula.isPresent()) { return formula.get().getFormula(); } else { - throw new IllegalArgumentException("DEFFORMULA not found: " + rule.getUSEFORMULA()); + throw new IllegalArgumentException("DEFFORMULA not found: " + rule.getUseformula()); } } else { @@ -484,17 +499,17 @@ else if (rule.getUSEFORMULA() != null) { } - private static List getObjects(Rule rule, AllAccessPermissionRules allAccess) { - if (rule.getOBJECTS() != null) { - return rule.getOBJECTS(); + private static List getObjects(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + if (rule.getObjects() != null) { + return rule.getObjects(); } - else if (rule.getUSEOBJECTS() != null) { - Optional objects = allAccess.getDEFOBJECTS().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + else if (rule.getUseobjects() != null) { + Optional objects = allAccess.getDefobjects().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); if (objects.isPresent()) { return objects.get().getObjects(); } else { - throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUSEFORMULA()); + throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseformula()); } } else { diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java index 0ae95e9a7..8b9b22268 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import java.time.LocalTime; import java.util.HashMap; import java.util.Map; @@ -75,8 +76,8 @@ public void complexFormula_withMatchingClaims() throws Exception { } """; - Map formula = MAPPER.readValue( - json, new TypeReference>() {}); + LogicalExpression formula = MAPPER.readValue( + json, new TypeReference<>() {}); Map ctx = new HashMap<>(); ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); // matches 2nd $eq @@ -118,8 +119,8 @@ public void regexFormula_withNonMatchingEmail() throws Exception { } """; - Map formula = MAPPER.readValue( - json, new TypeReference>() {}); + LogicalExpression formula = MAPPER.readValue( + json, new TypeReference<>() {}); Map ctx = new HashMap<>(); ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); ctx.put("CLAIM:email", "other.user@other-company.org"); // does NOT match @@ -177,8 +178,8 @@ public void fullFormula_allConditionsMet() throws Exception { } """; - Map formula = MAPPER.readValue( - json, new TypeReference>() {}); + LogicalExpression formula = MAPPER.readValue( + json, new TypeReference<>() {}); Map ctx = new HashMap<>(); ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); ctx.put("CLAIM:companyName", "company1-name"); @@ -252,8 +253,8 @@ public void testFormula_ConditionsNotMet() throws JsonProcessingException { ] } """; - Map formula = MAPPER.readValue( - json, new TypeReference>() {}); + LogicalExpression formula = MAPPER.readValue( + json, new TypeReference<>() {}); Map ctx = new HashMap<>(); ctx.put("CLAIM:organization", "Company2"); //ctx.put("CLAIM:email", "user2@company2.com"); diff --git a/model/pom.xml b/model/pom.xml index 56ec8258a..04144fbf2 100644 --- a/model/pom.xml +++ b/model/pom.xml @@ -207,6 +207,25 @@ org.apache.maven.plugins maven-checkstyle-plugin + + org.jsonschema2pojo + jsonschema2pojo-maven-plugin + ${jsonschema2pojo.version} + + ${project.basedir}/src/main/resources/schema.json + de.fraunhofer.iosb.ilt.faaast.service.model.query.json + false + true + true + + + + + generate + + + + diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java deleted file mode 100644 index 0c7eb6a6d..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/ACL.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; - - -/** - * Describes the access control list. - */ -public class ACL { - - @JsonProperty("ATTRIBUTES") - private List ATTRIBUTES; // e.g., "CLAIM", "GLOBAL" - - @JsonProperty("RIGHTS") - private List RIGHTS; - - @JsonProperty("ACCESS") - private String ACCESS; - - @JsonProperty("USEATTRIBUTES") - private String USEATTRIBUTES; - - public List getATTRIBUTES() { - return ATTRIBUTES; - } - - - public void setATTRIBUTES(List ATTRIBUTES) { - this.ATTRIBUTES = ATTRIBUTES; - } - - - public List getRIGHTS() { - return RIGHTS; - } - - - public void setRIGHTS(List RIGHTS) { - this.RIGHTS = RIGHTS; - } - - - public String getACCESS() { - return ACCESS; - } - - - public void setACCESS(String ACCESS) { - this.ACCESS = ACCESS; - } - - - public String getUSEATTRIBUTES() { - return USEATTRIBUTES; - } - - - public void setUSEATTRIBUTES(String USEATTRIBUTES) { - this.USEATTRIBUTES = USEATTRIBUTES; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AccessType.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AccessType.java deleted file mode 100644 index c707dfbdb..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AccessType.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -/** - * Enum for access types. - */ -public enum AccessType { - ALLOW, - DISABLED -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java deleted file mode 100644 index fda87f0ee..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRules.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; - - -/** - * Describes all access permissiom rules. - */ -public class AllAccessPermissionRules { - - @JsonProperty("DEFACLS") - private List DEFACLS; - - @JsonProperty("DEFOBJECTS") - private List DEFOBJECTS; - - @JsonProperty("DEFFORMULAS") - private List DEFFORMULAS; - - @JsonProperty("rules") - private List rules; - - @JsonProperty("DEFATTRIBUTES") - private List DEFATTRIBUTES; - - public List getDEFACLS() { - return DEFACLS; - } - - - public void setDEFACLS(List DEFACLS) { - this.DEFACLS = DEFACLS; - } - - - public List getDEFOBJECTS() { - return DEFOBJECTS; - } - - - public void setDEFOBJECTS(List DEFOBJECTS) { - this.DEFOBJECTS = DEFOBJECTS; - } - - - public List getDEFFORMULAS() { - return DEFFORMULAS; - } - - - public void setDEFFORMULAS(List DEFFORMULAS) { - this.DEFFORMULAS = DEFFORMULAS; - } - - - public List getRules() { - return rules; - } - - - public void setRules(List rules) { - this.rules = rules; - } - - - public List getDEFATTRIBUTES() { - return DEFATTRIBUTES; - } - - - public void setDEFATTRIBUTES(List DEFATTRIBUTES) { - this.DEFATTRIBUTES = DEFATTRIBUTES; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRulesRoot.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRulesRoot.java deleted file mode 100644 index 7e01ea4e6..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/AllAccessPermissionRulesRoot.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; - - -/** - * Root collection for all rules. - */ -public class AllAccessPermissionRulesRoot { - @JsonProperty("AllAccessPermissionRules") - private AllAccessPermissionRules allAccessPermissionRules; - - public AllAccessPermissionRules getAllAccessPermissionRules() { - return allAccessPermissionRules; - } - - - public void setAllAccessPermissionRules(AllAccessPermissionRules allAccessPermissionRules) { - this.allAccessPermissionRules = allAccessPermissionRules; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Attribute.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Attribute.java deleted file mode 100644 index 78d323406..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Attribute.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; - - -/** - * Attributes contain a CLAIM or GLOBAL. - */ -public class Attribute { - - @JsonProperty("CLAIM") - private String CLAIM; - - @JsonProperty("GLOBAL") - private String GLOBAL; - - public String getCLAIM() { - return CLAIM; - } - - - public void setCLAIM(String CLAIM) { - this.CLAIM = CLAIM; - } - - - public String getGLOBAL() { - return GLOBAL; - } - - - public void setGLOBAL(String GLOBAL) { - this.GLOBAL = GLOBAL; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Condition.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Condition.java deleted file mode 100644 index 0986d5836..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Condition.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.util.HashMap; -import java.util.Map; - - -/** - * Represents a flexible condition structure that can contain - * any JSON operators (e.g., "$or", "$match", "$eq", etc.). - */ -@JsonIgnoreProperties(ignoreUnknown = false) -public class Condition { - - // A catch-all map that stores any fields Jackson encounters - // (e.g., "$or", "$match", "$eq", "$regex", etc.) - private Map expression = new HashMap<>(); - - /** - * Set the expression. - * - * @param key key like eq - * @param value value of claim - */ - @JsonAnySetter - public void setExpression(String key, Object value) { - expression.put(key, value); - } - - - /** - * Get the expression. - * - * @return expression - */ - public Map getExpression() { - return expression; - } - - - /** - * Set the expression. - * - * @param expression full expression - */ - public void setExpression(Map expression) { - this.expression = expression; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java deleted file mode 100644 index a6c1aa678..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefACL.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; - - -/** - * Optional DefACL field. - */ -public class DefACL { - @JsonProperty("name") - private String name; - - @JsonProperty("acl") - private ACL acl; - - public String getName() { - return name; - } - - - public void setName(String value) { - name = value; - } - - - public ACL getAcl() { - return acl; - } - - - public void setAcl(ACL value) { - acl = value; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java deleted file mode 100644 index bbec0fa21..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefAttributes.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; - - -/** - * Reusable Defattributes. - */ -public class DefAttributes { - @JsonProperty("name") - private String name; - - @JsonProperty("attributes") - private List attributes; - - public String getName() { - return name; - } - - - public void setName(String value) { - name = value; - } - - - public List getAttributes() { - return attributes; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java deleted file mode 100644 index 008f4a3fa..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefFormula.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import java.util.Map; - - -/** - * Optional DefFormula field. - */ -public class DefFormula { - private String name; - private Map formula; - - public String getName() { - return name; - } - - - public void setName(String value) { - name = value; - } - - - public Map getFormula() { - return formula; - } - - - public void setFormula(Map formulaExpression) { - formula = formulaExpression; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java deleted file mode 100644 index 6d89ea517..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/DefObjects.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; - - -/** - * Reusable DefObjects. - */ -public class DefObjects { - @JsonProperty("name") - private String name; - - @JsonProperty("objects") - private List objects; - - public String getName() { - return name; - } - - - public void setName(String value) { - name = value; - } - - - public List getObjects() { - return objects; - } - - - public void setObjects(List value) { - objects = value; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Filter.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Filter.java deleted file mode 100644 index 272574fad..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Filter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - - -/** - * Describes the filter object, including FRAGMENT and a CONDITION - * that can hold arbitrary operators. - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class Filter { - - @JsonProperty("FRAGMENT") - private String FRAGMENT; - - @JsonProperty("CONDITION") - private Condition CONDITION; - - public String getFRAGMENT() { - return FRAGMENT; - } - - - public void setFRAGMENT(String FRAGMENT) { - this.FRAGMENT = FRAGMENT; - } - - - public Condition getCONDITION() { - return CONDITION; - } - - - public void setCONDITION(Condition CONDITION) { - this.CONDITION = CONDITION; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Objects.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Objects.java deleted file mode 100644 index 77a19d504..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Objects.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; - - -/** - * Objects can the routes and identifiables. - */ -public class Objects { - - @JsonProperty("ROUTE") - private String ROUTE; - - @JsonProperty("IDENTIFIABLE") - private String IDENTIFIABLE; - - @JsonProperty("REFERABLE") - private String REFERABLE; - - @JsonProperty("DESCRIPTOR") - private String DESCRIPTOR; - - public String getROUTE() { - return ROUTE; - } - - - public void setROUTE(String ROUTE) { - this.ROUTE = ROUTE; - } - - - public String getIDENTIFIABLE() { - return IDENTIFIABLE; - } - - - public void setIDENTIFIABLE(String IDENTIFIABLE) { - this.IDENTIFIABLE = IDENTIFIABLE; - } - - - public String getREFERABLE() { - return REFERABLE; - } - - - public void setREFERABLE(String REFERABLE) { - this.REFERABLE = REFERABLE; - } - - - public String getDESCRIPTOR() { - return DESCRIPTOR; - } - - - public void setDESCRIPTOR(String DESCRIPTOR) { - this.DESCRIPTOR = DESCRIPTOR; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Right.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Right.java deleted file mode 100644 index 9a3c76055..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Right.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -/** - * Enum for rights. - */ -public enum Right { - CREATE, - READ, - UPDATE, - DELETE, - EXECUTE, - VIEW, - ALL, - TREE -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Rule.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Rule.java deleted file mode 100644 index 94a324cc4..000000000 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/security/json/Rule.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.model.security.json; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; -import java.util.Map; - - -/** - * Rules contain all other definitions. - */ -public class Rule { - - @JsonProperty("ACL") - private ACL ACL; - - @JsonProperty("OBJECTS") - private List OBJECTS; - - @JsonProperty("FORMULA") - private Map FORMULA; - - @JsonProperty("FILTER") - private Filter FILTER; - - @JsonProperty("FRAGMENT") - private String FRAGMENT; - - // Reusable references - @JsonProperty("USEACL") - private String USEACL; - - @JsonProperty("USEOBJECTS") - private List USEOBJECTS; - - @JsonProperty("USEFORMULA") - private String USEFORMULA; - - public ACL getACL() { - return ACL; - } - - - public void setACL(ACL ACL) { - this.ACL = ACL; - } - - - public List getOBJECTS() { - return OBJECTS; - } - - - public void setOBJECTS(List OBJECTS) { - this.OBJECTS = OBJECTS; - } - - - public Map getFORMULA() { - return FORMULA; - } - - - public void setFORMULA(Map FORMULA) { - this.FORMULA = FORMULA; - } - - - public Filter getFILTER() { - return FILTER; - } - - - public void setFILTER(Filter FILTER) { - this.FILTER = FILTER; - } - - - public String getFRAGMENT() { - return FRAGMENT; - } - - - public void setFRAGMENT(String FRAGMENT) { - this.FRAGMENT = FRAGMENT; - } - - - public String getUSEACL() { - return USEACL; - } - - - public void setUSEACL(String USEACL) { - this.USEACL = USEACL; - } - - - public List getUSEOBJECTS() { - return USEOBJECTS; - } - - - public void setUSEOBJECTS(List USEOBJECTS) { - this.USEOBJECTS = USEOBJECTS; - } - - - public String getUSEFORMULA() { - return USEFORMULA; - } - - - public void setUSEFORMULA(String USEFORMULA) { - this.USEFORMULA = USEFORMULA; - } -} diff --git a/model/src/main/resources/schema.json b/model/src/main/resources/schema.json new file mode 100644 index 000000000..e72079d53 --- /dev/null +++ b/model/src/main/resources/schema.json @@ -0,0 +1,849 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Common JSON Schema for AAS Queries and Access Rules", + "description": "This schema contains all classes that are shared between the AAS Query Language and the AAS Access Rule Language.", + "definitions": { + "standardString": { + "type": "string", + "pattern": "^(?!\\$).*" + }, + "modelStringPattern": { + "type": "string", + "pattern": "^((?:\\$aas#(?:idShort|id|assetInformation\\.assetKind|assetInformation\\.assetType|assetInformation\\.globalAssetId|assetInformation\\.(?:specificAssetIds(\\[[0-9]*\\])(?:\\.(?:name|value|externalSubjectId(?:\\.type|\\.keys\\[\\d*\\](?:\\.(?:type|value))?)?)?)|submodels\\.(?:type|keys\\[\\d*\\](?:\\.(?:type|value))?))|submodels\\.(type|keys\\[\\d*\\](?:\\.(type|value))?)))|(?:\\$sm#(?:semanticId(?:\\.type|\\.keys\\[\\d*\\](?:\\.(type|value))?)?|idShort|id))|(?:\\$sme(?:\\.[a-zA-Z][a-zA-Z0-9_]*(\\[[0-9]*\\])?(?:\\.[a-zA-Z][a-zA-Z0-9_]*(\\[[0-9]*\\])?)*)?#(?:semanticId(?:\\.type|\\.keys\\[\\d*\\](?:\\.(type|value))?)?|idShort|value|valueType|language))|(?:\\$cd#(?:idShort|id))|(?:\\$aasdesc#(?:idShort|id|assetKind|assetType|globalAssetId|specificAssetIds(\\[[0-9]*\\])?(?:\\.(name|value|externalSubjectId(?:\\.type|\\.keys\\[\\d*\\](?:\\.(type|value))?)?)?)|endpoints(\\[[0-9]*\\])\\.(interface|protocolinformation\\.href)|submodelDescriptors(\\[[0-9]*\\])\\.(semanticId(?:\\.type|\\.keys\\[\\d*\\](?:\\.(type|value))?)?|idShort|id|endpoints(\\[[0-9]*\\])\\.(interface|protocolinformation\\.href))))|(?:\\$smdesc#(?:semanticId(?:\\.type|\\.keys\\[\\d*\\](?:\\.(type|value))?)?|idShort|id|endpoints(\\[[0-9]*\\])\\.(interface|protocolinformation\\.href))))$" + }, + "hexLiteralPattern": { + "type": "string", + "pattern": "^16#[0-9A-F]+$" + }, + "dateTimeLiteralPattern": { + "type": "string", + "format": "date-time" + }, + "timeLiteralPattern": { + "type": "string", + "pattern": "^[0-9][0-9]:[0-9][0-9](:[0-9][0-9])?$" + }, + "Value": { + "type": "object", + "properties": { + "$field": { + "$ref": "#/definitions/modelStringPattern" + }, + "$strVal": { + "$ref": "#/definitions/standardString" + }, + "$attribute": { + "$ref": "#/definitions/attributeItem" + }, + "$numVal": { + "type": "number" + }, + "$hexVal": { + "$ref": "#/definitions/hexLiteralPattern" + }, + "$dateTimeVal": { + "$ref": "#/definitions/dateTimeLiteralPattern" + }, + "$timeVal": { + "$ref": "#/definitions/timeLiteralPattern" + }, + "$boolean": { + "type": "boolean" + }, + "$strCast": { + "$ref": "#/definitions/Value" + }, + "$numCast": { + "$ref": "#/definitions/Value" + }, + "$hexCast": { + "$ref": "#/definitions/Value" + }, + "$boolCast": { + "$ref": "#/definitions/Value" + }, + "$dateTimeCast": { + "$ref": "#/definitions/Value" + }, + "$timeCast": { + "$ref": "#/definitions/Value" + }, + "$dayOfWeek": { + "$ref": "#/definitions/dateTimeLiteralPattern" + }, + "$dayOfMonth": { + "$ref": "#/definitions/dateTimeLiteralPattern" + }, + "$month": { + "$ref": "#/definitions/dateTimeLiteralPattern" + }, + "$year": { + "$ref": "#/definitions/dateTimeLiteralPattern" + } + }, + "oneOf": [ + { + "required": [ + "$field" + ] + }, + { + "required": [ + "$strVal" + ] + }, + { + "required": [ + "$attribute" + ] + }, + { + "required": [ + "$numVal" + ] + }, + { + "required": [ + "$hexVal" + ] + }, + { + "required": [ + "$dateTimeVal" + ] + }, + { + "required": [ + "$timeVal" + ] + }, + { + "required": [ + "$boolean" + ] + }, + { + "required": [ + "$strCast" + ] + }, + { + "required": [ + "$numCast" + ] + }, + { + "required": [ + "$hexCast" + ] + }, + { + "required": [ + "$boolCast" + ] + }, + { + "required": [ + "$dateTimeCast" + ] + }, + { + "required": [ + "$timeCast" + ] + }, + { + "required": [ + "$dayOfWeek" + ] + }, + { + "required": [ + "$dayOfMonth" + ] + }, + { + "required": [ + "$month" + ] + }, + { + "required": [ + "$year" + ] + } + ], + "additionalProperties": false + }, + "stringValue": { + "type": "object", + "properties": { + "$field": { + "$ref": "#/definitions/modelStringPattern" + }, + "$strVal": { + "$ref": "#/definitions/standardString" + }, + "$strCast": { + "$ref": "#/definitions/Value" + }, + "$attribute": { + "$ref": "#/definitions/attributeItem" + } + }, + "oneOf": [ + { + "required": [ + "$field" + ] + }, + { + "required": [ + "$strVal" + ] + }, + { + "required": [ + "$strCast" + ] + }, + { + "required": [ + "$attribute" + ] + } + ], + "additionalProperties": false + }, + "comparisonItems": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "$ref": "#/definitions/Value" + } + }, + "stringItems": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "$ref": "#/definitions/stringValue" + } + }, + "matchExpression": { + "type": "object", + "properties": { + "$match": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/matchExpression" + } + }, + "$eq": { + "$ref": "#/definitions/comparisonItems" + }, + "$ne": { + "$ref": "#/definitions/comparisonItems" + }, + "$gt": { + "$ref": "#/definitions/comparisonItems" + }, + "$ge": { + "$ref": "#/definitions/comparisonItems" + }, + "$lt": { + "$ref": "#/definitions/comparisonItems" + }, + "$le": { + "$ref": "#/definitions/comparisonItems" + }, + "$contains": { + "$ref": "#/definitions/stringItems" + }, + "$starts-with": { + "$ref": "#/definitions/stringItems" + }, + "$ends-with": { + "$ref": "#/definitions/stringItems" + }, + "$regex": { + "$ref": "#/definitions/stringItems" + }, + "$boolean": { + "type": "boolean" + } + }, + "oneOf": [ + { + "required": [ + "$eq" + ] + }, + { + "required": [ + "$ne" + ] + }, + { + "required": [ + "$gt" + ] + }, + { + "required": [ + "$ge" + ] + }, + { + "required": [ + "$lt" + ] + }, + { + "required": [ + "$le" + ] + }, + { + "required": [ + "$contains" + ] + }, + { + "required": [ + "$starts-with" + ] + }, + { + "required": [ + "$ends-with" + ] + }, + { + "required": [ + "$regex" + ] + }, + { + "required": [ + "$boolean" + ] + }, + { + "required": [ + "$match" + ] + } + ], + "additionalProperties": false + }, + "logicalExpression": { + "type": "object", + "properties": { + "$and": { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/definitions/logicalExpression" + } + }, + "$match": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/matchExpression" + } + }, + "$or": { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/definitions/logicalExpression" + } + }, + "$not": { + "$ref": "#/definitions/logicalExpression" + }, + "$eq": { + "$ref": "#/definitions/comparisonItems" + }, + "$ne": { + "$ref": "#/definitions/comparisonItems" + }, + "$gt": { + "$ref": "#/definitions/comparisonItems" + }, + "$ge": { + "$ref": "#/definitions/comparisonItems" + }, + "$lt": { + "$ref": "#/definitions/comparisonItems" + }, + "$le": { + "$ref": "#/definitions/comparisonItems" + }, + "$contains": { + "$ref": "#/definitions/stringItems" + }, + "$starts-with": { + "$ref": "#/definitions/stringItems" + }, + "$ends-with": { + "$ref": "#/definitions/stringItems" + }, + "$regex": { + "$ref": "#/definitions/stringItems" + }, + "$boolean": { + "type": "boolean" + } + }, + "oneOf": [ + { + "required": [ + "$and" + ] + }, + { + "required": [ + "$or" + ] + }, + { + "required": [ + "$not" + ] + }, + { + "required": [ + "$eq" + ] + }, + { + "required": [ + "$ne" + ] + }, + { + "required": [ + "$gt" + ] + }, + { + "required": [ + "$ge" + ] + }, + { + "required": [ + "$lt" + ] + }, + { + "required": [ + "$le" + ] + }, + { + "required": [ + "$contains" + ] + }, + { + "required": [ + "$starts-with" + ] + }, + { + "required": [ + "$ends-with" + ] + }, + { + "required": [ + "$regex" + ] + }, + { + "required": [ + "$boolean" + ] + }, + { + "required": [ + "$match" + ] + } + ], + "additionalProperties": false + }, + "attributeItem": { + "oneOf": [ + { + "required": [ + "CLAIM" + ] + }, + { + "required": [ + "GLOBAL" + ] + }, + { + "required": [ + "REFERENCE" + ] + } + ], + "properties": { + "CLAIM": { + "type": "string" + }, + "GLOBAL": { + "type": "string", + "enum": [ + "LOCALNOW", + "UTCNOW", + "CLIENTNOW", + "ANONYMOUS" + ] + }, + "REFERENCE": { + "type": "string" + } + }, + "additionalProperties": false + }, + "objectItem": { + "oneOf": [ + { + "required": [ + "ROUTE" + ] + }, + { + "required": [ + "IDENTIFIABLE" + ] + }, + { + "required": [ + "REFERABLE" + ] + }, + { + "required": [ + "FRAGMENT" + ] + }, + { + "required": [ + "DESCRIPTOR" + ] + } + ], + "properties": { + "ROUTE": { + "type": "string" + }, + "IDENTIFIABLE": { + "type": "string" + }, + "REFERABLE": { + "type": "string" + }, + "FRAGMENT": { + "type": "string" + }, + "DESCRIPTOR": { + "type": "string" + } + }, + "additionalProperties": false + }, + "rightsEnum": { + "type": "string", + "enum": [ + "CREATE", + "READ", + "UPDATE", + "DELETE", + "EXECUTE", + "VIEW", + "ALL", + "TREE" + ], + "additionalProperties": false + }, + "ACL": { + "type": "object", + "properties": { + "ATTRIBUTES": { + "type": "array", + "items": { + "$ref": "#/definitions/attributeItem" + } + }, + "USEATTRIBUTES": { + "type": "string" + }, + "RIGHTS": { + "type": "array", + "items": { + "$ref": "#/definitions/rightsEnum" + } + }, + "ACCESS": { + "type": "string", + "enum": [ + "ALLOW", + "DISABLED" + ] + } + }, + "required": [ + "RIGHTS", + "ACCESS" + ], + "oneOf": [ + { + "required": [ + "ATTRIBUTES" + ] + }, + { + "required": [ + "USEATTRIBUTES" + ] + } + ], + "additionalProperties": false + }, + "AccessPermissionRule": { + "type": "object", + "properties": { + "ACL": { + "$ref": "#/definitions/ACL" + }, + "USEACL": { + "type": "string" + }, + "OBJECTS": { + "type": "array", + "items": { + "$ref": "#/definitions/objectItem" + }, + "additionalProperties": false + }, + "USEOBJECTS": { + "type": "array", + "items": { + "type": "string" + } + }, + "FORMULA": { + "$ref": "#/definitions/logicalExpression", + "additionalProperties": false + }, + "USEFORMULA": { + "type": "string" + }, + "FRAGMENT": { + "type": "string" + }, + "FILTER": { + "$ref": "#/definitions/logicalExpression", + "additionalProperties": false + }, + "USEFILTER": { + "type": "string" + } + }, + "oneOf": [ + { + "required": [ + "ACL" + ] + }, + { + "required": [ + "USEACL" + ] + } + ], + "oneOf": [ + { + "required": [ + "OBJECTS" + ] + }, + { + "required": [ + "USEOBJECTS" + ] + } + ], + "oneOf": [ + { + "required": [ + "FORMULA" + ] + }, + { + "required": [ + "USEFORMULA" + ] + } + ], + "additionalProperties": false + } + }, + "type": "object", + "properties": { + "Query": { + "type": "object", + "properties": { + "$select": { + "type": "string", + "pattern": "^id$" + }, + "$condition": { + "$ref": "#/definitions/logicalExpression" + } + }, + "required": [ + "$condition" + ], + "additionalProperties": false + }, + "AllAccessPermissionRules": { + "type": "object", + "properties": { + "DEFATTRIBUTES": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/definitions/attributeItem" + } + } + }, + "required": [ + "name", + "attributes" + ], + "additionalProperties": false + } + }, + "DEFACLS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "acl": { + "$ref": "#/definitions/ACL" + } + }, + "required": [ + "name", + "acl" + ], + "additionalProperties": false + } + }, + "DEFOBJECTS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "objects": { + "type": "array", + "items": { + "$ref": "#/definitions/objectItem" + } + }, + "USEOBJECTS": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ], + "oneOf": [ + { + "required": [ + "objects" + ] + }, + { + "required": [ + "USEOBJECTS" + ] + } + ], + "additionalProperties": false + } + }, + "DEFFORMULAS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "formula": { + "$ref": "#/definitions/logicalExpression" + } + }, + "required": [ + "name", + "formula" + ], + "additionalProperties": false + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/AccessPermissionRule" + } + } + }, + "required": [ + "rules" + ], + "additionalProperties": false + } + }, + "oneOf": [ + { + "required": [ + "Query" + ] + }, + { + "required": [ + "AllAccessPermissionRules" + ] + } + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index db3e415ea..3108aa612 100644 --- a/pom.xml +++ b/pom.xml @@ -95,6 +95,7 @@ 2.6.0 1.5.3 2.9.0 + 1.2.1 4.13.2 0.22.1 1.5.20 From 4949770824d6e4ec2063b0fb49ef3222a3af1336 Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Mon, 27 Oct 2025 15:45:19 +0100 Subject: [PATCH 047/108] Support for SMT AID-AIMC (#1233) * Add new interface SubmodelTemplateProcessor * Add support for AID & AIMC SMTs --- .../provider/AbstractMultiFormatProvider.java | 10 +- .../MultiFormatSubscriptionProvider.java | 3 +- ...actMultiFormatOperationProviderConfig.java | 18 + .../AbstractMultiFormatProviderConfig.java | 22 + ...MultiFormatSubscriptionProviderConfig.java | 16 + ...bstractMultiFormatValueProviderConfig.java | 16 + .../config/MultiFormatProviderConfig.java | 5 +- .../http/HttpAssetConnectionConfig.java | 23 +- .../provider/HttpSubscriptionProvider.java | 2 +- .../config/HttpOperationProviderConfig.java | 18 + .../HttpSubscriptionProviderConfig.java | 20 +- .../config/HttpValueProviderConfig.java | 18 + .../mqtt/MqttAssetConnectionConfig.java | 25 +- .../provider/MqttSubscriptionProvider.java | 28 +- .../MqttSubscriptionProviderConfig.java | 16 + .../config/MqttValueProviderConfig.java | 16 + .../opcua/OpcUaAssetConnectionConfig.java | 36 +- .../opcua/provider/AbstractOpcUaProvider.java | 10 +- .../provider/OpcUaSubscriptionProvider.java | 6 + .../config/AbstractOpcUaProviderConfig.java | 14 + .../AbstractOpcUaProviderWithArrayConfig.java | 16 + .../config/OpcUaOperationProviderConfig.java | 21 + .../OpcUaSubscriptionProviderConfig.java | 15 + .../config/OpcUaValueProviderConfig.java | 25 + .../iosb/ilt/faaast/service/Service.java | 54 +- .../AbstractAssetConnection.java | 88 +- .../AbstractAssetOperationProviderConfig.java | 15 + .../assetconnection/AssetConnection.java | 14 +- .../AssetConnectionConfig.java | 47 +- .../AssetConnectionManager.java | 941 ++++++-- .../assetconnection/AssetProvider.java | 10 +- .../assetconnection/AssetProviderConfig.java | 12 +- .../assetconnection/AssetProviderType.java | 77 + .../AssetSubscriptionProvider.java | 8 + .../provider/LambdaOperationProvider.java | 14 +- .../provider/LambdaSubscriptionProvider.java | 13 + .../lambda/provider/LambdaValueProvider.java | 7 + .../faaast/service/config/ServiceConfig.java | 45 +- .../handler/AbstractRequestHandler.java | 29 - ...AbstractInvokeOperationRequestHandler.java | 6 +- .../InvokeOperationAsyncRequestHandler.java | 6 +- .../InvokeOperationSyncRequestHandler.java | 12 +- ...chSubmodelElementByPathRequestHandler.java | 2 +- .../DeleteSubmodelByIdRequestHandler.java | 2 +- .../PatchSubmodelByIdRequestHandler.java | 2 +- .../SubmodelTemplateManager.java | 219 ++ .../SubmodelTemplateProcessor.java | 66 + .../SubmodelTemplateProcessorConfig.java | 27 + .../faaast/service/typing/TypeExtractor.java | 9 +- .../lambda/LambdaAssetConnectionTest.java | 4 +- .../ilt/faaast/service/config/ConfigTest.java | 2 +- .../config/fixtures/DummyAssetConnection.java | 20 +- .../fixtures/DummyAssetConnectionConfig.java | 15 + .../DummyNodeBasedProviderConfig.java | 19 + .../DummySubscriptionBasedProviderConfig.java | 19 + .../handler/RequestHandlerManagerTest.java | 228 +- .../SubmodelTemplateProcessorTest.java | 182 ++ docs/source/basics/configuration.md | 2 +- docs/source/index.md | 7 +- docs/source/interfaces/asset-connection.md | 1 + .../interfaces/submodeltemplateprocessors.md | 131 ++ docs/source/other/release-notes.md | 10 + ...pointWithAutoGeneratedCertificateTest.java | 2 +- ...tpEndpointWithProvidedCertificateTest.java | 2 +- .../http/HttpEndpointWithSslDisabledTest.java | 2 +- .../opcua/OpcUaEndpointFullModelTest.java | 9 +- .../endpoint/opcua/helper/TestService.java | 5 +- .../assetconnection/TestAssetConnection.java | 23 +- .../TestAssetConnectionConfig.java | 5 + .../TestSubscriptionProviderConfig.java | 6 + .../TestValueProviderConfig.java | 6 + .../custom/CustomAssetConnectionConfig.java | 5 + .../provider/CustomOperationProvider.java | 7 + .../provider/CustomSubscriptionProvider.java | 9 +- .../custom/provider/CustomValueProvider.java | 9 + .../CustomSubscriptionProviderConfig.java | 40 + .../config/CustomValueProviderConfig.java | 41 + .../faaast/service/model/SemanticIdPath.java | 373 +++ .../model/api/modifier/QueryModifier.java | 4 + .../model/serialization/DataFormat.java | 3 +- .../model/submodeltemplate/Cardinality.java | 46 + .../SubmodelElementListValueMapper.java | 25 +- .../primitive/AbstractDateTimeValue.java | 11 +- .../faaast/service/util/DeepCopyHelper.java | 36 +- .../service/util/EnvironmentHelper.java | 220 +- .../service/util/ImplementationManager.java | 1 + .../service/util/JarFilePathHelper.java | 0 .../service/util/LambdaExceptionHelper.java | 22 + .../faaast/service/util/ReferenceHelper.java | 29 +- .../ilt/faaast/service/util/StringHelper.java | 15 + .../service/util/SubmodelTemplateHelper.java | 69 + .../service/model/SemanticIdPathTest.java | 256 +++ .../service/util/EnvironmentHelperTest.java | 91 - pom.xml | 1 + starter/pom.xml | 5 + .../pom.xml | 117 + .../aimc/AimcSubmodelTemplateProcessor.java | 315 +++ .../submodeltemplate/aimc/Constants.java | 57 + .../submodeltemplate/aimc/ProcessingMode.java | 24 + .../AimcSubmodelTemplateProcessorConfig.java | 96 + .../aimc/config/BasicCredentials.java | 77 + .../aimc/config/Credentials.java | 26 + .../aimc/model/AidEndpointMetadata.java | 72 + .../aimc/model/AidInterface.java | 74 + .../aimc/model/InterfaceData.java | 32 + .../aimc/model/InterfaceDataHttp.java | 96 + .../aimc/model/InterfaceDataMqtt.java | 96 + .../submodeltemplate/aimc/model/Protocol.java | 24 + .../aimc/model/ProviderType.java | 24 + .../aimc/model/RelationData.java | 67 + .../aimc/util/HttpHelper.java | 249 ++ .../aimc/util/MqttHelper.java | 164 ++ .../submodeltemplate/aimc/util/Util.java | 386 ++++ .../aimc/AimcSubmodelTemplateProcessorIT.java | 221 ++ .../submodeltemplate/aimc/ProcessorTest.java | 420 ++++ ...mcSubmodelTemplateProcessorConfigTest.java | 61 + .../aimc/model/Constants.java | 64 + .../aimc/model/HttpModel.java | 546 +++++ .../aimc/model/MqttModel.java | 434 ++++ .../src/test/resources/Example-config.json | 15 + .../src/test/resources/Test-Example.json | 2003 +++++++++++++++++ .../service/test/AssetConnectionIT.java | 431 +++- .../faaast/service/test/HttpEndpointIT.java | 94 +- .../ilt/faaast/service/test/ServiceIT.java | 3 +- .../faaast/service/test/util/ApiPaths.java | 5 + test/src/test/resources/logback.xml | 14 + 126 files changed, 9418 insertions(+), 1031 deletions(-) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderType.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateManager.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java create mode 100644 core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java create mode 100644 docs/source/interfaces/submodeltemplateprocessors.md create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java rename {core => model}/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java (81%) rename {core => model}/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java (98%) rename {core => model}/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/JarFilePathHelper.java (100%) create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java create mode 100644 model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/pom.xml create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessor.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/Constants.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessingMode.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfig.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/BasicCredentials.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/Credentials.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidEndpointMetadata.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidInterface.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceData.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataHttp.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataMqtt.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Protocol.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/ProviderType.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/RelationData.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/HttpHelper.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/MqttHelper.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/Util.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessorIT.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessorTest.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfigTest.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Constants.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/HttpModel.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/MqttModel.java create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Example-config.json create mode 100644 submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Test-Example.json create mode 100644 test/src/test/resources/logback.xml diff --git a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/AbstractMultiFormatProvider.java b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/AbstractMultiFormatProvider.java index 1589f3c1d..1d4e03eab 100644 --- a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/AbstractMultiFormatProvider.java +++ b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/AbstractMultiFormatProvider.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProvider; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config.MultiFormatProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import java.util.Objects; @@ -24,7 +26,7 @@ * * @param type of matching configuration */ -public abstract class AbstractMultiFormatProvider { +public abstract class AbstractMultiFormatProvider implements AssetProvider { protected final T config; @@ -34,6 +36,12 @@ protected AbstractMultiFormatProvider(T config) { } + @Override + public AssetProviderConfig asConfig() { + return config; + } + + @Override public int hashCode() { return Objects.hash(config); diff --git a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/MultiFormatSubscriptionProvider.java b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/MultiFormatSubscriptionProvider.java index e534d6c9c..b29fa363d 100644 --- a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/MultiFormatSubscriptionProvider.java +++ b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/MultiFormatSubscriptionProvider.java @@ -111,7 +111,8 @@ public void removeNewDataListener(NewDataListener listener) throws AssetConnecti * * @throws AssetConnectionException if unsubscribe fails */ - protected abstract void unsubscribe() throws AssetConnectionException; + @Override + public abstract void unsubscribe() throws AssetConnectionException; @Override diff --git a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatOperationProviderConfig.java b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatOperationProviderConfig.java index b88a65e1d..ca05e28f7 100644 --- a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatOperationProviderConfig.java +++ b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatOperationProviderConfig.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.ArgumentValidationMode; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -103,6 +104,23 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractMultiFormatOperationProviderConfig that = (AbstractMultiFormatOperationProviderConfig) other; + return super.sameAs(that) + && Objects.equals(inputValidationMode, that.inputValidationMode) + && Objects.equals(inoutputValidationMode, that.inoutputValidationMode) + && Objects.equals(outputValidationMode, that.outputValidationMode) + && Objects.equals(queries, that.queries); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), inputValidationMode, inoutputValidationMode, outputValidationMode, queries); diff --git a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatProviderConfig.java b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatProviderConfig.java index 399a782fc..1f37b6b2f 100644 --- a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatProviderConfig.java +++ b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatProviderConfig.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; @@ -60,6 +62,26 @@ public boolean equals(Object o) { } + /** + * Checks if the given provider is the same as this, meaning all properties are equal or the same. This is different + * from {@code equals()} as properties can be considered to be the same without being equal like null and empty string. + * + * @param other the other config + * @return true is other is same as this, false otherwise + */ + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractMultiFormatProviderConfig that = (AbstractMultiFormatProviderConfig) other; + return StringHelper.equalsNullOrEmpty(format, that.format) + && StringHelper.equalsNullOrEmpty(template, that.template); + } + + @Override public int hashCode() { return Objects.hash(format, template); diff --git a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatSubscriptionProviderConfig.java b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatSubscriptionProviderConfig.java index aa8df697a..898570581 100644 --- a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatSubscriptionProviderConfig.java +++ b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatSubscriptionProviderConfig.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; @@ -64,6 +66,20 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractMultiFormatSubscriptionProviderConfig that = (AbstractMultiFormatSubscriptionProviderConfig) other; + return StringHelper.equalsNullOrEmpty(format, that.format) + && StringHelper.equalsNullOrEmpty(query, that.query); + } + + @Override public int hashCode() { return Objects.hash(format, query); diff --git a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatValueProviderConfig.java b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatValueProviderConfig.java index 1816c6ccf..601abe898 100644 --- a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatValueProviderConfig.java +++ b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/AbstractMultiFormatValueProviderConfig.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; @@ -50,6 +52,20 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractMultiFormatValueProviderConfig that = (AbstractMultiFormatValueProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(query, that.query); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), query); diff --git a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/MultiFormatProviderConfig.java b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/MultiFormatProviderConfig.java index 5f4605b88..36ac3cfc4 100644 --- a/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/MultiFormatProviderConfig.java +++ b/assetconnection/common/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/common/provider/config/MultiFormatProviderConfig.java @@ -14,10 +14,13 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; + + /** * Base interface each config used for reading/writing multiple data formats must implement. */ -public interface MultiFormatProviderConfig { +public interface MultiFormatProviderConfig extends AssetProviderConfig { public String getFormat(); diff --git a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionConfig.java b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionConfig.java index 24d32a548..b39daab8a 100644 --- a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionConfig.java +++ b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionConfig.java @@ -22,6 +22,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpSubscriptionProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpValueProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.config.CertificateConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; @@ -102,6 +103,26 @@ public int hashCode() { } + @Override + public boolean equalsIgnoringProviders(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HttpAssetConnectionConfig other = (HttpAssetConnectionConfig) obj; + return Objects.equals(this.baseUrl, other.baseUrl) + && StringHelper.equalsNullOrEmpty(this.username, other.username) + && StringHelper.equalsNullOrEmpty(this.password, other.password) + && Objects.equals(this.headers, other.headers) + && Objects.equals(this.trustedCertificates, other.trustedCertificates); + } + + @Override public boolean equals(Object obj) { if (this == obj) { @@ -114,7 +135,7 @@ public boolean equals(Object obj) { return false; } final HttpAssetConnectionConfig other = (HttpAssetConnectionConfig) obj; - return super.equals(other) + return super.equals(obj) && Objects.equals(this.baseUrl, other.baseUrl) && Objects.equals(this.username, other.username) && Objects.equals(this.password, other.password) diff --git a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpSubscriptionProvider.java b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpSubscriptionProvider.java index 052825d20..f953ad9bb 100644 --- a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpSubscriptionProvider.java +++ b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpSubscriptionProvider.java @@ -137,7 +137,7 @@ protected void fireNewDataReceived(byte[] value) { @Override - protected void unsubscribe() throws AssetConnectionException { + public void unsubscribe() throws AssetConnectionException { if (executorHandler != null) { executorHandler.cancel(true); } diff --git a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpOperationProviderConfig.java b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpOperationProviderConfig.java index 6e8b34b2e..806dc90bd 100644 --- a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpOperationProviderConfig.java +++ b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpOperationProviderConfig.java @@ -14,7 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config.AbstractMultiFormatOperationProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -80,6 +82,22 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + HttpOperationProviderConfig that = (HttpOperationProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(path, that.path) + && StringHelper.equalsNullOrEmpty(method, that.method) + && Objects.equals(headers, that.headers); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), path, method, headers); diff --git a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpSubscriptionProviderConfig.java b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpSubscriptionProviderConfig.java index 11ad32383..ef8666b79 100644 --- a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpSubscriptionProviderConfig.java +++ b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpSubscriptionProviderConfig.java @@ -14,7 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config.AbstractMultiFormatSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -26,7 +28,7 @@ public class HttpSubscriptionProviderConfig extends AbstractMultiFormatSubscriptionProviderConfig { private String path; - private long interval; + private long interval = 1000; private Map headers; public HttpSubscriptionProviderConfig() { @@ -80,6 +82,22 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + HttpSubscriptionProviderConfig that = (HttpSubscriptionProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(path, that.path) + && Objects.equals(interval, that.interval) + && Objects.equals(headers, that.headers); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), path, interval, headers); diff --git a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpValueProviderConfig.java b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpValueProviderConfig.java index 3f88bf059..08ce65324 100644 --- a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpValueProviderConfig.java +++ b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/config/HttpValueProviderConfig.java @@ -14,7 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config.AbstractMultiFormatValueProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -80,6 +82,22 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + HttpValueProviderConfig that = (HttpValueProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(path, that.path) + && StringHelper.equalsNullOrEmpty(writeMethod, that.writeMethod) + && Objects.equals(headers, that.headers); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), path, writeMethod, headers); diff --git a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/MqttAssetConnectionConfig.java b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/MqttAssetConnectionConfig.java index a3ea7d656..ac79f4a2f 100644 --- a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/MqttAssetConnectionConfig.java +++ b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/MqttAssetConnectionConfig.java @@ -21,6 +21,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config.MqttOperationProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config.MqttSubscriptionProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config.MqttValueProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; import java.util.UUID; @@ -81,8 +82,20 @@ public void setPassword(String password) { @Override - public int hashCode() { - return Objects.hash(super.hashCode(), serverUri, clientId, username, password); + public boolean equalsIgnoringProviders(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final MqttAssetConnectionConfig other = (MqttAssetConnectionConfig) obj; + return StringHelper.equalsNullOrEmpty(this.serverUri, other.serverUri) + && StringHelper.equalsNullOrEmpty(this.username, other.username) + && StringHelper.equalsNullOrEmpty(this.password, other.password); } @@ -98,7 +111,7 @@ public boolean equals(Object obj) { return false; } final MqttAssetConnectionConfig other = (MqttAssetConnectionConfig) obj; - return super.equals(other) + return super.equals(obj) && Objects.equals(this.serverUri, other.serverUri) && Objects.equals(this.clientId, other.clientId) && Objects.equals(this.username, other.username) @@ -106,6 +119,12 @@ public boolean equals(Object obj) { } + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), serverUri, clientId, username, password); + } + + public static Builder builder() { return new Builder(); } diff --git a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/MqttSubscriptionProvider.java b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/MqttSubscriptionProvider.java index b0bec8656..5f2a627cd 100644 --- a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/MqttSubscriptionProvider.java +++ b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/MqttSubscriptionProvider.java @@ -23,8 +23,13 @@ import de.fraunhofer.iosb.ilt.faaast.service.typing.TypeInfo; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.function.Consumer; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -32,9 +37,11 @@ */ public class MqttSubscriptionProvider extends MultiFormatSubscriptionProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(MqttSubscriptionProvider.class); private final ServiceContext serviceContext; private final Reference reference; private final MqttSubscriptionMultiplexer multiplexer; + private final Map> handlers; public MqttSubscriptionProvider(ServiceContext serviceContext, Reference reference, MqttSubscriptionProviderConfig config, MqttSubscriptionMultiplexer multiplexer) { super(config); @@ -44,6 +51,7 @@ public MqttSubscriptionProvider(ServiceContext serviceContext, Reference referen this.serviceContext = serviceContext; this.reference = reference; this.multiplexer = multiplexer; + this.handlers = new HashMap<>(); } @@ -63,19 +71,30 @@ protected TypeInfo getTypeInfo() { @Override public void subscribe() throws AssetConnectionException { - multiplexer.addListener(config.getTopic(), this::fireNewDataReceived); + if (handlers.containsKey(config.getTopic())) { + LOGGER.debug("trying to subscribe but subscription already exists (topic: {})", config.getTopic()); + return; + } + Consumer handler = this::fireNewDataReceived; + handlers.put(config.getTopic(), handler); + multiplexer.addListener(config.getTopic(), handler); } @Override - protected void unsubscribe() throws AssetConnectionException { - multiplexer.removeListener(config.getTopic(), this::fireNewDataReceived); + public void unsubscribe() throws AssetConnectionException { + if (!handlers.containsKey(config.getTopic())) { + LOGGER.debug("trying to unsubscribe but subscription does not exist (topic: {})", config.getTopic()); + return; + } + Consumer handler = handlers.remove(config.getTopic()); + multiplexer.removeListener(config.getTopic(), handler); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), serviceContext, reference, multiplexer); + return Objects.hash(super.hashCode(), serviceContext, reference, handlers, multiplexer); } @@ -94,6 +113,7 @@ public boolean equals(Object obj) { return super.equals(obj) && Objects.equals(serviceContext, that.serviceContext) && Objects.equals(reference, that.reference) + && Objects.equals(handlers, that.handlers) && Objects.equals(multiplexer, that.multiplexer); } } diff --git a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttSubscriptionProviderConfig.java b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttSubscriptionProviderConfig.java index a0fd519a8..ac9564887 100644 --- a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttSubscriptionProviderConfig.java +++ b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttSubscriptionProviderConfig.java @@ -14,7 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config.AbstractMultiFormatSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; @@ -49,6 +51,20 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + MqttSubscriptionProviderConfig that = (MqttSubscriptionProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(topic, that.topic); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), topic); diff --git a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttValueProviderConfig.java b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttValueProviderConfig.java index 080244117..21b63bdaf 100644 --- a/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttValueProviderConfig.java +++ b/assetconnection/mqtt/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/mqtt/provider/config/MqttValueProviderConfig.java @@ -14,7 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.config.AbstractMultiFormatValueProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; @@ -49,6 +51,20 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + MqttValueProviderConfig that = (MqttValueProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(topic, that.topic); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), topic); diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionConfig.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionConfig.java index bae7db237..ef7d2255f 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionConfig.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionConfig.java @@ -22,6 +22,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config.OpcUaSubscriptionProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config.OpcUaValueProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.config.CertificateConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.nio.file.Path; import java.util.Objects; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; @@ -80,15 +81,40 @@ public OpcUaAssetConnectionConfig() { @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equalsIgnoringProviders(Object obj) { + if (this == obj) { return true; } - if (o == null || getClass() != o.getClass()) { + if (obj == null || getClass() != obj.getClass()) { return false; } - OpcUaAssetConnectionConfig that = (OpcUaAssetConnectionConfig) o; - return super.equals(that) + OpcUaAssetConnectionConfig that = (OpcUaAssetConnectionConfig) obj; + return StringHelper.equalsNullOrEmpty(host, that.host) + && StringHelper.equalsNullOrEmpty(username, that.username) + && StringHelper.equalsNullOrEmpty(password, that.password) + && Objects.equals(requestTimeout, that.requestTimeout) + && Objects.equals(acknowledgeTimeout, that.acknowledgeTimeout) + && Objects.equals(retries, that.retries) + && Objects.equals(securityBaseDir, that.securityBaseDir) + && Objects.equals(securityPolicy, that.securityPolicy) + && Objects.equals(securityMode, that.securityMode) + && Objects.equals(applicationCertificate, that.applicationCertificate) + && Objects.equals(authenticationCertificate, that.authenticationCertificate) + && Objects.equals(transportProfile, that.transportProfile) + && Objects.equals(userTokenType, that.userTokenType); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + OpcUaAssetConnectionConfig that = (OpcUaAssetConnectionConfig) obj; + return super.equals(obj) && Objects.equals(host, that.host) && Objects.equals(username, that.username) && Objects.equals(password, that.password) diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/AbstractOpcUaProvider.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/AbstractOpcUaProvider.java index ad9869ac3..60ddf5c95 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/AbstractOpcUaProvider.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/AbstractOpcUaProvider.java @@ -16,6 +16,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProvider; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.conversion.ValueConverter; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config.AbstractOpcUaProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.util.OpcUaHelper; @@ -33,7 +35,7 @@ * * @param type of the asset provider config */ -public abstract class AbstractOpcUaProvider { +public abstract class AbstractOpcUaProvider implements AssetProvider { protected final ServiceContext serviceContext; protected final Reference reference; @@ -66,6 +68,12 @@ public T getProviderConfig() { } + @Override + public AssetProviderConfig asConfig() { + return providerConfig; + } + + private void validateNode() throws InvalidConfigurationException, AssetConnectionException { String baseErrorMsg = "invalid OPC UA provider configuration"; try { diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaSubscriptionProvider.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaSubscriptionProvider.java index 806d23c4b..d7d5a0a85 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaSubscriptionProvider.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaSubscriptionProvider.java @@ -145,4 +145,10 @@ public boolean equals(Object obj) { && Objects.equals(multiplexer, that.multiplexer); } + + @Override + public void unsubscribe() throws AssetConnectionException { + close(); + } + } diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderConfig.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderConfig.java index 2d96d36bc..f557a9e4b 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderConfig.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderConfig.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; @@ -39,6 +40,19 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractOpcUaProviderConfig that = (AbstractOpcUaProviderConfig) other; + return StringHelper.equalsNullOrEmpty(nodeId, that.nodeId); + } + + public String getNodeId() { return nodeId; } diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderWithArrayConfig.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderWithArrayConfig.java index 23ac00338..62bfe7764 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderWithArrayConfig.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/AbstractOpcUaProviderWithArrayConfig.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; @@ -48,6 +50,20 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractOpcUaProviderWithArrayConfig that = (AbstractOpcUaProviderWithArrayConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(arrayIndex, that.arrayIndex); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), arrayIndex); diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaOperationProviderConfig.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaOperationProviderConfig.java index f8b8b515f..4d493d336 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaOperationProviderConfig.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaOperationProviderConfig.java @@ -16,6 +16,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.ArgumentValidationMode; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -127,6 +129,25 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + OpcUaOperationProviderConfig that = (OpcUaOperationProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(parentNodeId, that.parentNodeId) + && Objects.equals(inputArgumentMapping, that.inputArgumentMapping) + && Objects.equals(outputArgumentMapping, that.outputArgumentMapping) + && Objects.equals(inputValidationMode, that.inputValidationMode) + && Objects.equals(inoutputValidationMode, that.inoutputValidationMode) + && Objects.equals(outputValidationMode, that.outputValidationMode); + } + + @Override public int hashCode() { return Objects.hash(super.hashCode(), parentNodeId, inputArgumentMapping, outputArgumentMapping, inputValidationMode, inoutputValidationMode, outputValidationMode); diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaSubscriptionProviderConfig.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaSubscriptionProviderConfig.java index 8732afc93..75e38a5d0 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaSubscriptionProviderConfig.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaSubscriptionProviderConfig.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetSubscriptionProviderConfig; import java.util.Objects; @@ -45,6 +46,20 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + OpcUaSubscriptionProviderConfig that = (OpcUaSubscriptionProviderConfig) other; + return super.sameAs(that) + && Objects.equals(interval, that.interval); + } + + public long getInterval() { return interval; } diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaValueProviderConfig.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaValueProviderConfig.java index 0894a4160..c9fabd294 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaValueProviderConfig.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/config/OpcUaValueProviderConfig.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProviderConfig; @@ -22,6 +23,30 @@ */ public class OpcUaValueProviderConfig extends AbstractOpcUaProviderWithArrayConfig implements AssetValueProviderConfig { + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + + @Override + public boolean sameAs(AssetProviderConfig other) { + return super.sameAs(other); + } + + + @Override + public int hashCode() { + return super.hashCode(); + } + + public static Builder builder() { return new Builder(); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 3d9028ca4..d03d02914 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -44,6 +44,9 @@ import de.fraunhofer.iosb.ilt.faaast.service.request.RequestHandlerManager; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.DynamicRequestExecutionContext; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateManager; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessor; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessorConfig; import de.fraunhofer.iosb.ilt.faaast.service.typing.TypeExtractor; import de.fraunhofer.iosb.ilt.faaast.service.typing.TypeInfo; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; @@ -65,8 +68,7 @@ /** - * Central class of the FA³ST Service accumulating and connecting all different - * components. + * Central class of the FA³ST Service accumulating and connecting all different components. */ public class Service implements ServiceContext { @@ -78,6 +80,7 @@ public class Service implements ServiceContext { private Persistence persistence; private FileStorage fileStorage; private RequestExecutionContext requestExecutionContext; + private SubmodelTemplateManager submodelTemplateManager; private RegistrySynchronization registrySynchronization; private RequestHandlerManager requestHandler; @@ -91,21 +94,23 @@ public class Service implements ServiceContext { * @param messageBus message bus implementation * @param endpoints endpoints * @param assetConnections asset connections + * @param submodelTemplateProcessors submodel template processor to use * @throws IllegalArgumentException if coreConfig is null * @throws IllegalArgumentException if persistence is null + * @throws PersistenceException if storage error occurs + * @throws MessageBusException if message bus error occurs * @throws IllegalArgumentException if messageBus is null * @throws RuntimeException if creating a deep copy of aasEnvironment fails - * @throws ConfigurationException the configuration the - * {@link AssetConnectionManager} fails - * @throws AssetConnectionException when initializing asset connections - * fails + * @throws ConfigurationException the configuration the {@link AssetConnectionManager} fails + * @throws AssetConnectionException when initializing asset connections fails */ public Service(CoreConfig coreConfig, Persistence persistence, FileStorage fileStorage, MessageBus messageBus, List endpoints, - List assetConnections) throws ConfigurationException, AssetConnectionException { + List assetConnections, + List submodelTemplateProcessors) throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { Ensure.requireNonNull(coreConfig, "coreConfig must be non-null"); Ensure.requireNonNull(persistence, "persistence must be non-null"); Ensure.requireNonNull(messageBus, "messageBus must be non-null"); @@ -126,6 +131,7 @@ public Service(CoreConfig coreConfig, this.requestHandler = new RequestHandlerManager(config.getCore()); this.requestExecutionContext = new DynamicRequestExecutionContext(this); this.registrySynchronization = new RegistrySynchronization(config.getCore(), persistence, messageBus, endpoints); + this.submodelTemplateManager = new SubmodelTemplateManager(persistence, messageBus, assetConnectionManager, submodelTemplateProcessors); } @@ -135,11 +141,12 @@ public Service(CoreConfig coreConfig, * @param config service configuration * @throws IllegalArgumentException if config is null * @throws ConfigurationException if invalid configuration is provided - * @throws AssetConnectionException when initializing asset connections - * fails + * @throws PersistenceException if storage error occurs + * @throws MessageBusException if message bus error occurs + * @throws AssetConnectionException when initializing asset connections fails */ public Service(ServiceConfig config) - throws ConfigurationException, AssetConnectionException { + throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { Ensure.requireNonNull(config, "config must be non-null"); this.config = config; init(); @@ -184,7 +191,7 @@ public TypeInfo getTypeInfo(Reference reference) throws ResourceNotFoundExceptio @Override public boolean hasValueProvider(Reference reference) { - return Objects.nonNull(assetConnectionManager.getValueProvider(reference)); + return assetConnectionManager.hasValueProvider(reference); } @@ -217,8 +224,7 @@ public Environment getAASEnvironment() throws PersistenceException { * Executes a request asynchroniously. * * @param request request to execute - * @param callback callback handler that is called when execution if - * finished + * @param callback callback handler that is called when execution if finished * @throws IllegalArgumentException if request is null * @throws IllegalArgumentException if callback is null */ @@ -255,6 +261,11 @@ public Persistence getPersistence() { } + public SubmodelTemplateManager getSubmodelTemplateManager() { + return submodelTemplateManager; + } + + /** * Starts the service.This includes starting the message bus and endpoints. * @@ -276,16 +287,17 @@ public void start() throws MessageBusException, EndpointException, PersistenceEx } registrySynchronization.start(); assetConnectionManager.start(); + submodelTemplateManager.start(); LOGGER.debug("FA³ST Service is running!"); } /** - * Stop the service. This includes stopping the message bus and all - * endpoints. + * Stop the service. This includes stopping the message bus and all endpoints. */ public void stop() { LOGGER.debug("Get command for stopping FA³ST Service"); + submodelTemplateManager.stop(); messageBus.stop(); assetConnectionManager.stop(); registrySynchronization.stop(); @@ -303,6 +315,8 @@ private void init() throws ConfigurationException { persistence = (Persistence) config.getPersistence().newInstance(config.getCore(), this); fileStorage = (FileStorage) config.getFileStorage().newInstance(config.getCore(), this); messageBus = (MessageBus) config.getMessageBus().newInstance(config.getCore(), this); + this.requestHandler = new RequestHandlerManager(config.getCore()); + this.requestExecutionContext = new DynamicRequestExecutionContext(this); if (config.getAssetConnections() != null) { List assetConnections = new ArrayList<>(); for (AssetConnectionConfig assetConnectionConfig: config.getAssetConnections()) { @@ -310,6 +324,13 @@ private void init() throws ConfigurationException { } assetConnectionManager = new AssetConnectionManager(config.getCore(), assetConnections, this); } + if (submodelTemplateManager == null) { + List submodelTemplateProcessors = new ArrayList<>(); + for (SubmodelTemplateProcessorConfig submodelTemplateProcessorConfig: config.getSubmodelTemplateProcessors()) { + submodelTemplateProcessors.add((SubmodelTemplateProcessor) submodelTemplateProcessorConfig.newInstance(config.getCore(), this)); + } + submodelTemplateManager = new SubmodelTemplateManager(persistence, messageBus, assetConnectionManager, submodelTemplateProcessors); + } endpoints = new ArrayList<>(); if (config.getEndpoints() == null || config.getEndpoints().isEmpty()) { LOGGER.warn("no endpoint configuration found, starting service without endpoint which means the service will not be accessible via any kind of API"); @@ -320,8 +341,6 @@ private void init() throws ConfigurationException { endpoints.add(endpoint); } } - this.requestHandler = new RequestHandlerManager(config.getCore()); - this.requestExecutionContext = new DynamicRequestExecutionContext(this); this.registrySynchronization = new RegistrySynchronization(config.getCore(), persistence, messageBus, endpoints); } @@ -333,4 +352,5 @@ private void ensureInitialModelFilesAreLoaded() { config.getFileStorage().setInitialModelFile(config.getPersistence().getInitialModelFile()); } } + } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetConnection.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetConnection.java index d95c030ee..dfeb39a2f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetConnection.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetConnection.java @@ -17,11 +17,13 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationInitializationException; +import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; -import java.util.HashMap; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import java.util.Map; -import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.slf4j.LoggerFactory; /** @@ -41,6 +43,8 @@ public abstract class AbstractAssetConnection, C extends AssetConnectionConfig, VC extends AssetValueProviderConfig, V extends AssetValueProvider, OC extends AssetOperationProviderConfig, O extends AssetOperationProvider, SC extends AssetSubscriptionProviderConfig, S extends AssetSubscriptionProvider> implements AssetConnection { + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AbstractAssetConnection.class); + protected volatile boolean connected; protected static final String ERROR_MSG_REFERENCE_NOT_NULL = "reference must be non-null"; protected static final String ERROR_MSG_PROVIDER_CONFIG_NOT_NULL = "providerConfig must be non-null"; @@ -52,9 +56,9 @@ public abstract class AbstractAssetConnection(); - operationProviders = new HashMap<>(); - subscriptionProviders = new HashMap<>(); + valueProviders = new ConcurrentHashMap<>(); + operationProviders = new ConcurrentHashMap<>(); + subscriptionProviders = new ConcurrentHashMap<>(); } @@ -77,25 +81,25 @@ public void setConnected(boolean connected) { @Override public C asConfig() { - return config; + return (C) DeepCopyHelper.deepCopyAny(config, AssetConnectionConfig.class); } @Override public Map getValueProviders() { - return this.valueProviders; + return valueProviders; } @Override public Map getOperationProviders() { - return this.operationProviders; + return operationProviders; } @Override public Map getSubscriptionProviders() { - return this.subscriptionProviders; + return subscriptionProviders; } @@ -132,15 +136,9 @@ public void disconnect() throws AssetConnectionException { private void unregisterProviders() { - for (var providerConfig: config.getValueProviders().entrySet()) { - unregisterValueProvider(providerConfig.getKey()); - } - for (var providerConfig: config.getOperationProviders().entrySet()) { - unregisterOperationProvider(providerConfig.getKey()); - } - for (var providerConfig: config.getSubscriptionProviders().entrySet()) { - unregisterSubscriptionProvider(providerConfig.getKey()); - } + valueProviders.clear(); + subscriptionProviders.clear(); + operationProviders.clear(); } @@ -171,7 +169,11 @@ public void init(CoreConfig coreConfig, C config, ServiceContext serviceContext) public void registerValueProvider(Reference reference, VC providerConfig) throws AssetConnectionException { Ensure.requireNonNull(reference, ERROR_MSG_REFERENCE_NOT_NULL); Ensure.requireNonNull(providerConfig, ERROR_MSG_PROVIDER_CONFIG_NOT_NULL); - this.valueProviders.put(reference, createValueProvider(reference, providerConfig)); + config.getValueProviders().put(reference, providerConfig); + if (connected) { + V provider = createValueProvider(reference, providerConfig); + valueProviders.put(reference, provider); + } } @@ -179,7 +181,11 @@ public void registerValueProvider(Reference reference, VC providerConfig) throws public void registerOperationProvider(Reference reference, OC providerConfig) throws AssetConnectionException { Ensure.requireNonNull(reference, ERROR_MSG_REFERENCE_NOT_NULL); Ensure.requireNonNull(providerConfig, ERROR_MSG_PROVIDER_CONFIG_NOT_NULL); - this.operationProviders.put(reference, createOperationProvider(reference, providerConfig)); + config.getOperationProviders().put(reference, providerConfig); + if (connected) { + O provider = createOperationProvider(reference, providerConfig); + operationProviders.put(reference, provider); + } } @@ -187,7 +193,11 @@ public void registerOperationProvider(Reference reference, OC providerConfig) th public void registerSubscriptionProvider(Reference reference, SC providerConfig) throws AssetConnectionException { Ensure.requireNonNull(reference, ERROR_MSG_REFERENCE_NOT_NULL); Ensure.requireNonNull(providerConfig, ERROR_MSG_PROVIDER_CONFIG_NOT_NULL); - this.subscriptionProviders.put(reference, createSubscriptionProvider(reference, providerConfig)); + config.getSubscriptionProviders().put(reference, providerConfig); + if (connected) { + S provider = createSubscriptionProvider(reference, providerConfig); + subscriptionProviders.put(reference, provider); + } } @@ -224,27 +234,45 @@ public void registerSubscriptionProvider(Reference reference, SC providerConfig) protected abstract S createSubscriptionProvider(Reference reference, SC providerConfig) throws AssetConnectionException; - @Override - public boolean sameAs(AssetConnection other) { - return Objects.equals(this, other); - } - - @Override public void unregisterValueProvider(Reference reference) { - this.valueProviders.remove(reference); + config.getValueProviders().remove(reference); + valueProviders.remove(reference); } @Override public void unregisterOperationProvider(Reference reference) { - this.operationProviders.remove(reference); + config.getOperationProviders().remove(reference); + operationProviders.remove(reference); } @Override public void unregisterSubscriptionProvider(Reference reference) { - this.subscriptionProviders.remove(reference); + if (ReferenceHelper.containsSameReference(subscriptionProviders, reference)) { + var s = ReferenceHelper.getValueBySameReference(subscriptionProviders, reference); + try { + if (s != null) { + s.unsubscribe(); + } + } + catch (AssetConnectionException ex) { + LOGGER.error("unregisterSubscriptionProvider error in unsubscribe"); + } + } + config.getSubscriptionProviders().remove(reference); + subscriptionProviders.remove(reference); } + + @Override + public void stop() { + try { + disconnect(); + } + catch (AssetConnectionException ex) { + LOGGER.error("stop: error in disconnect", ex); + } + } } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetOperationProviderConfig.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetOperationProviderConfig.java index 5961880b7..a9a68fefd 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetOperationProviderConfig.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AbstractAssetOperationProviderConfig.java @@ -85,6 +85,21 @@ public boolean equals(Object o) { } + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractAssetOperationProviderConfig that = (AbstractAssetOperationProviderConfig) other; + return Objects.equals(inputValidationMode, that.inputValidationMode) + && Objects.equals(inoutputValidationMode, that.inoutputValidationMode) + && Objects.equals(outputValidationMode, that.outputValidationMode); + } + + @Override public int hashCode() { return Objects.hash(inputValidationMode, inoutputValidationMode, outputValidationMode); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnection.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnection.java index 97bcd1f42..d93d2ab5b 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnection.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnection.java @@ -105,15 +105,6 @@ public interface AssetConnection type of the operation providers of the corresponding asset connection * @param type of the subscription providers of the corresponding asset connection */ -public class AssetConnectionConfig +public abstract class AssetConnectionConfig extends Config { @JsonSerialize(keyUsing = ReferenceSerializer.class) @@ -50,9 +50,9 @@ public class AssetConnectionConfig valueProviders; public AssetConnectionConfig() { - operationProviders = new HashMap<>(); - subscriptionProviders = new HashMap<>(); - valueProviders = new HashMap<>(); + operationProviders = new ConcurrentHashMap<>(); + subscriptionProviders = new ConcurrentHashMap<>(); + valueProviders = new ConcurrentHashMap<>(); } @@ -116,6 +116,15 @@ public void setValueProviders(Map valueProviders) { } + /** + * Compares two instances of AssetConnectionConfig if they are the same on connection-level, ignoring the providers. + * + * @param obj other AssetConnectionConfig to compare to this + * @return true if other is the same as this (ignoring providers) + */ + public abstract boolean equalsIgnoringProviders(Object obj); + + @Override public boolean equals(Object o) { if (this == o) { @@ -195,32 +204,4 @@ public B subscriptionProvider(Reference key, SC value) { return getSelf(); } } - - /** - * Builder for AssetConnectionConfig class. - * - * @param type of the asset connection of the config to build - * @param type of the value provider config of the corresponding asset connection - * @param type of the value provider of the corresponding asset connection - * @param type of the operation provider config of the corresponding asset connection - * @param type of the operation provider of the corresponding asset connection - * @param type of the subscription provider config of the corresponding asset connection - * @param type of the subscription provider of the corresponding asset connection - */ - public static class Builder> - extends AbstractBuilder> { - - @Override - protected Builder getSelf() { - return this; - } - - - @Override - protected AssetConnectionConfig newBuildingInstance() { - return new AssetConnectionConfig<>(); - } - - } - } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnectionManager.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnectionManager.java index cf2f2e113..165823b1c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnectionManager.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetConnectionManager.java @@ -19,29 +19,39 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.lambda.provider.LambdaOperationProvider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.lambda.provider.LambdaSubscriptionProvider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.lambda.provider.LambdaValueProvider; +import de.fraunhofer.iosb.ilt.faaast.service.config.Config; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; import de.fraunhofer.iosb.ilt.faaast.service.exception.InvalidConfigurationException; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Message; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.PatchSubmodelElementValueByPathRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.value.DataElementValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.ElementValue; +import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ElementValueHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,31 +67,93 @@ public class AssetConnectionManager { private final List connections; private final CoreConfig coreConfig; private final Service service; - private ScheduledExecutorService scheduledExecutorService; + private ExecutorService executorService; private LambdaAssetConnection lambdaAssetConnection; private volatile boolean active; + private boolean started; public AssetConnectionManager(CoreConfig coreConfig, List connections, Service service) throws ConfigurationException { this.active = true; + this.started = false; this.coreConfig = coreConfig; - this.connections = connections != null ? new ArrayList<>(connections) : new ArrayList<>(); this.service = service; - validateConnections(); + this.connections = normalizeConnections(connections); + validateConnections(this.connections); init(); } - private void init() { - lambdaAssetConnection = new LambdaAssetConnection(); - ThreadFactory threadFactory = new ThreadFactory() { - AtomicLong count = new AtomicLong(0); + /** + * Cleans up dangling asset connections recursively after an element has been modified. + * + * @param modifiedElement the modified element + */ + public void cleanupDanglingConnectionsAfterModify(Reference modifiedElement) { + Predicate condition = x -> ReferenceHelper.startsWith(x, modifiedElement) && !service.getPersistence().submodelElementExists(x); + connections.stream() + .forEach(LambdaExceptionHelper.rethrowConsumer(connection -> { + for (var providerType: AssetProviderType.values()) { + providerType.getProvidersFromConnectionAccessor().apply(connection).keySet().stream() + .filter(condition) + .forEach(x -> { + try { + providerType.getUnregisterProviderAccessor().accept(connection, x); + } + catch (Exception e) { + LOGGER.info( + "failed to unregister asset {} provider after element has been modified/deleted (modified/deleted element: {}, provider reference: {}, reason: {})", + providerType, + ReferenceHelper.asString(modifiedElement), + ReferenceHelper.asString(x), + e.getMessage(), + e); + } + }); + } + if (connection.getValueProviders().isEmpty() + && connection.getOperationProviders().isEmpty() + && connection.getSubscriptionProviders().isEmpty()) { + try { + connection.disconnect(); + } + catch (AssetConnectionException e) { + LOGGER.warn("Failed to clean up empty asset connection", e); + } + } + })); + + } + + + /** + * Updates part of the existing asset connections by taking the delta between {@code oldConfigs} and {@code newConfigs}. + * Connections and providers not part of {@code oldConfigs} are not modified and will continue to work. Providers might + * be moved between connections during normalization step. + * + * @param oldConfigs the old connections to be updated/replaced + * @param newConfigs the new version of the connections + * @return a list of {@link Message} objects indicating issues encountered during processing. This can include infos, + * warnings, and exceptions. + */ + public List updateConnections(List oldConfigs, List newConfigs) { + List oldConnectionConfigs = normalizeConnectionConfigs(clone(oldConfigs)); + List newConnectionConfigs = normalizeConnectionConfigs(clone(newConfigs)); + ChangeSet changeSet = computeChangeSet(oldConnectionConfigs, newConnectionConfigs); + try { + List newConnections = mergeChanges(connections, changeSet); + validateConnectionConfigs(newConnections); + return apply(newConnectionConfigs); + } + catch (ExceptionWithDetails e) { + return e.getMessages(); + } + catch (ConfigurationException e) { + return List.of(Message.builder() + .messageType(MessageTypeEnum.EXCEPTION) + .text(e.getMessage()) + .build()); + } - @Override - public Thread newThread(Runnable target) { - return new Thread(target, String.format("asset connection establisher - %d", count.getAndIncrement())); - } - }; - scheduledExecutorService = Executors.newScheduledThreadPool(this.connections.size(), threadFactory); } @@ -97,6 +169,7 @@ public void start() { setupConnectionAsync(connection); } lambdaAssetConnection.start(); + started = true; } @@ -178,138 +251,16 @@ public void unregisterLambdaOperationProvider(Reference reference) { } - private void tryConnecting(AssetConnection connection) throws AssetConnectionException { - connection.connect(); - LOGGER.info("Asset connection established (endpoint: {})", connection.getEndpointInformation()); - } - - - private void tryConnectingUntilSuccess(AssetConnection connection) { - try { - tryConnecting(connection); - } - catch (AssetConnectionException e) { - LOGGER.info( - "Establishing asset connection failed on initial attempt (endpoint: {}, reason: {}). Connecting will be retried every {} ms but no more messages about failures will be shown.", - connection.getEndpointInformation(), - e.getMessage(), - coreConfig.getAssetConnectionRetryInterval(), - e); - } - while (active && !connection.isConnected()) { - try { - tryConnecting(connection); - } - catch (AssetConnectionException e) { - LOGGER.trace("Establishing asset connection failed (endpoint: {})", - connection.getEndpointInformation(), - e); - try { - Thread.sleep(coreConfig.getAssetConnectionRetryInterval()); - } - catch (InterruptedException e2) { - // intentionally empty - } - } - } - } - - - private void setupSubscription(Reference reference, AssetSubscriptionProvider provider) { - if (!active) { - return; - } - try { - provider.addNewDataListener((DataElementValue data) -> { - Response response = service.execute(PatchSubmodelElementValueByPathRequest.builder() - .submodelId(ReferenceHelper.findFirstKeyType(reference, KeyTypes.SUBMODEL)) - .path(ReferenceHelper.toPath(reference)) - .disableSyncWithAsset() - .value(data) - .build()); - if (!response.getStatusCode().isSuccess()) { - LOGGER.atInfo().log("Error updating value from asset connection subscription (reference: {})", - ReferenceHelper.toString(reference)); - LOGGER.debug("Error updating value from asset connection subscription (reference: {}, reason: {})", - ReferenceHelper.toString(reference), - response.getResult().getMessages()); - } - }); - } - catch (AssetConnectionException e) { - LOGGER.warn("Subscribing to asset connection failed (reference: {})", - ReferenceHelper.toString(reference), - e); - } - } - - - private void setupSubscriptions(AssetConnection connection) { - ((Map) connection. getSubscriptionProviders()).entrySet() - .forEach(x -> setupSubscription(x.getKey(), x.getValue())); - } - - - private void setupConnectionAsync(AssetConnection connection) { - scheduledExecutorService.schedule( - () -> { - tryConnectingUntilSuccess(connection); - setupSubscriptions(connection); - }, - 0, - TimeUnit.MILLISECONDS); - } - - - /** - * Adds a new AssetConnection created from an AssetConnectionConfig. - * - * @param connectionConfig the AssetConnectionConfig describing the AssetConnection to add - * @throws de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException if provided connectionConfig is - * invalid - * @throws de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException if initializing asset - * connection fails - */ - public void add(AssetConnectionConfig connectionConfig) - throws ConfigurationException, AssetConnectionException { - AssetConnection newConnection = connectionConfig.newInstance(coreConfig, service); - Optional connection = connections.stream().filter(x -> Objects.equals(x, newConnection)).findFirst(); - if (connection.isPresent()) { - connectionConfig.getValueProviders().forEach(LambdaExceptionHelper.rethrowBiConsumer( - (k, v) -> connection.get().registerValueProvider(k, (AssetValueProviderConfig) v))); - connectionConfig.getSubscriptionProviders().forEach(LambdaExceptionHelper.rethrowBiConsumer( - (k, v) -> connection.get().registerSubscriptionProvider(k, (AssetSubscriptionProviderConfig) v))); - connectionConfig.getOperationProviders().forEach(LambdaExceptionHelper.rethrowBiConsumer( - (k, v) -> connection.get().registerOperationProvider(k, (AssetOperationProviderConfig) v))); - } - else { - connections.add(newConnection); - validateConnections(); - } - validateConnections(); - } - - - /** - * Gets all connections managed by this AssetConnectionManager. - * - * @return all managed connections - */ - public List getConnections() { - return connections; - } - - /** * Stops all connection attempts and disconnects all connected assets. */ public void stop() { active = false; try { - scheduledExecutorService.awaitTermination(coreConfig.getAssetConnectionRetryInterval() * 2, TimeUnit.MILLISECONDS); + executorService.awaitTermination(coreConfig.getAssetConnectionRetryInterval() * 2, TimeUnit.MILLISECONDS); } catch (InterruptedException ex) { - scheduledExecutorService.shutdownNow(); + executorService.shutdownNow(); Thread.currentThread().interrupt(); } lambdaAssetConnection.stop(); @@ -336,7 +287,7 @@ public void stop() { * @return operation provider for the AAS element defined by reference or * null if there is none defined */ - public AssetOperationProvider getOperationProvider(Reference reference) { + protected AssetOperationProvider getOperationProvider(Reference reference) { if (lambdaAssetConnection.hasOperationProvider(reference)) { return lambdaAssetConnection.getOperationProvider(reference); } @@ -356,7 +307,7 @@ public AssetOperationProvider getOperati * @return subscription provider for the AAS element defined by reference or * null if there is none defined */ - public AssetSubscriptionProvider getSubscriptionProvider(Reference reference) { + protected AssetSubscriptionProvider getSubscriptionProvider(Reference reference) { if (lambdaAssetConnection.hasSubscriptionProvider(reference)) { return lambdaAssetConnection.getSubscriptionProvider(reference); } @@ -376,7 +327,7 @@ public AssetSubscriptionProvider getSubscriptionProvider(Reference reference) { * @return value provider for the AAS element defined by reference or null * if there is none defined */ - public AssetValueProvider getValueProvider(Reference reference) { + protected AssetValueProvider getValueProvider(Reference reference) { if (lambdaAssetConnection.hasValueProvider(reference)) { return lambdaAssetConnection.getValueProvider(reference); } @@ -422,12 +373,88 @@ public void setValue(Reference reference, ElementValue value) throws AssetConnec */ public Optional readValue(Reference reference) throws AssetConnectionException { if (hasValueProvider(reference)) { - try { - return Optional.ofNullable(getValueProvider(reference).getValue()); - } - catch (UnsupportedOperationException e) { - // ignored on purpose - } + return Optional.ofNullable(getValueProvider(reference).getValue()); + } + return Optional.empty(); + } + + + /** + * Invokes an operation provide synchronously and returns the result if one exists for this reference. + * + * @param reference the reference + * @param input the input + * @param inoutput the inoutput + * @return the result of the invocation or Optional.empty if none exists + * @throws AssetConnectionException if invocation fails + */ + public Optional invoke(Reference reference, OperationVariable[] input, OperationVariable[] inoutput) throws AssetConnectionException { + if (hasOperationProvider(reference)) { + return Optional.ofNullable(getOperationProvider(reference).invoke(input, inoutput)); + } + return Optional.empty(); + } + + + /** + * Invokes an operation provide asynchronously if one exists for this reference. + * + * @param reference the reference + * @param input the input + * @param inoutput the inoutput + * @param callbackSuccess callback upon success + * @param callbackFailure callback upon failure + * @throws AssetConnectionException if invocation fails + */ + public void invokeAsync(Reference reference, + OperationVariable[] input, + OperationVariable[] inoutput, + BiConsumer callbackSuccess, + Consumer callbackFailure) + throws AssetConnectionException { + if (hasOperationProvider(reference)) { + getOperationProvider(reference).invokeAsync(input, inoutput, callbackSuccess, callbackFailure); + } + } + + + /** + * Returns the input validation mode for the operation provider mapped to the reference if such a provider exists. + * + * @param reference the reference + * @return the input validation mode if provider exists, otherwise Optional.empty + */ + public Optional getOperationInputValidationMode(Reference reference) { + if (hasOperationProvider(reference)) { + return Optional.ofNullable(getOperationProvider(reference).getConfig().getInputValidationMode()); + } + return Optional.empty(); + } + + + /** + * Returns the inoutput validation mode for the operation provider mapped to the reference if such a provider exists. + * + * @param reference the reference + * @return the inoutput validation mode if provider exists, otherwise Optional.empty + */ + public Optional getOperationInoutputValidationMode(Reference reference) { + if (hasOperationProvider(reference)) { + return Optional.ofNullable(getOperationProvider(reference).getConfig().getInoutputValidationMode()); + } + return Optional.empty(); + } + + + /** + * Returns the output validation mode for the operation provider mapped to the reference if such a provider exists. + * + * @param reference the reference + * @return the output validation mode if provider exists, otherwise Optional.empty + */ + public Optional getOperationOutputValidationMode(Reference reference) { + if (hasOperationProvider(reference)) { + return Optional.ofNullable(getOperationProvider(reference).getConfig().getOutputValidationMode()); } return Optional.empty(); } @@ -472,36 +499,596 @@ public boolean hasValueProvider(Reference reference) { } - private void validateConnections() throws ConfigurationException { - Optional>> valueProviders = connections.stream() - .flatMap(x -> (Stream>) x.getValueProviders().entrySet().stream()) - .collect(Collectors.groupingBy(x -> x.getKey(), Collectors.mapping(x -> x.getValue(), Collectors.toList()))).entrySet().stream() - .filter(x -> x.getValue().size() > 1) - .findFirst(); - if (valueProviders.isPresent()) { - throw new InvalidConfigurationException(String.format("found %d value providers for reference %s but maximum 1 allowed", - valueProviders.get().getValue().size(), - ReferenceHelper.toString(valueProviders.get().getKey()))); - } - Optional>> operationProviders = connections.stream() - .flatMap(x -> (Stream>) x.getOperationProviders().entrySet().stream()) - .collect(Collectors.groupingBy(x -> x.getKey(), Collectors.mapping(x -> x.getValue(), Collectors.toList()))).entrySet().stream() - .filter(x -> x.getValue().size() > 1) - .findFirst(); - if (operationProviders.isPresent()) { - throw new InvalidConfigurationException(String.format("found %d operation providers for reference %s but maximum 1 allowed", - operationProviders.get().getValue().size(), - ReferenceHelper.toString(operationProviders.get().getKey()))); - } - Optional>> subscriptionProviders = connections.stream() - .flatMap(x -> (Stream>) x.getSubscriptionProviders().entrySet().stream()) - .collect(Collectors.groupingBy(x -> x.getKey(), Collectors.mapping(x -> x.getValue(), Collectors.toList()))).entrySet().stream() - .filter(x -> x.getValue().size() > 1) - .findFirst(); - if (subscriptionProviders.isPresent()) { - throw new InvalidConfigurationException(String.format("found %d subscription providers for reference %s but maximum 1 allowed", - subscriptionProviders.get().getValue().size(), - ReferenceHelper.toString(subscriptionProviders.get().getKey()))); + /** + * Returns if all connections are connected. + * + * @return true if all connections are connected, false otherwise + */ + public boolean isFullyConnected() { + return connections.stream().allMatch(AssetConnection::isConnected); + } + + + private void init() { + lambdaAssetConnection = new LambdaAssetConnection(); + ThreadFactory threadFactory = new ThreadFactory() { + AtomicLong count = new AtomicLong(0); + + @Override + public Thread newThread(Runnable target) { + return new Thread(target, String.format("asset connection establisher - %d", count.getAndIncrement())); + } + }; + executorService = Executors.newCachedThreadPool(threadFactory); + } + + + /** + * Normalizes a list of connections. Normalization means that there is only one connection with the same properties + * containing all providers. + * + * @param connections the connections to normalize + * @return normalized connections + */ + private List normalizeConnections(List connections) { + if (Objects.isNull(connections)) { + return new ArrayList<>(); + } + if (connections.stream().anyMatch(AssetConnection::isConnected)) { + LOGGER.debug("skipped asset connection normalization (reason: at least one connection is connected and only unconnected connectoins can be normalized)"); + return connections; + } + try { + return groupConfigs(connections.stream() + .map(AssetConnection::asConfig) + .map(AssetConnectionConfig.class::cast) + .toList()) + .entrySet().stream() + .map(entry -> { + var representative = entry.getKey(); + if (Objects.nonNull(entry.getValue())) { + entry.getValue().stream() + .filter(connection -> connection != representative) + .forEach(connection -> merge(connection, representative)); + } + return representative; + }) + .map(LambdaExceptionHelper.rethrowFunction(x -> x.newInstance(coreConfig, service))) + .map(AssetConnection.class::cast) + .collect(Collectors.toList()); + } + catch (ConfigurationException e) { + LOGGER.debug("skipped asset connection normalization (reason: re-creating asset connection from config after normalization failed)", e); + return connections; + } + } + + + /** + * Normalizes a list of connection configs. Normalization means that there is only one connection config with the same + * properties + * containing all providers. + * + * @param connections the connection configs to normalize + * @return normalized connection configs + */ + private List normalizeConnectionConfigs(List connections) { + if (Objects.isNull(connections)) { + return new ArrayList<>(); } + return groupConfigs(connections).entrySet().stream() + .map(entry -> { + AssetConnectionConfig representative = entry.getKey(); + if (Objects.nonNull(entry.getValue())) { + entry.getValue().stream() + .filter(connection -> connection != representative) + .forEach(connection -> merge(connection, representative)); + } + return representative; + }) + .collect(Collectors.toList()); + } + + + private void merge(AssetConnectionConfig source, AssetConnectionConfig target) { + if (Objects.isNull(source)) { + return; + } + if (Objects.isNull(target)) { + target = source; + } + for (var providerType: AssetProviderType.values()) { + Map targetProviders = providerType.getProvidersFromConfigAccessor().apply(target); + for (var sourceProvider: providerType.getProvidersFromConfigAccessor().apply(source).entrySet()) { + if (targetProviders.containsKey(sourceProvider.getKey())) { + LOGGER.warn("Found multiple {} providers for element - all but one will be ignored (reference: {})", + providerType.name().toLowerCase(), + ReferenceHelper.asString(sourceProvider.getKey())); + } + else { + targetProviders.put(sourceProvider.getKey(), sourceProvider.getValue()); + } + } + } + } + + + private List apply(List newConnections) { + List result = new ArrayList<>(); + for (var config: newConnections) { + var connection = connections.stream().filter(x -> config.equalsIgnoringProviders(x.asConfig())).findFirst(); + if (connection.isPresent()) { + result.addAll(addProvidersToConnection(connection.get(), config)); + } + else { + try { + AssetConnection newConnection = (AssetConnection) config.newInstance(coreConfig, service); + connections.add(newConnection); + if (started) { + setupConnectionAsync(newConnection); + } + } + catch (ConfigurationException e) { + result.add(Message.builder() + .messageType(MessageTypeEnum.EXCEPTION) + .text(String.format("Adding asset connection failed (reason: %s)", + e.getMessage())) + .build()); + } + } + } + var iterator = connections.iterator(); + while (iterator.hasNext()) { + AssetConnection connection = iterator.next(); + Config connectionConfig = connection.asConfig(); + if (newConnections.stream().noneMatch(x -> x.equalsIgnoringProviders(connectionConfig))) { + connection.stop(); + iterator.remove(); + } + } + return result; + } + + + private Map> groupConfigs(List connectionConfigs) { + Map> result = new LinkedHashMap<>(); + for (AssetConnectionConfig config: connectionConfigs) { + boolean added = false; + for (var entry: result.entrySet()) { + if (config.equalsIgnoringProviders(entry.getKey())) { + entry.getValue().add(config); + added = true; + break; + } + } + if (!added) { + result.put(config, new ArrayList<>(Arrays.asList(config))); + } + } + return result; + } + + + private List mergeChanges(List currentConnections, ChangeSet changeSet) throws ExceptionWithDetails { + List messages = new ArrayList<>(); + List result = currentConnections.stream() + .map(AssetConnection::asConfig) + .map(AssetConnectionConfig.class::cast) + .collect(Collectors.toList()); + for (var toAdd: changeSet.add) { + var connection = result.stream().filter(x -> toAdd.equalsIgnoringProviders(x)).findFirst(); + if (connection.isPresent()) { + messages.addAll(addProviders(connection.get(), toAdd)); + } + else { + result.add(toAdd); + } + } + for (var toDelete: changeSet.delete) { + var connection = result.stream().filter(x -> toDelete.equalsIgnoringProviders(x)).findFirst(); + if (connection.isPresent()) { + messages.addAll(deleteProviders(connection.get(), toDelete)); + boolean isEmpty = Stream.of(AssetProviderType.values()) + .map(x -> x.getProvidersFromConfigAccessor().apply(connection.get()).isEmpty()) + .allMatch(Boolean::booleanValue); + + if (isEmpty) { + result.remove(connection.get()); + } + } + else { + messages.add(Message.builder() + .messageType(MessageTypeEnum.INFO) + .text("Deleting asset connection skipped (reason: connection does not exist)") + .build()); + } + } + if (messages.stream().anyMatch(x -> x.getMessageType() == MessageTypeEnum.ERROR || x.getMessageType() == MessageTypeEnum.EXCEPTION)) { + throw new ExceptionWithDetails("failed to apply changes in asset connections", messages); + } + return result; + } + + + private List deleteProviders(AssetConnectionConfig target, AssetConnectionConfig source) { + List result = new ArrayList<>(); + for (var providerType: AssetProviderType.values()) { + for (var provider: providerType.getProvidersFromConfigAccessor().apply(source).entrySet()) { + Reference reference = ReferenceHelper.findSameReference(providerType.getProvidersFromConfigAccessor().apply(target).keySet(), provider.getKey()); + if (Objects.nonNull(reference)) { + if (provider.getValue().sameAs(providerType.getProvidersFromConfigAccessor().apply(target).get(reference))) { + providerType.getProvidersFromConfigAccessor().apply(target).remove(reference); + } + else { + result.add(Message.builder() + .messageType(MessageTypeEnum.WARNING) + .text(String.format( + "Failed to unregister %s provider (reference: %s, reason: existing provider details for reference differs from expected provider)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference))) + .build()); + } + } + else { + result.add(Message.builder() + .messageType(MessageTypeEnum.INFO) + .text(String.format("Failed to unregister %s provider (reference: %s, reason: provider does not exist)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference))) + .build()); + } + } + } + return result; + } + + + private List addProvidersToConnection(AssetConnection target, AssetConnectionConfig source) { + List result = new ArrayList<>(); + for (var providerType: AssetProviderType.values()) { + for (var provider: providerType.getProvidersFromConfigAccessor().apply(source).entrySet()) { + Reference reference = ReferenceHelper.findSameReference(providerType.getProvidersFromConnectionAccessor().apply(target).keySet(), provider.getKey()); + if (Objects.nonNull(reference)) { + if (provider.getValue().sameAs(providerType.getProvidersFromConnectionAccessor().apply(target).get(reference))) { + LOGGER.debug("Skipped adding {} provider (reference: %s, reason: already exists)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference)); + } + else { + LOGGER.debug("Skipped adding %s provider (reference: %s, reason: already exists but with different provider details)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference)); + } + } + else { + try { + providerType.getRegisterProviderAccessor().accept(target, provider.getKey(), provider.getValue()); + if (providerType == AssetProviderType.SUBSCRIPTION && target.isConnected()) { + setupSubscription(provider.getKey(), (AssetSubscriptionProvider) target.getSubscriptionProviders().get(provider.getKey())); + } + } + catch (AssetConnectionException e) { + result.add(Message.builder() + .messageType(MessageTypeEnum.EXCEPTION) + .text(String.format("Failed to register %s provider (reference: %s, reason: %s)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference), + e.getMessage())) + .build()); + } + } + } + // iterate target and remove those not present in source + for (var provider: providerType.getProvidersFromConnectionAccessor().apply(target).entrySet()) { + Reference reference = ReferenceHelper.findSameReference(providerType.getProvidersFromConfigAccessor().apply(source).keySet(), provider.getKey()); + if (Objects.isNull(reference)) { + try { + providerType.getUnregisterProviderAccessor().accept(target, provider.getKey()); + } + catch (AssetConnectionException e) { + result.add(Message.builder() + .messageType(MessageTypeEnum.EXCEPTION) + .text(String.format("Failed to unregister %s provider (reference: %s, reason: %s)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference), + e.getMessage())) + .build()); + } + } + } + } + return result; + } + + + private List addProviders(AssetConnectionConfig target, AssetConnectionConfig source) { + List result = new ArrayList<>(); + for (var providerType: AssetProviderType.values()) { + for (var provider: ((Map) providerType.getProvidersFromConfigAccessor().apply(source)).entrySet()) { + Reference reference = ReferenceHelper.findSameReference(providerType.getProvidersFromConfigAccessor().apply(target).keySet(), provider.getKey()); + if (Objects.nonNull(reference)) { + if (provider.getValue().equals(providerType.getProvidersFromConfigAccessor().apply(target).get(reference))) { + result.add(Message.builder() + .messageType(MessageTypeEnum.INFO) + .text(String.format("Skipped adding %s provider (reference: %s, reason: already exists)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference))) + .build()); + } + else { + result.add(Message.builder() + .messageType(MessageTypeEnum.WARNING) + .text(String.format("Skipped adding %s provider (reference: %s, reason: already exists but with different provider details)", + providerType.toString().toLowerCase(), + ReferenceHelper.asString(reference))) + .build()); + } + } + else { + providerType.getProvidersFromConfigAccessor().apply(target).put(provider.getKey(), provider.getValue()); + } + } + } + return result; + } + + + private ChangeSet computeChangeSet(List oldConnectionConfigs, List newConnectionConfigs) { + ChangeSet result = new ChangeSet(); + for (var oldConnectionConfig: oldConnectionConfigs) { + var newConnectionConfig = newConnectionConfigs.stream() + .filter(x -> oldConnectionConfig.equalsIgnoringProviders(x)) + .findFirst(); + if (!newConnectionConfig.isPresent()) { + result.delete.add(oldConnectionConfig); + } + else { + computeChangeSetForProviders(oldConnectionConfig, newConnectionConfig.get(), result); + } + } + for (var newConnectionConfig: newConnectionConfigs) { + if (oldConnectionConfigs.stream().noneMatch(x -> newConnectionConfig.equalsIgnoringProviders(x))) { + result.add.add(newConnectionConfig); + } + } + return result; + } + + + private void computeChangeSetForProviders(AssetConnectionConfig oldConnectionConfig, AssetConnectionConfig newConnectionConfig, ChangeSet changeSet) { + AssetConnectionConfig connectionConfigAdd = cloneWithoutProviders(oldConnectionConfig); + AssetConnectionConfig connectionConfigDelete = cloneWithoutProviders(oldConnectionConfig); + for (var providerType: AssetProviderType.values()) { + for (var oldProviderEntry: providerType.getProvidersFromConfigAccessor().apply(oldConnectionConfig).entrySet()) { + // references might be slightly different although refering to the same element + Reference referenceInNew = ReferenceHelper.findSameReference(providerType.getProvidersFromConfigAccessor().apply(newConnectionConfig).keySet(), + oldProviderEntry.getKey()); + if (Objects.isNull(referenceInNew)) { + // old has been deleted + providerType.getProvidersFromConfigAccessor().apply(connectionConfigDelete).put(oldProviderEntry.getKey(), oldProviderEntry.getValue()); + } + else { + // still present by key, but provider settings might have changed + var newProviderConfig = providerType.getProvidersFromConfigAccessor().apply(newConnectionConfig).get(referenceInNew); + if (!oldProviderEntry.getValue().sameAs(newProviderConfig)) { + providerType.getProvidersFromConfigAccessor().apply(connectionConfigDelete).put(oldProviderEntry.getKey(), oldProviderEntry.getValue()); + providerType.getProvidersFromConfigAccessor().apply(connectionConfigAdd).put(referenceInNew, newProviderConfig); + } + } + } + for (var newProviderEntry: providerType.getProvidersFromConfigAccessor().apply(newConnectionConfig).entrySet()) { + Reference referenceInOld = ReferenceHelper.findSameReference(providerType.getProvidersFromConfigAccessor().apply(oldConnectionConfig).keySet(), + newProviderEntry.getKey()); + if (Objects.isNull(referenceInOld)) { + providerType.getProvidersFromConfigAccessor().apply(connectionConfigAdd).put(newProviderEntry.getKey(), newProviderEntry.getValue()); + } + } + } + if (hasProviders(connectionConfigAdd)) { + changeSet.add.add(connectionConfigAdd); + } + if (hasProviders(connectionConfigDelete)) { + changeSet.delete.add(connectionConfigDelete); + } + } + + + private boolean hasProviders(AssetConnectionConfig connectionConfig) { + return Objects.nonNull(connectionConfig) + && Stream.of(AssetProviderType.values()).anyMatch(x -> !x.getProvidersFromConfigAccessor().apply(connectionConfig).isEmpty()); + } + + + private AssetConnectionConfig cloneWithoutProviders(AssetConnectionConfig connectionConfig) { + AssetConnectionConfig result = DeepCopyHelper.deepCopyAny(connectionConfig, AssetConnectionConfig.class); + result.getValueProviders().clear(); + result.getSubscriptionProviders().clear(); + result.getOperationProviders().clear(); + return result; + } + + + private List clone(List input) { + if (Objects.isNull(input)) { + return new ArrayList<>(); + } + return input.stream().map(x -> DeepCopyHelper.deepCopyAny(x, AssetConnectionConfig.class)) + .collect(Collectors.toList()); + } + + + private void tryConnecting(AssetConnection connection) throws AssetConnectionException { + connection.connect(); + LOGGER.info("Asset connection established (endpoint: {})", connection.getEndpointInformation()); + } + + + private void tryConnectingUntilSuccess(AssetConnection connection) { + try { + tryConnecting(connection); + } + catch (AssetConnectionException e) { + LOGGER.info( + "Establishing asset connection failed on initial attempt (endpoint: {}, reason: {}). Connecting will be retried every {} ms but no more messages about failures will be shown.", + connection.getEndpointInformation(), + e.getMessage(), + coreConfig.getAssetConnectionRetryInterval(), + e); + } + while (active && !connection.isConnected()) { + try { + tryConnecting(connection); + } + catch (AssetConnectionException e) { + LOGGER.trace("Establishing asset connection failed (endpoint: {})", + connection.getEndpointInformation(), + e); + try { + Thread.sleep(coreConfig.getAssetConnectionRetryInterval()); + } + catch (InterruptedException e2) { + // intentionally empty + } + } + } + } + + + private void setupSubscription(Reference reference, AssetSubscriptionProvider provider) { + if (!active) { + return; + } + try { + provider.addNewDataListener((DataElementValue data) -> { + Response response = service.execute(PatchSubmodelElementValueByPathRequest.builder() + .submodelId(ReferenceHelper.findFirstKeyType(reference, KeyTypes.SUBMODEL)) + .path(ReferenceHelper.toPath(reference)) + .disableSyncWithAsset() + .value(data) + .build()); + if (!response.getStatusCode().isSuccess()) { + LOGGER.atInfo().log("Error updating value from asset connection subscription (reference: {})", + ReferenceHelper.toString(reference)); + LOGGER.debug("Error updating value from asset connection subscription (reference: {}, reason: {})", + ReferenceHelper.toString(reference), + response.getResult().getMessages()); + } + }); + } + catch (AssetConnectionException e) { + LOGGER.warn("Subscribing to asset connection failed (reference: {})", + ReferenceHelper.toString(reference), + e); + } + } + + + private void setupSubscriptions(AssetConnection connection) { + ((Map) connection. getSubscriptionProviders()).entrySet() + .forEach(x -> setupSubscription(x.getKey(), x.getValue())); + } + + + private void setupConnectionAsync(AssetConnection connection) { + executorService.execute(() -> { + tryConnectingUntilSuccess(connection); + setupSubscriptions(connection); + }); + } + + + private void validateConnectionConfigs(List connectionsConfigs) throws ConfigurationException { + List messages = new ArrayList<>(); + List referencesForAllValueProviders = connectionsConfigs.stream() + .filter(Objects::nonNull) + .flatMap(x -> x.getValueProviders().keySet().stream()) + .toList(); + List referencesForAllSubscriptionProviders = connectionsConfigs.stream() + .filter(Objects::nonNull) + .flatMap(x -> x.getSubscriptionProviders().keySet().stream()) + .toList(); + List referencesForAllOperationProviders = connectionsConfigs.stream() + .filter(Objects::nonNull) + .flatMap(x -> x.getOperationProviders().keySet().stream()) + .toList(); + + for (var providerType: AssetProviderType.values()) { + List referencesForAllProviders = connectionsConfigs.stream() + .flatMap(x -> providerType.getProvidersFromConfigAccessor().apply(x).keySet().stream()) + .toList(); + List> duplicateProviders = ReferenceHelper.groupBySame(referencesForAllProviders).stream() + .filter(x -> x.size() > 1) + .toList(); + for (var duplicates: duplicateProviders) { + messages.add(String.format("Duplicate %s providers found for references (%s)", + providerType.toString().toLowerCase(), + duplicates.stream().map(ReferenceHelper::asString).collect(Collectors.joining(", ")))); + } + } + referencesForAllValueProviders.forEach(x -> { + Reference duplicate = ReferenceHelper.findSameReference(referencesForAllOperationProviders, x); + if (Objects.nonNull(duplicate)) { + messages.add(String.format("Duplicate providers found (provider1: [value]%s, provider2: [operation]%s)", + ReferenceHelper.asString(x), + ReferenceHelper.asString(duplicate))); + } + }); + referencesForAllSubscriptionProviders.forEach(x -> { + Reference duplicate = ReferenceHelper.findSameReference(referencesForAllOperationProviders, x); + if (Objects.nonNull(duplicate)) { + messages.add(String.format("Duplicate providers found (provider1: [subscription]%s, provider2: [operation]%s)", + ReferenceHelper.asString(x), + ReferenceHelper.asString(duplicate))); + } + }); + + if (!messages.isEmpty()) { + throw new InvalidConfigurationException(String.format("found %d validation errors for asset connections%s%s", + messages.size(), + System.lineSeparator(), + String.join(System.lineSeparator(), messages))); + } + } + + + private void validateConnections(List connections) throws ConfigurationException { + validateConnectionConfigs(connections.stream() + .map(AssetConnection::asConfig) + .filter(Objects::nonNull) + .map(AssetConnectionConfig.class::cast) + .toList()); + } + + private static class ChangeSet { + private List add = new ArrayList<>(); + private List delete = new ArrayList<>(); + } + + private static class ExceptionWithDetails extends AssetConnectionException { + + private final List messages; + + public ExceptionWithDetails(String msg, List messages) { + super(msg); + this.messages = messages; + } + + + public ExceptionWithDetails(Throwable err, List messages) { + super(err); + this.messages = messages; + } + + + public ExceptionWithDetails(String msg, Throwable err, List messages) { + super(msg, err); + this.messages = messages; + } + + + public List getMessages() { + return messages; + } + } } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProvider.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProvider.java index 075b97117..d7bb86d1c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProvider.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProvider.java @@ -17,4 +17,12 @@ /** * Parent-type for all asset provider implementations. */ -public interface AssetProvider {} +public interface AssetProvider { + + /** + * Returns the provider configuration. + * + * @return the provider config + */ + public AssetProviderConfig asConfig(); +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderConfig.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderConfig.java index a86c0e0ad..c35ecd21f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderConfig.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderConfig.java @@ -20,4 +20,14 @@ * * @param corresponding type of AssetProvider */ -public interface AssetProviderConfig {} +public interface AssetProviderConfig { + + /** + * Checks if the given provider is the same as this, meaning all properties are equal or the same. This is different + * from {@code equals()} as properties can be considered to be the same without being equal like null and empty string. + * + * @param other the other config + * @return true is other is same as this, false otherwise + */ + public boolean sameAs(AssetProviderConfig other); +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderType.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderType.java new file mode 100644 index 000000000..610aa5fc0 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetProviderType.java @@ -0,0 +1,77 @@ +/* + * 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.ilt.faaast.service.assetconnection; + +import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper.BiConsumerWithExceptions; +import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper.TriConsumerWithExceptions; +import java.util.Map; +import java.util.function.Function; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; + + +/** + * This enum represents the different types of asset providers and offers methods to easily access them via + * {@code AssetConnection} and {@code AssetConnectionConfig}. + */ +public enum AssetProviderType { + VALUE(x -> ((AssetConnectionConfig) x.asConfig()).getValueProviders(), + AssetConnectionConfig::getValueProviders, + (connection, reference, config) -> connection.registerValueProvider(reference, (AssetValueProviderConfig) config), + (connection, reference) -> connection.unregisterValueProvider(reference)), + SUBSCRIPTION(x -> ((AssetConnectionConfig) x.asConfig()).getSubscriptionProviders(), + AssetConnectionConfig::getSubscriptionProviders, + (connection, reference, config) -> connection.registerSubscriptionProvider(reference, (AssetSubscriptionProviderConfig) config), + (connection, reference) -> connection.unregisterSubscriptionProvider(reference)), + OPERATION(x -> ((AssetConnectionConfig) x.asConfig()).getOperationProviders(), + AssetConnectionConfig::getOperationProviders, + (connection, reference, config) -> connection.registerOperationProvider(reference, (AssetOperationProviderConfig) config), + (connection, reference) -> connection.unregisterOperationProvider(reference)); + + private final Function> providersFromConnectionAccessor; + private final Function> providersFromConfigAccessor; + private final TriConsumerWithExceptions registerProviderAccessor; + private final BiConsumerWithExceptions unregisterProviderAccessor; + + private AssetProviderType(Function> providersFromConnectionAccessor, + Function> mapFromConnectionConfigAccessor, + TriConsumerWithExceptions registerProviderAccessor, + BiConsumerWithExceptions unregisterProviderAccessor) { + this.providersFromConnectionAccessor = providersFromConnectionAccessor; + this.providersFromConfigAccessor = mapFromConnectionConfigAccessor; + this.registerProviderAccessor = registerProviderAccessor; + this.unregisterProviderAccessor = unregisterProviderAccessor; + } + + + public Function> getProvidersFromConnectionAccessor() { + return providersFromConnectionAccessor; + } + + + public Function> getProvidersFromConfigAccessor() { + return providersFromConfigAccessor; + } + + + public TriConsumerWithExceptions getRegisterProviderAccessor() { + return registerProviderAccessor; + } + + + public BiConsumerWithExceptions getUnregisterProviderAccessor() { + return unregisterProviderAccessor; + } + +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetSubscriptionProvider.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetSubscriptionProvider.java index aa22f0751..07e11774a 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetSubscriptionProvider.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/AssetSubscriptionProvider.java @@ -35,4 +35,12 @@ public interface AssetSubscriptionProvider extends AssetProvider { * @throws AssetConnectionException if removinglistener fails */ public void removeNewDataListener(NewDataListener listener) throws AssetConnectionException; + + + /** + * Unsubscribe via underlying protocol. + * + * @throws AssetConnectionException if unsubscribe fails + */ + public void unsubscribe() throws AssetConnectionException; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaOperationProvider.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaOperationProvider.java index 360de401b..6fcddff14 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaOperationProvider.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaOperationProvider.java @@ -18,6 +18,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProvider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import java.util.Objects; import java.util.function.BiFunction; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; @@ -36,7 +37,12 @@ private LambdaOperationProvider() {} @Override public AssetOperationProviderConfig getConfig() { - return new AbstractAssetOperationProviderConfig() {}; + return new AbstractAssetOperationProviderConfig() { + @Override + public boolean sameAs(AssetProviderConfig other) { + return equals(other); + } + }; } @@ -54,6 +60,12 @@ public OperationVariable[] invoke(OperationVariable[] input, OperationVariable[] } + @Override + public AssetProviderConfig asConfig() { + throw new UnsupportedOperationException("lambda provider do not have a config as they are runtime-only"); + } + + public static Builder builder() { return new Builder(); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaSubscriptionProvider.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaSubscriptionProvider.java index 877d2a860..b2ee4d33c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaSubscriptionProvider.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaSubscriptionProvider.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.lambda.provider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetSubscriptionProvider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.NewDataListener; import de.fraunhofer.iosb.ilt.faaast.service.model.value.DataElementValue; @@ -65,6 +66,18 @@ public static Builder builder() { return new Builder(); } + + @Override + public void unsubscribe() throws AssetConnectionException { + listeners.clear(); + } + + + @Override + public AssetProviderConfig asConfig() { + throw new UnsupportedOperationException("lambda providers do not have a config as they are runtime-only"); + } + public static class Builder extends ExtendableBuilder { public Builder generate(Consumer value) { diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaValueProvider.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaValueProvider.java index 616967667..953f69897 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaValueProvider.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/provider/LambdaValueProvider.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.lambda.provider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProvider; import de.fraunhofer.iosb.ilt.faaast.service.model.value.DataElementValue; import java.util.Objects; @@ -81,6 +82,12 @@ public void setValue(DataElementValue value) throws AssetConnectionException { } + @Override + public AssetProviderConfig asConfig() { + throw new UnsupportedOperationException("lambda providers do not have a config as they are runtime-only"); + } + + public static Builder builder() { return new Builder(); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java index 3fe6f41dd..31eaf1e0f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/config/ServiceConfig.java @@ -23,6 +23,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.filestorage.FileStorageConfig; import de.fraunhofer.iosb.ilt.faaast.service.messagebus.MessageBusConfig; import de.fraunhofer.iosb.ilt.faaast.service.persistence.PersistenceConfig; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessorConfig; import de.fraunhofer.iosb.ilt.faaast.service.util.ImplementationManager; import java.io.File; import java.io.IOException; @@ -42,12 +43,14 @@ public class ServiceConfig { private List endpoints; private FileStorageConfig fileStorage; private MessageBusConfig messageBus; + private List submodelTemplateProcessors; private PersistenceConfig persistence; public ServiceConfig() { this.assetConnections = new ArrayList<>(); this.endpoints = new ArrayList<>(); + this.submodelTemplateProcessors = new ArrayList<>(); } @@ -101,6 +104,16 @@ public void setMessageBus(MessageBusConfig messageBus) { } + public List getSubmodelTemplateProcessors() { + return submodelTemplateProcessors; + } + + + public void setSubmodelTemplateProcessors(List submodelTemplateProcessors) { + this.submodelTemplateProcessors = submodelTemplateProcessors; + } + + public PersistenceConfig getPersistence() { return persistence; } @@ -127,13 +140,14 @@ public boolean equals(Object obj) { && Objects.equals(this.assetConnections, other.assetConnections) && Objects.equals(this.endpoints, other.endpoints) && Objects.equals(this.persistence, other.persistence) - && Objects.equals(this.fileStorage, other.fileStorage); + && Objects.equals(this.fileStorage, other.fileStorage) + && Objects.equals(this.submodelTemplateProcessors, other.submodelTemplateProcessors); } @Override public int hashCode() { - return Objects.hash(core, assetConnections, endpoints, persistence, fileStorage); + return Objects.hash(core, assetConnections, endpoints, persistence, fileStorage, submodelTemplateProcessors); } @@ -186,11 +200,13 @@ public static class Builder { private PersistenceConfig persistence; private FileStorageConfig fileStorage; private MessageBusConfig messageBus; + private List submodelTemplateProcessors; public Builder() { this.core = new CoreConfig(); this.assetConnections = new ArrayList<>(); this.endpoints = new ArrayList<>(); + this.submodelTemplateProcessors = new ArrayList<>(); } @@ -290,6 +306,30 @@ public Builder endpoint(EndpointConfig value) { } + /** + * Sets the SubmodelTemplateProcessors. + * + * @param value The SubmodelTemplateProcessors. + * @return The builder. + */ + public Builder submodelTemplateProcessors(List value) { + this.submodelTemplateProcessors = value; + return this; + } + + + /** + * Adds a SubmodelTemplateProcessor to the list of SubmodelTemplateProcessors. + * + * @param value The SubmodelTemplateProcessor to add. + * @return The builder. + */ + public Builder submodelTemplateProcessor(SubmodelTemplateProcessorConfig value) { + this.submodelTemplateProcessors.add(value); + return this; + } + + /** * Builds a new instance of ServiceConfig as defined by the builder. * @@ -303,6 +343,7 @@ public ServiceConfig build() { result.setPersistence(persistence); result.setFileStorage(fileStorage); result.setMessageBus(messageBus); + result.setSubmodelTemplateProcessors(submodelTemplateProcessors); return result; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractRequestHandler.java index f3ced5c05..ae9cc5fd9 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractRequestHandler.java @@ -34,11 +34,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.value.DataElementValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.ElementValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.mapper.ElementValueMapper; -import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.FaaastConstants; -import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; -import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.HashMap; @@ -46,7 +43,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; @@ -149,31 +145,6 @@ else if (SubmodelElementCollection.class.isAssignableFrom(submodelElement.getCla } - /** - * Removes all asset connections to elements contained in this element.If there are no more providers registerd, the - * asset connection is disconnected. - * - * @param parent reference to the parent element, e.g. a submodel - * @param persistence persistence implementation needed to check if submodel elements still exist - * @param context the execution context - * @throws AssetConnectionException if disconnection fails - */ - protected void cleanupDanglingAssetConnectionsForParent(Reference parent, Persistence persistence, RequestExecutionContext context) throws AssetConnectionException { - Predicate condition = x -> ReferenceHelper.startsWith(x, parent) && !persistence.submodelElementExists(x); - context.getAssetConnectionManager().getConnections().stream() - .forEach(LambdaExceptionHelper.rethrowConsumer(connection -> { - connection.getValueProviders().keySet().removeIf(condition); - connection.getOperationProviders().keySet().removeIf(condition); - connection.getSubscriptionProviders().keySet().removeIf(condition); - if (connection.getValueProviders().isEmpty() - && connection.getOperationProviders().isEmpty() - && connection.getSubscriptionProviders().isEmpty()) { - connection.disconnect(); - } - })); - } - - /** * Creates an updated element based on a JSON merge patch. * diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java index a2144b38c..dc351e3de 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java @@ -15,7 +15,6 @@ package de.fraunhofer.iosb.ilt.faaast.service.request.handler.submodel; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.ArgumentValidationMode; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.QueryModifier; @@ -64,16 +63,15 @@ public U doProcess(T request, RequestExecutionContext context) throws ResourceNo ReferenceHelper.toString(reference))); } Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); - AssetOperationProviderConfig config = context.getAssetConnectionManager().getOperationProvider(reference).getConfig(); request.setInputArguments(validateAndPrepare( operation.getInputVariables(), request.getInputArguments(), - config.getInputValidationMode(), + context.getAssetConnectionManager().getOperationInputValidationMode(reference).get(), ArgumentType.INPUT)); request.setInoutputArguments(validateAndPrepare( operation.getInoutputVariables(), request.getInoutputArguments(), - config.getInoutputValidationMode(), + context.getAssetConnectionManager().getOperationInoutputValidationMode(reference).get(), ArgumentType.INOUTPUT)); return executeOperation(reference, request, context); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java index dfe8cc383..049585264 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java @@ -14,7 +14,6 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.request.handler.submodel; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Message; import de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode; @@ -95,13 +94,12 @@ private void handleOperationResult(Reference reference, RequestExecutionContext context) { try { Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); - AssetOperationProviderConfig config = context.getAssetConnectionManager().getOperationProvider(reference).getConfig(); if (operationResult.getSuccess()) { operationResult.setOutputArguments( validateAndPrepare( operation.getOutputVariables(), operationResult.getOutputArguments(), - config.getOutputValidationMode(), + context.getAssetConnectionManager().getOperationOutputValidationMode(reference).get(), ArgumentType.OUTPUT)); } } @@ -154,7 +152,7 @@ protected InvokeOperationAsyncResponse executeOperation(Reference reference, Inv // The timeout is ignored, as the server may choose to take it into account or ignore it. try { - context.getAssetConnectionManager().getOperationProvider(reference).invokeAsync( + context.getAssetConnectionManager().invokeAsync(reference, request.getInputArguments().toArray(new OperationVariable[0]), request.getInoutputArguments().toArray(new OperationVariable[0]), (output, inoutput) -> handleOperationSuccess(reference, operationHandle, inoutput, output, context), diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java index bf54857c9..0560fc430 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java @@ -14,8 +14,6 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.request.handler.submodel; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProvider; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.QueryModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.InvokeOperationSyncRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.InvokeOperationSyncResponse; @@ -68,13 +66,12 @@ public InvokeOperationSyncResponse doProcess(InvokeOperationSyncRequest request, .idShortPath(request.getPath()) .build(); Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); - AssetOperationProviderConfig config = context.getAssetConnectionManager().getOperationProvider(reference).getConfig(); if (result.getPayload().getSuccess()) { result.getPayload().setOutputArguments( validateAndPrepare( operation.getOutputVariables(), result.getPayload().getOutputArguments(), - config.getOutputValidationMode(), + context.getAssetConnectionManager().getOperationOutputValidationMode(reference).get(), ArgumentType.OUTPUT)); } return result; @@ -103,14 +100,15 @@ protected InvokeOperationSyncResponse executeOperation(Reference reference, Invo context); } } - AssetOperationProvider assetOperationProvider = context.getAssetConnectionManager().getOperationProvider(reference); ExecutorService executor = Executors.newSingleThreadExecutor(); Future future = executor.submit(new Callable() { @Override public OperationVariable[] call() throws Exception { - return assetOperationProvider.invoke( + return context.getAssetConnectionManager().invoke( + reference, request.getInputArguments().toArray(new OperationVariable[0]), - request.getInoutputArguments().toArray(new OperationVariable[0])); + request.getInoutputArguments().toArray(new OperationVariable[0])) + .get(); } }); OperationResult result; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java index 7c7ec23c6..587b9a3fd 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java @@ -63,7 +63,7 @@ public PatchSubmodelElementByPathResponse doProcess(PatchSubmodelElementByPathRe SubmodelElement newSubmodelElement = applyMergePatch(request.getChanges(), oldSubmodelElement, SubmodelElement.class); ModelValidator.validate(newSubmodelElement, context.getCoreConfig().getValidationOnUpdate()); context.getPersistence().update(reference, newSubmodelElement); - cleanupDanglingAssetConnectionsForParent(reference, context.getPersistence(), context); + context.getAssetConnectionManager().cleanupDanglingConnectionsAfterModify(reference); if (!request.isInternal() && Objects.isNull(oldSubmodelElement)) { context.getMessageBus().publish(ElementCreateEventMessage.builder() .element(reference) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java index 3be10d3b2..b049a054d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java @@ -45,7 +45,7 @@ public DeleteSubmodelByIdResponse process(DeleteSubmodelByIdRequest request, Req Submodel submodel = context.getPersistence().getSubmodel(request.getSubmodelId(), QueryModifier.DEFAULT); context.getPersistence().deleteSubmodel(request.getSubmodelId()); response.setStatusCode(StatusCode.SUCCESS_NO_CONTENT); - cleanupDanglingAssetConnectionsForParent(ReferenceBuilder.forSubmodel(submodel), context.getPersistence(), context); + context.getAssetConnectionManager().cleanupDanglingConnectionsAfterModify(ReferenceBuilder.forSubmodel(submodel)); if (!request.isInternal()) { context.getMessageBus().publish(ElementDeleteEventMessage.builder() .element(submodel) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/PatchSubmodelByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/PatchSubmodelByIdRequestHandler.java index 63a95595d..60773387c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/PatchSubmodelByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/PatchSubmodelByIdRequestHandler.java @@ -50,7 +50,7 @@ public PatchSubmodelByIdResponse process(PatchSubmodelByIdRequest request, Reque ModelValidator.validate(updated, context.getCoreConfig().getValidationOnUpdate()); context.getPersistence().save(updated); Reference reference = ReferenceBuilder.forSubmodel(updated); - cleanupDanglingAssetConnectionsForParent(reference, context.getPersistence(), context); + context.getAssetConnectionManager().cleanupDanglingConnectionsAfterModify(reference); syncWithAsset(reference, updated.getSubmodelElements(), !request.isInternal(), context); if (!request.isInternal()) { context.getMessageBus().publish(ElementUpdateEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateManager.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateManager.java new file mode 100644 index 000000000..f9405c914 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateManager.java @@ -0,0 +1,219 @@ +/* + * 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.ilt.faaast.service.submodeltemplate; + +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.MessageBus; +import de.fraunhofer.iosb.ilt.faaast.service.model.SubmodelElementIdentifier; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.QueryModifier; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.PagingInfo; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.SubscriptionId; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.SubscriptionInfo; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementCreateEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementDeleteEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementUpdateEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Manages all submodel template processors. + */ +public class SubmodelTemplateManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubmodelTemplateManager.class); + + private final Persistence persistence; + private final MessageBus messageBus; + private final AssetConnectionManager assetConnectionManager; + private final List subscriptions = new ArrayList<>(); + private List submodelTemplateProcessors = new ArrayList<>(); + + public SubmodelTemplateManager(Persistence persistence, MessageBus messageBus, AssetConnectionManager assetConnectionManager, + List submodelTemplateProcessors) { + Ensure.requireNonNull(submodelTemplateProcessors, "submodelTemplateProcessors must be non-null"); + this.persistence = persistence; + this.messageBus = messageBus; + this.assetConnectionManager = assetConnectionManager; + this.submodelTemplateProcessors = submodelTemplateProcessors; + } + + + /** + * Starts the processing submodel templates. + * + * @throws PersistenceException if storage error occurs + * @throws MessageBusException if message bus error occurs + */ + public void start() throws PersistenceException, MessageBusException { + if (submodelTemplateProcessors.isEmpty()) { + return; + } + List submodels = persistence.getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent(); + for (var submodel: submodels) { + addSubmodel(submodel); + } + subscribeMessageBus(); + } + + + /** + * Stops all submodel template processors. + */ + public void stop() { + unsubscribeMessageBus(); + } + + + /** + * Callback message for Create event from the MessageBus. + * + * @param event The event from the MessageBus. + */ + public void handleCreateEvent(ElementCreateEventMessage event) { + if (event.getValue() instanceof Submodel submodel) { + addSubmodel(submodel); + } + else if (event.getValue() instanceof SubmodelElement) { + // if a SubmodelElement changed, we use updateSubodel + SubmodelElementIdentifier submodelElementIdentifier = SubmodelElementIdentifier.fromReference(event.getElement()); + try { + updateSubmodel(persistence.getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT)); + } + catch (ResourceNotFoundException | PersistenceException e) { + LOGGER.warn("Failed to read submodel (submodelId: {})", submodelElementIdentifier.getSubmodelId(), e); + } + } + } + + + /** + * Callback message for Update event from the MessageBus. + * + * @param event The event from the MessageBus. + */ + public void handleUpdateEvent(ElementUpdateEventMessage event) { + if (event.getValue() instanceof Submodel submodel) { + updateSubmodel(submodel); + } + else if (event.getValue() instanceof SubmodelElement) { + // if a SubmodelElement changed, we use updateSubodel + SubmodelElementIdentifier submodelElementIdentifier = SubmodelElementIdentifier.fromReference(event.getElement()); + try { + updateSubmodel(persistence.getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT)); + } + catch (ResourceNotFoundException | PersistenceException e) { + LOGGER.warn("Failed to read submodel (submodelId: {})", submodelElementIdentifier.getSubmodelId(), e); + } + } + } + + + /** + * Callback message for Delete event from the MessageBus. + * + * @param event The event from the MessageBus. + */ + public void handleDeleteEvent(ElementDeleteEventMessage event) { + if (event.getValue() instanceof Submodel submodel) { + deleteSubmodel(submodel); + } + else if (event.getValue() instanceof SubmodelElement) { + // if a SubmodelElement changed, we use updateSubodel + SubmodelElementIdentifier submodelElementIdentifier = SubmodelElementIdentifier.fromReference(event.getElement()); + try { + updateSubmodel(persistence.getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT)); + } + catch (ResourceNotFoundException | PersistenceException e) { + LOGGER.warn("Failed to read submodel (submodelId: {})", submodelElementIdentifier.getSubmodelId(), e); + } + } + } + + + private void addSubmodel(Submodel submodel) { + for (var submodelTemplateProcessor: submodelTemplateProcessors) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.add(submodel, assetConnectionManager)) { + LOGGER.debug("addSubmodel: submodelTemplate processed successfully"); + try { + persistence.save(submodel); + } + catch (PersistenceException e) { + LOGGER.warn("Failed to save submodel added by SMT processor (submodelId: {}, SMT processor type: {})", + submodel.getId(), + submodelTemplateProcessor.getClass().getSimpleName(), + e); + } + } + } + } + + + private void updateSubmodel(Submodel submodel) { + for (var submodelTemplateProcessor: submodelTemplateProcessors) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.update(submodel, assetConnectionManager)) { + LOGGER.debug("updateSubmodel: submodelTemplate processed successfully"); + try { + persistence.save(submodel); + } + catch (PersistenceException e) { + LOGGER.warn("Failed to save submodel updated by SMT processor (submodelId: {}, SMT processor type: {})", + submodel.getId(), + submodelTemplateProcessor.getClass().getSimpleName(), + e); + } + } + } + } + + + private void deleteSubmodel(Submodel submodel) { + for (var submodelTemplateProcessor: submodelTemplateProcessors) { + if (submodelTemplateProcessor.accept(submodel) && submodelTemplateProcessor.delete(submodel, assetConnectionManager)) { + LOGGER.debug("deleteSubmodel: submodelTemplate processed successfully"); + } + } + } + + + private void subscribeMessageBus() throws MessageBusException { + subscriptions.add(messageBus.subscribe(SubscriptionInfo.create(ElementCreateEventMessage.class, this::handleCreateEvent))); + subscriptions.add(messageBus.subscribe(SubscriptionInfo.create(ElementUpdateEventMessage.class, this::handleUpdateEvent))); + subscriptions.add(messageBus.subscribe(SubscriptionInfo.create(ElementDeleteEventMessage.class, this::handleDeleteEvent))); + // ValueChangeEventMessage + } + + + private void unsubscribeMessageBus() { + for (var subscription: subscriptions) { + try { + messageBus.unsubscribe(subscription); + } + catch (MessageBusException e) { + LOGGER.warn("failed to unsubscribe from message bus (subscriptionId: {})", subscription.getValue(), e); + } + } + subscriptions.clear(); + } +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java new file mode 100644 index 000000000..70c70366d --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessor.java @@ -0,0 +1,66 @@ +/* + * 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.ilt.faaast.service.submodeltemplate; + +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; +import de.fraunhofer.iosb.ilt.faaast.service.config.Configurable; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; + + +/** + * Interface for template processors. + * + * @param type of the matching configuration + */ +public interface SubmodelTemplateProcessor extends Configurable { + + /** + * Checks if given submodel uses the template. + * + * @param submodel the submodel to check + * @return true if uses the template, otherwise false + */ + public boolean accept(Submodel submodel); + + + /** + * Processes a given submodel, by adding elements. + * + * @param submodel the submodel to check + * @param assetConnectionManager manager for asset connection, can be used to modify underlying asset connection + * @return true if submodel has been modified, false otherwise + */ + public boolean add(Submodel submodel, AssetConnectionManager assetConnectionManager); + + + /** + * Processes a given submodel to update the information. + * + * @param submodel the submodel to check + * @param assetConnectionManager manager for asset connection, can be used to modify underlying asset connection + * @return true if submodel has been modified, false otherwise + */ + public boolean update(Submodel submodel, AssetConnectionManager assetConnectionManager); + + + /** + * Processes a given submodel to delete the information. + * + * @param submodel the submodel to check + * @param assetConnectionManager manager for asset connection, can be used to modify underlying asset connection + * @return true if submodel has been modified, false otherwise + */ + public boolean delete(Submodel submodel, AssetConnectionManager assetConnectionManager); +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java new file mode 100644 index 000000000..a964e8873 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorConfig.java @@ -0,0 +1,27 @@ +/* + * 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.ilt.faaast.service.submodeltemplate; + +import de.fraunhofer.iosb.ilt.faaast.service.config.Config; + + +/** + * Base class for configurations of submodel template processors. + * + * @param type of the matching processor + */ +public class SubmodelTemplateProcessorConfig extends Config { + +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java index d76c8bb87..02f292da7 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/typing/TypeExtractor.java @@ -73,8 +73,13 @@ else if (SubmodelElementList.class.isAssignableFrom(type)) { if (Objects.nonNull(submodelElementList.getTypeValueListElement()) && (submodelElementList.getTypeValueListElement() == AasSubmodelElements.SUBMODEL_ELEMENT_COLLECTION || submodelElementList.getTypeValueListElement() == AasSubmodelElements.SUBMODEL_ELEMENT_LIST)) { - for (int i = 0; i < submodelElementList.getValue().size(); i++) { - builder.element(Integer.toString(i), extractTypeInfoForSubmodelElement(submodelElementList.getValue().get(i))); + if (submodelElementList.getValue().size() == 1) { + builder.element(null, extractTypeInfoForSubmodelElement(submodelElementList.getValue().get(0))); + } + else { + for (int i = 0; i < submodelElementList.getValue().size(); i++) { + builder.element(Integer.toString(i), extractTypeInfoForSubmodelElement(submodelElementList.getValue().get(i))); + } } } else { diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java index 32407cad4..d723d79a1 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java @@ -72,11 +72,11 @@ public class LambdaAssetConnectionTest { private Persistence persistence; @Before - public void init() throws ConfigurationInitializationException, ConfigurationException, AssetConnectionException, MessageBusException, EndpointException { + public void init() throws ConfigurationInitializationException, ConfigurationException, AssetConnectionException, MessageBusException, EndpointException, PersistenceException { persistence = mock(Persistence.class); FileStorage fileStorage = mock(FileStorage.class); MessageBus messageBus = mock(MessageBus.class); - service = new Service(CoreConfig.DEFAULT, persistence, fileStorage, messageBus, List.of(), List.of()); + service = new Service(CoreConfig.DEFAULT, persistence, fileStorage, messageBus, List.of(), List.of(), List.of()); } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/ConfigTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/ConfigTest.java index df7a05155..01c28ff9f 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/ConfigTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/ConfigTest.java @@ -54,7 +54,7 @@ public static void init() { .assetConnection(assetConnection) .build(); mapper = new JsonMapperFactory().create(new SimpleAbstractTypeResolverFactory().create()) - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY);; + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnection.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnection.java index 9e90ecc60..9995fb446 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnection.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnection.java @@ -22,7 +22,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProvider; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import java.util.Map; -import java.util.Objects; import java.util.TreeMap; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; @@ -135,26 +134,13 @@ public Map getSubscriptionProviders() { @Override - public boolean sameAs(AssetConnection obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final DummyAssetConnection other = (DummyAssetConnection) obj; - return Objects.equals(port, other.port) - && Objects.equals(this.host, other.host) - && Objects.equals(this.coreConfig, other.coreConfig); + public void unregisterValueProvider(Reference reference) { + throw new UnsupportedOperationException("Not supported yet."); } @Override - public void unregisterValueProvider(Reference reference) { + public void stop() { throw new UnsupportedOperationException("Not supported yet."); } - } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnectionConfig.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnectionConfig.java index d79ae1db2..84608a375 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnectionConfig.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyAssetConnectionConfig.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.config.fixtures; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionConfig; +import java.util.Objects; public class DummyAssetConnectionConfig @@ -42,4 +43,18 @@ public void setPort(int port) { this.port = port; } + + @Override + public boolean equalsIgnoringProviders(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + DummyAssetConnectionConfig that = (DummyAssetConnectionConfig) obj; + return Objects.equals(host, that.host) + && Objects.equals(port, that.port); + } + } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyNodeBasedProviderConfig.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyNodeBasedProviderConfig.java index 3161ecdea..1c5554a0f 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyNodeBasedProviderConfig.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummyNodeBasedProviderConfig.java @@ -15,7 +15,9 @@ package de.fraunhofer.iosb.ilt.faaast.service.config.fixtures; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AbstractAssetOperationProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; @@ -54,4 +56,21 @@ public boolean equals(Object obj) { return Objects.equals(this.nodeId, other.nodeId); } + + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + final DummyNodeBasedProviderConfig that = (DummyNodeBasedProviderConfig) other; + return super.sameAs(that) + && StringHelper.equalsNullOrEmpty(nodeId, that.nodeId); + } + } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummySubscriptionBasedProviderConfig.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummySubscriptionBasedProviderConfig.java index 1e46c2561..ceb1198d9 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummySubscriptionBasedProviderConfig.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/config/fixtures/DummySubscriptionBasedProviderConfig.java @@ -14,7 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.config.fixtures; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; import java.util.Objects; @@ -65,4 +67,21 @@ public boolean equals(Object obj) { && Objects.equals(this.nodeId, other.nodeId); } + + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + final DummySubscriptionBasedProviderConfig that = (DummySubscriptionBasedProviderConfig) other; + return StringHelper.equalsNullOrEmpty(nodeId, that.nodeId) + && Objects.equals(interval, that.interval); + } + } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java index a3ecaebc4..5581a27d8 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java @@ -16,6 +16,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -24,12 +25,9 @@ import static org.mockito.Mockito.when; import de.fraunhofer.iosb.ilt.faaast.service.Service; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AbstractAssetOperationProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.ArgumentValidationMode; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProvider; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProvider; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; @@ -73,8 +71,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetConceptDescriptionByIdRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.PostConceptDescriptionRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.PutConceptDescriptionByIdRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.proprietary.ImportRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.proprietary.ResetRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.DeleteSubmodelElementByPathRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.GetAllSubmodelElementsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.GetFileByPathRequest; @@ -120,8 +116,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.conceptdescription.GetConceptDescriptionByIdResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.conceptdescription.PostConceptDescriptionResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.conceptdescription.PutConceptDescriptionByIdResponse; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.proprietary.ImportResponse; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.proprietary.ResetResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.DeleteSubmodelElementByPathResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetAllSubmodelElementsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetFileByPathResponse; @@ -150,7 +144,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueMappingException; import de.fraunhofer.iosb.ilt.faaast.service.model.value.DataElementValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.ElementValue; -import de.fraunhofer.iosb.ilt.faaast.service.model.value.ElementValueParser; import de.fraunhofer.iosb.ilt.faaast.service.model.value.PropertyValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.mapper.ElementValueMapper; import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.StringValue; @@ -165,11 +158,10 @@ import de.fraunhofer.iosb.ilt.faaast.service.util.ResponseHelper; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; -import java.util.function.Consumer; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.AasUtils; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; @@ -226,7 +218,6 @@ public class RequestHandlerManagerTest { private static AssetConnectionManager assetConnectionManager; private static FileStorage fileStorage; private static RequestHandlerManager manager; - private static AssetValueProvider assetValueProvider; private static Service service; private static StaticRequestExecutionContext context; @@ -237,7 +228,7 @@ public void createRequestHandlerManager() throws ConfigurationException, AssetCo messageBus = mock(MessageBus.class); persistence = spy(Persistence.class); service = mock(Service.class); - assetConnectionManager = spy(new AssetConnectionManager(coreConfig, List.of(), service)); + assetConnectionManager = mock(AssetConnectionManager.class); fileStorage = mock(FileStorage.class); context = new StaticRequestExecutionContext( coreConfig, @@ -246,8 +237,15 @@ public void createRequestHandlerManager() throws ConfigurationException, AssetCo messageBus, assetConnectionManager); manager = new RequestHandlerManager(coreConfig); - assetValueProvider = mock(AssetValueProvider.class); - when(assetConnectionManager.getValueProvider(any())).thenReturn(assetValueProvider); + when(assetConnectionManager.hasOperationProvider(any())).thenReturn(true); + when(assetConnectionManager.getOperationInputValidationMode(any())).thenReturn(Optional.of(ArgumentValidationMode.REQUIRE_PRESENT_OR_DEFAULT)); + when(assetConnectionManager.getOperationInoutputValidationMode(any())).thenReturn(Optional.of(ArgumentValidationMode.REQUIRE_PRESENT_OR_DEFAULT)); + when(assetConnectionManager.getOperationOutputValidationMode(any())).thenReturn(Optional.of(ArgumentValidationMode.REQUIRE_PRESENT_OR_DEFAULT)); + doAnswer(call -> { + OperationVariable[] input = call.getArgument(1); + OperationVariable[] inoutput = call.getArgument(2); + return Optional.of(invokeMockOperation(input, inoutput)); + }).when(assetConnectionManager).invoke(any(), any(), any()); } @@ -422,8 +420,6 @@ public void testGetAssetAdministrationShellRequest() throws ResourceNotFoundExce @Test public void testPutAssetAdministrationShellRequest() throws ResourceNotFoundException, Exception { - // @TODO: open/unclear - // expected Identifiable PutAssetAdministrationShellRequest request = new PutAssetAdministrationShellRequest.Builder() .aas(environment.getAssetAdministrationShells().get(0)) .id(AAS.getId()) @@ -909,7 +905,7 @@ public void testGetSubmodelElementByPathRequest() throws ResourceNotFoundExcepti when(persistence.getSubmodelElement((SubmodelElementIdentifier) any(), eq(OutputModifier.DEFAULT))) .thenReturn(cur_submodelElement); when(assetConnectionManager.hasValueProvider(any())).thenReturn(true); - when(assetValueProvider.getValue()).thenReturn(propertyValue); + when(assetConnectionManager.readValue(any())).thenReturn(Optional.of(propertyValue)); GetSubmodelElementByPathRequest request = new GetSubmodelElementByPathRequest.Builder() .submodelId(submodel.getId()) @@ -1048,7 +1044,10 @@ public void testPutSubmodelElementByPathRequest() throws ResourceNotFoundExcepti .statusCode(StatusCode.SUCCESS_NO_CONTENT) .build(); Assert.assertTrue(ResponseHelper.equalsIgnoringTime(expected, actual)); - verify(assetValueProvider).setValue(ElementValueMapper.toValue(newSubmodelElement, DataElementValue.class)); + + verify(assetConnectionManager).setValue( + ReferenceBuilder.forSubmodel(environment.getSubmodels().get(0), currentSubmodelElement.getIdShort()), + ElementValueMapper.toValue(newSubmodelElement, DataElementValue.class)); verify(persistence).update(ReferenceBuilder.forSubmodel(request.getSubmodelId(), request.getSubmodelElement().getIdShort()), newSubmodelElement); } @@ -1064,12 +1063,6 @@ public void testPatchSubmodelElementValueByPathRequest() throws ResourceNotFound PatchSubmodelElementValueByPathRequest request = new PatchSubmodelElementValueByPathRequest.Builder() .submodelId(environment.getSubmodels().get(0).getId()) .value(propertyValue) - .valueParser(new ElementValueParser() { - @Override - public U parse(ElementValue raw, Class type) { - return (U) raw; - } - }) .path(ReferenceHelper.toPath(SUBMODEL_ELEMENT_REF)) .build(); @@ -1078,7 +1071,9 @@ public U parse(ElementValue raw, Class type) { .statusCode(StatusCode.SUCCESS_NO_CONTENT) .build(); Assert.assertTrue(ResponseHelper.equalsIgnoringTime(expected, actual)); - verify(assetValueProvider).setValue(propertyValue); + verify(assetConnectionManager).setValue( + any(), + eq(propertyValue)); } @@ -1133,22 +1128,11 @@ private Operation getTestOperation() { @Test public void testInvokeOperationAsyncRequest() throws Exception { String submodelId = "http://example.org"; - CoreConfig coreConfig = CoreConfig.builder().build(); - Persistence persistence = mock(Persistence.class); - MessageBus messageBus = mock(MessageBus.class); - AssetConnectionManager assetConnectionManager = mock(AssetConnectionManager.class); - AssetOperationProvider assetOperationProvider = mock(AssetOperationProvider.class); - FileStorage fileStorage = mock(FileStorage.class); - StaticRequestExecutionContext context = new StaticRequestExecutionContext(coreConfig, persistence, fileStorage, messageBus, assetConnectionManager); Operation operation = getTestOperation(); when(persistence.getOperationResult(any())).thenReturn(new DefaultOperationResult.Builder().build()); - when(assetConnectionManager.hasOperationProvider(any())).thenReturn(true); - when(assetConnectionManager.getOperationProvider(any())).thenReturn(assetOperationProvider); - when(persistence.getSubmodelElement(ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, Operation.class)) .thenReturn(operation); - when(assetOperationProvider.getConfig()).thenReturn(new AbstractAssetOperationProviderConfig() {}); InvokeOperationAsyncRequest invokeOperationAsyncRequest = new InvokeOperationAsyncRequest.Builder() .submodelId(submodelId) @@ -1168,15 +1152,6 @@ public void testInvokeOperationAsyncRequest() throws Exception { @Test public void testInvokeOperationSyncRequest() throws Exception { String submodelId = "http://example.org"; - CoreConfig coreConfig = CoreConfig.builder().build(); - Persistence persistence = mock(Persistence.class); - MessageBus messageBus = mock(MessageBus.class); - AssetConnectionManager assetConnectionManager = mock(AssetConnectionManager.class); - FileStorage fileStorage = mock(FileStorage.class); - StaticRequestExecutionContext context = new StaticRequestExecutionContext(coreConfig, persistence, fileStorage, messageBus, assetConnectionManager); - when(assetConnectionManager.hasOperationProvider(any())).thenReturn(true); - when(assetConnectionManager.getOperationProvider(any())).thenAnswer(x -> new CustomAssetOperationProvider()); - Operation operation = getTestOperation(); when(persistence.getSubmodelElement(ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, Operation.class)) @@ -1211,20 +1186,9 @@ public void testInvokeOperationSyncRequest() throws Exception { @Test(expected = InvalidRequestException.class) public void testInvokeOperationSyncRequestMissingInputArgument() throws Exception { String submodelId = "http://example.org"; - CoreConfig coreConfig = CoreConfig.builder().build(); - Persistence persistence = mock(Persistence.class); - MessageBus messageBus = mock(MessageBus.class); - AssetConnectionManager assetConnectionManager = mock(AssetConnectionManager.class); - FileStorage fileStorage = mock(FileStorage.class); - StaticRequestExecutionContext context = new StaticRequestExecutionContext(coreConfig, persistence, fileStorage, messageBus, assetConnectionManager); - when(assetConnectionManager.hasOperationProvider(any())).thenReturn(true); - when(assetConnectionManager.getOperationProvider(any())).thenAnswer(x -> new CustomAssetOperationProvider( - CustomAssetOperationProviderConfig.builder() - .inputValidationMode(ArgumentValidationMode.REQUIRE_PRESENT) - .build())); - Operation operation = getTestOperation(); + when(assetConnectionManager.getOperationInputValidationMode(any())).thenReturn(Optional.of(ArgumentValidationMode.REQUIRE_PRESENT)); when(persistence.getSubmodelElement(ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, Operation.class)) .thenReturn(operation); @@ -1242,17 +1206,6 @@ public void testInvokeOperationSyncRequestMissingInputArgument() throws Exceptio @Test public void testInvokeOperationSyncRequestWithDefaultInputArgument() throws Exception { String submodelId = "http://example.org"; - CoreConfig coreConfig = CoreConfig.builder().build(); - Persistence persistence = mock(Persistence.class); - MessageBus messageBus = mock(MessageBus.class); - AssetConnectionManager assetConnectionManager = mock(AssetConnectionManager.class); - FileStorage fileStorage = mock(FileStorage.class); - StaticRequestExecutionContext context = new StaticRequestExecutionContext(coreConfig, persistence, fileStorage, messageBus, assetConnectionManager); - when(assetConnectionManager.hasOperationProvider(any())).thenReturn(true); - when(assetConnectionManager.getOperationProvider(any())).thenAnswer(x -> new CustomAssetOperationProvider( - CustomAssetOperationProviderConfig.builder() - .inputValidationMode(ArgumentValidationMode.REQUIRE_PRESENT_OR_DEFAULT) - .build())); Operation operation = getTestOperation(); when(persistence.getSubmodelElement(ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, Operation.class)) @@ -1264,7 +1217,6 @@ public void testInvokeOperationSyncRequestWithDefaultInputArgument() throws Exce .submodelId(submodelId) .path(operation.getIdShort()) .build(); - InvokeOperationSyncResponse actual = manager.execute(invokeOperationSyncRequest, context); InvokeOperationSyncResponse expected = new InvokeOperationSyncResponse.Builder() .statusCode(StatusCode.SUCCESS) @@ -1283,75 +1235,6 @@ public void testInvokeOperationSyncRequestWithDefaultInputArgument() throws Exce Assert.assertTrue(ResponseHelper.equalsIgnoringTime(expected, actual)); } - static class CustomAssetOperationProviderConfig extends AbstractAssetOperationProviderConfig { - - public static Builder builder() { - return new Builder(); - } - - public abstract static class AbstractBuilder> - extends AbstractAssetOperationProviderConfig.AbstractBuilder { - - } - - public static class Builder extends AbstractBuilder { - - @Override - protected Builder getSelf() { - return this; - } - - - @Override - protected CustomAssetOperationProviderConfig newBuildingInstance() { - return new CustomAssetOperationProviderConfig(); - } - - } - } - - static class CustomAssetOperationProvider implements AssetOperationProvider { - - private final CustomAssetOperationProviderConfig config; - - public CustomAssetOperationProvider() { - config = new CustomAssetOperationProviderConfig(); - } - - - public CustomAssetOperationProvider(CustomAssetOperationProviderConfig config) { - this.config = config; - } - - - @Override - public CustomAssetOperationProviderConfig getConfig() { - return config; - } - - - @Override - public OperationVariable[] invoke(OperationVariable[] input, OperationVariable[] inoutput) throws AssetConnectionException { - Property property = (Property) inoutput[0].getValue(); - property.setValue("TestOutput"); - return new OperationVariable[] { - new DefaultOperationVariable.Builder() - .value(new DefaultProperty.Builder() - .idShort("TestPropOutput") - .value("TestValue") - .build()) - .build() - }; - } - - - @Override - public void invokeAsync(OperationVariable[] input, OperationVariable[] inoutput, BiConsumer callbackSuccess, - Consumer callbackFailure) - throws AssetConnectionException { - // intentionally left empty - } - } @Test public void testGetAllConceptDescriptionsRequest() throws ResourceNotFoundException, Exception { @@ -1581,24 +1464,13 @@ public void testGetReferableWithMessageBusExceptionRequest() throws ResourceNotF public void testGetValueWithInvalidAssetConnectionRequest() throws ResourceNotFoundException, AssetConnectionException, Exception { when(persistence.getSubmodelElement((SubmodelElementIdentifier) any(), any())) .thenReturn(new DefaultProperty()); - AssetValueProvider assetValueProvider = mock(AssetValueProvider.class); - when(assetConnectionManager.hasValueProvider(any())).thenReturn(true); - when(assetConnectionManager.getValueProvider(any())).thenReturn(assetValueProvider); - when(assetValueProvider.getValue()).thenThrow(new AssetConnectionException("Invalid Assetconnection")); + when(assetConnectionManager.readValue(any())).thenThrow(new AssetConnectionException("Invalid Assetconnection")); GetSubmodelElementByPathRequest request = getExampleGetSubmodelElementByPathRequest(); AssetConnectionException exception = Assert.assertThrows(AssetConnectionException.class, () -> manager.execute(request, context)); Assert.assertEquals("Invalid Assetconnection", exception.getMessage()); } - private GetSubmodelElementByPathRequest getExampleGetSubmodelElementByPathRequest() { - return new GetSubmodelElementByPathRequest.Builder() - .path("testProperty") - .submodelId("test") - .build(); - } - - @Test public void testGetAllAssetAdministrationShellRequestAsync() throws InterruptedException, PersistenceException { when(persistence.findAssetAdministrationShells(eq(AssetAdministrationShellSearchCriteria.NONE), any(), any())) @@ -1654,26 +1526,11 @@ public void testReadValueFromAssetConnectionAndUpdatePersistence() Reference propertyStaticRef = AasUtils.toReference(AasUtils.toReference(parentRef, collection), propertyStatic); Reference rangeUpdatedRef = AasUtils.toReference(parentRef, rangeUpdated); List submodelElements = new ArrayList<>(List.of(propertyUpdated, rangeUpdated, collection)); - AssetValueProvider propertyUpdatedProvider = mock(AssetValueProvider.class); - AssetValueProvider propertyStaticProvider = mock(AssetValueProvider.class); - AssetValueProvider rangeUpdatedProvider = mock(AssetValueProvider.class); - when(assetConnectionManager.hasValueProvider(propertyUpdatedRef)).thenReturn(true); - when(assetConnectionManager.hasValueProvider(propertyStaticRef)).thenReturn(true); - when(assetConnectionManager.hasValueProvider(rangeUpdatedRef)).thenReturn(true); - - when(assetConnectionManager.getValueProvider(propertyUpdatedRef)).thenReturn(propertyUpdatedProvider); - when(propertyUpdatedProvider.getValue()).thenReturn(ElementValueMapper.toValue(propertyExpected, DataElementValue.class)); - - when(assetConnectionManager.getValueProvider(propertyStaticRef)).thenReturn(propertyStaticProvider); - when(propertyStaticProvider.getValue()).thenReturn(ElementValueMapper.toValue(propertyStatic, DataElementValue.class)); - - when(assetConnectionManager.getValueProvider(rangeUpdatedRef)).thenReturn(rangeUpdatedProvider); - when(rangeUpdatedProvider.getValue()).thenReturn(ElementValueMapper.toValue(rangeExpected, DataElementValue.class)); - - // TODO fix - //when(persistence.put(null, propertyUpdatedRef, propertyExpected)).thenReturn(propertyExpected); - //when(persistence.put(null, propertyStaticRef, propertyStatic)).thenReturn(propertyStatic); - //when(persistence.put(null, rangeUpdatedRef, rangeExpected)).thenReturn(rangeExpected); + + when(assetConnectionManager.readValue(propertyUpdatedRef)).thenReturn(Optional.of(ElementValueMapper.toValue(propertyExpected, DataElementValue.class))); + when(assetConnectionManager.readValue(propertyStaticRef)).thenReturn(Optional.of(ElementValueMapper.toValue(propertyStatic, DataElementValue.class))); + when(assetConnectionManager.readValue(rangeUpdatedRef)).thenReturn(Optional.of(ElementValueMapper.toValue(rangeExpected, DataElementValue.class))); + requestHandler.syncWithAsset( parentRef, submodelElements, @@ -1689,25 +1546,24 @@ public void testReadValueFromAssetConnectionAndUpdatePersistence() } - public void testImport() throws Exception { - ImportRequest request = new ImportRequest.Builder() - .content("{}".getBytes()) - .contentType("application/json") - .build(); - ImportResponse actual = manager.execute(request, context); - ImportResponse expected = new ImportResponse.Builder() - .statusCode(StatusCode.SUCCESS) + private GetSubmodelElementByPathRequest getExampleGetSubmodelElementByPathRequest() { + return new GetSubmodelElementByPathRequest.Builder() + .path("testProperty") + .submodelId("test") .build(); - Assert.assertTrue(ResponseHelper.equalsIgnoringTime(expected, actual)); } - public void testReset() throws Exception { - ResetRequest request = new ResetRequest.Builder().build(); - ResetResponse actual = manager.execute(request, context); - ResetResponse expected = new ResetResponse.Builder() - .statusCode(StatusCode.SUCCESS_NO_CONTENT) - .build(); - Assert.assertTrue(ResponseHelper.equalsIgnoringTime(expected, actual)); + private OperationVariable[] invokeMockOperation(OperationVariable[] input, OperationVariable[] inoutput) throws AssetConnectionException { + Property property = (Property) inoutput[0].getValue(); + property.setValue("TestOutput"); + return new OperationVariable[] { + new DefaultOperationVariable.Builder() + .value(new DefaultProperty.Builder() + .idShort("TestPropOutput") + .value("TestValue") + .build()) + .build() + }; } } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java new file mode 100644 index 000000000..3aa34b630 --- /dev/null +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java @@ -0,0 +1,182 @@ +/* + * 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.ilt.faaast.service.submodeltemplate; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import de.fraunhofer.iosb.ilt.faaast.service.Service; +import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.filestorage.FileStorage; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.MessageBus; +import de.fraunhofer.iosb.ilt.faaast.service.model.AASFull; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementCreateEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementDeleteEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementUpdateEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.invocation.InvocationOnMock; + + +public class SubmodelTemplateProcessorTest { + + private SubmodelTemplateProcessor processor; + private Submodel submodel0; + private Submodel submodel1; + private Environment environment; + private MessageBus messageBus; + private Service service; + + @Before + public void init() throws MessageBusException { + processor = mock(SubmodelTemplateProcessor.class); + mockMessageBus(); + environment = AASFull.createEnvironment(); + submodel0 = environment.getSubmodels().get(0); + submodel1 = environment.getSubmodels().get(1); + ArgumentMatcher isSubmodel0 = submodel -> Objects.equals(submodel, submodel0); + when(processor.accept(argThat(isSubmodel0))).thenReturn(true); + when(processor.add(eq(submodel0), any())).thenReturn(true); + when(processor.update(eq(submodel0), any())).thenReturn(true); + when(processor.delete(eq(submodel0), any())).thenReturn(true); + } + + + @Test + public void testStart() throws Exception { + List submodels = environment.getSubmodels(); + createService(submodels); + verify(processor, times(submodels.size())).accept(any()); + verify(processor, times(1)).add(eq(submodel0), any()); + verify(processor, times(0)).add(eq(submodel1), any()); + verify(processor, times(0)).update(any(), any()); + Assert.assertNotNull(service); + } + + + @Test + public void testUpdate() throws Exception { + List submodels = new ArrayList<>(); + createService(submodels); + + // Send update event to MessageBus + ElementUpdateEventMessage msg = new ElementUpdateEventMessage(); + msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + msg.setValue(submodel0); + service.getMessageBus().publish(msg); + // called for every Submodel in createService and for the Submodel to update + verify(processor, times(submodels.size() + 1)).accept(any()); + verify(processor, times(1)).update(eq(submodel0), any()); + Assert.assertNotNull(service); + } + + + @Test + public void testDelete() throws Exception { + List submodels = new ArrayList<>(); + createService(submodels); + + // Send delete event to MessageBus + ElementDeleteEventMessage msg = new ElementDeleteEventMessage(); + msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + msg.setValue(submodel0); + service.getMessageBus().publish(msg); + // called for every Submodel in createService and for the Submodel to delete + verify(processor, times(submodels.size() + 1)).accept(any()); + verify(processor, times(1)).delete(eq(submodel0), any()); + Assert.assertNotNull(service); + } + + + @Test + public void testCreate() throws Exception { + List submodels = new ArrayList<>(); + createService(submodels); + + // Send create event to MessageBus + ElementCreateEventMessage msg = new ElementCreateEventMessage(); + msg.setElement(ReferenceBuilder.forSubmodel(submodel0)); + msg.setValue(submodel0); + service.getMessageBus().publish(msg); + // called for every Submodel in createService and for the Submodel to delete + verify(processor, times(submodels.size() + 1)).accept(any()); + verify(processor, times(1)).add(eq(submodel0), any()); + Assert.assertNotNull(service); + } + + + private void createService(List submodels) throws Exception { + Persistence persistence = mock(Persistence.class); + FileStorage fileStorage = mock(FileStorage.class); + when(persistence.getAllSubmodels(any(), any())).thenReturn(Page.of(submodels)); + service = new Service(CoreConfig.DEFAULT, persistence, fileStorage, messageBus, List.of(), List.of(), List.of(processor)); + service.start(); + } + + + private void mockMessageBus() throws MessageBusException { + messageBus = mock(MessageBus.class); + + doAnswer((InvocationOnMock invocation) -> { + ElementCreateEventMessage eventMessage = invocation.getArgument(0); + try { + service.getSubmodelTemplateManager().handleCreateEvent(eventMessage); + } + catch (Exception e) { + fail(); + } + return null; + }).when(messageBus).publish(any(ElementCreateEventMessage.class)); + doAnswer((InvocationOnMock invocation) -> { + ElementUpdateEventMessage eventMessage = invocation.getArgument(0); + try { + service.getSubmodelTemplateManager().handleUpdateEvent(eventMessage); + } + catch (Exception e) { + fail(); + } + return null; + }).when(messageBus).publish(any(ElementUpdateEventMessage.class)); + + doAnswer((InvocationOnMock invocation) -> { + ElementDeleteEventMessage eventMessage = invocation.getArgument(0); + try { + service.getSubmodelTemplateManager().handleDeleteEvent(eventMessage); + } + catch (Exception e) { + fail(); + } + return null; + }).when(messageBus).publish(any(ElementDeleteEventMessage.class)); + } +} diff --git a/docs/source/basics/configuration.md b/docs/source/basics/configuration.md index 9c1b9ac6f..b7f8c2fb0 100644 --- a/docs/source/basics/configuration.md +++ b/docs/source/basics/configuration.md @@ -77,7 +77,7 @@ The `core` configuration block contains properties not related to the implementa "idShortUniqueness": true, "identifierUniqueness": true }, - "minInflateRatio": 0.01 + "minInflateRatio": 0.01 }, // ... } diff --git a/docs/source/index.md b/docs/source/index.md index aa914c92f..417cfb9a3 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -35,7 +35,7 @@ We strongly recommend to be careful when using external AAS models or submodels. ::: -```{toctree} +```{toctree} :hidden: :titlesonly: :caption: Basics @@ -46,7 +46,7 @@ basics/configuration.md basics/faq.md ``` -```{toctree} +```{toctree} :hidden: :caption: Interfaces interfaces/endpoint.md @@ -54,9 +54,10 @@ interfaces/asset-connection.md interfaces/persistence.md interfaces/file-storage.md interfaces/message-bus.md +interfaces/submodeltemplateprocessors.md ``` -```{toctree} +```{toctree} :hidden: :caption: Other other/release-notes.md diff --git a/docs/source/interfaces/asset-connection.md b/docs/source/interfaces/asset-connection.md index 0bb2f47e6..f2e3e7a4f 100644 --- a/docs/source/interfaces/asset-connection.md +++ b/docs/source/interfaces/asset-connection.md @@ -1,5 +1,6 @@ # AssetConnection +(assetconnection)= The AssetConnection interface is responsible for synchronizing values of the model with assets. Although asset synchronization is not part of the AAS specification, we believe this functionality is essential for digital twins, at least when located on edge-level, i.e., close to an actual machine/asset. diff --git a/docs/source/interfaces/submodeltemplateprocessors.md b/docs/source/interfaces/submodeltemplateprocessors.md new file mode 100644 index 000000000..932e8ab49 --- /dev/null +++ b/docs/source/interfaces/submodeltemplateprocessors.md @@ -0,0 +1,131 @@ +# SubmodelTemplate Processors + +Specific SubmodelTemplates require special handling. SubmodelTemplate Processors provide the necessary functionality offered by these SubmodelTemplates. + +## Interfaces + +Each SubmodelTemplate Processor must implement the interface SubmodelTemplateProcessor, which contains 4 methods: + +```{code-block} Java + public boolean accept(Submodel submodel); + public boolean add(Submodel submodel, AssetConnectionManager assetConnectionManager); + public boolean update(Submodel submodel, AssetConnectionManager assetConnectionManager); + public boolean delete(Submodel submodel, AssetConnectionManager assetConnectionManager); +``` + +- "accept" is used to find out whether the given SubmodelTemplate Processor uses the specified Submodel. +- "add" is called from the Service if the given Submodel was added. +- "update" is called from the Service if the given Submodel was updated. +- "delete" is called from the Service if the given Submodel was deleted. + +## Asset Interfaces Description and Asset Interfaces Mapping Configuration + +The SubmodelTemplate [Asset Interfaces Description](https://industrialdigitaltwin.org/wp-content/uploads/2024/01/IDTA-02017-1-0_Submodel_Asset-Interfaces-Description.pdf) (AID) specifies an information model and a common representation for describing the interfaces of an asset service or asset related service. Based on this information, it is possible to initiate a connection to such a service and request or subscribe to served datapoints. +The Asset Interfaces Description (AID) in version 1.0 supports the description of interfaces based on three specific protocols: Modbus, HTTP and MQTT. Fa³st currently supports HTTP and MQTT, Modbus is currently not supported. + +The SubmodelTemplate [Asset Interfaces Mapping Configuration](https://industrialdigitaltwin.org/wp-content/uploads/2024/06/IDTA-02027-1-0_Submodel_AssetInterfacesMappingConfiguration.pdf) (AIMC) specifies an information model and a common representation for describing the mapping of interface(s) of an asset service or asset-related service already described in an Asset Interfaces Description (AID) Submodel. It can be understood as a configuration Submodel for south-bound communication between AAS and asset. Based on this information, it's possible to create an [AssetConnection](#assetconnection) and map the payloads to the intended locations in an AAS automatically. + +This SubmodelTemplate Processor uses these SubmodelTemplates to create AssetConnections from the given data and links the asset data to the corresponding SubmodelElements, as defined in AIMC. + +The Processor looks for the SubmodelTemplate AIMC and maps all relations in the supported Asset Interfaces. + +:::{caution} +Besides AID / AIMC you also have the possibility to configure Asset Interfaces with our Asset Connections in the Fa³st Configuration. +As AID / AIMC currently has some limitations, for several use cases you must use Asset Connections, e.g. if you need write access or if you want to call operations in the Asset. + +Just be aware, that you can change AID during Runtime, but changes of the Asset Connections in the Configuration require a restart of the Serice. +::: + +### Generic Configuration + +The processor uses the following configuration structure: + +```{code-block} json +:caption: Configuration structure for AID + AIMC SubmodelTemplate Processor. +:lineno-start: 1 +{ + "submodelTemplateProcessors": [ + { + "@class": "de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.AimcSubmodelTemplateProcessor", + "credentials": { + "{Server URL}": [ + // credential configuration + ] + } + } + ] +} +``` + +The value of `{Server URL}` is the URL of the Server. This must match the Base URL in the AID Submodel. +An example value could look like this `http://myserver.example.com:8088`. + +Add a list of Credentials for each Server URL. + +### Credential configuration + +:::{table} Configuration properties of Credential configuration. +| Name | Allowed Value | Description | Default Value | +| ----------------------------------- | ----------------------------------------------------------- |----------------------------------------------------------------------------------------------- | ------------- | +| password | String | Password for connecting to the Asset server. | | +| username | String | Username for connecting to the Asset server. | | +::: + +In the EndpointMetadata of AID the following attributes are currently evaluated: + +- base +- contentType +- security + +:::{caution} +Currently, we only support the following Security Schemes: + +- NoSecurityScheme (nosec_sc) +- BasicSecurityScheme (basic_sc) +::: + +If BasicSecurityScheme is configured, username and password from the SMT configuration is used. In that case, make sure, that valid username and password is configured. + +In the Property of AID the following attributes are currently evaluated: + +- observable (only HTTP) + +For HTTP: If observable is true, a SubscriptionProvider, otherwise a ValueProvider is created. +In case of MQTT, a SubscriptionProvider is created always. + +The subscriptionInterval for a SubscriptionProvider is taken from the corresponding configuration section of the Processor (if available). If no value is provided, the default value will be used. + +In the Property Forms of AID, the following attributes are currently evaluated: + +- href +- contentType +- htv_headers (for HTTP) + +:::{table} Overview of the mappings of the used AID attributes. +| AID Attribute | Value in Asset Connection HTTP | Value in Asset Connection MQTT | +| ----------------------------------- | ----------------------------------------- |------------------------------------------------------------ | +| base (EndpointMetadata) | baseUrl | serverUri | +| contentType | format | format | +| href | path | topic | +| htv_headers | headers | - | +::: + +```{code-block} json +:caption: Example configuration section for AID + AIMC SubmodelTemplate Processor. +:lineno-start: 1 +{ + "@class": "de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.AimcSubmodelTemplateProcessor", + "credentials": { + "http://myserver.example.com:8088": [ + { + "username": "user1", + "password": "pw1" + }, + { + "username": "user2", + "password": "pw2" + } + ] + } +} +``` diff --git a/docs/source/other/release-notes.md b/docs/source/other/release-notes.md index 74f4e3ab3..7ba3bff70 100644 --- a/docs/source/other/release-notes.md +++ b/docs/source/other/release-notes.md @@ -2,12 +2,22 @@ ## 1.3.0-SNAPSHOT (current development version) +**New Features & Major Changes** +- General + - New interface `SubmodelTemplateProcessor` that enables handling of SMTs +- SubmodelTemplateProcessors + - Added processor for SMT Asset Interfaces Description (AID) and Asset Interfaces Mapping Configuration (AIMC), allowing to create/update/delete asset connections on-the-fly + + **Internal changes & bugfixes** - General - Fixed bug that auxiliary files were not loaded when starting from code with an initial model file - Fixed bug that caused deleting submodel-refs from AAS to fail when the submodel-ref had referredSemanticId set - Minor corrections in Logging - Make minInflateRatio configurable to be able to prevent zip bomb error when loading AASX files. +- Asset Connection + - Asset connections are now normalized, i.e., connections with exactly the same properties (e.g. type, server, credentials, etc.) are merged into a single connection + - Direct modification of asset connections and providers no longer supported, instead every interaction with connections or providers must be made via `AssetConnectionManager` - Endpoint - HTTP - URL query parameters are now correctly URL-decoded diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithAutoGeneratedCertificateTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithAutoGeneratedCertificateTest.java index fab081226..fc5c9667b 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithAutoGeneratedCertificateTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithAutoGeneratedCertificateTest.java @@ -51,7 +51,7 @@ private static void startServer() throws Exception { scheme = HttpScheme.HTTPS.toString(); endpoint = new HttpEndpoint(); server = new Server(); - service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of())); + service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of(), List.of())); endpoint.init( CoreConfig.DEFAULT, HttpEndpointConfig.builder() diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithProvidedCertificateTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithProvidedCertificateTest.java index 852767d3c..6e62e68aa 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithProvidedCertificateTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithProvidedCertificateTest.java @@ -80,7 +80,7 @@ private static void startServer() throws Exception { scheme = HttpScheme.HTTPS.toString(); endpoint = new HttpEndpoint(); server = new Server(); - service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of())); + service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of(), List.of())); endpoint.init( CoreConfig.DEFAULT, HttpEndpointConfig.builder() diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithSslDisabledTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithSslDisabledTest.java index 27d8b40d9..b0bc60d89 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithSslDisabledTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointWithSslDisabledTest.java @@ -49,7 +49,7 @@ private static void startServer() throws Exception { scheme = HttpScheme.HTTP.toString(); endpoint = new HttpEndpoint(); server = new Server(); - service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of())); + service = spy(new Service(CoreConfig.DEFAULT, persistence, fileStorage, mock(MessageBus.class), List.of(endpoint), List.of(), List.of())); endpoint.init( CoreConfig.DEFAULT, HttpEndpointConfig.builder() diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullModelTest.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullModelTest.java index bf46a8859..4bde95053 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullModelTest.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullModelTest.java @@ -55,7 +55,6 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import opc.i4aas.VariableIds; import opc.i4aas.datatypes.AASDataTypeDefXsd; @@ -140,12 +139,8 @@ public static void startTest() throws Exception { .keys(keys) .build(); - assetConnectionConfig.setOperationProviders(new HashMap() { - { - put(ref, new TestOperationProviderConfig(outputArgs)); - put(ref2, new TestOperationProviderConfig(null)); - } - }); + assetConnectionConfig.getOperationProviders().put(ref, new TestOperationProviderConfig(outputArgs)); + assetConnectionConfig.getOperationProviders().put(ref2, new TestOperationProviderConfig(null)); service = new TestService(config, assetConnectionConfig, true); service.start(); diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java index 36b155a27..0d2acf48c 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestService.java @@ -21,10 +21,12 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.OpcUaEndpointConfig; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.helper.assetconnection.TestAssetConnectionConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.filestorage.memory.FileStorageInMemoryConfig; import de.fraunhofer.iosb.ilt.faaast.service.messagebus.internal.MessageBusInternalConfig; import de.fraunhofer.iosb.ilt.faaast.service.model.AASFull; import de.fraunhofer.iosb.ilt.faaast.service.model.AASSimple; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; import java.util.ArrayList; import java.util.List; @@ -35,7 +37,8 @@ */ public class TestService extends Service { - public TestService(OpcUaEndpointConfig config, TestAssetConnectionConfig assetConnectionConfig, boolean full) throws ConfigurationException, AssetConnectionException { + public TestService(OpcUaEndpointConfig config, TestAssetConnectionConfig assetConnectionConfig, boolean full) + throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { super(ServiceConfig.builder() .core(CoreConfig.builder() .requestHandlerThreadPoolSize(2) diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnection.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnection.java index 3b31e4680..a1e8fc99b 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnection.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnection.java @@ -19,6 +19,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProvider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetSubscriptionProvider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProvider; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; @@ -41,6 +42,7 @@ public class TestAssetConnection implements private final Map valueProviders; private final Map operationProviders; private final Map subscriptionProviders; + private TestAssetConnectionConfig config; public TestAssetConnection() { valueProviders = new HashMap<>(); @@ -90,6 +92,12 @@ public void invokeAsync(OperationVariable[] input, OperationVariable[] inoutput, public AssetOperationProviderConfig getConfig() { return operationProvider; } + + + @Override + public AssetProviderConfig asConfig() { + return operationProvider; + } }); } catch (Exception e) { @@ -134,15 +142,9 @@ public Map getSubscriptionProviders() { } - @Override - public boolean sameAs(AssetConnection other) { - return false; - } - - @Override public TestAssetConnectionConfig asConfig() { - return null; + return config; } @@ -181,6 +183,7 @@ public void unregisterValueProvider(Reference reference) throws AssetConnectionE @Override public void init(CoreConfig coreConfig, TestAssetConnectionConfig config, ServiceContext context) { LOGGER.trace("init called"); + this.config = config; for (var provider: config.getValueProviders().entrySet()) { registerValueProvider(provider.getKey(), provider.getValue()); } @@ -191,4 +194,10 @@ public void init(CoreConfig coreConfig, TestAssetConnectionConfig config, Servic registerSubscriptionProvider(provider.getKey(), provider.getValue()); } } + + + @Override + public void stop() { + // nothing to do here + } } diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnectionConfig.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnectionConfig.java index aeddc4fd2..14f68e81d 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnectionConfig.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestAssetConnectionConfig.java @@ -19,4 +19,9 @@ public class TestAssetConnectionConfig extends AssetConnectionConfig { + @Override + public boolean equalsIgnoringProviders(Object obj) { + return true; + } + } diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestSubscriptionProviderConfig.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestSubscriptionProviderConfig.java index 16cba44e1..771765ab2 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestSubscriptionProviderConfig.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestSubscriptionProviderConfig.java @@ -14,9 +14,15 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.helper.assetconnection; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetSubscriptionProviderConfig; public class TestSubscriptionProviderConfig implements AssetSubscriptionProviderConfig { + @Override + public boolean sameAs(AssetProviderConfig other) { + return equals(other); + } + } diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestValueProviderConfig.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestValueProviderConfig.java index a2e9eaa2f..a74521912 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestValueProviderConfig.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/assetconnection/TestValueProviderConfig.java @@ -14,9 +14,15 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.helper.assetconnection; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProviderConfig; public class TestValueProviderConfig implements AssetValueProviderConfig { + @Override + public boolean sameAs(AssetProviderConfig other) { + return equals(other); + } + } diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/CustomAssetConnectionConfig.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/CustomAssetConnectionConfig.java index d22082e86..c9d3d1ae3 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/CustomAssetConnectionConfig.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/CustomAssetConnectionConfig.java @@ -23,4 +23,9 @@ public class CustomAssetConnectionConfig extends AssetConnectionConfig { + @Override + public boolean equalsIgnoringProviders(Object obj) { + return true; + } + } diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java index e95e0fdcf..2a8d92b5a 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java @@ -17,6 +17,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProvider; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.provider.config.CustomOperationProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.util.RandomValueGenerator; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationInitializationException; @@ -92,4 +93,10 @@ public OperationVariable[] invoke(OperationVariable[] input, OperationVariable[] return result; } + + @Override + public AssetProviderConfig asConfig() { + return config; + } + } diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomSubscriptionProvider.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomSubscriptionProvider.java index 505e3533c..c76136cf7 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomSubscriptionProvider.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomSubscriptionProvider.java @@ -16,6 +16,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetSubscriptionProvider; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.NewDataListener; import de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.provider.config.CustomSubscriptionProviderConfig; @@ -114,7 +115,8 @@ private void fireNewDataReceived(DataElementValue value) { } - private void unsubscribe() throws AssetConnectionException { + @Override + public void unsubscribe() throws AssetConnectionException { if (executorHandler != null) { executorHandler.cancel(true); } @@ -123,4 +125,9 @@ private void unsubscribe() throws AssetConnectionException { } } + + @Override + public AssetProviderConfig asConfig() { + return config; + } } diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomValueProvider.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomValueProvider.java index d496b07e6..0e2f96cdc 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomValueProvider.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomValueProvider.java @@ -16,6 +16,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProvider; import de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.provider.config.CustomValueProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.util.AasHelper; @@ -37,6 +38,7 @@ public class CustomValueProvider implements AssetValueProvider { private static final Logger LOGGER = LoggerFactory.getLogger(CustomValueProvider.class); + private final CustomValueProviderConfig config; private final Reference reference; private final Datatype datatype; @@ -47,6 +49,7 @@ public CustomValueProvider(Reference reference, CustomValueProviderConfig config Ensure.requireNonNull(serviceContext, "serviceContext must be non-null"); AasHelper.ensureType(reference, Property.class, serviceContext); this.reference = reference; + this.config = config; this.datatype = AasHelper.getDatatype(reference, serviceContext); LOGGER.debug(String.format("custom property 'note' of 'CustomValueProvider': %s", config.getNote())); } @@ -68,4 +71,10 @@ public void setValue(DataElementValue value) throws AssetConnectionException { throw new UnsupportedOperationException(String.format("%s does not support writing", getClass().getName())); } + + @Override + public AssetProviderConfig asConfig() { + return config; + } + } diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomSubscriptionProviderConfig.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomSubscriptionProviderConfig.java index a5540287a..d93a83ba7 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomSubscriptionProviderConfig.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomSubscriptionProviderConfig.java @@ -14,7 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetSubscriptionProviderConfig; +import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; @@ -37,6 +39,44 @@ public void setInterval(long interval) { this.interval = interval; } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + final CustomSubscriptionProviderConfig that = (CustomSubscriptionProviderConfig) other; + return Objects.equals(interval, that.interval); + } + + + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + final CustomSubscriptionProviderConfig that = (CustomSubscriptionProviderConfig) other; + return Objects.equals(interval, that.interval); + } + + + @Override + public int hashCode() { + return Objects.hashCode(interval); + } + public class Builder extends ExtendableBuilder { @Override diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomValueProviderConfig.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomValueProviderConfig.java index c5e6ae449..1df5f7aa5 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomValueProviderConfig.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/config/CustomValueProviderConfig.java @@ -14,7 +14,10 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.provider.config; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetValueProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; +import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; @@ -27,6 +30,44 @@ public CustomValueProviderConfig() { this.note = DEFAULT_NOTE; } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + final CustomValueProviderConfig that = (CustomValueProviderConfig) other; + return Objects.equals(note, that.note); + } + + + @Override + public boolean sameAs(AssetProviderConfig other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + final CustomValueProviderConfig that = (CustomValueProviderConfig) other; + return StringHelper.equalsNullOrEmpty(note, that.note); + } + + + @Override + public int hashCode() { + return Objects.hashCode(note); + } + public class Builder extends ExtendableBuilder { @Override diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java new file mode 100644 index 000000000..aba19eb41 --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPath.java @@ -0,0 +1,373 @@ +/* + * 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.ilt.faaast.service.model; + +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementVisitor; +import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementSubtypeResolvingVisitor; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.EnvironmentHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.model.HasSemantics; +import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; + + +/** + * Reprensts a semanticIdPath path addressing one or more SubmodelElement within a submodel. + */ +public class SemanticIdPath { + + List elements; + + public SemanticIdPath() { + this.elements = new ArrayList<>(); + } + + + public List getElements() { + return List.copyOf(elements); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SemanticIdPath other = (SemanticIdPath) o; + if (elements.size() != other.elements.size()) { + return false; + } + for (int i = 0; i < elements.size(); i++) { + if (!ReferenceHelper.equals(elements.get(i), other.elements.get(i))) { + return false; + } + } + return true; + } + + + @Override + public int hashCode() { + return Objects.hash(elements); + } + + + @Override + public String toString() { + return elements.stream() + .map(ReferenceHelper::asString) + .collect(Collectors.joining(" --> ")); + } + + + /** + * Creates a new idShortPath without the first/parent segment. + * + * @return idShortPath without the first/parent segment + */ + public SemanticIdPath withoutParent() { + SemanticIdPath result = new SemanticIdPath(); + result.elements = Objects.isNull(elements) || elements.size() <= 1 + ? List.of() + : elements.subList(1, elements.size()); + return result; + } + + + /** + * Resolves a semanticIdPath. + * + * @param type of the root element + * @param root the root to resolve the path in + * @return a list of references to elements that match the semanticIdPath + */ + public List resolve(T root) { + Ensure.requireNonNull(root, "root must be non-null"); + return resolveRecursive(root, this, null); + } + + + /** + * Resolves a semanticIdPath to elements of a given type. Elements matching the semanticIdPath having a different type + * are ignored. + * + * @param type of the root element + * @param type of the target elements + * @param root the root to resolve the path in + * @param type the type of the target elements + * @return a list of references to elements that match the semanticIdPath and type + */ + public List resolve(I root, Class type) { + Ensure.requireNonNull(root, "root must be non-null"); + return resolveRecursive(root, this, null).stream() + .map(x -> { + try { + return EnvironmentHelper.resolve(x, root); + } + catch (ResourceNotFoundException e) { + return null; + } + }) + .filter(Objects::nonNull) + .filter(type::isInstance) + .map(type::cast) + .collect(Collectors.toList()); + } + + + /** + * Uniquely resolves a semanticIdPath inside a given root element. + * + * @param the type of the root + * @param root the root to resolve the path in + * @return the reference to element that matches the semanticIdPath + * @throws ResourceNotFoundException if no matching element is found + * @throws IllegalArgumentException if the semanticIdPath resolves to more than one element + */ + public Reference resolveUnique(T root) throws ResourceNotFoundException { + Ensure.requireNonNull(root, "root must be non-null"); + List result = resolveRecursive(root, this, null); + if (Objects.isNull(result) || result.isEmpty()) { + throw new ResourceNotFoundException("no matching element for semanticIdPath"); + } + if (result.size() > 1) { + throw new IllegalArgumentException( + String.format("semanticIdPath did resolve to more than one element (path: %s, elements: %s)", + toString(), + result.stream().map(ReferenceHelper::asString).collect(Collectors.joining(", ")))); + } + return result.get(0); + } + + + /** + * Uniquely resolves a semanticIdPath inside a given root element. + * + * @param the type of the root + * @param root the root to resolve the path in + * @param type the expected effective type of the reference + * @return the reference to element that matches the semanticIdPath + * @throws IllegalArgumentException if the semanticIdPath resolves to more than one element + * @throws ResourceNotFoundException if no matching element is found + */ + public Reference resolveUnique(T root, KeyTypes type) throws ResourceNotFoundException { + Ensure.requireNonNull(type, "type must be non-null"); + Reference result = resolveUnique(root); + KeyTypes actualType = ReferenceHelper.getEffectiveKeyType(result); + if (!Objects.equals(type, actualType)) { + throw new IllegalArgumentException(String.format( + "semanticIdPath does not resolve to correct type of element (expected: %s, actual: %s)", + type, + actualType)); + } + return result; + } + + + /** + * Uniquely resolves a semanticIdPath inside a given root element. + * + * @param type of the root element + * @param type of the target elements + * @param root the root to resolve the path in + * @param type the type of the target elements + * @return the reference to element that matches the semanticIdPath + * @throws IllegalArgumentException if the semanticIdPath resolves to more than one element + */ + public T resolveUnique(I root, Class type) { + Ensure.requireNonNull(type, "type must be non-null"); + try { + return EnvironmentHelper.resolve(resolveUnique(root), root, type); + } + catch (ResourceNotFoundException e) { + throw new IllegalArgumentException( + String.format("semanticIdPath did not match any element (path: %s)", toString()), + e); + } + } + + + /** + * Uniquely resolves a semanticIdPath inside a given root element. + * + * @param type of the root element + * @param type of the target elements + * @param root the root to resolve the path in + * @param type the type of the target elements + * @return the element that matches the semanticIdPath if present, otherwise Optional.empty() + * @throws IllegalArgumentException if the semanticIdPath resolves to more than one element + */ + public Optional resolveOptional(I root, Class type) { + Ensure.requireNonNull(type, "type must be non-null"); + try { + return Optional.ofNullable(EnvironmentHelper.resolve(resolveUnique(root), root, type)); + } + catch (ResourceNotFoundException e) { + return Optional.empty(); + } + } + + + private static List resolveRecursive(T current, SemanticIdPath remainingPath, Reference parentRef) { + Ensure.requireNonNull(current, "current must be non-null"); + Ensure.requireNonNull(remainingPath, "path must be non-null"); + Reference currentRef = ReferenceHelper.combine( + parentRef, + new ReferenceBuilder() + .element( + Identifiable.class.isInstance(current) + ? ((Identifiable) current).getId() + : current.getIdShort(), + ReferenceHelper.toKeyType(current.getClass())) + .build()); + if (remainingPath.isEmpty()) { + return List.of(currentRef); + } + if (SubmodelElementList.class.isInstance(current)) { + SubmodelElementList submodelElementList = (SubmodelElementList) current; + List children = submodelElementList.getValue(); + List result = new ArrayList<>(); + boolean matchesSemanticIdListElement = Objects.nonNull(submodelElementList.getSemanticIdListElement()) + && ReferenceHelper.equals(remainingPath.getElements().get(0), submodelElementList.getSemanticIdListElement()); + for (int i = 0; i < children.size(); i++) { + if (matchesSemanticIdListElement || ReferenceHelper.equals(children.get(i).getSemanticId(), remainingPath.getElements().get(0))) { + final String newChildId = Integer.toString(i); + List childRefs = resolveRecursive(children.get(i), remainingPath.withoutParent(), currentRef); + result.addAll(childRefs.stream() + .map(x -> { + x.getKeys().get(currentRef.getKeys().size()).setValue(newChildId); + return x; + }) + .toList()); + } + } + return result; + } + return getChildren(current).stream() + .filter(x -> ReferenceHelper.equals(x.getSemanticId(), remainingPath.getElements().get(0))) + .flatMap(x -> resolveRecursive(x, remainingPath.withoutParent(), currentRef).stream()) + .collect(Collectors.toList()); + } + + + private static List getChildren(T parent) { + final List children = new ArrayList<>(); + AssetAdministrationShellElementVisitor visitor = new DefaultAssetAdministrationShellElementSubtypeResolvingVisitor() { + @Override + public void visit(Submodel submodel) { + add(submodel.getSubmodelElements()); + } + + + @Override + public void visit(SubmodelElementCollection submodelElementCollection) { + add(submodelElementCollection.getValue()); + } + + + @Override + public void visit(SubmodelElementList submodelElementList) { + add(submodelElementList.getValue()); + } + + + private void add(List elements) { + elements.forEach(x -> children.add((T) x)); + } + }; + visitor.visit((Referable) parent); + return children; + } + + + /** + * Checks if this path is empty, i.e. does not contain any elements. + * + * @return true if empty, otherwise false + */ + public boolean isEmpty() { + return Objects.isNull(elements) || elements.isEmpty(); + } + + + public static Builder builder() { + return new Builder(); + } + + public abstract static class AbstractBuilder> extends ExtendableBuilder { + + public B from(SemanticIdPath value) { + getBuildingInstance().elements = new ArrayList<>(value.elements); + return getSelf(); + } + + + public B semanticId(Reference value) { + getBuildingInstance().elements.add(value); + return getSelf(); + } + + + public B globalReference(String value) { + getBuildingInstance().elements.add(ReferenceBuilder.global(value)); + return getSelf(); + } + + + public B semanticIds(Reference... value) { + getBuildingInstance().elements.addAll(Arrays.asList(value)); + return getSelf(); + } + + + public B semanticIds(List value) { + getBuildingInstance().elements.addAll(value); + return getSelf(); + } + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected SemanticIdPath newBuildingInstance() { + return new SemanticIdPath(); + } + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java index 724997136..504b00329 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/modifier/QueryModifier.java @@ -28,6 +28,10 @@ public class QueryModifier { .level(Level.CORE) .extend(Extent.WITHOUT_BLOB_VALUE) .build(); + public static final QueryModifier MAXIMAL = new Builder() + .level(Level.DEEP) + .extend(Extent.WITH_BLOB_VALUE) + .build(); protected Level level; protected Extent extent; diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/serialization/DataFormat.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/serialization/DataFormat.java index ff8d4c811..b3af3fe8d 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/serialization/DataFormat.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/serialization/DataFormat.java @@ -18,7 +18,6 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -65,7 +64,7 @@ public static List forFileExtension(String fileExtension) { */ public static DataFormat forContentType(MediaType contentType) { return Stream.of(DataFormat.values()) - .filter(x -> Objects.equals(x.getContentType(), contentType)) + .filter(x -> x.getContentType().is(contentType)) .findAny() .orElseThrow(() -> new IllegalArgumentException(String.format("unsupported dataformat '%s'", contentType))); } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java new file mode 100644 index 000000000..cc94a3e4f --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/submodeltemplate/Cardinality.java @@ -0,0 +1,46 @@ +/* + * 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.ilt.faaast.service.model.submodeltemplate; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.serialization.EnumSerializer; + + +/** + * Describes the cardinality of relationships used with submodel templates. + */ +public enum Cardinality { + ONE(false), + ZERO_TO_ONE(false), + ZERO_TO_MANY(true), + ONE_TO_MANY(true); + + public static final String SEMANTIC_ID = "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0"; + public static final Cardinality DEFAULT = Cardinality.ONE; + private final boolean allowsMultipleValues; + + private Cardinality(boolean allowsMultipleValues) { + this.allowsMultipleValues = allowsMultipleValues; + } + + + public boolean getAllowsMultipleValues() { + return allowsMultipleValues; + } + + + public String getNameForSerialization() { + return EnumSerializer.serializeEnumName(name()); + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java index 50500875c..b18dbbe3e 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/mapper/SubmodelElementListValueMapper.java @@ -17,11 +17,16 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueMappingException; import de.fraunhofer.iosb.ilt.faaast.service.model.value.ElementValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.SubmodelElementListValue; +import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ElementValueHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.SubmodelTemplateHelper; import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -30,6 +35,8 @@ */ public class SubmodelElementListValueMapper implements DataValueMapper { + private static final Logger LOGGER = LoggerFactory.getLogger(SubmodelElementListValueMapper.class); + @Override public SubmodelElementListValue toValue(SubmodelElementList submodelElement) throws ValueMappingException { if (submodelElement == null) { @@ -57,13 +64,21 @@ public SubmodelElementList setValue(SubmodelElementList submodelElement, Submode int valueSize = Objects.nonNull(value.getValues()) ? value.getValues().size() : 0; - if (elementSize < valueSize) { - throw new ValueMappingException(String.format( - "Loss of information - setting a value with size %d to a SubmodelElementList of size %d results in loss of information", + if (elementSize == 1 + && valueSize > 1 + && SubmodelTemplateHelper.getCardinality(submodelElement.getValue().get(0)).getAllowsMultipleValues()) { + // we have a template that supports multiple elements + submodelElement.setValue(Stream.generate(() -> DeepCopyHelper.deepCopy(submodelElement.getValue().get(0))) + .limit(valueSize) + .collect(Collectors.toList())); + elementSize = valueSize; + } + else if (elementSize < valueSize) { + LOGGER.warn("Loss of information - setting a value with size {} to a SubmodelElementList of size {} results in loss of information (id: {})", valueSize, - elementSize)); + elementSize, + submodelElement.getIdShort()); } - for (int i = 0; i < Math.min(elementSize, valueSize); i++) { ElementValueMapper.setValue(submodelElement.getValue().get(i), value.getValues().get(i)); } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/AbstractDateTimeValue.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/AbstractDateTimeValue.java index 746309c03..8ba855146 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/AbstractDateTimeValue.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/AbstractDateTimeValue.java @@ -22,6 +22,7 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.Temporal; +import java.util.Objects; import org.apache.commons.lang3.StringUtils; @@ -145,10 +146,12 @@ value, getDataType()), @Override public String asString() { - return new DateTimeFormatterBuilder().append(isLocal - ? getFormatLocal() - : getFormatOffset()) - .toFormatter().format(value); + return Objects.nonNull(value) + ? new DateTimeFormatterBuilder().append(isLocal + ? getFormatLocal() + : getFormatOffset()) + .toFormatter().format(value) + : null; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java similarity index 81% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java index 2fc2a2773..4bbc3ff28 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/DeepCopyHelper.java @@ -14,6 +14,10 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.util; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -38,6 +42,16 @@ */ public class DeepCopyHelper { + private static final ObjectMapper mapper; + + static { + mapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + mapper.setTypeFactory(mapper.getTypeFactory().withClassLoader(ImplementationManager.getClassLoader())); + } + private DeepCopyHelper() {} @@ -94,7 +108,7 @@ public static T deepCopy(Referable referable, Class out String.format("type mismatch - can not create deep copy of instance of type %s with target type %s", referable.getClass(), outputClass)); } try { - return (T) new JsonDeserializer().read(new JsonSerializer().write(referable), outputClass); + return new JsonDeserializer().read(new JsonSerializer().write(referable), outputClass); } catch (SerializationException | DeserializationException e) { throw new RuntimeException("deep copy of AAS environment failed", e); @@ -140,4 +154,24 @@ public static List deepCopy(Collection ori .build()) .collect(Collectors.toList()); } + + + /** + * Create a deep copy of any object by serializing and deserializing it using Jackson. + * + * @param type of the object + * @param original the original object + * @param type type of the object + * @return a deep copy of the object + * @throws RuntimeException when deep copy fails + */ + public static T deepCopyAny(T original, Class type) { + try { + String json = mapper.writeValueAsString(original); + return mapper.readValue(json, type); + } + catch (Exception e) { + throw new RuntimeException("Deep copy failed", e); + } + } } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java index 42951c36b..cc669a838 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelper.java @@ -14,26 +14,15 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.util; -import de.fraunhofer.iosb.ilt.faaast.service.model.IdShortPath; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.AmbiguousElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementVisitor; -import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementSubtypeResolvingVisitor; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.ReferenceCollector; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.BiFunction; import java.util.stream.Collectors; -import org.eclipse.digitaltwin.aas4j.v3.model.Environment; -import org.eclipse.digitaltwin.aas4j.v3.model.HasSemantics; import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; /** @@ -53,20 +42,20 @@ private EnvironmentHelper() {} * * @param return type of the element * @param reference the reference to resolve - * @param environment the environment to resolve the reference in + * @param root the root element to resolve the reference in, e.g., Environment, Submodel, SubmodelElementCollection * @param returnType return type of the element * @return the resolved element * @throws IllegalArgumentException if reference is null or empty * @throws IllegalArgumentException if environment is null * @throws IllegalArgumentException if resolved element does not match the return type - * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist in environment + * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist */ - public static T resolve(Reference reference, Environment environment, Class returnType) throws ResourceNotFoundException { + public static T resolve(Reference reference, Object root, Class returnType) throws ResourceNotFoundException { Ensure.requireNonNull(reference, "reference must be non-null"); Ensure.require(!reference.getKeys().isEmpty(), "reference must contain at least one key"); - Ensure.requireNonNull(environment, "environment must be non-null"); + Ensure.requireNonNull(root, "root must be non-null"); Ensure.requireNonNull(returnType, "type must be non-null"); - Referable result = ReferenceCollector.collect(environment).entrySet().stream() + Referable result = ReferenceCollector.collect(root).entrySet().stream() .filter(x -> ReferenceHelper.equals(reference, x.getKey())) .map(Map.Entry::getValue) .findFirst() @@ -87,21 +76,21 @@ public static T resolve(Reference reference, Environment e * {@link org.eclipse.digitaltwin.aas4j.v3.model.Environment}. * * @param reference the reference to resolve - * @param environment the eenvironment to resolve the reference in + * @param root the root element to resolve the reference in, e.g., Environment, Submodel, SubmodelElementCollection * @return the resolved element * @throws IllegalArgumentException if reference is null or empty * @throws IllegalArgumentException if environment is null * @throws IllegalArgumentException if returnType is null - * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist in environment + * @throws ResourceNotFoundException if reference cannot be resolved because element does not exist */ - public static Referable resolve(Reference reference, Environment environment) throws ResourceNotFoundException { - return resolve(reference, environment, Referable.class); + public static Referable resolve(Reference reference, Object root) throws ResourceNotFoundException { + return resolve(reference, root, Referable.class); } /** * Generates a {@link org.eclipse.digitaltwin.aas4j.v3.model.Reference} for a - * {@link org.eclipse.digitaltwin.aas4j.v3.model.Referable} given an + * {{@link org.eclipse.digitaltwin.aas4j.v3.model.Referable} given an * {@link org.eclipse.digitaltwin.aas4j.v3.model.Environment} as context. * *

This method does not work in all cases as it tries to find the referable in the environment by equality. However, @@ -109,17 +98,17 @@ public static Referable resolve(Reference reference, Environment environment) th * parents. * * @param referable the referable to generate the reference for - * @param environment the environment containing the referable + * @param root the root element containing the referable * @return a reference pointing to the referable * @throws de.fraunhofer.iosb.ilt.faaast.service.model.exception.AmbiguousElementException if there are multiple * matching elements in the environment * @throws IllegalArgumentException if referable or environment is null * @throws IllegalArgumentException if referable is not present in environment */ - public static Reference asReference(Referable referable, Environment environment) throws AmbiguousElementException { + public static Reference asReference(Referable referable, Object root) throws AmbiguousElementException { Ensure.requireNonNull(referable, "referable must be non-null"); - Ensure.requireNonNull(environment, "environment must be non-null"); - List result = ReferenceCollector.collect(environment).entrySet().stream() + Ensure.requireNonNull(root, "root must be non-null"); + List result = ReferenceCollector.collect(root).entrySet().stream() .filter(x -> Objects.equals(referable, x.getValue())) .map(Map.Entry::getKey) .collect(Collectors.toList()); @@ -133,185 +122,4 @@ public static Reference asReference(Referable referable, Environment environment } return result.get(0); } - - - /** - * Resolves an idShortPath within a given submodel. - * - * @param expected return type - * @param idShortPath the idShortPath to resolve - * @param submodel the submodle to resolve the idShortPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(IdShortPath idShortPath, Submodel submodel, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(idShortPath, "idShortPath must be non-null"); - Ensure.requireNonNull(submodel, "submodel must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - List result = submodel.getSubmodelElements().stream() - .flatMap(x -> resolvePathRecursive(idShortPath.getElements(), x, (id, element) -> Objects.equals(id, element.getIdShort())).stream()) - .collect(Collectors.toList()); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve idShortPath on submodel (idShortPath: %s, submodel id: %s)", - idShortPath, - submodel.getId())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("idShortPath did resolve to more than one element"); - } - return type.cast(result.get(0)); - } - - - /** - * Resolves an idShortPath within a given submodel element. - * - * @param expected return type - * @param idShortPath the idShortPath to resolve - * @param element the submodle element to resolve the idShortPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(IdShortPath idShortPath, SubmodelElement element, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(idShortPath, "idShortPath must be non-null"); - Ensure.requireNonNull(element, "root must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - - List result = resolvePathRecursive(idShortPath.getElements(), element, (id, sme) -> Objects.equals(id, sme.getIdShort())); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve idShortPath on submodel element (idShortPath: %s, submodel element idShort: %s)", - idShortPath, - element.getIdShort())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("idShortPath did resolve to more than one element"); - } - return type.cast(result.get(0)); - } - - - /** - * Resolves a semanticIdPath within a given submodel. - * - * @param expected return type - * @param semanticIdPath the semanticIdPath to resolve - * @param submodel the submodle to resolve the semanticIdPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static List resolvePath(List semanticIdPath, Submodel submodel, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(semanticIdPath, "semanticIdPath must be non-null"); - Ensure.requireNonNull(submodel, "submodel must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - return submodel.getSubmodelElements().stream() - .flatMap(x -> resolvePathRecursive(semanticIdPath, x, - (reference, element) -> ReferenceHelper.equals(reference, element.getSemanticId())).stream()) - .map(type::cast) - .collect(Collectors.toList()); - } - - - /** - * Resolves a semanticIdPath within a given submodel. - * - * @param expected return type - * @param semanticIdPath the semanticIdPath to resolve - * @param submodel the submodle to resolve the semanticIdPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(List semanticIdPath, Submodel submodel, Class type) throws ResourceNotFoundException { - List result = resolvePath(semanticIdPath, submodel, type); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve semanticIdPath on submodel (semanticIdPath: %s, submodel id: %s)", - semanticIdPath.stream().map(ReferenceHelper::asString).collect(Collectors.joining(" -> ")), - submodel.getId())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("semanticIdPath did resolve to more than one element"); - } - return result.get(0); - } - - - /** - * Resolves an idShortPath within a given submodel element. - * - * @param expected return type - * @param semanticIdPath the semanticIdPath to resolve - * @param submodelElement the submodel element to resolve the semanticIdPath in - * @param type the expected return type - * @return the resolved {@link SubmodelElement} - * @throws ResourceNotFoundException if path is not resolvable - * @throws IllegalArgumentException if path does resolve to more than one element. - */ - public static T resolveUniquePath(List semanticIdPath, SubmodelElement submodelElement, Class type) throws ResourceNotFoundException { - Ensure.requireNonNull(semanticIdPath, "semanticIdPath must be non-null"); - Ensure.requireNonNull(submodelElement, "submodelElement must be non-null"); - Ensure.requireNonNull(type, "type must be non-null"); - - List result = resolvePathRecursive(semanticIdPath, submodelElement, - (reference, element) -> ReferenceHelper.equals(reference, element.getSemanticId())); - if (result.isEmpty()) { - throw new ResourceNotFoundException(String.format("unable to resolve semanticIdPath on submodelElement (semanticIdPath: %s, submodelElement id: %s)", - semanticIdPath.stream().map(ReferenceHelper::asString).collect(Collectors.joining(" -> ")), - submodelElement.getIdShort())); - } - if (result.size() > 1) { - throw new IllegalArgumentException("semanticIdPath did resolve to more than one element"); - } - return type.cast(result.get(0)); - } - - - private static List resolvePathRecursive(List path, U element, BiFunction equalsTester) { - List remainingPath = path.size() > 1 ? path.subList(1, path.size()) : List.of(); - if (path.isEmpty() || !equalsTester.apply(path.get(0), element)) { - return List.of(); - } - if (remainingPath.isEmpty()) { - return List.of(element); - } - return getChildren(element).stream() - .flatMap(x -> resolvePathRecursive(remainingPath, x, equalsTester).stream()) - .collect(Collectors.toList()); - } - - - private static List getChildren(T parent) { - final List children = new ArrayList<>(); - AssetAdministrationShellElementVisitor visitor = new DefaultAssetAdministrationShellElementSubtypeResolvingVisitor() { - @Override - public void visit(Submodel submodel) { - add(submodel.getSubmodelElements()); - } - - - @Override - public void visit(SubmodelElementCollection submodelElementCollection) { - add(submodelElementCollection.getValue()); - } - - - @Override - public void visit(SubmodelElementList submodelElementList) { - add(submodelElementList.getValue()); - } - - - private void add(List elements) { - elements.forEach(x -> children.add((T) x)); - } - }; - visitor.visit((Referable) parent); - return children; - } } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java similarity index 98% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java index 78456f124..8defe8c4f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ImplementationManager.java @@ -90,6 +90,7 @@ public static synchronized void init() { catch (SecurityException e) { LOGGER.error("Scanning directory '{}' for jar files failed", dir, e); } + Thread.currentThread().setContextClassLoader(classLoader); isInitialized = true; } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/JarFilePathHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/JarFilePathHelper.java similarity index 100% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/JarFilePathHelper.java rename to model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/JarFilePathHelper.java diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/LambdaExceptionHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/LambdaExceptionHelper.java index d3a614afe..a7dcd07dd 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/LambdaExceptionHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/LambdaExceptionHelper.java @@ -64,6 +64,28 @@ public interface BiConsumerWithExceptions { public void accept(T t, U u) throws E; } + /** + * Wrapper for {@link java.util.function.BiConsumer} with expected exception. + * + * @param type of first input argument + * @param type of second input argument + * @param type of third input argument + * @param type of expected exception + */ + @FunctionalInterface + public interface TriConsumerWithExceptions { + + /** + * Wrapper for {@link java.util.function.TriConsumer#accept(java.lang.Object, java.lang.Object)}. + * + * @param t the first input argument + * @param u the second input argument + * @param v the third input argument + * @throws E if operation fails + */ + public void accept(T t, U u, V v) throws E; + } + /** * Wrapper for {@link java.util.function.Function} with expected exception. * diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java index 7c8b6d76e..23df70b8d 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/ReferenceHelper.java @@ -19,11 +19,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -677,6 +679,31 @@ public static Reference findSameReference(Collection collection, Refe } + /** + * Groups references by {@code ReferenceHelper.equals(...)}. + * + * @param references the references to group + * @return list of groups of references where all elements of each group are equal according to + * {@code ReferenceHelper.equals(...)} + */ + public static List> groupBySame(Collection references) { + List> groups = new ArrayList<>(); + Set visited = new HashSet<>(); + + for (Reference reference: references) { + if (visited.contains(reference)) + continue; + List group = references.stream() + .filter(x -> x != reference && ReferenceHelper.equals(reference, x)) + .collect(Collectors.toList()); + group.add(reference); + groups.add(group); + visited.addAll(group); + } + return groups; + } + + private static ReferenceTypes determineReferenceType(Key key) { switch (key.getType()) { case FRAGMENT_REFERENCE: @@ -706,7 +733,7 @@ private static boolean equals(Key key1, Key key2) { if (Objects.isNull(type1) != Objects.isNull(type2) || Objects.isNull(type1) || (!(type1.isAssignableFrom(type2) || type2.isAssignableFrom(type1)))) { - LOGGER.warn(String.format( + LOGGER.debug(String.format( "encountered reference keys with same value but incompatible types (key value: %s, key type 1: %s, key type 2: %s)", key1.getValue(), type1, diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/StringHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/StringHelper.java index d208b28ba..cc5b0a962 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/StringHelper.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/StringHelper.java @@ -59,4 +59,19 @@ public static String decapitalize(String value) { } return Character.toLowerCase(value.charAt(0)) + value.substring(1); } + + + /** + * Checks if two strings are equal while treating null and and empty string as the same, i.e. + * {@code equalsNullOrEmpty(null, "")} returns true. + * + * @param s1 first string to compare + * @param s2 second string to compare + * @return true if the strings are equal treating null and empty string as the same, false otherwise + */ + public static boolean equalsNullOrEmpty(String s1, String s2) { + return isEmpty(s1) + ? isEmpty(s2) + : s1.equals(s2); + } } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java new file mode 100644 index 000000000..59db24b0a --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/util/SubmodelTemplateHelper.java @@ -0,0 +1,69 @@ +/* + * 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.ilt.faaast.service.util; + +import de.fraunhofer.iosb.ilt.faaast.service.model.submodeltemplate.Cardinality; +import java.util.Objects; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.deserialization.EnumDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Qualifiable; +import org.eclipse.digitaltwin.aas4j.v3.model.Qualifier; +import org.eclipse.digitaltwin.aas4j.v3.model.QualifierKind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Helper class for working with submodel templates. + */ +public class SubmodelTemplateHelper { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubmodelTemplateHelper.class); + + private SubmodelTemplateHelper() {} + + + /** + * Gets the cardinality defined by the qualifiable. If not (valid) cardinality is found, {@link Cardinality#ONE} is + * returned. + * + * @param element the element to check + * @return the cardinaility or {@link Cardinality#ONE} is none is defined + */ + public static Cardinality getCardinality(Qualifiable element) { + if (Objects.isNull(element) + || Objects.isNull(element.getQualifiers()) + || element.getQualifiers().isEmpty()) { + return Cardinality.DEFAULT; + } + Optional cardinalityQualifier = element.getQualifiers().stream() + .filter(Objects::nonNull) + .filter(x -> x.getKind() == QualifierKind.TEMPLATE_QUALIFIER) + .filter(x -> x.getValueType() == DataTypeDefXsd.STRING) + .filter(x -> Objects.equals(x.getType(), Cardinality.class.getSimpleName())) + .findFirst(); + if (cardinalityQualifier.isEmpty()) { + return Cardinality.DEFAULT; + } + try { + return Cardinality.valueOf(EnumDeserializer.deserializeEnumName(cardinalityQualifier.get().getValue())); + } + catch (IllegalArgumentException e) { + LOGGER.debug(String.format("Encountered invalid SMT cardinality qualifier: %s", cardinalityQualifier.get().getValue())); + return Cardinality.DEFAULT; + } + } +} diff --git a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java new file mode 100644 index 000000000..41cc9f608 --- /dev/null +++ b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/SemanticIdPathTest.java @@ -0,0 +1,256 @@ +/* + * 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.ilt.faaast.service.model; + +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; +import org.junit.Assert; +import org.junit.Test; + + +public class SemanticIdPathTest { + + @Test + public void resolveSematicIdPathInSubmodelUnique() throws ResourceNotFoundException { + final String idShortCollection1 = "idShortCollection1"; + final String idShortCollection2 = "idShortCollection2"; + final String idShortProperty1 = "idShortProperty1"; + Reference semanticIdSubmodel = ReferenceBuilder.global("submodel"); + Reference semanticIdCollection = ReferenceBuilder.global("collection"); + Reference semanticIdProperty1 = ReferenceBuilder.global("property1"); + + Submodel submodel = new DefaultSubmodel.Builder() + .id("submodel-id") + .semanticId(semanticIdSubmodel) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection1) + .semanticId(semanticIdCollection) + .build()) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection2) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty1) + .semanticId(semanticIdProperty1) + .build()) + .build()) + .build(); + + Reference expected = new ReferenceBuilder() + .submodel(submodel) + .element(idShortCollection2, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty1, KeyTypes.PROPERTY) + .build(); + Reference actual = SemanticIdPath.builder() + .semanticId(semanticIdCollection) + .semanticId(semanticIdProperty1) + .build() + .resolveUnique(submodel); + Assert.assertTrue(ReferenceHelper.equals(expected, actual)); + } + + + @Test + public void resolveSematicIdPathInCollection() throws ResourceNotFoundException { + final String idShortCollection = "idShortCollection"; + final String idShortProperty = "idShortProperty"; + Reference semanticIdCollection = ReferenceBuilder.global("collection"); + Reference semanticIdProperty = ReferenceBuilder.global("property"); + + SubmodelElementCollection submodel = new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty) + .semanticId(semanticIdProperty) + .build()) + .build(); + + Reference expected = new ReferenceBuilder() + .element(idShortCollection, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty, KeyTypes.PROPERTY) + .build(); + Reference actual = SemanticIdPath.builder() + .semanticId(semanticIdProperty) + .build() + .resolveUnique(submodel); + Assert.assertTrue(ReferenceHelper.equals(expected, actual)); + } + + + @Test + public void resolveSematicIdPathInList() throws ResourceNotFoundException { + final String idShortList1 = "idShortList1"; + final String idShortList2 = "idShortList2"; + final String idShortProperty1 = "idShortProperty1"; + final String idShortProperty2 = "idShortProperty2"; + Reference semanticIdList1 = ReferenceBuilder.global("list1"); + Reference semanticIdList2 = ReferenceBuilder.global("list2"); + Reference semanticIdProperty1 = ReferenceBuilder.global("property1"); + Reference semanticIdProperty2 = ReferenceBuilder.global("property2"); + + SubmodelElementList submodel = new DefaultSubmodelElementList.Builder() + .idShort(idShortList1) + .semanticId(semanticIdList1) + .value(new DefaultProperty.Builder() + .idShort("dummy1") + .build()) + .value(new DefaultProperty.Builder() + .idShort("dummy2") + .build()) + .value(new DefaultSubmodelElementList.Builder() + .idShort(idShortList2) + .semanticId(semanticIdList2) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty1) + .semanticId(semanticIdProperty1) + .build()) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty2) + .semanticId(semanticIdProperty2) + .build()) + .build()) + .build(); + + Reference expected = new ReferenceBuilder() + .element(idShortList1, KeyTypes.SUBMODEL_ELEMENT_LIST) + .element("2", KeyTypes.SUBMODEL_ELEMENT_LIST) + .element("1", KeyTypes.PROPERTY) + .build(); + Reference actual = SemanticIdPath.builder() + .semanticId(semanticIdList2) + .semanticId(semanticIdProperty2) + .build() + .resolveUnique(submodel); + Assert.assertTrue(ReferenceHelper.equals(expected, actual)); + } + + + @Test + public void resolveSematicIdPathInList_semanticIdListElement() throws ResourceNotFoundException { + final String idShortList1 = "idShortList1"; + final String idShortList2 = "idShortList2"; + final String idShortProperty1 = "idShortProperty1"; + final String idShortProperty2 = "idShortProperty2"; + Reference semanticIdList1 = ReferenceBuilder.global("list1"); + Reference semanticIdList2 = ReferenceBuilder.global("list2"); + Reference semanticIdListElement = ReferenceBuilder.global("listElement"); + + SubmodelElementList submodel = new DefaultSubmodelElementList.Builder() + .idShort(idShortList1) + .semanticId(semanticIdList1) + .value(new DefaultProperty.Builder() + .idShort("dummy1") + .build()) + .value(new DefaultProperty.Builder() + .idShort("dummy2") + .build()) + .value(new DefaultSubmodelElementList.Builder() + .idShort(idShortList2) + .semanticId(semanticIdList2) + .semanticIdListElement(semanticIdListElement) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty1) + .build()) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty2) + .build()) + .build()) + .build(); + List expected = List.of( + new ReferenceBuilder() + .element(idShortList1) + .index(2) + .index(0) + .build(), + new ReferenceBuilder() + .element(idShortList1) + .index(2) + .index(1) + .build()); + List actual = SemanticIdPath.builder() + .semanticId(semanticIdList2) + .semanticId(semanticIdListElement) + .build() + .resolve(submodel); + Assert.assertEquals(expected.size(), actual.size()); + Assert.assertTrue(expected.stream().allMatch(exp -> actual.stream().anyMatch(act -> ReferenceHelper.equals(exp, act)))); + } + + + @Test + public void resolveSematicIdPathInSubmodelNonUnique() { + final String idShortCollection1 = "idShortCollection1"; + final String idShortCollection2 = "idShortCollection2"; + final String idShortProperty1 = "idShortProperty1"; + final String idShortProperty2 = "idShortProperty2"; + Reference semanticIdSubmodel = ReferenceBuilder.global("submodel"); + Reference semanticIdCollection = ReferenceBuilder.global("collection"); + Reference semanticIdProperty = ReferenceBuilder.global("property"); + + Submodel submodel = new DefaultSubmodel.Builder() + .id("submodel-id") + .semanticId(semanticIdSubmodel) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection1) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty1) + .semanticId(semanticIdProperty) + .build()) + .build()) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(idShortCollection2) + .semanticId(semanticIdCollection) + .value(new DefaultProperty.Builder() + .idShort(idShortProperty2) + .semanticId(semanticIdProperty) + .build()) + .build()) + .build(); + + List expected = List.of( + new ReferenceBuilder() + .submodel(submodel) + .element(idShortCollection1, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty1, KeyTypes.PROPERTY) + .build(), + new ReferenceBuilder() + .submodel(submodel) + .element(idShortCollection2, KeyTypes.SUBMODEL_ELEMENT_COLLECTION) + .element(idShortProperty2, KeyTypes.PROPERTY) + .build()); + List actual = SemanticIdPath.builder() + .semanticId(semanticIdCollection) + .semanticId(semanticIdProperty) + .build() + .resolve(submodel); + Assert.assertEquals(expected, actual); + for (int i = 0; i < expected.size(); i++) { + Assert.assertTrue(ReferenceHelper.equals(expected.get(i), actual.get(i))); + } + } +} diff --git a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java index 0e650ba9e..2f61db4c2 100644 --- a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java +++ b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/util/EnvironmentHelperTest.java @@ -15,20 +15,13 @@ package de.fraunhofer.iosb.ilt.faaast.service.util; import de.fraunhofer.iosb.ilt.faaast.service.model.AASFull; -import de.fraunhofer.iosb.ilt.faaast.service.model.IdShortPath; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.AmbiguousElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementWalker; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementVisitor; -import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; -import org.eclipse.digitaltwin.aas4j.v3.model.Property; import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; import org.junit.Assert; import org.junit.Test; @@ -55,90 +48,6 @@ public void visit(Referable referable) { } - @Test - public void resolveIdShortPathInSubmodel() throws ResourceNotFoundException { - Property expected = new DefaultProperty.Builder() - .idShort("property1") - .build(); - Submodel submodel = new DefaultSubmodel.Builder() - .idShort("foo") - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .idShort("collection") - .value(expected) - .build()) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .idShort("collection") - .value(new DefaultProperty.Builder() - .idShort("property2") - .build()) - .build()) - .build(); - Property actual = EnvironmentHelper.resolveUniquePath( - IdShortPath.parse("collection.property1"), - submodel, - Property.class); - Assert.assertEquals(expected, actual); - } - - - @Test - public void resolveSematicIdPathInSubmodel() throws ResourceNotFoundException { - Reference semanticIdCollection = ReferenceBuilder.global("collection"); - Reference semanticIdProperty1 = ReferenceBuilder.global("property1"); - Property expected = new DefaultProperty.Builder() - .semanticId(semanticIdProperty1) - .build(); - Submodel submodel = new DefaultSubmodel.Builder() - .semanticId(ReferenceBuilder.global("foo")) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(expected) - .build()) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(new DefaultProperty.Builder() - .semanticId(ReferenceBuilder.global("property")) - .build()) - .build()) - .build(); - Property actual = EnvironmentHelper.resolveUniquePath( - List.of(semanticIdCollection, semanticIdProperty1), - submodel, - Property.class); - Assert.assertEquals(expected, actual); - } - - - @Test - public void resolveSematicIdPathInSubmodel_NonUnique() throws ResourceNotFoundException { - Reference semanticIdCollection = ReferenceBuilder.global("collection"); - Reference semanticIdProperty = ReferenceBuilder.global("property"); - Property property1 = new DefaultProperty.Builder() - .semanticId(semanticIdProperty) - .build(); - Property property2 = new DefaultProperty.Builder() - .semanticId(semanticIdProperty) - .build(); - List expected = List.of(property1, property2); - Submodel submodel = new DefaultSubmodel.Builder() - .semanticId(ReferenceBuilder.global("foo")) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(property1) - .build()) - .submodelElements(new DefaultSubmodelElementCollection.Builder() - .semanticId(semanticIdCollection) - .value(property2) - .build()) - .build(); - List actual = EnvironmentHelper.resolvePath( - List.of(semanticIdCollection, semanticIdProperty), - submodel, - Property.class); - Assert.assertEquals(expected, actual); - } - - private void assertResolve(Referable expected, Environment environment) throws ResourceNotFoundException, AmbiguousElementException { Reference reference = EnvironmentHelper.asReference(expected, environment); Referable actual = EnvironmentHelper.resolve(reference, environment); diff --git a/pom.xml b/pom.xml index f63191dee..fa8f1cf7e 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ assetconnection/http dataformat/json starter + submodeltemplate/asset-interfaces-mapping-configuration scm:git:git://github.com/FraunhoferIOSB/FAAAST-Service.git diff --git a/starter/pom.xml b/starter/pom.xml index 85b7ded7f..1ff651427 100644 --- a/starter/pom.xml +++ b/starter/pom.xml @@ -104,6 +104,11 @@ persistence-mongo ${project.version} + + ${project.groupId} + submodeltemplate-asset-interfaces-mapping-configuration + ${project.version} + ch.qos.logback logback-classic diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml b/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml new file mode 100644 index 000000000..601fddbca --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml @@ -0,0 +1,117 @@ + + + 4.0.0 + + de.fraunhofer.iosb.ilt.faaast.service + service + 1.3.0-SNAPSHOT + ../../pom.xml + + submodeltemplate-asset-interfaces-mapping-configuration + submodeltemplate-asset-interfaces-mapping-configuration + Submodeltemplate processor for SubmodelTemplate Asset Interfaces Mapping Configuration + + ${project.parent.basedir} + + + + ${project.groupId} + assetconnection-http + ${project.version} + + + ${project.groupId} + assetconnection-mqtt + ${project.version} + + + ${project.groupId} + core + ${project.version} + + + ${project.groupId} + endpoint-http + ${project.version} + test + + + ${project.groupId} + filestorage-memory + ${project.version} + test + + + ${project.groupId} + messagebus-internal + ${project.version} + test + + + ${project.groupId} + messagebus-mqtt + ${project.version} + test + + + ${project.groupId} + model + ${project.version} + + + ${project.groupId} + persistence-memory + ${project.version} + test + + + com.github.tomakehurst + wiremock-jre8-standalone + ${wiremock.version} + test + + + org.awaitility + awaitility + ${awaitility.version} + test + + + org.eclipse.digitaltwin.aas4j + aas4j-model + ${aas4j.version} + + + com.mycila + license-maven-plugin + + + + + org.mockito + mockito-core + ${mockito.version} + test + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven.plugin.jar.version} + + + + test-jar + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessor.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessor.java new file mode 100644 index 000000000..6a7f3ecd3 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessor.java @@ -0,0 +1,315 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc; + +import static org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum.ERROR; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; +import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; +import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; +import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationInitializationException; +import de.fraunhofer.iosb.ilt.faaast.service.model.SemanticIdPath; +import de.fraunhofer.iosb.ilt.faaast.service.model.SubmodelElementIdentifier; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Message; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.GetSubmodelElementByPathRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.GetSubmodelByIdRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessor; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.config.AimcSubmodelTemplateProcessorConfig; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.util.HttpHelper; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.util.MqttHelper; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.util.Util; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Adds logic for submodel instances of template Asset Interfaces Mapping Configuration. + */ +public class AimcSubmodelTemplateProcessor implements SubmodelTemplateProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(AimcSubmodelTemplateProcessor.class); + + private AimcSubmodelTemplateProcessorConfig config; + private ServiceContext serviceContext; + // keeps track of relations between AID and AIMC submodels as changes to an AID submodel needs to trigger + // an update on all AIMC refering to/using that AID submodel. + private final Map> aidToAimcRelations; + + private final Map> connectionsCurrent; + + public AimcSubmodelTemplateProcessor() { + aidToAimcRelations = new HashMap<>(); + connectionsCurrent = new HashMap<>(); + } + + + @Override + public AimcSubmodelTemplateProcessorConfig asConfig() { + return config; + } + + + @Override + public void init(CoreConfig coreConfig, AimcSubmodelTemplateProcessorConfig config, ServiceContext serviceContext) throws ConfigurationInitializationException { + this.config = config; + this.serviceContext = serviceContext; + } + + + @Override + public boolean accept(Submodel submodel) { + return Objects.nonNull(submodel) + && (Util.semanticIdEquals(submodel, Constants.AIMC_SUBMODEL_SEMANTIC_ID) + || Util.semanticIdEquals(submodel, Constants.AID_SUBMODEL_SEMANTIC_ID)); + } + + + @Override + public boolean add(Submodel submodel, AssetConnectionManager assetConnectionManager) { + Ensure.requireNonNull(submodel); + Ensure.requireNonNull(assetConnectionManager); + if (Util.semanticIdEquals(submodel, Constants.AID_SUBMODEL_SEMANTIC_ID)) { + handleAidChange(submodel, assetConnectionManager, ProcessingMode.ADD); + return false; + } + updateAidToAimcRelations(submodel, ProcessingMode.ADD); + try { + LOGGER.atInfo().log("process submodel {} ({})", submodel.getIdShort(), ReferenceHelper.asString(ReferenceBuilder.forSubmodel(submodel))); + processSubmodel(submodel, assetConnectionManager, ProcessingMode.ADD); + } + catch (Exception e) { + LOGGER.error("error processing SMT AIMC (submodel: {})", ReferenceHelper.asString(ReferenceBuilder.forSubmodel(submodel)), e); + } + return false; + } + + + @Override + public boolean update(Submodel submodel, AssetConnectionManager assetConnectionManager) { + Ensure.requireNonNull(submodel); + Ensure.requireNonNull(assetConnectionManager); + if (Util.semanticIdEquals(submodel, Constants.AID_SUBMODEL_SEMANTIC_ID)) { + handleAidChange(submodel, assetConnectionManager, ProcessingMode.UPDATE); + return false; + } + updateAidToAimcRelations(submodel, ProcessingMode.UPDATE); + try { + LOGGER.atInfo().log("update submodel {} ({})", submodel.getIdShort(), ReferenceHelper.asString(ReferenceBuilder.forSubmodel(submodel))); + processSubmodel(submodel, assetConnectionManager, ProcessingMode.UPDATE); + } + catch (Exception e) { + LOGGER.error("error updating SMT AIMC (submodel: {})", ReferenceHelper.asString(ReferenceBuilder.forSubmodel(submodel)), e); + } + return false; + } + + + @Override + public boolean delete(Submodel submodel, AssetConnectionManager assetConnectionManager) { + Ensure.requireNonNull(submodel); + Ensure.requireNonNull(assetConnectionManager); + if (Util.semanticIdEquals(submodel, Constants.AID_SUBMODEL_SEMANTIC_ID)) { + handleAidChange(submodel, assetConnectionManager, ProcessingMode.DELETE); + return false; + } + updateAidToAimcRelations(submodel, ProcessingMode.DELETE); + try { + LOGGER.atInfo().log("delete submodel {} ({})", submodel.getIdShort(), ReferenceHelper.asString(ReferenceBuilder.forSubmodel(submodel))); + processSubmodel(submodel, assetConnectionManager, ProcessingMode.DELETE); + } + catch (Exception e) { + LOGGER.error("error deleting SMT AIMC (submodel: {})", ReferenceHelper.asString(ReferenceBuilder.forSubmodel(submodel)), e); + } + return false; + } + + + private void handleAidChange(Submodel submodel, AssetConnectionManager assetConnectionManager, ProcessingMode mode) { + String aidSubmodelId = submodel.getId(); + if (!aidToAimcRelations.containsKey(aidSubmodelId)) { + if (mode == ProcessingMode.ADD) { + aidToAimcRelations.put(aidSubmodelId, new HashSet<>()); + } + return; + } + aidToAimcRelations.get(aidSubmodelId).forEach(x -> { + try { + // if an AID Submodel is deleted, we process this like an update + processSubmodel(getSubmodel(x), assetConnectionManager, mode == ProcessingMode.DELETE ? ProcessingMode.UPDATE : mode); + } + catch (Exception e) { + LOGGER.warn("Failed to update AIMC submodel (submodelId: {})", x); + } + }); + } + + + private void updateAidToAimcRelations(Submodel submodel, ProcessingMode mode) { + List aidSubmodelIds = SemanticIdPath.builder() + .globalReference(Constants.AIMC_MAPPING_CONFIGURATIONS_SEMANTIC_ID) + .globalReference(Constants.AIMC_CONFIGURATION_SEMANTIC_ID) + .globalReference(Constants.AIMC_INTERFACE_REFERENCE_SEMANTIC_ID) + .build() + .resolve(submodel, ReferenceElement.class) + .stream() + .map(ReferenceElement::getValue) + .map(SubmodelElementIdentifier::fromReference) + .map(SubmodelElementIdentifier::getSubmodelId).toList(); + String aimcSubmodelId = submodel.getId(); + aidSubmodelIds.forEach(x -> { + if ((mode == ProcessingMode.DELETE || mode == ProcessingMode.UPDATE) && aidToAimcRelations.containsKey(x)) { + aidToAimcRelations.get(x).remove(aimcSubmodelId); + } + + if (mode == ProcessingMode.ADD || mode == ProcessingMode.UPDATE) { + if (!aidToAimcRelations.containsKey(x)) { + aidToAimcRelations.put(x, new HashSet<>()); + } + aidToAimcRelations.get(x).add(aimcSubmodelId); + } + }); + } + + + private void processSubmodel(Submodel submodel, AssetConnectionManager assetConnectionManager, ProcessingMode mode) + throws PersistenceException, ResourceNotFoundException, MalformedURLException, ConfigurationException, AssetConnectionException, URISyntaxException, Exception { + List configs = new ArrayList<>(); + if (mode != ProcessingMode.DELETE) { + SemanticIdPath.builder() + .globalReference(Constants.AIMC_MAPPING_CONFIGURATIONS_SEMANTIC_ID) + .globalReference(Constants.AIMC_CONFIGURATION_SEMANTIC_ID) + .build() + .resolve(submodel, SubmodelElementCollection.class) + .forEach(LambdaExceptionHelper.rethrowConsumer(x -> configs.add(processConfiguration(x)))); + configs.removeAll(Collections.singleton(null)); + } + List old = null; + if (connectionsCurrent.containsKey(submodel.getId())) { + old = connectionsCurrent.get(submodel.getId()); + } + log(assetConnectionManager.updateConnections(old, configs)); + + connectionsCurrent.put(submodel.getId(), configs); + } + + + private void log(List messages) { + for (var message: messages) { + switch (message.getMessageType()) { + case ERROR -> LOGGER.error(message.getText()); + case EXCEPTION -> LOGGER.error(message.getText()); + case INFO -> LOGGER.info(message.getText()); + case WARNING -> LOGGER.warn(message.getText()); + default -> LOGGER.debug(message.getText()); + } + } + } + + + private AssetConnectionConfig processConfiguration(SubmodelElementCollection configuration) + throws PersistenceException, ResourceNotFoundException, MalformedURLException, ConfigurationException, AssetConnectionException, URISyntaxException { + List relations = SemanticIdPath.builder() + .globalReference(Constants.AIMC_MAPPING_RELATIONS_SEMANTIC_ID) + .globalReference(Constants.AIMC_MAPPING_RELATION_SEMANTIC_ID) + .build() + .resolve(configuration, RelationshipElement.class); + return processInterfaceReference(configuration, relations); + } + + + private Referable getElement(Reference reference) { + SubmodelElementIdentifier identifier = SubmodelElementIdentifier.fromReference(reference); + // need to make sure serviceContext.execute already ready to receive requests! + return serviceContext.execute(GetSubmodelElementByPathRequest.builder() + .internal() + .submodelId(identifier.getSubmodelId()) + .path(identifier.getIdShortPath().toString()) + .build()) + .getPayload(); + } + + + private Submodel getSubmodel(String submodelId) { + return serviceContext.execute(GetSubmodelByIdRequest.builder() + .internal() + .id(submodelId) + .build()).getPayload(); + } + + + private AssetConnectionConfig processInterfaceReference(SubmodelElementCollection configuration, List relations) + throws ResourceNotFoundException, ConfigurationException, PersistenceException, MalformedURLException, AssetConnectionException, IllegalArgumentException, + URISyntaxException { + Reference interfaceReferenceValue = SemanticIdPath.builder() + .globalReference(Constants.AIMC_INTERFACE_REFERENCE_SEMANTIC_ID) + .build() + .resolveUnique(configuration, ReferenceElement.class) + .getValue(); + Referable referenceElement = getElement(interfaceReferenceValue); + if (!(referenceElement instanceof SubmodelElementCollection)) { + LOGGER.warn("Invalid AIMC configuration - target of InterfaceReference is not of type SubmodelElementCollection"); + return null; + } + SubmodelElementCollection assetInterface = (SubmodelElementCollection) referenceElement; + if (!Util.semanticIdEquals(assetInterface, Constants.AID_INTERFACE_SEMANTIC_ID)) { + LOGGER.atWarn().log("Invalid AIMC configuration - target of InterfaceReference does not have correct semanticId (expected: {}, actual: {})", + Constants.AID_INTERFACE_SEMANTIC_ID, + ReferenceHelper.asString(assetInterface.getSemanticId())); + return null; + } + if (Objects.isNull(assetInterface.getSupplementalSemanticIds()) || assetInterface.getSupplementalSemanticIds().isEmpty()) { + LOGGER.atWarn().log( + "Invalid AIMC configuration - target of InterfaceReference does not have any supplementalSemanticId, but at least one is required (expected: {}, actual: {})", + Constants.AID_INTERFACE_SEMANTIC_ID, + ReferenceHelper.asString(assetInterface.getSemanticId())); + return null; + } + + if (Util.containsSupplementalSemanticId(assetInterface, Constants.AID_INTERFACE_SUPP_SEMANTIC_ID_HTTP)) { + // HTTP Interface + return HttpHelper.processInterface(serviceContext, assetInterface, relations, config.getCredentials()); + } + else if (Util.containsSupplementalSemanticId(assetInterface, Constants.AID_INTERFACE_SUPP_SEMANTIC_ID_MQTT)) { + // MQTT Interface + return MqttHelper.processInterface(serviceContext, assetInterface, relations, config.getCredentials()); + } + return null; + } + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/Constants.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/Constants.java new file mode 100644 index 000000000..2281361b8 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/Constants.java @@ -0,0 +1,57 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc; + +/** + * Constants related to SMT Asset Interfaces Mapping Configuration. + */ +public class Constants { + public static final String AID_SUBMODEL_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel"; + public static final String AIMC_SUBMODEL_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/Submodel"; + public static final String AID_INTERFACE_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface"; + public static final String AID_INTERFACE_SUPP_SEMANTIC_ID_HTTP = "http://www.w3.org/2011/http"; + public static final String AID_INTERFACE_SUPP_SEMANTIC_ID_MQTT = "http://www.w3.org/2011/mqtt"; + public static final String AID_INTERACTION_METADATA_SEMANTIC_ID = "https://www.w3.org/2019/wot/td#InteractionAffordance"; + public static final String AID_PROPERTY_TYPE = "type"; + public static final String AID_PROPERTY_PROPERTIES = "properties"; + public static final String AID_PROPERTY_OBSERVABLE = "observable"; + public static final String AID_SECURITY_NOSEC = "nosec_sc"; + public static final String AID_SECURITY_BASIC = "basic_sc"; + public static final String AID_TYPE_OBJECT = "object"; + public static final String AIMC_MAPPING_CONFIGURATIONS_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfigurations"; + public static final String AIMC_CONFIGURATION_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfiguration"; + public static final String AIMC_MAPPING_RELATIONS_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations"; + public static final String AIMC_MAPPING_RELATION_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation"; + public static final String AIMC_INTERFACE_REFERENCE_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference"; + + public static final String AID_PROPERTY_ROOT_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition"; + public static final String AID_PROPERTY_NESTED_SEMANTIC_ID = "https://www.w3.org/2019/wot/json-schema#propertyName"; + public static final String AID_PROPERTY_OBSERVABLE_SEMANTIC_ID = "https://www.w3.org/2019/wot/td#isObservable"; + public static final String AID_FORMS_HREF_SEMANTIC_ID = "https://www.w3.org/2019/wot/hypermedia#hasTarget"; + public static final String AID_METADATA_BASE_SEMANTIC_ID = "https://www.w3.org/2019/wot/td#baseURI"; + public static final String AID_CONTENT_TYPE_SEMANTIC_ID = "https://www.w3.org/2019/wot/hypermedia#forContentType"; + public static final String AID_INTERFACE_TITLE_SEMANTIC_ID = "https://www.w3.org/2019/wot/td#title"; + public static final String AID_ENDPOINT_METADATA_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata"; + public static final String AID_SECURITY_NOSEC_SEMANTIC_ID = "https://www.w3.org/2019/wot/security#NoSecurityScheme"; + public static final String AID_SECURITY_BASIC_SEMANTIC_ID = "https://www.w3.org/2019/wot/security#BasicSecurityScheme"; + public static final String AID_PROPERTY_KEY_SEMANTIC_ID = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key"; + public static final String AID_PROPERTY_FORMS_SEMANTIC_ID = "https://www.w3.org/2019/wot/td#hasForm"; + public static final String AID_METADATA_SECURITY_SEMANTIC_ID = "https://www.w3.org/2019/wot/td#hasSecurityConfiguration"; + public static final String AID_FORMS_HEADERS_SEMANTIC_ID = "https://www.w3.org/2011/http#headers"; + public static final String AID_HEADER_FIELD_NAME_SEMANTIC_ID = "https://www.w3.org/2011/http#fieldName"; + public static final String AID_HEADER_FIELD_VALUE_SEMANTIC_ID = "https://www.w3.org/2011/http#fieldValue"; + + private Constants() {} +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessingMode.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessingMode.java new file mode 100644 index 000000000..2863bf905 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessingMode.java @@ -0,0 +1,24 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc; + +/** + * Enumeration for the processing mode. + */ +public enum ProcessingMode { + ADD, + UPDATE, + DELETE +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfig.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfig.java new file mode 100644 index 000000000..f8a579b06 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfig.java @@ -0,0 +1,96 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.config; + +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.SubmodelTemplateProcessorConfig; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.AimcSubmodelTemplateProcessor; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; + + +/** + * Configuration for SMT Asset Interfaces Mapping Configuration processor. + */ +public class AimcSubmodelTemplateProcessorConfig extends SubmodelTemplateProcessorConfig { + + private Map> credentials; + + public AimcSubmodelTemplateProcessorConfig() { + credentials = new HashMap<>(); + } + + + public Map> getCredentials() { + return credentials; + } + + + public void setCredentials(Map> credentials) { + this.credentials = credentials; + } + + + @Override + public int hashCode() { + return Objects.hash(credentials); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AimcSubmodelTemplateProcessorConfig other = (AimcSubmodelTemplateProcessorConfig) obj; + return Objects.equals(credentials, other.credentials); + } + + protected abstract static class AbstractBuilder> + extends ExtendableBuilder { + + public B connectionLevelCredentials(Map> value) { + getBuildingInstance().setCredentials(value); + return getSelf(); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected AimcSubmodelTemplateProcessorConfig newBuildingInstance() { + return new AimcSubmodelTemplateProcessorConfig(); + } + } + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/BasicCredentials.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/BasicCredentials.java new file mode 100644 index 000000000..68a2715d3 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/BasicCredentials.java @@ -0,0 +1,77 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.config; + +import java.util.Objects; + + +/** + * Class for User credentials. + */ +public class BasicCredentials implements Credentials { + private String username; + private String password; + + public BasicCredentials() {} + + + public BasicCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + + 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; + } + + + @Override + public int hashCode() { + return Objects.hash(username, password); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BasicCredentials other = (BasicCredentials) obj; + return Objects.equals(username, other.username) + && Objects.equals(password, other.password); + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/Credentials.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/Credentials.java new file mode 100644 index 000000000..3fb0f95ca --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/Credentials.java @@ -0,0 +1,26 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.config; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + + +/** + * Interface for User credentials. + */ +@JsonDeserialize(as = BasicCredentials.class) +public interface Credentials { + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidEndpointMetadata.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidEndpointMetadata.java new file mode 100644 index 000000000..94757479a --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidEndpointMetadata.java @@ -0,0 +1,72 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +import de.fraunhofer.iosb.ilt.faaast.service.model.SemanticIdPath; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.Constants; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.util.Util; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; + + +/** + * Class with AID Endpoint metadata. + */ +public class AidEndpointMetadata { + private String base; + private String contentType; + + public String getBase() { + return base; + } + + + public String getContentType() { + return contentType; + } + + + /** + * Parse the AID Endpoint Metadata from the given SubmodelElement. + * + * @param element The desired SubmodelElement. + * @return The AID Endpoint Metadata. + */ + public static AidEndpointMetadata parse(SubmodelElement element) { + Ensure.requireNonNull(element, "element must be non-null"); + Ensure.require(element instanceof SubmodelElementCollection, "element must be a SubmodelElementCollection"); + Ensure.require( + Util.semanticIdEquals(element, Constants.AID_ENDPOINT_METADATA_SEMANTIC_ID), + String.format("Failed to read EndpointMetadata from AID - invalid semanticId (expected: %s, actual: %s)", + Constants.AID_ENDPOINT_METADATA_SEMANTIC_ID, + ReferenceHelper.asString(element.getSemanticId()))); + AidEndpointMetadata instance = new AidEndpointMetadata(); + instance.base = SemanticIdPath.builder() + .globalReference(Constants.AID_METADATA_BASE_SEMANTIC_ID) + .build() + .resolveUnique(element, Property.class) + .getValue(); + instance.contentType = SemanticIdPath.builder() + .globalReference(Constants.AID_CONTENT_TYPE_SEMANTIC_ID) + .build() + .resolveOptional(element, Property.class) + .map(Property::getValue) + .orElse(null); + return instance; + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidInterface.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidInterface.java new file mode 100644 index 000000000..1c6b24899 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/AidInterface.java @@ -0,0 +1,74 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +import de.fraunhofer.iosb.ilt.faaast.service.model.SemanticIdPath; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.Constants; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.util.Util; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; + + +/** + * Class with AID Interface data. + */ +public class AidInterface { + private String title; + private List endpointMetadata; + + public String getTitle() { + return title; + } + + + public List getEndpointMetadata() { + return endpointMetadata; + } + + + /** + * Parse the AID Interface from the given SubmodelElement. + * + * @param element The desired SubmodelElement. + * @return The AID Interface. + */ + public static AidInterface parse(SubmodelElement element) { + Ensure.requireNonNull(element, "element must be non-null"); + Ensure.require(element instanceof SubmodelElementCollection, "element must be a SubmodelElementCollection"); + Ensure.require( + Util.semanticIdEquals(element, Constants.AID_ENDPOINT_METADATA_SEMANTIC_ID), + String.format("Failed to read Interface from AID - invalid semanticId (expected: %s, actual: %s)", + Constants.AID_INTERFACE_SEMANTIC_ID, + ReferenceHelper.asString(element.getSemanticId()))); + AidInterface instance = new AidInterface(); + instance.title = SemanticIdPath.builder() + .globalReference(Constants.AID_INTERFACE_TITLE_SEMANTIC_ID) + .build() + .resolveUnique(element, Property.class) + .getValue(); + instance.endpointMetadata = SemanticIdPath.builder() + .globalReference(Constants.AID_ENDPOINT_METADATA_SEMANTIC_ID) + .build() + .resolve(element, SubmodelElementCollection.class).stream() + .map(AidEndpointMetadata::parse) + .toList(); + return instance; + } + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceData.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceData.java new file mode 100644 index 000000000..abd896bcb --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceData.java @@ -0,0 +1,32 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +/** + * Class with data for interfaces. + */ +public abstract class InterfaceData { + + private String baseUrl; + + public String getBaseUrl() { + return baseUrl; + } + + + public void setBaseUrl(String value) { + baseUrl = value; + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataHttp.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataHttp.java new file mode 100644 index 000000000..69c3db05f --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataHttp.java @@ -0,0 +1,96 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpValueProviderConfig; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; + + +/** + * Class with data for HTTP interfaces. + */ +public class InterfaceDataHttp extends InterfaceData { + + private Map valueProvider; + private Map subscriptionProvider; + + public InterfaceDataHttp() { + valueProvider = new HashMap<>(); + subscriptionProvider = new HashMap<>(); + } + + + /** + * Gets the value providers. + * + * @return The value providers. + */ + public Map getValueProvider() { + return valueProvider; + } + + + /** + * Sets the value providers. + * + * @param value The value providers. + */ + public void setValueProvider(Map value) { + valueProvider = value; + } + + + /** + * Adds the given list of value provider. + * + * @param value The desired value providers. + */ + public void addValueProvider(Map value) { + valueProvider.putAll(value); + } + + + /** + * Gets the subscription providers. + * + * @return The subscription providers. + */ + public Map getSubscriptionProvider() { + return subscriptionProvider; + } + + + /** + * Sets the subscription providers. + * + * @param value The subscription providers. + */ + public void setSubscriptionProvider(Map value) { + subscriptionProvider = value; + } + + + /** + * Adds the fiven subscription providers. + * + * @param value The desired subscription providers. + */ + public void addSubscriptionProviders(Map value) { + subscriptionProvider.putAll(value); + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataMqtt.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataMqtt.java new file mode 100644 index 000000000..f1c5c6a5f --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/InterfaceDataMqtt.java @@ -0,0 +1,96 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config.MqttSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config.MqttValueProviderConfig; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; + + +/** + * Class with data for MQTT interfaces. + */ +public class InterfaceDataMqtt extends InterfaceData { + + private Map valueProvider; + private Map subscriptionProvider; + + public InterfaceDataMqtt() { + valueProvider = new HashMap<>(); + subscriptionProvider = new HashMap<>(); + } + + + /** + * Gets the value providers. + * + * @return The value providers. + */ + public Map getValueProvider() { + return valueProvider; + } + + + /** + * Sets the value providers. + * + * @param value The value providers. + */ + public void setValueProvider(Map value) { + valueProvider = value; + } + + + /** + * Adds the given list of value provider. + * + * @param value The desired value providers. + */ + public void addValueProvider(Map value) { + valueProvider.putAll(value); + } + + + /** + * Gets the subscription providers. + * + * @return The subscription providers. + */ + public Map getSubscriptionProvider() { + return subscriptionProvider; + } + + + /** + * Sets the subscription providers. + * + * @param value The subscription providers. + */ + public void setSubscriptionProvider(Map value) { + subscriptionProvider = value; + } + + + /** + * Adds the fiven subscription providers. + * + * @param value The desired subscription providers. + */ + public void addSubscriptionProviders(Map value) { + subscriptionProvider.putAll(value); + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Protocol.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Protocol.java new file mode 100644 index 000000000..9d47e7e2f --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Protocol.java @@ -0,0 +1,24 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +/** + * Enumeration of protocolls. + */ +public enum Protocol { + HTTP, + MQTT, + OPC_UA +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/ProviderType.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/ProviderType.java new file mode 100644 index 000000000..3fa725014 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/ProviderType.java @@ -0,0 +1,24 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +/** + * Enumeration of Provider types. + */ +public enum ProviderType { + VALUE, + SUBSCRIPTION, + OPERATION +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/RelationData.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/RelationData.java new file mode 100644 index 000000000..3090d4b12 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/RelationData.java @@ -0,0 +1,67 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; + + +/** + * Class with data of AIMC relations. + */ +public class RelationData { + + private final ServiceContext serviceContext; + private final List relations; + private final String contentType; + + public RelationData(ServiceContext serviceContext, List relations, String contentType) { + this.serviceContext = serviceContext; + this.relations = relations; + this.contentType = contentType; + } + + + /** + * Get the service context. + * + * @return the serviceContext + */ + public ServiceContext getServiceContext() { + return serviceContext; + } + + + /** + * Get the list of relations. + * + * @return the relations + */ + public List getRelations() { + return relations; + } + + + /** + * Get the content type. + * + * @return the contentType + */ + public String getContentType() { + return contentType; + } + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/HttpHelper.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/HttpHelper.java new file mode 100644 index 000000000..0bf8db226 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/HttpHelper.java @@ -0,0 +1,249 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.util; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.HttpAssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpValueProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.Constants; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.config.BasicCredentials; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.config.Credentials; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.model.RelationData; +import de.fraunhofer.iosb.ilt.faaast.service.util.EnvironmentHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Helper class for HTTP connections. + */ +public class HttpHelper { + + public static final long DEFAULT_INTERVAL = 1000; + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + private HttpHelper() {} + + + /** + * Process a HTTP interface. + * + * @param serviceContext The service context. + * @param assetInterface The desired Asset Interface. + * @param relations The list of rekations. + * @param credentials The list of credentials. + * @return The Asset Connection configuration from this interface. + * @throws MalformedURLException Invalif URL. + * @throws PersistenceException if storage error occurs + * @throws ResourceNotFoundException if the resource dcesn't exist. + */ + public static AssetConnectionConfig processInterface(ServiceContext serviceContext, SubmodelElementCollection assetInterface, + List relations, Map> credentials) + throws MalformedURLException, PersistenceException, ResourceNotFoundException { + String title = Util.getInterfaceTitle(assetInterface); + LOGGER.debug("process HTTP interface {} with {} relations", title, relations.size()); + + // Endpoint Metadata + SubmodelElementCollection metadata = Util.getEndpointMetadata(assetInterface); + String base = Util.getBaseUrl(metadata); + + // contentType + String contentType = Util.getContentType(metadata); + + Map valueProviders = new HashMap<>(); + Map subscriptionProviders = new HashMap<>(); + + processRelations(new RelationData(serviceContext, relations, contentType), subscriptionProviders, base, valueProviders); + + HttpAssetConnectionConfig.Builder assetConfigBuilder = HttpAssetConnectionConfig.builder().baseUrl(base); + + List serverCredentials = new ArrayList<>(); + if (credentials.containsKey(base)) { + serverCredentials = credentials.get(base); + } + + // security + Optional element = metadata.getValue().stream().filter(e -> Util.semanticIdEquals(e, Constants.AID_METADATA_SECURITY_SEMANTIC_ID)).findFirst(); + if (element.isEmpty()) { + throw new IllegalArgumentException("Submodel AID (HTTP) invalid: EndpointMetadata security not found."); + } + else if (element.get() instanceof SubmodelElementList securityList) { + assetConfigBuilder = configureSecurity(serviceContext, securityList, assetConfigBuilder, serverCredentials); + } + + return assetConfigBuilder + .valueProviders(valueProviders) + .subscriptionProviders(subscriptionProviders) + .build(); + } + + + private static void processRelations(RelationData data, + Map subscriptionProviders, String base, + Map valueProviders) + throws PersistenceException, ResourceNotFoundException { + for (var r: data.getRelations()) { + if (EnvironmentHelper.resolve(r.getFirst(), data.getServiceContext().getAASEnvironment()) instanceof SubmodelElementCollection property) { + if (isObservable(property)) { + LOGGER.atDebug().log("processRelations: createSubscriptionProvider for: {}", ReferenceHelper.asString(r.getSecond())); + subscriptionProviders.put(r.getSecond(), createSubscriptionProvider(property, base, data, r.getFirst())); + } + else { + LOGGER.atDebug().log("processRelations: createValueProvider for: {}", ReferenceHelper.asString(r.getSecond())); + valueProviders.put(r.getSecond(), createValueProvider(property, base, data, r.getFirst())); + } + } + } + } + + + private static HttpSubscriptionProviderConfig createSubscriptionProvider(SubmodelElementCollection property, String baseUrl, + RelationData data, Reference propertyReference) + throws PersistenceException, ResourceNotFoundException { + HttpSubscriptionProviderConfig retval; + + SubmodelElementCollection forms = Util.getPropertyForms(property, propertyReference, data); + String contentType = Util.getContentType(data.getContentType(), forms); + + String path = getPath(baseUrl, forms); + Map headers = getHeaders(forms); + LOGGER.debug("createSubscriptionProvider: href: {}; contentType: {}", path, contentType); + String jsonPath = Util.getJsonPath(property, propertyReference, data); + HttpSubscriptionProviderConfig.Builder configBuilder = HttpSubscriptionProviderConfig.builder() + .format(Util.getFormatFromContentType(contentType)) + .path(path) + .headers(headers); + if (!jsonPath.isEmpty()) { + configBuilder.query(jsonPath); + } + retval = configBuilder.build(); + return retval; + } + + + private static HttpValueProviderConfig createValueProvider(SubmodelElementCollection property, String baseUrl, RelationData data, Reference propertyReference) + throws PersistenceException, ResourceNotFoundException { + HttpValueProviderConfig retval; + + SubmodelElementCollection forms = Util.getPropertyForms(property, propertyReference, data); + String contentType = Util.getContentType(data.getContentType(), forms); + + String path = getPath(baseUrl, forms); + Map headers = getHeaders(forms); + LOGGER.debug("createValueProvider: href: {}; contentType: {}", path, contentType); + String jsonPath = Util.getJsonPath(property, propertyReference, data); + HttpValueProviderConfig.Builder configBuilder = HttpValueProviderConfig.builder() + .format(Util.getFormatFromContentType(contentType)) + .path(path) + .headers(headers); + if (!jsonPath.isEmpty()) { + configBuilder.query(jsonPath); + } + retval = configBuilder.build(); + + return retval; + } + + + private static Map getHeaders(SubmodelElementCollection forms) { + Map retval = new HashMap<>(); + Optional element = forms.getValue().stream().filter(e -> Util.semanticIdEquals(e, Constants.AID_FORMS_HEADERS_SEMANTIC_ID)).findFirst(); + if (element.isPresent() && (element.get() instanceof SubmodelElementList list)) { + for (var h: list.getValue()) { + addHeader(retval, h); + } + } + return retval; + } + + + private static void addHeader(Map headers, SubmodelElement headerElement) { + if (headerElement instanceof SubmodelElementCollection header) { + Optional nameElement = header.getValue().stream().filter(h -> Util.semanticIdEquals(h, Constants.AID_HEADER_FIELD_NAME_SEMANTIC_ID)).findFirst(); + Optional valueElement = header.getValue().stream().filter(h -> Util.semanticIdEquals(h, Constants.AID_HEADER_FIELD_VALUE_SEMANTIC_ID)).findFirst(); + if (nameElement.isPresent() && valueElement.isPresent() && (nameElement.get() instanceof Property name) && (valueElement.get() instanceof Property value)) { + headers.put(name.getValue(), value.getValue()); + } + } + } + + + private static String getPath(String baseUrl, SubmodelElementCollection forms) throws IllegalArgumentException { + String retval = Util.getFormsHref(forms); + // make path relative to baseUrl + if (retval.startsWith(baseUrl)) { + retval = retval.substring(0, baseUrl.length()); + } + return retval; + } + + + //private static HttpAssetConnectionConfig.Builder configureSecurity(ServiceContext serviceContext, InterfaceConfiguration config, + private static HttpAssetConnectionConfig.Builder configureSecurity(ServiceContext serviceContext, SubmodelElementList securityList, + HttpAssetConnectionConfig.Builder assetConfigBuilder, List credentials) + throws ResourceNotFoundException, PersistenceException { + HttpAssetConnectionConfig.Builder retval = assetConfigBuilder; + List supportedSecurity = Util.getSupportedSecurityList(serviceContext, securityList); + + if (supportedSecurity.contains(Constants.AID_SECURITY_NOSEC)) { + // no security found. We choose that. + LOGGER.trace("configureSecurity: use no security"); + } + else if (supportedSecurity.contains(Constants.AID_SECURITY_BASIC)) { + // use basic security. Username and password are used from the configuration. + LOGGER.trace("configureSecurity: use basic security"); + Optional basic = credentials.stream().filter(BasicCredentials.class::isInstance).map(c -> (BasicCredentials) c).findFirst(); + if (basic.isEmpty()) { + LOGGER.warn("configureSecurity: basic security configured, but no username given"); + } + else { + retval = retval.username(basic.get().getUsername()).password(basic.get().getPassword()); + } + } + + return retval; + } + + + private static boolean isObservable(SubmodelElementCollection property) { + boolean retval = false; + // only available in the root object + Optional element = property.getValue().stream().filter(e -> Util.semanticIdEquals(e, Constants.AID_PROPERTY_OBSERVABLE_SEMANTIC_ID)).findFirst(); + if (element.isPresent() && (element.get() instanceof Property prop)) { + String obsText = prop.getValue(); + retval = Boolean.parseBoolean(obsText); + } + return retval; + } + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/MqttHelper.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/MqttHelper.java new file mode 100644 index 000000000..4d933efea --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/MqttHelper.java @@ -0,0 +1,164 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.util; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.MqttAssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config.MqttSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.Constants; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.config.BasicCredentials; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.config.Credentials; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.model.RelationData; +import de.fraunhofer.iosb.ilt.faaast.service.util.EnvironmentHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Helper class for MQTT connections. + */ +public class MqttHelper { + + private static final Logger LOGGER = LoggerFactory.getLogger(MqttHelper.class); + + private MqttHelper() {} + + + /** + * Process a MQTT interface. + * + * @param serviceContext The service context. + * @param assetInterface The desired Asset Interface. + * @param relations The list of rekations. + * @param credentials The list of credentials. + * @return The Asset Connection configuration from this interface. + * @throws PersistenceException if a storage error occurs. + * @throws ResourceNotFoundException if the resource dcesn't exist.. + */ + public static AssetConnectionConfig processInterface(ServiceContext serviceContext, SubmodelElementCollection assetInterface, + List relations, Map> credentials) + throws ResourceNotFoundException, PersistenceException { + String title = Util.getInterfaceTitle(assetInterface); + LOGGER.debug("process MQTT interface {} with {} relations", title, relations.size()); + + // Endpoint Metadata + SubmodelElementCollection metadata = Util.getEndpointMetadata(assetInterface); + + // base + String base = Util.getBaseUrl(metadata); + + // contentType + String contentType = Util.getContentType(metadata); + + Map subscriptionProviders = new HashMap<>(); + + processRelations(new RelationData(serviceContext, relations, contentType), subscriptionProviders); + + List serverCredentials = new ArrayList<>(); + if (credentials.containsKey(base)) { + serverCredentials = credentials.get(base); + } + + MqttAssetConnectionConfig.Builder assetConfigBuilder = MqttAssetConnectionConfig.builder().serverUri(base); + + // security + Optional element = metadata.getValue().stream().filter(e -> Util.semanticIdEquals(e, Constants.AID_METADATA_SECURITY_SEMANTIC_ID)).findFirst(); + if (element.isEmpty()) { + throw new IllegalArgumentException("Submodel AID (MQTT) invalid: EndpointMetadata security not found."); + } + else if (element.get() instanceof SubmodelElementList securityList) { + assetConfigBuilder = configureSecurity(serviceContext, securityList, assetConfigBuilder, serverCredentials); + } + + LOGGER.debug("processInterface: add {} subscriptionProviders", subscriptionProviders.size()); + return assetConfigBuilder + .subscriptionProviders(subscriptionProviders) + .build(); + } + + + private static MqttSubscriptionProviderConfig createSubscriptionProvider(SubmodelElementCollection property, RelationData data, Reference propertyReference) + throws PersistenceException, ResourceNotFoundException { + MqttSubscriptionProviderConfig retval; + + SubmodelElementCollection forms = Util.getPropertyForms(property, propertyReference, data); + String contentType = Util.getContentType(data.getContentType(), forms); + + String href = Util.getFormsHref(forms); + LOGGER.debug("createSubscriptionProvider: href: {}; contentType: {}", href, contentType); + String jsonPath = Util.getJsonPath(property, propertyReference, data); + MqttSubscriptionProviderConfig.Builder configBuilder = MqttSubscriptionProviderConfig.builder() + .format(Util.getFormatFromContentType(contentType)) + .topic(href); + if (!jsonPath.isEmpty()) { + configBuilder.query(jsonPath); + } + retval = configBuilder.build(); + return retval; + + } + + + private static void processRelations(RelationData data, Map subscriptionProviders) + throws PersistenceException, ResourceNotFoundException { + for (var r: data.getRelations()) { + if (EnvironmentHelper.resolve(r.getFirst(), data.getServiceContext().getAASEnvironment()) instanceof SubmodelElementCollection property) { + LOGGER.atDebug().log("processRelations: createSubscriptionProvider for: {}", ReferenceHelper.asString(r.getSecond())); + subscriptionProviders.put(r.getSecond(), createSubscriptionProvider(property, data, r.getFirst())); + } + } + } + + + private static MqttAssetConnectionConfig.Builder configureSecurity(ServiceContext serviceContext, SubmodelElementList securityList, + MqttAssetConnectionConfig.Builder assetConfigBuilder, List credentials) + throws ResourceNotFoundException, PersistenceException { + MqttAssetConnectionConfig.Builder retval = assetConfigBuilder; + List supportedSecurity = Util.getSupportedSecurityList(serviceContext, securityList); + + if (supportedSecurity.contains(Constants.AID_SECURITY_NOSEC)) { + // no security found. We choose that. + LOGGER.trace("configureSecurity: use no security"); + } + else if (supportedSecurity.contains(Constants.AID_SECURITY_BASIC)) { + // use basic security. Username and password are used from the configuration. + LOGGER.trace("configureSecurity: use basic security"); + Optional basic = credentials.stream().filter(BasicCredentials.class::isInstance).map(c -> (BasicCredentials) c).findFirst(); + if (basic.isEmpty()) { + LOGGER.warn("configureSecurity: basic security configured, but no username given"); + } + else { + retval = retval.username(basic.get().getUsername()).password(basic.get().getPassword()); + } + } + + return retval; + } + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/Util.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/Util.java new file mode 100644 index 000000000..0fb2be8f2 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/util/Util.java @@ -0,0 +1,386 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.util; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.model.SemanticIdPath; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.Constants; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.model.RelationData; +import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.EnvironmentHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.model.HasSemantics; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; + + +/** + * Class with general utilty methods. + */ +public class Util { + + private Util() {} + + + /** + * Extracts the Forms property from the given SubmodelElementCollection. + * + * @param property The list of properties. + * @param propertyReference The refence to the property. + * @param data The relation data. + * @return The Forms property. + * @throws PersistenceException if storage error occurs + * @throws ResourceNotFoundException if the resource dcesn't exist. + */ + public static SubmodelElementCollection getPropertyForms(SubmodelElementCollection property, Reference propertyReference, RelationData data) + throws PersistenceException, ResourceNotFoundException { + // search root object + SubmodelElementCollection current = property; + Reference currentReference = propertyReference; + while (semanticIdEquals(current, Constants.AID_PROPERTY_NESTED_SEMANTIC_ID)) { + Reference grandParent = getGrandParent(currentReference); + if ((grandParent != null) + && (EnvironmentHelper.resolve(grandParent, data.getServiceContext().getAASEnvironment()) instanceof SubmodelElementCollection grandParentObject)) { + current = grandParentObject; + currentReference = grandParent; + } + else { + throw new IllegalArgumentException("Submodel AID invalid: Root Property not found."); + } + } + + Optional element; + if (semanticIdEquals(current, Constants.AID_PROPERTY_ROOT_SEMANTIC_ID)) { + element = SemanticIdPath.builder() + .globalReference(Constants.AID_PROPERTY_FORMS_SEMANTIC_ID) + .build() + .resolveOptional(current, SubmodelElementCollection.class); + + if (element.isEmpty()) { + throw new IllegalArgumentException("Submodel AID invalid: Property forms not found."); + } + } + else { + throw new IllegalArgumentException("Submodel AID invalid: Root Property not found."); + } + return element.get(); + } + + + /** + * Converts the content type in MIME format to the desired format string. + * + * @param contentType The content type in MIME format. + * @return The desired format. + */ + public static String getFormatFromContentType(String contentType) { + Ensure.requireNonNull(contentType); + switch (contentType) { + case "application/xml", "text/xml": + return "XML"; + + case "application/json": + return "JSON"; + + default: + throw new IllegalArgumentException("unsupported contentType: " + contentType); + } + } + + + /** + * Extracts the Href property from the Forms Property list. + * + * @param forms The Forms Property list. + * @return The desired Href property. + */ + public static String getFormsHref(SubmodelElementCollection forms) { + return SemanticIdPath.builder() + .globalReference(Constants.AID_FORMS_HREF_SEMANTIC_ID) + .build() + .resolveUnique(forms, Property.class) + .getValue(); + } + + + /** + * Extracts the Base URL from the metadata. + * + * @param metadata The Metadata. + * @return The base URL. + */ + public static String getBaseUrl(SubmodelElementCollection metadata) { + return SemanticIdPath.builder() + .globalReference(Constants.AID_METADATA_BASE_SEMANTIC_ID) + .build() + .resolveUnique(metadata, Property.class) + .getValue(); + } + + + /** + * Extracts the ContentType from the metadata. + * + * @param metadata The Metadata. + * @return The ContentType. + */ + public static String getContentType(SubmodelElementCollection metadata) { + return SemanticIdPath.builder() + .globalReference(Constants.AID_CONTENT_TYPE_SEMANTIC_ID) + .build() + .resolveUnique(metadata, Property.class) + .getValue(); + } + + + /** + * Get the interface title from the interface. + * + * @param assetInterface The desired interface. + * @return The title. + */ + public static String getInterfaceTitle(SubmodelElementCollection assetInterface) { + return SemanticIdPath.builder() + .globalReference(Constants.AID_INTERFACE_TITLE_SEMANTIC_ID) + .build() + .resolveUnique(assetInterface, Property.class) + .getValue(); + } + + + /** + * Gets the Endpoint Metadata from the interface. + * + * @param assetInterface The desired interface. + * @return The Endpoint Metadata. + */ + public static SubmodelElementCollection getEndpointMetadata(SubmodelElementCollection assetInterface) { + return SemanticIdPath.builder() + .globalReference(Constants.AID_ENDPOINT_METADATA_SEMANTIC_ID) + .build() + .resolveUnique(assetInterface, SubmodelElementCollection.class); + } + + + /** + * Extracts the list of supported security definitions. + * + * @param serviceContext The serviceContext. + * @param securityList The complete list of security definitions. + * @return The list of supported security definitions + * @throws PersistenceException if a storage error occurs. + * @throws ResourceNotFoundException if the resource dcesn't exist. + */ + public static List getSupportedSecurityList(ServiceContext serviceContext, SubmodelElementList securityList) throws PersistenceException, ResourceNotFoundException { + List supportedSecurity = new ArrayList<>(); + for (SubmodelElement se: securityList.getValue()) { + if (se instanceof ReferenceElement refElem) { + Referable securityReferable = EnvironmentHelper.resolve(refElem.getValue(), serviceContext.getAASEnvironment()); + if (securityReferable instanceof SubmodelElement securityElement) { + if (semanticIdEquals(securityElement, Constants.AID_SECURITY_NOSEC_SEMANTIC_ID)) { + supportedSecurity.add(Constants.AID_SECURITY_NOSEC); + } + else if (semanticIdEquals(securityElement, Constants.AID_SECURITY_BASIC_SEMANTIC_ID)) { + supportedSecurity.add(Constants.AID_SECURITY_BASIC); + } + } + else { + throw new IllegalArgumentException("SecurityElement invalid: no SubmodelElement"); + } + } + } + + return supportedSecurity; + } + + + /** + * Gets the content for the desired element. + * + * @param baseContentType The base content type. + * @param forms The forms attribute of the desired element. + * @return The contentr type of the element. + */ + public static String getContentType(String baseContentType, SubmodelElementCollection forms) { + String contentType = baseContentType; + Optional prop = SemanticIdPath.builder() + .globalReference(Constants.AID_CONTENT_TYPE_SEMANTIC_ID) + .build() + .resolveOptional(forms, Property.class); + if (prop.isPresent()) { + contentType = prop.get().getValue(); + } + return contentType; + } + + + /** + * Creates the JsonPath from the given list of keys. + * + * @param pathList The list of keys. + * @return The Jsonpath. + */ + public static String createJsonPath(List pathList) { + if ((pathList == null) || pathList.isEmpty()) { + return ""; + } + else { + StringBuilder path = new StringBuilder("$"); + for (String s: pathList) { + path.append("."); + path.append(s); + } + return path.toString(); + } + } + + + /** + * Checks if the given object is the InteractionMetadata or not. + * + * @param object The object to check. + * @return True if it's the InteractionMetadata object, false if not. + */ + public static boolean isInteractionMetadata(SubmodelElementCollection object) { + return semanticIdEquals(object, Constants.AID_INTERACTION_METADATA_SEMANTIC_ID); + } + + + /** + * Gets the Key for the given object. + * + * @param object The desired object. + * @return The key for the object. + */ + public static String getKey(SubmodelElementCollection object) { + String retval = object.getIdShort(); + Optional prop = SemanticIdPath.builder() + .globalReference(Constants.AID_PROPERTY_KEY_SEMANTIC_ID) + .build() + .resolveOptional(object, Property.class); + if (prop.isPresent()) { + retval = prop.get().getValue(); + } + return retval; + } + + + /** + * Gets the grandparent of the given object. + * + * @param reference The reference of the desired object. + * @return The refence to the grandparent, null if not available. + */ + public static Reference getGrandParent(Reference reference) { + Reference parent = ReferenceHelper.getParent(reference); + if (parent != null) { + return ReferenceHelper.getParent(parent); + } + return null; + } + + + /** + * Checks if the SemanticId of the given object is the same as the given SemanticId. + * As GlobalReference and ConceptDescription is possible, we only compare the key value. + * + * @param object The object whose reference is to be compared. + * @param semanticId The SemanticId String to compare. Must not be null. + * @return True if it's equal, false if not. + */ + public static boolean semanticIdEquals(HasSemantics object, String semanticId) { + if ((object != null) && (object.getSemanticId() != null)) { + Reference ref = object.getSemanticId(); + return semanticReferenceEquals(ref, semanticId); + } + else { + return false; + } + } + + + /** + * Checks if the given SemanticId is contained in the SupplementalSemanticIds. + * As GlobalReference and ConceptDescription is possible, we only compare the key value. + * + * @param object The object whose reference is to be compared. + * @param semanticId The SemanticId String to compare. Must not be null. + * @return True if it's cinatined, false if not. + */ + public static boolean containsSupplementalSemanticId(HasSemantics object, String semanticId) { + if ((object != null) && (object.getSupplementalSemanticIds() != null)) { + for (Reference ref: object.getSupplementalSemanticIds()) { + if (semanticReferenceEquals(ref, semanticId)) { + return true; + } + } + } + return false; + } + + + /** + * Gets the JSON query path for the specified property. + * + * @param property The desired property. + * @param propertyReference The reference for the given property. + * @param data The relation data. + * @return The JSON query path. + * @throws PersistenceException if storage error occurs + * @throws ResourceNotFoundException if the resource dcesn't exist. + */ + public static String getJsonPath(SubmodelElementCollection property, Reference propertyReference, RelationData data) throws PersistenceException, ResourceNotFoundException { + List pathList = new ArrayList<>(); + boolean ende = false; + pathList.add(Util.getKey(property)); + Reference current = propertyReference; + while (!ende) { + ende = true; + Reference parent = ReferenceHelper.getParent(current); + if (parent != null) { + Reference grandParent = ReferenceHelper.getParent(parent); + if ((grandParent != null) + && (EnvironmentHelper.resolve(grandParent, data.getServiceContext().getAASEnvironment()) instanceof SubmodelElementCollection grandParentObject) + && (!Util.isInteractionMetadata(grandParentObject))) { + pathList.add(Util.getKey(grandParentObject)); + current = grandParent; + ende = false; + } + } + } + // reverse the order and remove the top level object + if (!pathList.isEmpty()) { + Collections.reverse(pathList); + pathList.remove(0); + } + return Util.createJsonPath(pathList); + } + + + private static boolean semanticReferenceEquals(Reference ref, String semanticId) { + return (ref.getKeys().size() == 1) && semanticId.equals(ref.getKeys().get(0).getValue()); + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessorIT.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessorIT.java new file mode 100644 index 000000000..bc37e4b7f --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/AimcSubmodelTemplateProcessorIT.java @@ -0,0 +1,221 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.request; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.awaitility.Awaitility.await; + +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; +import de.fraunhofer.iosb.ilt.faaast.service.Service; +import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; +import de.fraunhofer.iosb.ilt.faaast.service.config.ServiceConfig; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.HttpEndpointConfig; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.filestorage.memory.FileStorageInMemoryConfig; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.internal.MessageBusInternalConfig; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.mqtt.MessageBusMqttConfig; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.mqtt.MoquetteServer; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.mqtt.PahoClient; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.GetSubmodelElementByPathRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelElementByPathResponse; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.config.AimcSubmodelTemplateProcessorConfig; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.model.HttpModel; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.model.MqttModel; +import de.fraunhofer.iosb.ilt.faaast.service.util.PortHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; + + +public class AimcSubmodelTemplateProcessorIT { + + @ClassRule + public static final WireMockClassRule wireMockRule = new WireMockClassRule(options().port(PortHelper.findFreePort())); + + @Rule + public WireMockClassRule instanceRule = wireMockRule; + + private static final String CONTENT_TYPE = "Content-Type"; + private static final String APPLICATION_JSON = "application/json"; + private static final Duration POLL_TIMEOUT = Duration.ofMillis(100); + private static final Duration MAX_TIMEOUT = Duration.ofSeconds(10); + + private static MoquetteServer server; + private static PahoClient client; + private static int httpServerPort; + private static int mqttPort; + private Service service; + + @BeforeClass + public static void initClass() throws IOException, MessageBusException { + httpServerPort = wireMockRule.port(); + mqttPort = PortHelper.findFreePort(); + MessageBusMqttConfig messageBusConfig = MessageBusMqttConfig.builder() + .port(mqttPort) + .build(); + server = new MoquetteServer(messageBusConfig); + server.start(); + client = new PahoClient(messageBusConfig); + client.start(); + } + + + @AfterClass + public static void stopClass() { + if (client != null) { + client.stop(); + } + if (server != null) { + server.stop(); + } + } + + + @Before + public void init() { + instanceRule = wireMockRule; + } + + + @After + public void shutdown() { + if (service != null) { + service.stop(); + } + } + + + @Test + public void testAimcHttp() throws Exception { + int http = PortHelper.findFreePort(); + service = new Service(serviceConfig(http, HttpModel.create(httpServerPort))); + service.start(); + // wait for asset connections to be established + await().atMost(60, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> service.getAssetConnectionManager().isFullyConnected()); + + String path = HttpModel.P1_URL; + String newval = Double.toString(74.68); + instanceRule.stubFor(request("GET", urlEqualTo(path)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, APPLICATION_JSON) + .withBody(newval))); + + String path2 = HttpModel.P2_URL; + String newval2 = Integer.toString(156); + instanceRule.stubFor(request("GET", urlEqualTo(path2)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, APPLICATION_JSON) + .withBody(newval2))); + + await() + .alias("check Property value") + .pollInterval(POLL_TIMEOUT) + .atMost(MAX_TIMEOUT) + .until(() -> { + Reference prop1Ref = ReferenceBuilder.forSubmodel(HttpModel.SUBMODEL_OPER_DATA_ID, HttpModel.OPER_DATA_HTTP, HttpModel.OPER_DATA_HTTP_P1); + String prop1val = readPropertyValue(HttpModel.SUBMODEL_OPER_DATA_ID, prop1Ref); + return newval.equals(prop1val); + }); + + Reference prop2Ref = ReferenceBuilder.forSubmodel(HttpModel.SUBMODEL_OPER_DATA_ID, HttpModel.OPER_DATA_HTTP, HttpModel.OPER_DATA_HTTP_P2); + String prop2val = readPropertyValue(HttpModel.SUBMODEL_OPER_DATA_ID, prop2Ref); + Assert.assertEquals(newval2, prop2val); + } + + + @Test + public void testAimcMqtt() throws Exception { + int http = PortHelper.findFreePort(); + service = new Service(serviceConfig(http, MqttModel.create(mqttPort))); + service.start(); + // wait for asset connections to be established + await().atMost(60, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> service.getAssetConnectionManager().isFullyConnected()); + + String newval = Float.toString(12.4f); + client.publish(MqttModel.PROP1_TOPIC, newval); + await() + .alias("check property value") + .pollInterval(POLL_TIMEOUT) + .atMost(MAX_TIMEOUT) + .until(() -> { + Reference prop1Ref = ReferenceBuilder.forSubmodel(MqttModel.SUBMODEL_OPER_DATA_ID, MqttModel.OPER_DATA_MQTT, MqttModel.OPER_DATA_MQTT_P1); + String prop1val = readPropertyValue(MqttModel.SUBMODEL_OPER_DATA_ID, prop1Ref); + return newval.equals(prop1val); + }); + } + + + private static ServiceConfig serviceConfig(int portHttp, Environment initialModel) { + return new ServiceConfig.Builder() + .core(new CoreConfig.Builder().requestHandlerThreadPoolSize(2).build()) + .persistence(PersistenceInMemoryConfig.builder() + .initialModel(initialModel) + .build()) + .fileStorage(new FileStorageInMemoryConfig()) + .endpoint(HttpEndpointConfig.builder() + .port(portHttp) + .ssl(false) + .build()) + .messageBus(new MessageBusInternalConfig()) + .submodelTemplateProcessors(List.of(new AimcSubmodelTemplateProcessorConfig.Builder() + //.interfaceConfiguration(ReferenceBuilder.forSubmodel(HttpModel.SUBMODEL_AID_ID, HttpModel.INTERFACE_HTTP), + // new InterfaceConfiguration.Builder().subscriptionInterval(50).build()) + .build())) + .build(); + } + + + private String readPropertyValue(String submodelId, Reference refElement) { + String retval = null; + GetSubmodelElementByPathRequest request = new GetSubmodelElementByPathRequest.Builder().submodelId(submodelId).path(ReferenceHelper.toPath(refElement)).build(); + Response response = service.execute(request); + if ((response.getStatusCode() == StatusCode.SUCCESS) && (GetSubmodelElementByPathResponse.class.isAssignableFrom(response.getClass()))) { + SubmodelElement element = ((GetSubmodelElementByPathResponse) response).getPayload(); + Assert.assertTrue(element instanceof Property); + retval = ((Property) element).getValue(); + } + + return retval; + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessorTest.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessorTest.java new file mode 100644 index 000000000..909a11d9c --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/ProcessorTest.java @@ -0,0 +1,420 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import de.fraunhofer.iosb.ilt.faaast.service.Service; +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.HttpAssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpValueProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.MqttAssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.mqtt.provider.config.MqttSubscriptionProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; +import de.fraunhofer.iosb.ilt.faaast.service.dataformat.EnvironmentSerializationManager; +import de.fraunhofer.iosb.ilt.faaast.service.filestorage.FileStorage; +import de.fraunhofer.iosb.ilt.faaast.service.messagebus.MessageBus; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; +import de.fraunhofer.iosb.ilt.faaast.service.request.handler.StaticRequestExecutionContext; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.config.AimcSubmodelTemplateProcessorConfig; +import de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.util.Util; +import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReflectionHelper; +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; +import org.junit.Assert; +import org.junit.Test; + + +public class ProcessorTest { + + private static final String SERVER_URL = "http://plugfest.thingweb.io:8083"; + private static final String SUBMODEL_ID_AIMC = "https://example.com/ids/sm/AssetInterfacesMappingConfiguration"; + + private AimcSubmodelTemplateProcessor smtProcessor; + private static AssetConnectionManager assetConnectionManager; + + private void initMocks(Environment model) throws Exception { + smtProcessor = AimcSubmodelTemplateProcessorConfig.builder() + .connectionLevelCredentials(Map.of()) + .build() + .newInstance(CoreConfig.DEFAULT, null); + Service service = spy(new Service( + CoreConfig.DEFAULT, + PersistenceInMemoryConfig.builder() + .initialModel(DeepCopyHelper.deepCopy(model)) + .build() + .newInstance(CoreConfig.DEFAULT, mock(ServiceContext.class)), + mock(FileStorage.class), + mock(MessageBus.class), + null, + null, + List.of(smtProcessor))); + assetConnectionManager = spy(service.getAssetConnectionManager()); + ReflectionHelper.setField(service, "assetConnectionManager", assetConnectionManager); + StaticRequestExecutionContext requestExecutionContext = new StaticRequestExecutionContext( + CoreConfig.DEFAULT, + service.getPersistence(), + service.getFileStorage(), + service.getMessageBus(), + assetConnectionManager); + ReflectionHelper.setField(service, "requestExecutionContext", requestExecutionContext); + ReflectionHelper.setField(smtProcessor, "serviceContext", service); + } + + + @Test + public void testAimc() throws Exception { + Environment model = EnvironmentSerializationManager.deserialize(new File("src/test/resources/Test-Example.json")).getEnvironment(); + initMocks(model); + Optional submodel = model.getSubmodels().stream() + .filter(x -> x.getId().equals(SUBMODEL_ID_AIMC)) + .findFirst(); + assertTrue(submodel.isPresent()); + smtProcessor.add(submodel.get(), assetConnectionManager); + + HttpAssetConnectionConfig httpExpected = new HttpAssetConnectionConfig.Builder() + .baseUrl(SERVER_URL) + .subscriptionProvider(new DefaultReference.Builder() + .type(ReferenceTypes.MODEL_REFERENCE) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL).value("https://example.com/ids/sm/OperationalData").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL_ELEMENT_COLLECTION).value("HTTP_Data").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.PROPERTY).value("voltage").build()) + .build(), + HttpSubscriptionProviderConfig.builder() + .format("JSON") + .path("/sampleDevice/properties/voltage") + .interval(1000) + .build()) + .valueProvider(new DefaultReference.Builder() + .type(ReferenceTypes.MODEL_REFERENCE) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL).value("https://example.com/ids/sm/OperationalData").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL_ELEMENT_COLLECTION).value("HTTP_Data").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.PROPERTY).value("status").build()) + .build(), + HttpValueProviderConfig.builder() + .format("JSON") + .path("/sampleDevice/properties/status") + .build()) + .build(); + + MqttAssetConnectionConfig mqttExpected = new MqttAssetConnectionConfig.Builder() + .serverUri("mqtt://iot.platform.com:8088") + .subscriptionProvider(new DefaultReference.Builder() + .type(ReferenceTypes.MODEL_REFERENCE) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL).value("https://example.com/ids/sm/OperationalData").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL_ELEMENT_COLLECTION).value("MQTT_Data").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.PROPERTY).value("voltage").build()) + .build(), + MqttSubscriptionProviderConfig.builder() + .format("JSON") + .topic("/sampleDevice/properties/voltage") + .build()) + .subscriptionProvider(new DefaultReference.Builder() + .type(ReferenceTypes.MODEL_REFERENCE) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL).value("https://example.com/ids/sm/OperationalData").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL_ELEMENT_COLLECTION).value("MQTT_Data").build()) + .keys(new DefaultKey.Builder().type(KeyTypes.PROPERTY).value("status").build()) + .build(), + MqttSubscriptionProviderConfig.builder() + .format("JSON") + .topic("/sampleDevice/properties/status") + .build()) + .build(); + verify(assetConnectionManager).updateConnections(isNull(), argThat((List actual) -> { + if (actual.size() != 2) { + return false; + } + Optional mqttConfig = actual.stream() + .filter(MqttAssetConnectionConfig.class::isInstance) + .map(MqttAssetConnectionConfig.class::cast) + .findFirst(); + if (mqttConfig.isEmpty()) { + return false; + } + mqttExpected.setClientId(mqttConfig.get().getClientId()); + return Objects.equals(Set.of(mqttExpected, httpExpected), new HashSet<>(actual)); + })); + } + + + @Test + public void testGlobalContentType() { + String expectedContentType = "application/json"; + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort("EndpointMetadata") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata") + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("contentType") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/hypermedia#forContentType") + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(expectedContentType) + .build()) + .build(); + String actualContentType = Util.getContentType(smec); + Assert.assertEquals(expectedContentType, actualContentType); + } + + + @Test + public void testPropertyContentType() { + String expectedContentType = "application/xml"; + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort("forms") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/td#hasForm") + .build()) + .build()) + .value(new DefaultProperty.Builder() + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/hypermedia#forContentType") + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(expectedContentType) + .build()) + .build(); + String actualContentType = Util.getContentType("application/json", smec); + Assert.assertEquals(expectedContentType, actualContentType); + } + + + @Test + public void testForms() throws PersistenceException, ResourceNotFoundException { + SubmodelElementCollection expectedForms = new DefaultSubmodelElementCollection.Builder() + .idShort("forms") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/td#hasForm") + .build()) + .build()) + .build(); + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort("voltage") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition") + .build()) + .build()) + .value(expectedForms) + .build(); + SubmodelElementCollection actualForms = Util.getPropertyForms(smec, null, null); + Assert.assertEquals(expectedForms, actualForms); + } + + + @Test + public void testFormsHref() { + String expectedHref = "/path/value"; + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort("forms") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/td#hasForm") + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("href") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/hypermedia#hasTarget") + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(expectedHref) + .build()) + .build(); + String actualHref = Util.getFormsHref(smec); + Assert.assertEquals(expectedHref, actualHref); + } + + + @Test + public void testBaseUrl() { + String expectedBase = "http://localhost:9000"; + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort("EndpointMetadata") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata") + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("base") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/td#baseURI") + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(expectedBase) + .build()) + .build(); + String actualBase = Util.getBaseUrl(smec); + Assert.assertEquals(expectedBase, actualBase); + } + + + @Test + public void testInterfaceTitle() { + String expectedTitle = "DeviceSample"; + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort("InterfaceHTTP") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface") + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("title") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/td#title") + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(expectedTitle) + .build()) + .build(); + String actualTitle = Util.getInterfaceTitle(smec); + Assert.assertEquals(expectedTitle, actualTitle); + } + + + @Test + public void testEndpointMetadata() { + SubmodelElementCollection expectedMetadata = new DefaultSubmodelElementCollection.Builder() + .idShort("EndpointMetadata") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata") + .build()) + .build()) + .build(); + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort("InterfaceHTTP") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface") + .build()) + .build()) + .value(expectedMetadata) + .build(); + SubmodelElementCollection actualMetadata = Util.getEndpointMetadata(smec); + Assert.assertEquals(expectedMetadata, actualMetadata); + } + + + /** + * Tests the key value of a Property. If a Property has a key element, the + * value of this is used. Otherwise the IdShort of the Property itself is + * used. + */ + @Test + public void testPropertyKey() { + String idShort = "foo"; + SubmodelElementCollection smec = new DefaultSubmodelElementCollection.Builder() + .idShort(idShort) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://www.w3.org/2019/wot/json-schema#propertyName") + .build()) + .build()) + .build(); + String actualKey = Util.getKey(smec); + Assert.assertEquals(idShort, actualKey); + + String keyValue = "bar"; + smec.setValue(List.of(new DefaultProperty.Builder() + .idShort("key") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key") + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(keyValue) + .build())); + actualKey = Util.getKey(smec); + Assert.assertEquals(keyValue, actualKey); + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfigTest.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfigTest.java new file mode 100644 index 000000000..6b322ae5a --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/config/AimcSubmodelTemplateProcessorConfigTest.java @@ -0,0 +1,61 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonMapperFactory; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.SimpleAbstractTypeResolverFactory; +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + + +public class AimcSubmodelTemplateProcessorConfigTest { + + private static final File CONFIG_FILE = new File("src/test/resources/Example-config.json"); + private static final ObjectMapper MAPPER = new JsonMapperFactory() + .create(new SimpleAbstractTypeResolverFactory().create()) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + private static final AimcSubmodelTemplateProcessorConfig CONFIG = new AimcSubmodelTemplateProcessorConfig.Builder() + .connectionLevelCredentials(Map.of( + "http://plugfest.thingweb.io:8083", + List.of( + new BasicCredentials("user1", "pw1"), + new BasicCredentials("user2", "pw2")))) + .build(); + + @Test + public void testConfigDeserialization() throws JsonProcessingException, IOException { + AimcSubmodelTemplateProcessorConfig actual = MAPPER.readValue(CONFIG_FILE, AimcSubmodelTemplateProcessorConfig.class); + Assert.assertEquals(CONFIG, actual); + } + + + @Test + public void testConfigSerialization() throws IOException, JSONException { + String expected = Files.readString(CONFIG_FILE.toPath()); + String actual = MAPPER.writeValueAsString(CONFIG); + JSONAssert.assertEquals(expected, actual, JSONCompareMode.NON_EXTENSIBLE); + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Constants.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Constants.java new file mode 100644 index 000000000..3c4cc11c3 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/Constants.java @@ -0,0 +1,64 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +public class Constants { + + public static final String TITLE = "title"; + public static final String AID_ID_SHORT = "AssetInterfacesDescription"; + public static final String AIMC_ID_SHORT = "AssetInterfacesMappingConfiguration"; + public static final String ENDPOINT_METADATA = "EndpointMetadata"; + public static final String SECURITY_DEFINITIONS = "securityDefinitions"; + public static final String NO_SECURITY = "nosec_sc"; + public static final String INTERACTION_METADATA = "InteractionMetadata"; + public static final String INTERFACE_REFERENCE = "InterfaceReference"; + public static final String MAPPING_RELATIONS = "MappingSourceSinkRelations"; + public static final String CONTENT_TYPE = "contentType"; + public static final String PROPERTIES = "properties"; + public static final String TYPE = "type"; + public static final String OBSERVABLE = "observable"; + public static final String UNIT = "unit"; + public static final String FORMS = "forms"; + public static final String HREF = "href"; + public static final String CONTENT_TYPE_JSON = "application/json";; + + public static final String SEMANTIC_ID_TITLE = "https://www.w3.org/2019/wot/td#title"; + public static final String SEMANTIC_ID_CONTENT_TYPE = "https://www.w3.org/2019/wot/hypermedia#forContentType"; + public static final String SEMANTIC_ID_AIMC = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/Submodel"; + public static final String SEMANTIC_ID_ENDPOINT_METADATA = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata"; + public static final String SEMANTIC_ID_INTERFACE = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface"; + public static final String SEMANTIC_ID_INTERFACE_MQTT = "http://www.w3.org/2011/mqtt"; + public static final String SEMANTIC_ID_INTERFACE_HTTP = "http://www.w3.org/2011/http"; + public static final String SEMANTIC_ID_INTERFACE_TD = "https://www.w3.org/2019/wot/td"; + public static final String SEMANTIC_ID_BASE = "https://www.w3.org/2019/wot/td#baseURI"; + public static final String SEMANTIC_ID_SECURITY = "https://www.w3.org/2019/wot/td#hasSecurityConfiguration"; + public static final String SEMANTIC_ID_SECURITY_DEFINITIONS = "https://www.w3.org/2019/wot/td#definesSecurityScheme"; + public static final String SEMANTIC_ID_NO_SECURITY = "https://www.w3.org/2019/wot/security#NoSecurityScheme"; + public static final String SEMANTIC_ID_SECURITY_SCHEME = "https://www.w3.org/2019/wot/td#definesSecurityScheme"; + public static final String SEMANTIC_ID_MAPPING_CONFIGURATIONS = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfigurations"; + public static final String SEMANTIC_ID_MAPPING_CONFIGURATION = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfiguration"; + public static final String SEMANTIC_ID_INTERFACE_REFERENCE = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference"; + public static final String SEMANTIC_ID_MAPPING_RELATIONS = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations"; + public static final String SEMANTIC_ID_MAPPING_RELATION = "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation"; + public static final String SEMANTIC_ID_INTERACTION_METADATA = "https://www.w3.org/2019/wot/td#InteractionAffordance"; + public static final String SEMANTIC_ID_PROPERTIES = "https://www.w3.org/2019/wot/td#PropertyAffordance"; + public static final String SEMANTIC_ID_PROPERTY_DEFINITION = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition"; + public static final String SEMANTIC_ID_PROPERTY_DEFINITION_NAME = "https://www.w3.org/2019/wot/td#name"; + public static final String SEMANTIC_ID_PROPERTY_TYPE = "https://www.w3.org/1999/02/22-rdf-syntax-ns#type"; + public static final String SEMANTIC_ID_PROPERTY_OBSERVABLE = "https://www.w3.org/2019/wot/td#isObservable"; + public static final String SEMANTIC_ID_PROPERTY_UNIT = "http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0"; + public static final String SEMANTIC_ID_PROPERTY_FORMS = "https://www.w3.org/2019/wot/td#hasForm"; + public static final String SEMANTIC_ID_PROPERTY_HREF = "https://www.w3.org/2019/wot/hypermedia#hasTarget"; +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/HttpModel.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/HttpModel.java new file mode 100644 index 000000000..47ba7dca7 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/HttpModel.java @@ -0,0 +1,546 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import org.eclipse.digitaltwin.aas4j.v3.model.AasSubmodelElements; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; + + +public class HttpModel { + + public static final String SUBMODEL_OPER_DATA_ID = "https://www.example.com/ids/sm/6545_8524_6842_7236"; + public static final String OPER_DATA_HTTP = "HttpValues"; + public static final String OPER_DATA_HTTP_P1 = "L2"; + public static final String OPER_DATA_HTTP_P2 = "L3"; + public static final String INTERFACE_HTTP = "InterfaceHTTP"; + public static final String SUBMODEL_AID_ID = "https://example.com/ids/sm/1458_5485_6587_3247"; + public static final String P1_URL = "/L2"; + public static final String P2_URL = "/L3"; + private static final String SUBMODEL_AIMC_ID = "https://example.com/ids/sm/1234_5458_6587_1975"; + private static final String PROPERTY_1 = "Voltage_L2_N"; + private static final String PROPERTY_2 = "Voltage_L3"; + + public static Environment create(int httpPort) { + return new DefaultEnvironment.Builder() + .assetAdministrationShells(createAas()) + .submodels(createSubmodelAid(httpPort)) + .submodels(createSubmodelAimc()) + .submodels(createSubmodelOperationalData()) + .build(); + } + + + private static Submodel createSubmodelAimc() { + return new DefaultSubmodel.Builder() + .idShort(Constants.AIMC_ID_SHORT) + .id(SUBMODEL_AIMC_ID) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value(Constants.SEMANTIC_ID_AIMC) + .build()) + .build()) + .submodelElements(createMappingConfiguration()) + .build(); + + } + + + private static Submodel createSubmodelAid(int httpPort) { + return new DefaultSubmodel.Builder() + .idShort(Constants.AID_ID_SHORT) + .id(SUBMODEL_AID_ID) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel") + .build()) + .build()) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(INTERFACE_HTTP) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE) + .build()) + .build()) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE_HTTP) + .build()) + .build()) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE_TD) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TITLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_TITLE) + .build()) + .build()) + .build()) + .value(createEndpointMetadata(httpPort)) + .value(createInteractionMetadata()) + .build()) + .build(); + } + + + private static Submodel createSubmodelOperationalData() { + return new DefaultSubmodel.Builder() + .id(SUBMODEL_OPER_DATA_ID) + .idShort("OperationalData") + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(OPER_DATA_HTTP) + .value(new DefaultProperty.Builder() + .idShort(OPER_DATA_HTTP_P1) + .valueType(DataTypeDefXsd.DOUBLE) + .value("0") + .build()) + .value(new DefaultProperty.Builder() + .idShort(OPER_DATA_HTTP_P2) + .valueType(DataTypeDefXsd.INT) + .value("0") + .build()) + .build()) + .build(); + } + + + private static DefaultSubmodelElementList createMappingConfiguration() { + return new DefaultSubmodelElementList.Builder() + .idShort("MappingConfigurations") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_MAPPING_CONFIGURATIONS) + .build()) + .build()) + .typeValueListElement(AasSubmodelElements.SUBMODEL_ELEMENT_COLLECTION) + .semanticIdListElement(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_CONFIGURATION) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_CONFIGURATION) + .build()) + .build()) + .value(new DefaultReferenceElement.Builder() + .idShort(Constants.INTERFACE_REFERENCE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE_REFERENCE) + .build()) + .build()) + .value(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID, INTERFACE_HTTP)) + .build()) + .value(new DefaultSubmodelElementList.Builder() + .idShort(Constants.MAPPING_RELATIONS) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_RELATIONS) + .build()) + .build()) + .typeValueListElement(AasSubmodelElements.RELATIONSHIP_ELEMENT) + .semanticIdListElement(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_RELATION) + .build()) + .build()) + .value(new DefaultRelationshipElement.Builder() + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_RELATION) + .build()) + .build()) + .first(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID, INTERFACE_HTTP, Constants.INTERACTION_METADATA, Constants.PROPERTIES, PROPERTY_1)) + .second(ReferenceBuilder.forSubmodel(SUBMODEL_OPER_DATA_ID, OPER_DATA_HTTP, OPER_DATA_HTTP_P1)) + .build()) + .value(new DefaultRelationshipElement.Builder() + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_RELATION) + .build()) + .build()) + .first(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID, INTERFACE_HTTP, Constants.INTERACTION_METADATA, Constants.PROPERTIES, PROPERTY_2)) + .second(ReferenceBuilder.forSubmodel(SUBMODEL_OPER_DATA_ID, OPER_DATA_HTTP, OPER_DATA_HTTP_P2)) + .build()) + .build()) + .build()) + .build(); + } + + + private static DefaultSubmodelElementCollection createEndpointMetadata(int httpPort) { + return new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.ENDPOINT_METADATA) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_ENDPOINT_METADATA) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("base") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_BASE) + .build()) + .build()) + .valueType(DataTypeDefXsd.ANY_URI) + .value(String.format("http://localhost:%d", httpPort)) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.CONTENT_TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_CONTENT_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("application/octet-stream") + .build()) + .value(new DefaultSubmodelElementList.Builder() + .idShort("security") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_SECURITY) + .build()) + .build()) + .value(new DefaultReferenceElement.Builder() + .value(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID, INTERFACE_HTTP, Constants.ENDPOINT_METADATA, Constants.SECURITY_DEFINITIONS, + Constants.NO_SECURITY)) + .build()) + .typeValueListElement(AasSubmodelElements.SUBMODEL_ELEMENT) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.SECURITY_DEFINITIONS) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_SECURITY_DEFINITIONS) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.NO_SECURITY) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_NO_SECURITY) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("scheme") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_SECURITY_SCHEME) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("nosec") + .build()) + .build()) + .build()) + .build(); + } + + + private static DefaultSubmodelElementCollection createInteractionMetadata() { + return new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.INTERACTION_METADATA) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERACTION_METADATA) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.PROPERTIES) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTIES) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(PROPERTY_1) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_DEFINITION) + .build()) + .build()) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_PROPERTY_DEFINITION_NAME) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TITLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_TITLE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("Voltage L2 to N") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("double") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.OBSERVABLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_OBSERVABLE) + .build()) + .build()) + .valueType(DataTypeDefXsd.BOOLEAN) + .value("true") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.UNIT) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_PROPERTY_UNIT) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("V") + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.FORMS) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_FORMS) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.HREF) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_HREF) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(P1_URL) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.CONTENT_TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_CONTENT_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(Constants.CONTENT_TYPE_JSON) + .build()) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(PROPERTY_2) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_DEFINITION) + .build()) + .build()) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_PROPERTY_DEFINITION_NAME) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TITLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_TITLE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("Voltage L3") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("int") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.OBSERVABLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_OBSERVABLE) + .build()) + .build()) + .valueType(DataTypeDefXsd.BOOLEAN) + .value("false") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.UNIT) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_PROPERTY_UNIT) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("V") + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.FORMS) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_FORMS) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.HREF) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_HREF) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(P2_URL) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.CONTENT_TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_CONTENT_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(Constants.CONTENT_TYPE_JSON) + .build()) + .build()) + .build()) + .build()) + .build(); + } + + + private static AssetAdministrationShell createAas() { + return new DefaultAssetAdministrationShell.Builder() + .submodels(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID)) + .submodels(ReferenceBuilder.forSubmodel(SUBMODEL_AIMC_ID)) + .submodels(ReferenceBuilder.forSubmodel(SUBMODEL_OPER_DATA_ID)) + .build(); + } +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/MqttModel.java b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/MqttModel.java new file mode 100644 index 000000000..16373bc2c --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/aimc/model/MqttModel.java @@ -0,0 +1,434 @@ +/* + * 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.ilt.faaast.service.submodeltemplate.aimc.model; + +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import org.eclipse.digitaltwin.aas4j.v3.model.AasSubmodelElements; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; + + +public class MqttModel { + public static final String PROP1_TOPIC = "voltage/L1"; + public static final String SUBMODEL_OPER_DATA_ID = "https://www.example.com/ids/sm/6387_8041_1042_7429"; + public static final String OPER_DATA_MQTT = "MqttValues"; + public static final String OPER_DATA_MQTT_P1 = "L1"; + private static final String INTERFACE_MQTT = "InterfaceMQTT"; + private static final String SUBMODEL_AID_ID = "https://example.com/ids/sm/5452_9041_7022_4586"; + private static final String SUBMODEL_AIMC_ID = "https://example.com/ids/sm/3652_7031_2157_1458"; + private static final String PROPERTY_1 = "Voltage_L1_N"; + + public static Environment create(int mqttPort) { + return new DefaultEnvironment.Builder() + .assetAdministrationShells(createAas()) + .submodels(createSubmodelAid(mqttPort)) + .submodels(createSubmodelAimc()) + .submodels(createSubmodelOperationalData()) + .build(); + } + + + private static Submodel createSubmodelAid(int mqttPort) { + return new DefaultSubmodel.Builder() + .idShort(Constants.AID_ID_SHORT) + .id(SUBMODEL_AID_ID) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel") + .build()) + .build()) + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(INTERFACE_MQTT) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE) + .build()) + .build()) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE_MQTT) + .build()) + .build()) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE_TD) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TITLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_TITLE) + .build()) + .build()) + .build()) + .value(createEndpointMetadata(mqttPort)) + .value(createInteractionMetadata()) + .build()) + .build(); + } + + + private static DefaultSubmodelElementCollection createInteractionMetadata() { + return new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.INTERACTION_METADATA) + //.semanticId(new DefaultReference.Builder() + // .type(ReferenceTypes.EXTERNAL_REFERENCE) + // .keys(new DefaultKey.Builder() + // .type(KeyTypes.CONCEPT_DESCRIPTION) + // .value("https://admin-shell.io/idta/AssetInterfacesDescription/1/0/InteractionMetadata") + // .build()) + // .build()) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERACTION_METADATA) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.PROPERTIES) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTIES) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(PROPERTY_1) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_DEFINITION) + .build()) + .build()) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_PROPERTY_DEFINITION_NAME) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TITLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_TITLE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("Voltage L1 to N") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("float") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.OBSERVABLE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_OBSERVABLE) + .build()) + .build()) + .valueType(DataTypeDefXsd.BOOLEAN) + .value("true") + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.UNIT) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_PROPERTY_UNIT) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("V") + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.FORMS) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_FORMS) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.HREF) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_PROPERTY_HREF) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(PROP1_TOPIC) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.CONTENT_TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_CONTENT_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value(Constants.CONTENT_TYPE_JSON) + .build()) + .build()) + .build()) + .build()) + .build(); + } + + + private static DefaultSubmodelElementCollection createEndpointMetadata(int mqttPort) { + return new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.ENDPOINT_METADATA) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_ENDPOINT_METADATA) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("base") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_BASE) + .build()) + .build()) + .valueType(DataTypeDefXsd.ANY_URI) + .value(String.format("tcp://127.0.0.1:%d", mqttPort)) + .build()) + .value(new DefaultProperty.Builder() + .idShort(Constants.CONTENT_TYPE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_CONTENT_TYPE) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("application/octet-stream") + .build()) + .value(new DefaultSubmodelElementList.Builder() + .idShort("security") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_SECURITY) + .build()) + .build()) + .value(new DefaultReferenceElement.Builder() + .value(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID, INTERFACE_MQTT, Constants.ENDPOINT_METADATA, Constants.SECURITY_DEFINITIONS, + Constants.NO_SECURITY)) + .build()) + .typeValueListElement(AasSubmodelElements.SUBMODEL_ELEMENT) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.SECURITY_DEFINITIONS) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_SECURITY_DEFINITIONS) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort(Constants.NO_SECURITY) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_NO_SECURITY) + .build()) + .build()) + .value(new DefaultProperty.Builder() + .idShort("scheme") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_SECURITY_SCHEME) + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .value("nosec") + .build()) + .build()) + .build()) + .build(); + } + + + private static Submodel createSubmodelAimc() { + return new DefaultSubmodel.Builder() + .idShort(Constants.AIMC_ID_SHORT) + .id(SUBMODEL_AIMC_ID) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value(Constants.SEMANTIC_ID_AIMC) + .build()) + .build()) + .submodelElements(createMappingConfiguration()) + .build(); + } + + + private static DefaultSubmodelElementList createMappingConfiguration() { + return new DefaultSubmodelElementList.Builder() + .idShort("MappingConfigurations") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value(Constants.SEMANTIC_ID_MAPPING_CONFIGURATIONS) + .build()) + .build()) + .typeValueListElement(AasSubmodelElements.SUBMODEL_ELEMENT_COLLECTION) + .semanticIdListElement(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_CONFIGURATION) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_CONFIGURATION) + .build()) + .build()) + .value(new DefaultReferenceElement.Builder() + .idShort(Constants.INTERFACE_REFERENCE) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_INTERFACE_REFERENCE) + .build()) + .build()) + .value(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID, INTERFACE_MQTT)) + .build()) + .value(new DefaultSubmodelElementList.Builder() + .idShort(Constants.MAPPING_RELATIONS) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_RELATIONS) + .build()) + .build()) + .typeValueListElement(AasSubmodelElements.RELATIONSHIP_ELEMENT) + .semanticIdListElement(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_RELATION) + .build()) + .build()) + .value(new DefaultRelationshipElement.Builder() + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(Constants.SEMANTIC_ID_MAPPING_RELATION) + .build()) + .build()) + .first(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID, INTERFACE_MQTT, Constants.INTERACTION_METADATA, Constants.PROPERTIES, PROPERTY_1)) + .second(ReferenceBuilder.forSubmodel(SUBMODEL_OPER_DATA_ID, OPER_DATA_MQTT, OPER_DATA_MQTT_P1)) + .build()) + .build()) + .build()) + .build(); + } + + + private static Submodel createSubmodelOperationalData() { + return new DefaultSubmodel.Builder() + .id(SUBMODEL_OPER_DATA_ID) + .idShort("OperationalData") + .submodelElements(new DefaultSubmodelElementCollection.Builder() + .idShort(OPER_DATA_MQTT) + .value(new DefaultProperty.Builder() + .idShort(OPER_DATA_MQTT_P1) + .valueType(DataTypeDefXsd.FLOAT) + .value("0") + .build()) + .build()) + .build(); + } + + + private static AssetAdministrationShell createAas() { + return new DefaultAssetAdministrationShell.Builder() + .submodels(ReferenceBuilder.forSubmodel(SUBMODEL_AID_ID)) + .submodels(ReferenceBuilder.forSubmodel(SUBMODEL_AIMC_ID)) + .submodels(ReferenceBuilder.forSubmodel(SUBMODEL_OPER_DATA_ID)) + .build(); + } + +} diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Example-config.json b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Example-config.json new file mode 100644 index 000000000..7cf137cc6 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Example-config.json @@ -0,0 +1,15 @@ +{ + "@class": "de.fraunhofer.iosb.ilt.faaast.service.submodeltemplate.aimc.AimcSubmodelTemplateProcessor", + "credentials": { + "http://plugfest.thingweb.io:8083": [ + { + "username": "user1", + "password": "pw1" + }, + { + "username": "user2", + "password": "pw2" + } + ] + } +} \ No newline at end of file diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Test-Example.json b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Test-Example.json new file mode 100644 index 000000000..b8c2643c1 --- /dev/null +++ b/submodeltemplate/asset-interfaces-mapping-configuration/src/test/resources/Test-Example.json @@ -0,0 +1,2003 @@ +{ + "assetAdministrationShells": [ + { + "idShort": "SampleAAS", + "id": "https://example.com/ids/aas/7474_9002_6022_1115", + "assetInformation": { + "assetKind": "Type", + "globalAssetId": "https://example.com/ids/asset/3071_4170_8032_4893", + "specificAssetIds": [ + ], + "assetType": "", + "defaultThumbnail": { + "path": "", + "contentType": "image/png" + } + }, + "submodels": [ + { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + } + ] + }, + { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + } + ] + }, + { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesMappingConfiguration" + } + ] + } + ], + "modelType": "AssetAdministrationShell" + } + ], + "submodels": [ + { + "idShort": "AssetInterfacesDescription", + "description": [ + { + "language": "en", + "text": "Counter example Thing" + }, + { + "language": "de", + "text": "Zähler Beispiel Ding" + } + ], + "id": "https://example.com/ids/sm/AssetInterfacesDescription", + "kind": "Instance", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel" + } + ] + }, + "submodelElements": [ + { + "idShort": "InterfaceHTTP", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://www.w3.org/2011/http" + } + ] + } + ], + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "DeviceSample", + "modelType": "Property" + }, + { + "idShort": "created", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/created" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "modified", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/modified" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "support", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#supportContact" + } + ] + }, + "valueType": "xs:anyURI", + "value": "https://github.com/eclipse-thingweb/node-wot/", + "modelType": "Property" + }, + { + "idShort": "EndpointMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "base", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#baseURI" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:anyURI", + "value": "http://plugfest.thingweb.io:8083", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + }, + { + "idShort": "security", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasSecurityConfiguration" + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + }, + { + "type": "SubmodelElementCollection", + "value": "EndpointMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "securityDefinitions" + }, + { + "type": "SubmodelElementCollection", + "value": "nosec_sc" + } + ] + }, + "modelType": "ReferenceElement" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "securityDefinitions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#definesSecurityScheme" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "nosec_sc", + "semanticId": { + "type": "ExternalReference", + "keys": [ + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "scheme", + "valueType": "xs:string", + "value": "nosec", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "InterfaceMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#InteractionAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "Properties", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#PropertyAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "voltage", + "description": [ + { + "language": "en", + "text": "Current counter value" + }, + { + "language": "de", + "text": "Derzeitiger Zählerwert" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition" + } + ] + }, + "qualifiers": [ + ], + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "range", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "100", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "voltage", + "modelType": "Property" + }, + { + "idShort": "observable", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#isObservable" + } + ] + }, + "valueType": "xs:boolean", + "value": "true", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "V", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "/sampleDevice/properties/voltage", + "modelType": "Property" + }, + { + "idShort": "htv_methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2011/http#methodName" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "status", + "description": [ + { + "language": "en", + "text": "Current counter value as SVG image" + }, + { + "language": "de", + "text": "Aktueller Zählerwert als SVG-Bild" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition" + } + ] + }, + "qualifiers": [ + ], + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "string", + "modelType": "Property" + }, + { + "idShort": "lengthRange", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "25", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "status", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "enum", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "value": "Good", + "modelType": "Property" + }, + { + "idShort": "Bad", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "", + "modelType": "Property" + }, + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "false", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "/sampleDevice/properties/status", + "modelType": "Property" + }, + { + "idShort": "htv_methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [ + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Actions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#ActionAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Events", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#EventAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "externalDescriptor", + "semanticId": { + "type": "ExternalReference", + "keys": [ + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "ThingDescription", + "semanticId": { + "type": "ExternalReference", + "keys": [ + ] + }, + "embeddedDataSpecifications": [ + ], + "value": "/aasx/files/counter-http-simple.td.jsonld", + "contentType": "application/json", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "InterfaceMQTT", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://www.w3.org/2011/mqtt" + } + ] + } + ], + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "DeviceSample", + "modelType": "Property" + }, + { + "idShort": "created", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/created" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "modified", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://purl.org/dc/terms/modified" + } + ] + }, + "valueType": "xs:dateTime", + "value": "2022-12-27 08:26:49.219717", + "modelType": "Property" + }, + { + "idShort": "support", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#supportContact" + } + ] + }, + "valueType": "xs:anyURI", + "value": "https://github.com/eclipse-thingweb/node-wot/", + "modelType": "Property" + }, + { + "idShort": "EndpointMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "base", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#baseURI" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:anyURI", + "value": "mqtt://iot.platform.com:8088", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + }, + { + "idShort": "security", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasSecurityConfiguration" + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "valueType": "xs:string", + "value": "nosec_sc", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "securityDefinitions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#definesSecurityScheme" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "nosec_sc", + "semanticId": { + "type": "ExternalReference", + "keys": [ + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "scheme", + "valueType": "xs:string", + "value": "nosec", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "InterfaceMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#InteractionAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "Properties", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#PropertyAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "voltage", + "description": [ + { + "language": "en", + "text": "Current counter value" + }, + { + "language": "de", + "text": "Derzeitiger Zählerwert" + }, + { + "language": "it", + "text": "Valore attuale del contatore" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition" + } + ] + }, + "qualifiers": [ + ], + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "range", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "100", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "voltage", + "modelType": "Property" + }, + { + "idShort": "observable", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#isObservable" + } + ] + }, + "valueType": "xs:boolean", + "value": "true", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "V", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "/sampleDevice/properties/voltage", + "modelType": "Property" + }, + { + "idShort": "mqv_retain", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/mqtt#hasRetainFlag" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:boolean", + "value": "True", + "modelType": "Property" + }, + { + "idShort": "mqv_controlPacket", + "valueType": "xs:string", + "value": "subscribe", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "status", + "description": [ + { + "language": "en", + "text": "Current counter value as SVG image" + }, + { + "language": "de", + "text": "Aktueller Zählerwert als SVG-Bild" + }, + { + "language": "it", + "text": "Valore attuale del contatore come immagine SVG" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition" + } + ] + }, + "qualifiers": [ + ], + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "type", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" + } + ] + }, + "valueType": "xs:string", + "value": "string", + "modelType": "Property" + }, + { + "idShort": "lengthRange", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#minimum" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#maximum" + } + ] + } + ], + "valueType": "xs:integer", + "min": "1", + "max": "25", + "modelType": "Range" + }, + { + "idShort": "title", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#title" + } + ] + }, + "valueType": "xs:string", + "value": "status", + "modelType": "Property" + }, + { + "idShort": "const", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#const" + } + ] + }, + "valueType": "xs:int", + "value": "22", + "modelType": "Property" + }, + { + "idShort": "enum", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "value": "Good", + "modelType": "Property" + }, + { + "idShort": "Bad", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "]https://www.w3.org/2019/wot/json-schema#enum" + } + ] + }, + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "default", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/json-schema#default" + } + ] + }, + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "unit", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://schema.org/unitCode" + } + ] + }, + "valueType": "xs:string", + "value": "", + "modelType": "Property" + }, + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "false", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#hasForm" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#hasTarget" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "/sampleDevice/properties/status", + "modelType": "Property" + }, + { + "idShort": "mqv_retain", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/mqtt#hasRetainFlag" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:boolean", + "value": "True", + "modelType": "Property" + }, + { + "idShort": "mqv_controlPacket", + "valueType": "xs:string", + "value": "subscribe", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/hypermedia#forContentType" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Actions", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#ActionAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Events", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.w3.org/2019/wot/td#EventAffordance" + } + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "externalDescriptor", + "semanticId": { + "type": "ExternalReference", + "keys": [ + ] + }, + "embeddedDataSpecifications": [ + ], + "value": [ + { + "idShort": "ThingDescription", + "semanticId": { + "type": "ExternalReference", + "keys": [ + ] + }, + "embeddedDataSpecifications": [ + ], + "value": "/aasx/files/counter-http-simple.td.jsonld", + "contentType": "application/json", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "Submodel" + }, + { + "idShort": "OperationalData", + "id": "https://example.com/ids/sm/OperationalData", + "submodelElements": [ + { + "idShort": "HTTP_Data", + "value": [ + { + "idShort": "voltage", + "valueType": "xs:string", + "modelType": "Property" + }, + { + "idShort": "status", + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "MQTT_Data", + "value": [ + { + "idShort": "voltage", + "valueType": "xs:string", + "modelType": "Property" + }, + { + "idShort": "status", + "valueType": "xs:string", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "Submodel" + }, + { + "idShort": "AssetInterfacesMappingConfiguration", + "id": "https://example.com/ids/sm/AssetInterfacesMappingConfiguration", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/Submodel" + } + ] + }, + "submodelElements": [ + { + "idShort": "MappingConfigurations", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfigurations" + } + ] + }, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingConfiguration" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "idShort": "", + "value": [ + { + "idShort": "InterfaceReference", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference" + } + ] + }, + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + } + ] + }, + "modelType": "ReferenceElement" + }, + { + "idShort": "MappingSourceSinkRelations", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations" + } + ] + }, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation" + } + ] + }, + "typeValueListElement": "RelationshipElement", + "value": [ + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "voltage" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "HTTP_Data" + }, + { + "type": "Property", + "value": "voltage" + } + ] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceHTTP" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "status" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "HTTP_Data" + }, + { + "type": "Property", + "value": "status" + } + ] + }, + "modelType": "RelationshipElement" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "", + "value": [ + { + "idShort": "InterfaceReference", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference" + } + ] + }, + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMQTT" + } + ] + }, + "modelType": "ReferenceElement" + }, + { + "idShort": "MappingSourceSinkRelations", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations" + } + ] + }, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelation" + } + ] + }, + "typeValueListElement": "RelationshipElement", + "value": [ + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMQTT" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "voltage" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "MQTT_Data" + }, + { + "type": "Property", + "value": "voltage" + } + ] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "", + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/AssetInterfacesDescription" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMQTT" + }, + { + "type": "SubmodelElementCollection", + "value": "InterfaceMetadata" + }, + { + "type": "SubmodelElementCollection", + "value": "Properties" + }, + { + "type": "SubmodelElementCollection", + "value": "status" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/OperationalData" + }, + { + "type": "SubmodelElementCollection", + "value": "MQTT_Data" + }, + { + "type": "Property", + "value": "status" + } + ] + }, + "modelType": "RelationshipElement" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "Submodel" + } + ], + "conceptDescriptions": [ + ] +} \ No newline at end of file diff --git a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java index 5d6f918fc..c0808682d 100644 --- a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java +++ b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java @@ -17,9 +17,14 @@ import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper.toHttpStatusCode; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import com.prosysopc.ua.stack.core.UserTokenType; import de.fraunhofer.iosb.ilt.faaast.service.Service; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.HttpAssetConnectionConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider.config.HttpValueProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.OpcUaAssetConnectionConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider.config.OpcUaValueProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.util.OpcUaHelper; @@ -30,34 +35,48 @@ import de.fraunhofer.iosb.ilt.faaast.service.dataformat.SerializationException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.HttpEndpointConfig; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.OpcUaEndpointConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationInitializationException; import de.fraunhofer.iosb.ilt.faaast.service.filestorage.memory.FileStorageInMemoryConfig; import de.fraunhofer.iosb.ilt.faaast.service.messagebus.internal.MessageBusInternalConfig; +import de.fraunhofer.iosb.ilt.faaast.service.model.SubmodelElementIdentifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode; import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.Content; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.QueryModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.PatchSubmodelElementValueByPathRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.GetSubmodelElementByPathRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.PatchSubmodelElementValueByPathResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelElementByPathResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedModifierException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueFormatException; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.Datatype; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.PropertyValue; import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; import de.fraunhofer.iosb.ilt.faaast.service.test.util.ApiPaths; import de.fraunhofer.iosb.ilt.faaast.service.test.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.PortHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpResponse; import java.nio.file.Files; +import java.nio.file.Path; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.AasUtils; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; -import org.eclipse.digitaltwin.aas4j.v3.model.ModellingKind; import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; @@ -69,50 +88,102 @@ import org.json.JSONException; import org.junit.After; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; -import org.skyscreamer.jsonassert.JSONAssert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class AssetConnectionIT extends AbstractIntegrationTest { - private static final String NODE_ID_SOURCE = "ns=3;s=1.Value"; + private static final Logger LOGGER = LoggerFactory.getLogger(AssetConnectionIT.class); + private static final String initialValue = "initial value"; + private static final Property propertySource1 = new DefaultProperty.Builder() + .idShort("source1") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Property propertySource2 = new DefaultProperty.Builder() + .idShort("source2") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Property propertySource3 = new DefaultProperty.Builder() + .idShort("source3") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Property propertySource4 = new DefaultProperty.Builder() + .idShort("source4") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Submodel submodelSource = new DefaultSubmodel.Builder() + .idShort("SubmodelSource") + .id("http://example.org/submodel/source") + .submodelElements(propertySource1) + .submodelElements(propertySource2) + .submodelElements(propertySource3) + .submodelElements(propertySource4) + .build(); + private static final Property propertyTarget1 = new DefaultProperty.Builder() + .idShort("target1") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Property propertyTarget2 = new DefaultProperty.Builder() + .idShort("target2") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Property propertyTarget3 = new DefaultProperty.Builder() + .idShort("target3") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Property propertyTarget4 = new DefaultProperty.Builder() + .idShort("target4") + .value(initialValue) + .valueType(DataTypeDefXsd.STRING) + .build(); + private static final Submodel submodelTarget = new DefaultSubmodel.Builder() + .idShort("SubmodelTarget") + .id("http://example.org/submodel/target") + .submodelElements(propertyTarget1) + .submodelElements(propertyTarget2) + .submodelElements(propertyTarget3) + .submodelElements(propertyTarget4) + .build(); + private static final Reference referenceSubmodelSource = ReferenceBuilder.forSubmodel(submodelSource); + private static final Reference referenceSubmodelTarget = ReferenceBuilder.forSubmodel(submodelTarget); + private static final Reference referencePropertySource1 = ReferenceBuilder.forSubmodel(submodelSource, propertySource1); + private static final Reference referencePropertySource2 = ReferenceBuilder.forSubmodel(submodelSource, propertySource2); + private static final Reference referencePropertySource3 = ReferenceBuilder.forSubmodel(submodelSource, propertySource3); + private static final Reference referencePropertySource4 = ReferenceBuilder.forSubmodel(submodelSource, propertySource4); + private static final Reference referencePropertyTarget1 = ReferenceBuilder.forSubmodel(submodelTarget, propertyTarget1); + private static final Reference referencePropertyTarget2 = ReferenceBuilder.forSubmodel(submodelTarget, propertyTarget2); + private static final Reference referencePropertyTarget3 = ReferenceBuilder.forSubmodel(submodelTarget, propertyTarget3); + private static final Reference referencePropertyTarget4 = ReferenceBuilder.forSubmodel(submodelTarget, propertyTarget4); + private static final Environment environment = new DefaultEnvironment.Builder() + .assetAdministrationShells(new DefaultAssetAdministrationShell.Builder() + .idShort("AAS1") + .id("https://example.org/aas/1") + .submodels(referenceSubmodelSource) + .submodels(referenceSubmodelTarget) + .build()) + .submodels(submodelSource) + .submodels(submodelTarget) + .build(); + private static final String nodeIdSource1 = "ns=3;s=1.Value"; + private static final String nodeIdSource2 = "ns=3;s=2.Value"; + private static final String nodeIdSource3 = "ns=3;s=3.Value"; + private static final String nodeIdSource4 = "ns=3;s=4.Value"; - private static final int SOURCE_VALUE = 42; - private static final int TARGET_VALUE = 0; private static Service service; - private static Environment environment; - private static Property source; - private static Submodel submodel; - private static Property target; + private static Path securityBaseDir; @BeforeClass - public static void initClass() throws IOException { - source = new DefaultProperty.Builder() - .idShort("source") - .value(Integer.toString(SOURCE_VALUE)) - .valueType(DataTypeDefXsd.INTEGER) - .build(); - target = new DefaultProperty.Builder() - .idShort("target") - .value(Integer.toString(TARGET_VALUE)) - .valueType(DataTypeDefXsd.INTEGER) - .build(); - submodel = new DefaultSubmodel.Builder() - .idShort("Submodel1") - .id("http://example.org/submodel/1") - .kind(ModellingKind.INSTANCE) - .submodelElements(source) - .submodelElements(target) - .build(); - environment = new DefaultEnvironment.Builder() - .assetAdministrationShells(new DefaultAssetAdministrationShell.Builder() - .idShort("AAS1") - .id("https://example.org/aas/1") - .submodels(ReferenceBuilder.forSubmodel(submodel)) - .build()) - .submodels(submodel) - .build(); + public static void init() throws IOException { + securityBaseDir = Files.createTempDirectory("asset-connection"); } @@ -124,57 +195,216 @@ public void shutdown() { @Test public void testServiceStartInvalidAssetConnection() throws Exception { - int http = PortHelper.findFreePort(); - int opcua = PortHelper.findFreePort(); - service = new Service( - withAssetConnection( - serviceConfig(http, opcua), - "invalid", - opcua)); + int portHttp = PortHelper.findFreePort(); + int portOpcUa = PortHelper.findFreePort(); + ServiceConfig config = serviceConfig(portHttp, portOpcUa); + config.getAssetConnections().add(connectionOpcUa(portOpcUa, referencePropertyTarget1, "invalid node id")); + service = new Service(config); service.start(); - assertServiceAvailabilityHttp(http); + assertServiceAvailabilityHttp(portHttp); } @Test - // TODO re-add once OPC UA Endpoint is updated to AAS4j - @Ignore public void testServiceStartValidAssetConnection() throws Exception { - int http = PortHelper.findFreePort(); - int opcua = PortHelper.findFreePort(); - service = new Service( - withAssetConnection( - serviceConfig(http, opcua), - NODE_ID_SOURCE, - opcua)); + int portHttp = PortHelper.findFreePort(); + int portOpcUa = PortHelper.findFreePort(); + ServiceConfig config = serviceConfig(portHttp, portOpcUa); + config.getAssetConnections().add(connectionOpcUa(portOpcUa, referencePropertyTarget1, nodeIdSource1)); + service = new Service(config); + service.start(); + String newValue = "new value"; + setValue(referencePropertySource1, newValue); + awaitAssetConnected(service); + assertValue(referencePropertyTarget1, newValue); + } + + + @Test + public void testAssetConnectionUpdateRuntime_addConnection() throws Exception { + int portHttp = PortHelper.findFreePort(); + int portOpcUa = PortHelper.findFreePort(); + service = new Service(serviceConfig(portHttp, portOpcUa)); + service.start(); + assertServiceAvailabilityHttp(portHttp); + assertValue(referencePropertyTarget1, initialValue); + List newConnections = List.of( + connectionOpcUa(portOpcUa, referencePropertyTarget1, nodeIdSource1)); + service.getAssetConnectionManager().updateConnections(null, newConnections); + awaitAssetConnected(service); + String newValue = "new value"; + setValue(referencePropertySource1, newValue); + assertValue(referencePropertyTarget1, newValue); + } + + + @Test + public void testAssetConnectionUpdateRuntime_complexUpdate() throws Exception { + int portHttp = PortHelper.findFreePort(); + int portOpcUa = PortHelper.findFreePort(); + int portHttpAsset = PortHelper.findFreePort(); + int portOpcUaAsset = PortHelper.findFreePort(); + ServiceConfig config = serviceConfig(portHttp, portOpcUa); + config.getAssetConnections().add( + OpcUaAssetConnectionConfig.builder() + .host("opc.tcp://localhost:" + portOpcUaAsset) + .securityBaseDir(securityBaseDir) + .valueProvider(referencePropertyTarget1, + OpcUaValueProviderConfig.builder() + .nodeId(nodeIdSource1) + .build()) + .valueProvider(referencePropertyTarget2, + OpcUaValueProviderConfig.builder() + .nodeId(nodeIdSource2) + .build()) + .build()); + config.getAssetConnections().add( + OpcUaAssetConnectionConfig.builder() + .host("opc.tcp://localhost:" + portOpcUaAsset) + .securityBaseDir(securityBaseDir) + .valueProvider(referencePropertyTarget3, + OpcUaValueProviderConfig.builder() + .nodeId(nodeIdSource3) + .build()) + .build()); + Service serviceAsset = new Service(serviceConfig(portHttpAsset, portOpcUaAsset)); + serviceAsset.start(); + assertServiceAvailabilityOpcUa(portOpcUaAsset); + assertServiceAvailabilityHttp(portHttpAsset); + String newValue1 = "new value 1"; + setValue(serviceAsset, referencePropertySource1, newValue1); + setValue(serviceAsset, referencePropertySource2, newValue1); + setValue(serviceAsset, referencePropertySource3, newValue1); + setValue(serviceAsset, referencePropertySource4, newValue1); + + service = new Service(config); service.start(); awaitAssetConnected(service); - assertServiceAvailabilityHttp(http); - assertTargetValue(http, SOURCE_VALUE); + assertValue(referencePropertyTarget1, newValue1); + assertValue(referencePropertyTarget2, newValue1); + assertValue(referencePropertyTarget3, newValue1); + assertValue(referencePropertyTarget4, initialValue); + + List oldConfigs = List.of( + OpcUaAssetConnectionConfig.builder() + .host("opc.tcp://localhost:" + portOpcUaAsset) + .securityBaseDir(securityBaseDir) + .valueProvider(referencePropertyTarget1, + OpcUaValueProviderConfig.builder() + .nodeId(nodeIdSource1) + .build()) + .valueProvider(referencePropertyTarget2, + OpcUaValueProviderConfig.builder() + .nodeId(nodeIdSource2) + .build()) + .build()); + List newConfigs = List.of( + OpcUaAssetConnectionConfig.builder() + .host("opc.tcp://localhost:" + portOpcUaAsset) + .securityBaseDir(securityBaseDir) + .valueProvider(referencePropertyTarget1, + OpcUaValueProviderConfig.builder() + .nodeId(nodeIdSource1) + .build()) + .valueProvider(referencePropertyTarget3, + OpcUaValueProviderConfig.builder() + .nodeId(nodeIdSource3) + .build()) + .build(), + connectionHttp(portHttpAsset, referencePropertyTarget4, referencePropertySource4)); + service.getAssetConnectionManager().updateConnections(oldConfigs, newConfigs); + awaitAssetConnected(service); + String newValue2 = "new value 2"; + setValue(serviceAsset, referencePropertySource1, newValue2); + setValue(serviceAsset, referencePropertySource2, newValue2); + setValue(serviceAsset, referencePropertySource3, newValue2); + setValue(serviceAsset, referencePropertySource4, newValue2); + + assertValue(referencePropertyTarget1, newValue2); + assertValue(referencePropertyTarget2, newValue1); + assertValue(referencePropertyTarget3, newValue2); + assertValue(referencePropertyTarget4, newValue2); + serviceAsset.stop(); + service.stop(); } @Test - // TODO re-add once OPC UA Endpoint is updated to AAS4j - @Ignore public void testServiceStartValidAssetConnectionDelayed() throws Exception { - int http = PortHelper.findFreePort(); - int opcua = PortHelper.findFreePort(); - int http2 = PortHelper.findFreePort(); - int opcua2 = PortHelper.findFreePort(); - ServiceConfig config = withAssetConnection(serviceConfig(http, opcua), - NODE_ID_SOURCE, - opcua2); + int portHttp = PortHelper.findFreePort(); + int portOpcUa = PortHelper.findFreePort(); + int portHttpAsset = PortHelper.findFreePort(); + int portOpcUaAsset = PortHelper.findFreePort(); + ServiceConfig config = serviceConfig(portHttp, portOpcUa); + config.getAssetConnections().add(connectionOpcUa(portOpcUaAsset, referencePropertyTarget1, nodeIdSource1)); service = new Service(config); service.start(); - assertServiceAvailabilityHttp(http); - assertTargetValue(http, TARGET_VALUE); - Service service2 = new Service(serviceConfig(http2, opcua2)); - service2.start(); - assertServiceAvailabilityOpcUa(opcua2); + assertServiceAvailabilityHttp(portHttp); + assertValue(referencePropertyTarget1, initialValue); + Service serviceAsset = new Service(serviceConfig(portHttpAsset, portOpcUaAsset)); + serviceAsset.start(); + assertServiceAvailabilityOpcUa(portOpcUaAsset); awaitAssetConnected(service); - assertTargetValue(http, SOURCE_VALUE); - service2.stop(); + String newValue = "new value"; + setValue(serviceAsset, referencePropertySource1, newValue); + assertValue(referencePropertyTarget1, newValue); + serviceAsset.stop(); + } + + + private AssetConnectionConfig connectionOpcUa(int port, Reference reference, String nodeId) throws IOException { + return OpcUaAssetConnectionConfig.builder() + .host("opc.tcp://localhost:" + port) + .securityBaseDir(securityBaseDir) + .valueProvider(reference, + OpcUaValueProviderConfig.builder() + .nodeId(nodeId) + .build()) + .build(); + } + + + private AssetConnectionConfig connectionHttp(int port, Reference target, Reference source) throws IOException, URISyntaxException { + ApiPaths paths = new ApiPaths(HOST, port); + + SubmodelElementIdentifier identifier = SubmodelElementIdentifier.fromReference(source); + URI uri = new URI(paths.submodelRepository() + .submodelInterface(identifier.getSubmodelId()) + .submodelElement(identifier.getIdShortPath(), Content.VALUE)); + String baseUrl = uri.getScheme() + "://" + uri.getAuthority(); + String urlPath = uri.getRawPath() + (uri.getRawQuery() != null ? "?" + uri.getRawQuery() : ""); + String idShort = identifier.getIdShortPath().getElements().get(identifier.getIdShortPath().getElements().size() - 1); + return HttpAssetConnectionConfig.builder() + .baseUrl(baseUrl) + .trustedCertificates(CertificateConfig.builder() + .keyStorePath(httpEndpointKeyStoreFile) + .keyStoreType(HTTP_ENDPOINT_KEYSTORE_TYPE) + .keyStorePassword(HTTP_ENDPOINT_KEYSTORE_PASSWORD) + .build()) + .valueProvider(target, + HttpValueProviderConfig.builder() + .format("JSON") + .path(urlPath) + .query("$." + idShort) + .build()) + .build(); + } + + + private void setValue(Reference reference, String value) throws ResourceNotFoundException, PersistenceException, ValueFormatException { + setValue(service, reference, value); + } + + + private void setValue(Service service, Reference reference, String value) throws ResourceNotFoundException, PersistenceException, ValueFormatException { + Property property = (Property) service.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Property.class); + property.setValue(value); + PatchSubmodelElementValueByPathResponse response = service.execute(PatchSubmodelElementValueByPathRequest.builder() + .submodelId(SubmodelElementIdentifier.fromReference(reference).getSubmodelId()) + .path(SubmodelElementIdentifier.fromReference(reference).getIdShortPath().toString()) + .value(PropertyValue.of(Datatype.STRING, value)) + .build()); + assertTrue(response.getStatusCode().isSuccess()); } @@ -185,11 +415,10 @@ private static ServiceConfig serviceConfig(int portHttp, int portOpcUa) { .initialModel(DeepCopyHelper.deepCopy(environment)) .build()) .fileStorage(new FileStorageInMemoryConfig()) - // TODO re-add once OPC UA Endpoint is updated to AAS4j - //.endpoint(OpcUaEndpointConfig.builder() - // .tcpPort(portOpcUa) - // .supportedAuthentication(UserTokenType.Anonymous) - // .build()) + .endpoint(OpcUaEndpointConfig.builder() + .tcpPort(portOpcUa) + .supportedAuthentication(UserTokenType.Anonymous) + .build()) .endpoint(HttpEndpointConfig.builder() .port(portHttp) .certificate(CertificateConfig.builder() @@ -204,29 +433,15 @@ private static ServiceConfig serviceConfig(int portHttp, int portOpcUa) { } - private static ServiceConfig withAssetConnection(ServiceConfig config, String nodeIdSource, int port) throws IOException { - config.getAssetConnections().add(OpcUaAssetConnectionConfig.builder() - .host("opc.tcp://" + "localhost:" + port) - .securityBaseDir(Files.createTempDirectory("asset-connection")) - .valueProvider(AasUtils.toReference(AasUtils.toReference(submodel), target), - OpcUaValueProviderConfig.builder() - .nodeId(nodeIdSource) - .build()) - .build()); - return config; - } - - private void assertServiceAvailabilityHttp(int port) throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, NoSuchAlgorithmException, KeyManagementException, UnsupportedModifierException { - List expected = environment.getAssetAdministrationShells(); assertExecutePage( HttpMethod.GET, new ApiPaths(HOST, port).aasRepository().assetAdministrationShells(), StatusCode.SUCCESS, null, - expected, + null, AssetAdministrationShell.class); } @@ -234,25 +449,25 @@ private void assertServiceAvailabilityHttp(int port) private void assertServiceAvailabilityOpcUa(int port) throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, AssetConnectionException, ConfigurationInitializationException, UaException, ExecutionException { OpcUaClient client = OpcUaHelper.connect(OpcUaAssetConnectionConfig.builder() - .securityBaseDir(Files.createTempDirectory("asset-connection")) - .host("opc.tcp://" + "localhost:" + port) + .securityBaseDir(securityBaseDir) + .host("opc.tcp://localhost:" + port) .build()); - DataValue value = OpcUaHelper.readValue(client, NODE_ID_SOURCE); - assertEquals(SOURCE_VALUE, Integer.parseInt(value.getValue().getValue().toString())); + DataValue value = OpcUaHelper.readValue(client, nodeIdSource1); + assertTrue(value.getStatusCode().isGood()); } - private void assertTargetValue(int port, int expectedValue) + private void assertValue(Reference reference, Object expectedValue) throws IOException, InterruptedException, URISyntaxException, JSONException, NoSuchAlgorithmException, KeyManagementException { - HttpResponse response = HttpHelper.get( - httpClient, - new ApiPaths(HOST, port) - .submodelRepository() - .submodelInterface(submodel) - .submodelElement(target, Content.VALUE)); - assertEquals(toHttpStatusCode(StatusCode.SUCCESS), response.statusCode()); - String expected = String.format("{\"target\": %d}", expectedValue); - JSONAssert.assertEquals(expected, response.body(), false); + GetSubmodelElementByPathResponse response = service.execute(GetSubmodelElementByPathRequest.builder() + .submodelId(SubmodelElementIdentifier.fromReference(reference).getSubmodelId()) + .path(SubmodelElementIdentifier.fromReference(reference).getIdShortPath().toString()) + .build()); + if (!response.getStatusCode().isSuccess()) { + throw new RuntimeException("failed to get property value for reference " + ReferenceHelper.asString(reference)); + } + String actual = ((Property) response.getPayload()).getValue(); + assertEquals(expectedValue.toString(), actual); } @@ -270,10 +485,10 @@ private Page assertExecutePage(HttpMethod method, String url, StatusCode private void awaitAssetConnected(Service service) { - await().atMost(30, TimeUnit.SECONDS) + await().atMost(60, TimeUnit.SECONDS) .with() .pollInterval(1, TimeUnit.SECONDS) - .until(() -> service.getAssetConnectionManager().getConnections().get(0).isConnected()); + .until(() -> service.getAssetConnectionManager().isFullyConnected()); } } diff --git a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/HttpEndpointIT.java b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/HttpEndpointIT.java index 03bdf3e8b..35e74d84e 100644 --- a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/HttpEndpointIT.java +++ b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/HttpEndpointIT.java @@ -18,17 +18,17 @@ import static de.fraunhofer.iosb.ilt.faaast.service.test.util.MessageBusHelper.DEFAULT_TIMEOUT; import static de.fraunhofer.iosb.ilt.faaast.service.test.util.MessageBusHelper.assertEvent; import static de.fraunhofer.iosb.ilt.faaast.service.test.util.MessageBusHelper.assertEvents; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.net.MediaType; import de.fraunhofer.iosb.ilt.faaast.service.Service; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AbstractAssetOperationProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.ArgumentValidationMode; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionManager; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProvider; -import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProviderConfig; import de.fraunhofer.iosb.ilt.faaast.service.config.CertificateConfig; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.config.ServiceConfig; @@ -61,10 +61,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.InvokeOperationSyncRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.proprietary.ImportResult; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedContentModifierException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedModifierException; -import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueFormatException; -import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueMappingException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.EventMessage; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.ElementReadEventMessage; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.OperationFinishEventMessage; @@ -91,6 +88,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.PortHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceBuilder; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ReflectionHelper; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -110,10 +108,13 @@ import java.util.Objects; import java.util.Optional; import java.util.Random; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.stream.Collectors; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; @@ -155,13 +156,14 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSpecificAssetId; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; -import org.json.JSONException; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.skyscreamer.jsonassert.JSONAssert; @@ -276,21 +278,38 @@ public void shutdown() { } - private void mockOperation(Reference reference, BiFunction logic) { - when(assetConnectionManager.hasOperationProvider(reference)) - .thenReturn(true); - when(assetConnectionManager.getOperationProvider(reference)) - .thenReturn(new AssetOperationProvider() { - @Override - public OperationVariable[] invoke(OperationVariable[] input, OperationVariable[] inoutput) throws AssetConnectionException { - return logic.apply(input, inoutput); - } - - - public AssetOperationProviderConfig getConfig() { - return new AbstractAssetOperationProviderConfig() {}; - } - }); + private void mockOperation(Reference reference, BiFunction logic) throws AssetConnectionException { + doReturn(true).when(assetConnectionManager).hasOperationProvider(reference); + doReturn(Optional.of(ArgumentValidationMode.REQUIRE_PRESENT_OR_DEFAULT)).when(assetConnectionManager).getOperationInputValidationMode(reference); + doReturn(Optional.of(ArgumentValidationMode.REQUIRE_PRESENT_OR_DEFAULT)).when(assetConnectionManager).getOperationInoutputValidationMode(reference); + doReturn(Optional.of(ArgumentValidationMode.REQUIRE_PRESENT_OR_DEFAULT)).when(assetConnectionManager).getOperationOutputValidationMode(reference); + doAnswer(call -> { + Reference actualReference = call.getArgument(0); + if (!ReferenceHelper.equals(reference, actualReference)) { + return Optional.empty(); + } + OperationVariable[] input = call.getArgument(1); + OperationVariable[] inoutput = call.getArgument(2); + return Optional.of(logic.apply(input, inoutput)); + }).when(assetConnectionManager).invoke(any(), any(), any()); + + doAnswer((Answer) (InvocationOnMock invocation) -> { + Reference actualReference = invocation.getArgument(0); + OperationVariable[] input = invocation.getArgument(1); + OperationVariable[] inoutput = invocation.getArgument(2); + BiConsumer callbackSuccess = invocation.getArgument(3); + Consumer callbackFailure = invocation.getArgument(4); + if (ReferenceHelper.equals(reference, actualReference)) { + CompletableFuture + .supplyAsync(LambdaExceptionHelper.rethrowSupplier(() -> logic.apply(input, inoutput))) + .thenAccept(x -> callbackSuccess.accept(x, inoutput)) + .exceptionally(e -> { + callbackFailure.accept(e); + return null; + }); + } + return null; + }).when(assetConnectionManager).invokeAsync(any(), any(), any(), any(), any()); } @@ -1656,9 +1675,7 @@ public void testSubmodelInterfaceGetSubmodelLevelDeep() @Test - public void testSubmodelInterfaceInvokeOperationAsync() - throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, MessageBusException, NoSuchAlgorithmException, - KeyManagementException, UnsupportedModifierException { + public void testSubmodelInterfaceInvokeOperationAsync() throws Exception { int inputValue = 4; Reference reference = operationSquareIdentifier.toReference(); CountDownLatch condition = new CountDownLatch(1); @@ -1667,13 +1684,12 @@ public void testSubmodelInterfaceInvokeOperationAsync() condition.await(); return operationSqaureDefaultImplementation(input, inoutput); } - catch (InterruptedException ex) { - throw new RuntimeException(); + catch (InterruptedException e) { + throw new RuntimeException(e); } }); AtomicReference operationStatusUrl = new AtomicReference<>(); // assert OperationStarted on messagebus - assertEvent( messageBus, OperationInvokeEventMessage.class, @@ -1807,9 +1823,7 @@ private static T getOperationSqaureInvokeRequ @Test - public void testSubmodelInterfaceInvokeOperationAsyncValueOnly() - throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, MessageBusException, NoSuchAlgorithmException, - KeyManagementException, JSONException, UnsupportedContentModifierException, UnsupportedModifierException { + public void testSubmodelInterfaceInvokeOperationAsyncValueOnly() throws Exception { int inputValue = 4; Reference reference = operationSquareIdentifier.toReference(); @@ -1897,9 +1911,7 @@ public void testSubmodelInterfaceInvokeOperationAsyncValueOnly() @Test - public void testSubmodelInterfaceInvokeOperationSync() - throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, MessageBusException, NoSuchAlgorithmException, - KeyManagementException, ValueFormatException, ValueMappingException { + public void testSubmodelInterfaceInvokeOperationSync() throws Exception { int inputValue = 4; Reference reference = operationSquareIdentifier.toReference(); mockOperation(reference, HttpEndpointIT::operationSqaureDefaultImplementation); @@ -1930,9 +1942,7 @@ public void testSubmodelInterfaceInvokeOperationSync() @Test - public void testSubmodelInterfaceInvokeOperationSyncValueOnly() - throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, MessageBusException, NoSuchAlgorithmException, - KeyManagementException, ValueFormatException, ValueMappingException { + public void testSubmodelInterfaceInvokeOperationSyncValueOnly() throws Exception { int inputValue = 4; Reference reference = operationSquareIdentifier.toReference(); mockOperation(reference, HttpEndpointIT::operationSqaureDefaultImplementation); @@ -1967,9 +1977,7 @@ public void testSubmodelInterfaceInvokeOperationSyncValueOnly() @Test - public void testSubmodelInterfaceInvokeOperationSyncWithExceptionInOperation() - throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, MessageBusException, NoSuchAlgorithmException, - KeyManagementException, ValueFormatException, ValueMappingException { + public void testSubmodelInterfaceInvokeOperationSyncWithExceptionInOperation() throws Exception { Reference reference = operationSquareIdentifier.toReference(); mockOperation(reference, (input, inoutput) -> { throw new IllegalArgumentException(); @@ -2002,9 +2010,7 @@ public void testSubmodelInterfaceInvokeOperationSyncWithExceptionInOperation() @Test - public void testSubmodelInterfaceInvokeOperationAsyncWithExceptionInOperation() - throws IOException, DeserializationException, InterruptedException, URISyntaxException, SerializationException, MessageBusException, NoSuchAlgorithmException, - KeyManagementException, ValueMappingException, UnsupportedModifierException { + public void testSubmodelInterfaceInvokeOperationAsyncWithExceptionInOperation() throws Exception { Reference reference = operationSquareIdentifier.toReference(); mockOperation(reference, (input, inoutput) -> { throw new UnsupportedOperationException("not implemented"); diff --git a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/ServiceIT.java b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/ServiceIT.java index b0055b186..608743acb 100644 --- a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/ServiceIT.java +++ b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/ServiceIT.java @@ -20,6 +20,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.dataformat.EnvironmentSerializationManager; import de.fraunhofer.iosb.ilt.faaast.service.dataformat.SerializationException; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.filestorage.memory.FileStorageInMemoryConfig; import de.fraunhofer.iosb.ilt.faaast.service.messagebus.internal.MessageBusInternalConfig; import de.fraunhofer.iosb.ilt.faaast.service.model.EnvironmentContext; @@ -40,7 +41,7 @@ public class ServiceIT { @Test public void testLoadingAuxiliaryFilesFromInitialModel() - throws ConfigurationException, AssetConnectionException, IOException, SerializationException, ResourceNotFoundException, PersistenceException { + throws ConfigurationException, AssetConnectionException, IOException, SerializationException, ResourceNotFoundException, PersistenceException, MessageBusException { final String submodelId = "http://example.com/submodel/1"; final String submodelIdShort = "submodel1"; final String fileIdShort = "file1"; diff --git a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/util/ApiPaths.java b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/util/ApiPaths.java index 09058cc3d..7619fb208 100644 --- a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/util/ApiPaths.java +++ b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/util/ApiPaths.java @@ -537,6 +537,11 @@ public String submodelElement(String idShortPath, Content content) { } + public String submodelElement(IdShortPath idShortPath, Content content) { + return submodelElement(idShortPath.toString(), content); + } + + public String submodelElement(String idShortPath, Content content, Extent extent) { return String.format("%s%s?%s", submodelElement(idShortPath), diff --git a/test/src/test/resources/logback.xml b/test/src/test/resources/logback.xml new file mode 100644 index 000000000..d067229bb --- /dev/null +++ b/test/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file From ebb9a3c8bbdad577a96f244d18b6b32286c29e62 Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Mon, 27 Oct 2025 16:17:18 +0100 Subject: [PATCH 048/108] Release v1.3.0 --- README.md | 8 +- assetconnection/common/pom.xml | 2 +- assetconnection/http/pom.xml | 2 +- assetconnection/mqtt/pom.xml | 2 +- assetconnection/opcua/pom.xml | 2 +- checks/pom.xml | 2 +- core/pom.xml | 2 +- dataformat/json/pom.xml | 2 +- docs/source/basics/installation.md | 8 +- docs/source/other/release-notes.md | 3 +- .../aggregate-third-party-report.html | 2056 ++++++++++------- endpoint/http/pom.xml | 2 +- endpoint/opcua/pom.xml | 2 +- examples/assetconnection-custom/pom.xml | 2 +- filestorage/filesystem/pom.xml | 2 +- filestorage/memory/pom.xml | 2 +- messagebus/internal/pom.xml | 2 +- messagebus/mqtt/pom.xml | 2 +- model/pom.xml | 2 +- persistence/file/pom.xml | 2 +- persistence/memory/pom.xml | 2 +- persistence/mongo/pom.xml | 2 +- pom.xml | 4 +- starter/pom.xml | 2 +- .../pom.xml | 2 +- test/pom.xml | 2 +- 26 files changed, 1222 insertions(+), 899 deletions(-) diff --git a/README.md b/README.md index f2b1a79af..2d4cc9a76 100644 --- a/README.md +++ b/README.md @@ -37,23 +37,23 @@ The features of FA³ST Service include ### Download pre-compiled JAR -[Download latest RELEASE version (1.2.0)](https://repo1.maven.org/maven2/de/fraunhofer/iosb/ilt/faaast/service/starter/1.2.0/starter-1.2.0.jar) +[Download latest RELEASE version (1.3.0)](https://repo1.maven.org/maven2/de/fraunhofer/iosb/ilt/faaast/service/starter/1.3.0/starter-1.3.0.jar) -[Download latest SNAPSHOT version (1.3.0-SNAPSHOT)](https://purl.archive.org/faaast/service/snapshot/latest) + ### As Maven Dependency ```xml de.fraunhofer.iosb.ilt.faaast.service starter - 1.2.0 + 1.3.0 ``` ### As Gradle Dependency ```kotlin -implementation 'de.fraunhofer.iosb.ilt.faaast.service:starter:1.2.0' +implementation 'de.fraunhofer.iosb.ilt.faaast.service:starter:1.3.0' ``` ## Building from Source diff --git a/assetconnection/common/pom.xml b/assetconnection/common/pom.xml index a3ffd38c4..3fa485aee 100644 --- a/assetconnection/common/pom.xml +++ b/assetconnection/common/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/assetconnection/http/pom.xml b/assetconnection/http/pom.xml index aae3af27a..4985e8009 100644 --- a/assetconnection/http/pom.xml +++ b/assetconnection/http/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/assetconnection/mqtt/pom.xml b/assetconnection/mqtt/pom.xml index c9472cad4..2c62d6307 100644 --- a/assetconnection/mqtt/pom.xml +++ b/assetconnection/mqtt/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/assetconnection/opcua/pom.xml b/assetconnection/opcua/pom.xml index 2ff6aa9b5..61a286076 100644 --- a/assetconnection/opcua/pom.xml +++ b/assetconnection/opcua/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/checks/pom.xml b/checks/pom.xml index 4515c6ee1..18ac041f5 100644 --- a/checks/pom.xml +++ b/checks/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/core/pom.xml b/core/pom.xml index 9bfcc1da6..ecd61d9e2 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/dataformat/json/pom.xml b/dataformat/json/pom.xml index a3e1a2733..00a0a34fc 100644 --- a/dataformat/json/pom.xml +++ b/dataformat/json/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml dataformat-json diff --git a/docs/source/basics/installation.md b/docs/source/basics/installation.md index 2f86aa7d5..a36f84418 100644 --- a/docs/source/basics/installation.md +++ b/docs/source/basics/installation.md @@ -7,10 +7,10 @@ ## Precompiled JAR -{download}`Latest RELEASE version (1.2.0) ` +{download}`Latest RELEASE version (1.3.0) ` -{download}`Latest SNAPSHOT version (1.3.0-SNAPSHOT) ` + ## Maven Dependency @@ -18,14 +18,14 @@ de.fraunhofer.iosb.ilt.faaast.service starter - 1.2.0 + 1.3.0 ``` ## Gradle Dependency ```groovy -implementation 'de.fraunhofer.iosb.ilt.faaast.service:starter:1.2.0' +implementation 'de.fraunhofer.iosb.ilt.faaast.service:starter:1.3.0' ``` ## Build from Source diff --git a/docs/source/other/release-notes.md b/docs/source/other/release-notes.md index 7ba3bff70..bbb495735 100644 --- a/docs/source/other/release-notes.md +++ b/docs/source/other/release-notes.md @@ -1,6 +1,5 @@ # Release Notes - -## 1.3.0-SNAPSHOT (current development version) +## 1.3.0 **New Features & Major Changes** - General diff --git a/docs/third_party_licenses_report/aggregate-third-party-report.html b/docs/third_party_licenses_report/aggregate-third-party-report.html index c447533b0..dbbceb64b 100644 --- a/docs/third_party_licenses_report/aggregate-third-party-report.html +++ b/docs/third_party_licenses_report/aggregate-third-party-report.html @@ -2,7 +2,7 @@ @@ -27,9 +27,9 @@

@@ -42,11 +42,11 @@

Overview

# of total dependencies -246 +258
# of dependencies with license declared in their pom -245 +257
# of dependencies with no license in their pom, but filled in the third-party missing file @@ -80,7 +80,7 @@

Third-parties list

BSD License 2.0
-org.slf4j:slf4j-api:2.0.16 +org.slf4j:slf4j-api:2.0.17 compile - jar @@ -94,21 +94,21 @@

Third-parties list

Apache Software License, Version 2.0
-com.google.errorprone:error_prone_annotations:2.28.0 +com.google.errorprone:error_prone_annotations:2.41.0 compile - jar Apache Software License, Version 2.0
-com.google.guava:failureaccess:1.0.2 +com.google.guava:failureaccess:1.0.3 compile - -bundle +jar Apache Software License, Version 2.0
-com.google.guava:guava:33.3.1-jre +com.google.guava:guava:33.5.0-jre compile - bundle @@ -122,116 +122,88 @@

Third-parties list

Apache Software License, Version 2.0
-com.google.j2objc:j2objc-annotations:3.0.0 +com.google.j2objc:j2objc-annotations:3.1 compile - jar Apache Software License, Version 2.0
-com.puppycrawl.tools:checkstyle:10.20.2 +com.puppycrawl.tools:checkstyle:12.1.1 compile - jar LGPL-2.1-or-later
-commons-beanutils:commons-beanutils:1.9.4 +commons-beanutils:commons-beanutils:1.11.0 compile - jar Apache Software License, Version 2.0
-commons-codec:commons-codec:1.15 -runtime -- -jar -Apache Software License, Version 2.0 - -
commons-collections:commons-collections:3.2.2 compile - jar Apache Software License, Version 2.0 - +
commons-logging:commons-logging:1.2 compile - jar Apache Software License, Version 2.0 - +
-info.picocli:picocli:4.7.6 +info.picocli:picocli:4.7.7 compile - jar Apache Software License, Version 2.0 - +
-net.sf.saxon:Saxon-HE:12.5 +net.sf.saxon:Saxon-HE:12.9 compile - jar Mozilla Public License Version 2.0 - +
org.antlr:antlr4-runtime:4.13.2 compile - jar BSD 3-Clause - +
org.apache.commons:commons-lang3:3.8.1 compile - jar Apache Software License, Version 2.0 - +
org.apache.commons:commons-text:1.3 compile - jar Apache Software License, Version 2.0 - +
org.apache.httpcomponents:httpclient:4.5.13 compile - jar Apache Software License, Version 2.0 - +
org.apache.httpcomponents:httpcore:4.4.14 compile - jar Apache Software License, Version 2.0 - -
-org.apache.httpcomponents.client5:httpclient5:5.1.3 -runtime -- -jar -Apache Software License, Version 2.0 - -
-org.apache.httpcomponents.core5:httpcore5:5.1.3 -runtime -- -jar -Apache Software License, Version 2.0 - -
-org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 -runtime -- -jar -Apache Software License, Version 2.0
org.apache.maven.doxia:doxia-core:1.12.0 @@ -269,7 +241,7 @@

Third-parties list

Apache Software License, Version 2.0
-org.checkerframework:checker-qual:3.48.2 +org.checkerframework:checker-qual:3.51.1 compile - jar @@ -311,70 +283,98 @@

Third-parties list

Apache Software License, Version 2.0
LGPL 2.1
MPL 1.1
+org.jspecify:jspecify:1.0.0 +compile +- +jar +Apache Software License, Version 2.0 + +
org.reflections:reflections:0.10.2 compile - jar Apache Software License, Version 2.0
WTFPL + +
+org.xmlresolver:xmlresolver:5.3.3 +compile +- +jar +Apache Software License, Version 2.0 + +
+com.apicatalog:titanium-jcs:1.1.1 +compile +- +jar +Apache Software License, Version 2.0 + +
+com.apicatalog:titanium-json-ld:1.7.0 +compile +- +jar +Apache Software License, Version 2.0
-org.xmlresolver:xmlresolver:5.2.2 +com.apicatalog:titanium-rdf-api:1.0.0 compile - jar Apache Software License, Version 2.0
-com.apicatalog:titanium-json-ld:1.4.1 +com.apicatalog:titanium-rdf-n-quads:1.0.2 compile - jar Apache Software License, Version 2.0
-com.ethlo.time:itu:1.10.2 +com.ethlo.time:itu:1.10.3 compile - bundle Apache Software License, Version 2.0
-com.fasterxml.jackson.core:jackson-annotations:2.18.2 +com.fasterxml.jackson.core:jackson-annotations:2.20 compile - jar Apache Software License, Version 2.0
-com.fasterxml.jackson.core:jackson-core:2.18.2 +com.fasterxml.jackson.core:jackson-core:2.20.0 compile - jar Apache Software License, Version 2.0
-com.fasterxml.jackson.core:jackson-databind:2.18.2 +com.fasterxml.jackson.core:jackson-databind:2.20.0 compile - jar Apache Software License, Version 2.0
-com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.1 +com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.0 compile - jar Apache Software License, Version 2.0
-com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1 +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.0 compile - jar Apache Software License, Version 2.0
-com.fasterxml.woodstox:woodstox-core:7.0.0 +com.fasterxml.woodstox:woodstox-core:7.1.1 compile - bundle @@ -388,7 +388,7 @@

Third-parties list

The MIT License (MIT)
-com.github.ben-manes.caffeine:caffeine:3.1.8 +com.github.ben-manes.caffeine:caffeine:3.2.2 compile - jar @@ -430,193 +430,207 @@

Third-parties list

BSD License
-com.google.code.gson:gson:2.11.0 +com.google.code.findbugs:jsr305:2.0.1 compile - jar Apache Software License, Version 2.0
-com.google.protobuf:protobuf-java:4.29.1 +com.google.code.gson:gson:2.13.2 compile - jar -BSD 3-Clause +Apache Software License, Version 2.0
-com.networknt:json-schema-validator:1.5.3 +com.google.protobuf:protobuf-java:4.33.0 +compile +- +jar +BSD 3-Clause + +
+com.networknt:json-schema-validator:1.5.8 compile - bundle Apache Software License, Version 2.0 - +
com.vaadin.external.google:android-json:0.0.20131108.vaadin1 compile - jar Apache Software License, Version 2.0 - +
com.zaxxer:SparseBitSet:1.3 compile - jar Apache Software License, Version 2.0 + +
+commons-codec:commons-codec:1.19.0 +compile +- +jar +Apache Software License, Version 2.0
-commons-codec:commons-codec:1.17.1 +commons-io:commons-io:2.20.0 compile - jar Apache Software License, Version 2.0
-commons-io:commons-io:2.16.1 +io.github.classgraph:classgraph:4.8.184 compile - jar -Apache Software License, Version 2.0 +The MIT License (MIT)
-io.github.classgraph:classgraph:4.8.179 +jakarta.activation:jakarta.activation-api:2.1.4 compile - jar -The MIT License (MIT) +Eclipse Distribution License - v 1.0
-jakarta.activation:jakarta.activation-api:2.1.3 +jakarta.xml.bind:jakarta.xml.bind-api:4.0.4 compile - jar Eclipse Distribution License - v 1.0
-jakarta.xml.bind:jakarta.xml.bind-api:4.0.2 +org.apache.commons:commons-collections4:4.4 compile - jar -Eclipse Distribution License - v 1.0 +Apache Software License, Version 2.0
-org.apache.commons:commons-collections4:4.4 +org.apache.commons:commons-compress:1.28.0 compile - jar Apache Software License, Version 2.0
-org.apache.commons:commons-compress:1.27.1 +org.apache.commons:commons-csv:1.14.1 compile - jar Apache Software License, Version 2.0
-org.apache.commons:commons-csv:1.12.0 +org.apache.commons:commons-lang3:3.19.0 compile - jar Apache Software License, Version 2.0
-org.apache.commons:commons-lang3:3.17.0 +org.apache.commons:commons-math3:3.6.1 compile - jar Apache Software License, Version 2.0
-org.apache.commons:commons-math3:3.6.1 +org.apache.jena:jena-arq:5.6.0 compile - jar Apache Software License, Version 2.0
-org.apache.jena:jena-arq:5.2.0 +org.apache.jena:jena-base:5.6.0 compile - jar Apache Software License, Version 2.0
-org.apache.jena:jena-base:5.2.0 +org.apache.jena:jena-core:5.6.0 compile - jar Apache Software License, Version 2.0
-org.apache.jena:jena-core:5.2.0 +org.apache.jena:jena-iri:5.6.0 compile - jar Apache Software License, Version 2.0
-org.apache.jena:jena-iri:5.2.0 +org.apache.jena:jena-iri3986:5.6.0 compile - jar Apache Software License, Version 2.0
-org.apache.jena:jena-shacl:5.2.0 +org.apache.jena:jena-langtag:5.6.0 compile - jar Apache Software License, Version 2.0
-org.apache.logging.log4j:log4j-api:2.23.1 +org.apache.jena:jena-shacl:5.6.0 compile - jar Apache Software License, Version 2.0
-org.apache.poi:poi:5.3.0 +org.apache.logging.log4j:log4j-api:2.24.3 compile - jar Apache Software License, Version 2.0
-org.apache.poi:poi-ooxml:5.3.0 +org.apache.poi:poi:5.4.1 compile - jar Apache Software License, Version 2.0
-org.apache.poi:poi-ooxml-lite:5.3.0 +org.apache.poi:poi-ooxml:5.4.1 compile - jar Apache Software License, Version 2.0
-org.apache.thrift:libthrift:0.21.0 +org.apache.poi:poi-ooxml-lite:5.4.1 compile - jar Apache Software License, Version 2.0
-org.apache.xmlbeans:xmlbeans:5.2.1 +org.apache.thrift:libthrift:0.22.0 compile - jar Apache Software License, Version 2.0
-org.checkerframework:checker-qual:3.43.0 +org.apache.xmlbeans:xmlbeans:5.3.0 compile - jar -The MIT License (MIT) +Apache Software License, Version 2.0
org.codehaus.woodstox:stax2-api:4.2.2 @@ -626,35 +640,35 @@

Third-parties list

The BSD 2-Clause License
-org.eclipse.digitaltwin.aas4j:aas4j-dataformat-aasx:1.0.3 +org.eclipse.digitaltwin.aas4j:aas4j-dataformat-aasx:1.0.5 compile - jar Apache Software License, Version 2.0
-org.eclipse.digitaltwin.aas4j:aas4j-dataformat-core:1.0.3 +org.eclipse.digitaltwin.aas4j:aas4j-dataformat-core:1.0.5 compile - jar Apache Software License, Version 2.0
-org.eclipse.digitaltwin.aas4j:aas4j-dataformat-json:1.0.3 +org.eclipse.digitaltwin.aas4j:aas4j-dataformat-json:1.0.5 compile - jar Apache Software License, Version 2.0
-org.eclipse.digitaltwin.aas4j:aas4j-dataformat-xml:1.0.3 +org.eclipse.digitaltwin.aas4j:aas4j-dataformat-xml:1.0.5 compile - jar Apache Software License, Version 2.0
-org.eclipse.digitaltwin.aas4j:aas4j-model:1.0.3 +org.eclipse.digitaltwin.aas4j:aas4j-model:1.0.5 compile - jar @@ -682,78 +696,64 @@

Third-parties list

Apache Software License, Version 2.0
-org.slf4j:jcl-over-slf4j:2.0.16 +org.slf4j:jcl-over-slf4j:2.0.17 compile - jar Apache Software License, Version 2.0
-org.slf4j:slf4j-simple:2.0.16 +org.slf4j:slf4j-simple:2.0.17 test - jar The MIT License (MIT)
-org.yaml:snakeyaml:2.3 +org.yaml:snakeyaml:2.4 compile - bundle Apache Software License, Version 2.0
-com.fasterxml.woodstox:woodstox-core:7.1.0 -compile -- -bundle -Apache Software License, Version 2.0 - -
com.github.jknack:handlebars:4.3.1 test - jar Apache Software License, Version 2.0 - +
com.github.jknack:handlebars-helpers:4.3.1 test - jar Apache Software License, Version 2.0 - +
com.github.tomakehurst:wiremock-jre8:2.35.2 test - jar Apache Software License, Version 2.0 - +
com.jayway.jsonpath:json-path:2.7.0 test - jar Apache Software License, Version 2.0 - +
commons-fileupload:commons-fileupload:1.4 test - jar Apache Software License, Version 2.0 - -
-commons-io:commons-io:2.18.0 -compile -- -jar -Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:model:1.2.0 -compile +de.fraunhofer.iosb.ilt.faaast.service:model:1.3.0 +test - jar Apache Software License, Version 2.0 @@ -766,14 +766,14 @@

Third-parties list

CDDL + GPLv2 with classpath exception
-net.bytebuddy:byte-buddy:1.15.4 +net.bytebuddy:byte-buddy:1.17.7 test - jar Apache Software License, Version 2.0
-net.bytebuddy:byte-buddy-agent:1.15.4 +net.bytebuddy:byte-buddy-agent:1.17.7 test - jar @@ -808,994 +808,1078 @@

Third-parties list

The MIT License (MIT)
-org.apache.logging.log4j:log4j-api:2.24.2 -compile +org.apache.httpcomponents.client5:httpclient5:5.1.3 +test +- +jar +Apache Software License, Version 2.0 + +
+org.apache.httpcomponents.core5:httpcore5:5.1.3 +test +- +jar +Apache Software License, Version 2.0 + +
+org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 +test - jar Apache Software License, Version 2.0
-org.apache.logging.log4j:log4j-to-slf4j:2.24.2 +org.apache.logging.log4j:log4j-api:2.25.2 compile - jar Apache Software License, Version 2.0
-org.bouncycastle:bcpkix-jdk18on:1.79 +org.apache.logging.log4j:log4j-to-slf4j:2.25.2 compile - jar -Bouncy Castle Licence +Apache Software License, Version 2.0
-org.bouncycastle:bcprov-jdk18on:1.79 +org.bouncycastle:bcpkix-jdk18on:1.82 compile - jar Bouncy Castle Licence
-org.bouncycastle:bcutil-jdk18on:1.79 +org.bouncycastle:bcprov-jdk18on:1.82 compile - jar Bouncy Castle Licence
+org.bouncycastle:bcutil-jdk18on:1.82 +compile +- +jar +Bouncy Castle Licence + +
org.eclipse.jetty:jetty-alpn-client:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-alpn-java-client:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-alpn-java-server:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-alpn-openjdk8-client:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-alpn-openjdk8-server:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-alpn-server:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-client:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-continuation:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-http:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-io:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-proxy:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-security:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-server:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-servlet:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-servlets:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-util:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-util-ajax:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-webapp:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty:jetty-xml:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty.http2:http2-common:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty.http2:http2-hpack:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
org.eclipse.jetty.http2:http2-server:9.4.49.v20220914 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 - +
-org.mockito:mockito-core:5.14.2 +org.mockito:mockito-core:5.20.0 test - jar The MIT License (MIT) - +
org.objenesis:objenesis:3.3 test - jar Apache Software License, Version 2.0 - +
org.ow2.asm:asm:9.4 test - jar BSD 3-Clause - +
org.xmlunit:xmlunit-core:2.9.0 test - jar Apache Software License, Version 2.0 - +
org.xmlunit:xmlunit-legacy:2.9.0 test - jar BSD 3-Clause - +
org.xmlunit:xmlunit-placeholders:2.9.0 test - jar Apache Software License, Version 2.0 - +
-com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2 +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.0 compile - bundle Apache Software License, Version 2.0 - +
-de.fraunhofer.iosb.ilt.faaast.service:core:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:core:1.3.0 compile - jar Apache Software License, Version 2.0 - +
-com.github.curious-odd-man:rgxgen:2.0 +com.github.curious-odd-man:rgxgen:3.1 compile - jar Apache Software License, Version 2.0 - +
-commons-fileupload:commons-fileupload:1.5 +commons-fileupload:commons-fileupload:1.6.0 compile - jar Apache Software License, Version 2.0 - +
-de.fraunhofer.iosb.ilt.faaast.service:dataformat-json:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:dataformat-json:1.3.0 compile - jar Apache Software License, Version 2.0 - +
jakarta.json:jakarta.json-api:2.1.3 compile - jar Eclipse Public License 2.0
GNU General Public License, version 2 with the GNU Classpath Exception - +
jakarta.servlet:jakarta.servlet-api:6.0.0 compile - jar Eclipse Public License - Version 2.0
GPL2 w/ CPE + +
+org.apache.httpcomponents.client5:httpclient5:5.5.1 +test +- +jar +Apache Software License, Version 2.0
-org.apache.httpcomponents.client5:httpclient5:5.4.1 +org.apache.httpcomponents.core5:httpcore5:5.3.6 test - jar Apache Software License, Version 2.0
-org.apache.httpcomponents.core5:httpcore5:5.3.1 +org.apache.httpcomponents.core5:httpcore5-h2:5.3.6 test - jar Apache Software License, Version 2.0
-org.apache.httpcomponents.core5:httpcore5-h2:5.3.1 +org.eclipse.jetty:jetty-alpn-client:12.1.3 test - jar -Apache Software License, Version 2.0 +Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-alpn-client:12.0.15 +org.eclipse.jetty:jetty-client:12.1.3 test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-client:12.0.15 -test +org.eclipse.jetty:jetty-http:12.1.3 +compile - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-http:12.0.15 +org.eclipse.jetty:jetty-io:12.1.3 compile - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-io:12.0.15 +org.eclipse.jetty:jetty-security:12.1.3 compile - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-security:12.0.15 +org.eclipse.jetty:jetty-server:12.1.3 compile - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-server:12.0.15 +org.eclipse.jetty:jetty-session:12.1.3 compile - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-session:12.0.15 +org.eclipse.jetty:jetty-util:12.1.3 compile - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty:jetty-util:12.0.15 -compile +org.eclipse.jetty.compression:jetty-compression-common:12.1.3 +test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.15 -compile +org.eclipse.jetty.compression:jetty-compression-gzip:12.1.3 +test - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
-org.eclipse.jetty.ee10:jetty-ee10-servlets:12.0.15 +org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.3 compile - jar Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
+org.eclipse.jetty.ee10:jetty-ee10-servlets:12.1.3 +compile +- +jar +Apache Software License, Version 2.0
Eclipse Public License - Version 2.0 + +
org.eclipse.parsson:parsson:1.1.7 compile - jar Eclipse Public License 2.0
GNU General Public License, version 2 with the GNU Classpath Exception - +
ch.qos.reload4j:reload4j:1.2.22 runtime - jar Apache Software License, Version 2.0 + +
+com.zaxxer:HikariCP:2.4.7 +compile +- +bundle +Apache Software License, Version 2.0
-com.librato.metrics:metrics-librato:5.1.4 +commons-codec:commons-codec:1.18.0 compile - jar Apache Software License, Version 2.0
-com.zaxxer:HikariCP:2.4.7 +de.fraunhofer.iosb.io.moquette:moquette-broker:0.18.3 compile - -bundle -Apache Software License, Version 2.0 +jar +Apache Software License, Version 2.0
Eclipse Public License - Version 1.0
-de.fraunhofer.iosb.io.moquette:moquette-broker:0.15.2 +io.netty:netty-buffer:4.2.7.Final compile - jar -Apache Software License, Version 2.0
Eclipse Public License - Version 1.0 +Apache Software License, Version 2.0
-io.dropwizard.metrics:metrics-core:3.2.2 +io.netty:netty-codec:4.2.7.Final compile - -bundle +jar Apache Software License, Version 2.0
-io.dropwizard.metrics:metrics-jvm:3.2.2 +io.netty:netty-codec-base:4.2.7.Final compile - -bundle +jar Apache Software License, Version 2.0
-io.netty:netty-buffer:4.1.115.Final +io.netty:netty-codec-compression:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-codec:4.1.115.Final +io.netty:netty-codec-http:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-codec-http:4.1.115.Final +io.netty:netty-codec-marshalling:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-codec-mqtt:4.1.115.Final +io.netty:netty-codec-mqtt:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-common:4.1.115.Final +io.netty:netty-codec-protobuf:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-handler:4.1.115.Final +io.netty:netty-common:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-resolver:4.1.115.Final +io.netty:netty-handler:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-transport:4.1.115.Final +io.netty:netty-resolver:4.2.7.Final compile - jar Apache Software License, Version 2.0
-io.netty:netty-transport-native-unix-common:4.1.115.Final +io.netty:netty-transport:4.2.7.Final compile - jar Apache Software License, Version 2.0
+io.netty:netty-transport-native-unix-common:4.2.7.Final +compile +- +jar +Apache Software License, Version 2.0 + +
org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5 compile - eclipse-plugin Eclipse Public License - Version 2.0 - +
-org.slf4j:slf4j-reload4j:2.0.7 +org.slf4j:slf4j-reload4j:2.0.17 runtime - jar The MIT License (MIT) - +
-de.fraunhofer.iosb.ilt.faaast.service:persistence-memory:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:persistence-memory:1.3.0 compile - jar Apache Software License, Version 2.0 + +
+de.flapdoodle:de.flapdoodle.os:1.9.2 +test +- +jar +Apache Software License, Version 2.0
-de.flapdoodle:de.flapdoodle.os:1.9.0 +de.flapdoodle:de.flapdoodle.os-api:1.7.1 test - jar Apache Software License, Version 2.0
-de.flapdoodle:de.flapdoodle.os-api:1.7.0 +de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.21.0 test - -jar +bundle Apache Software License, Version 2.0
-de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.18.1 +de.flapdoodle.embed:de.flapdoodle.embed.mongo.packageresolver:4.21.0 test - -bundle +jar Apache Software License, Version 2.0
-de.flapdoodle.embed:de.flapdoodle.embed.mongo.packageresolver:4.18.2 +de.flapdoodle.embed:de.flapdoodle.embed.process:4.17.0 test - -jar +bundle Apache Software License, Version 2.0
-de.flapdoodle.embed:de.flapdoodle.embed.process:4.15.1 +de.flapdoodle.graph:de.flapdoodle.graph:1.3.4 test - -bundle +jar Apache Software License, Version 2.0
-de.flapdoodle.graph:de.flapdoodle.graph:1.3.3 +de.flapdoodle.java8:de.flapdoodle.java8:1.7.2 test - jar Apache Software License, Version 2.0
-de.flapdoodle.java8:de.flapdoodle.java8:1.7.0 +de.flapdoodle.reverse:de.flapdoodle.reverse:1.9.1 test - jar Apache Software License, Version 2.0
-de.flapdoodle.reverse:de.flapdoodle.reverse:1.8.1 +net.java.dev.jna:jna:5.17.0 test - jar -Apache Software License, Version 2.0 +Apache Software License, Version 2.0
LGPL-2.1-or-later
-net.java.dev.jna:jna:5.15.0 +net.java.dev.jna:jna-platform:5.17.0 test - jar Apache Software License, Version 2.0
LGPL-2.1-or-later
-net.java.dev.jna:jna-platform:5.15.0 +org.jgrapht:jgrapht-core:1.4.0 test - jar -Apache Software License, Version 2.0
LGPL-2.1-or-later +Eclipse Public License (EPL) 2.0
GNU Lesser General Public License Version 2.1, February 1999
-org.jgrapht:jgrapht-core:1.4.0 +org.jheaps:jheaps:0.11 test - jar -Eclipse Public License (EPL) 2.0
GNU Lesser General Public License Version 2.1, February 1999 +Apache Software License, Version 2.0
-org.jheaps:jheaps:0.11 -test +org.mongodb:bson:5.6.1 +compile - jar Apache Software License, Version 2.0
-org.mongodb:bson:5.2.1 -compile +org.mongodb:bson-record-codec:5.6.1 +runtime - jar Apache Software License, Version 2.0
-org.mongodb:bson-record-codec:5.2.1 -runtime +org.mongodb:mongodb-driver-core:5.6.1 +compile - jar Apache Software License, Version 2.0
-org.mongodb:mongodb-driver-core:5.2.1 +org.mongodb:mongodb-driver-sync:5.6.1 compile - jar Apache Software License, Version 2.0
-org.mongodb:mongodb-driver-sync:5.2.1 +com.jayway.jsonpath:json-path:2.9.0 compile - jar Apache Software License, Version 2.0
-com.jayway.jsonpath:json-path:2.9.0 +net.minidev:accessors-smart:2.6.0 compile - -jar +bundle Apache Software License, Version 2.0
-net.minidev:accessors-smart:2.5.1 +net.minidev:json-smart:2.6.0 compile - bundle Apache Software License, Version 2.0
-net.minidev:json-smart:2.5.1 +net.thisptr:jackson-jq:1.6.0 compile - bundle Apache Software License, Version 2.0
-org.ow2.asm:asm:9.6 +org.jruby.jcodings:jcodings:1.0.63 compile - jar -BSD 3-Clause +The MIT License (MIT)
-com.github.valfirst:slf4j-test:3.0.1 -test +org.jruby.joni:joni:2.2.6 +compile - jar The MIT License (MIT)
-de.fraunhofer.iosb.ilt.faaast.service:assetconnection-common:1.2.0 +org.ow2.asm:asm:9.7.1 compile - jar -Apache Software License, Version 2.0 +BSD 3-Clause
-net.bytebuddy:byte-buddy:1.15.10 +com.github.valfirst:slf4j-test:3.0.3 test - jar -Apache Software License, Version 2.0 +The MIT License (MIT)
-nl.jqno.equalsverifier:equalsverifier:3.17.5 +de.fraunhofer.iosb.ilt.faaast.service:assetconnection-common:1.3.0 +compile +- +jar +Apache Software License, Version 2.0 + +
+nl.jqno.equalsverifier:equalsverifier:4.2 test - pom Apache Software License, Version 2.0 - +
-org.awaitility:awaitility:4.2.2 +org.awaitility:awaitility:4.3.0 test - jar Apache Software License, Version 2.0 - +
org.hamcrest:hamcrest:2.1 test - jar BSD Licence 3 - +
org.objenesis:objenesis:3.4 test - jar Apache Software License, Version 2.0 - +
uk.org.lidalia:lidalia-slf4j-ext:1.0.0-jdk6 test - jar MIT X11 License - +
com.digitalpetri.fsm:strict-machine:0.7 compile - jar Apache Software License, Version 2.0 - +
com.digitalpetri.netty:netty-channel-fsm:0.9 compile - jar Apache Software License, Version 2.0 - +
com.sun.activation:jakarta.activation:1.2.2 compile - jar Eclipse Distribution License - v 1.0 - +
com.sun.istack:istack-commons-runtime:3.0.12 compile - jar Eclipse Distribution License - v 1.0 - +
javax.activation:javax.activation-api:1.2.0 compile - jar CDDL/GPLv2+CE - +
javax.xml.bind:jaxb-api:2.3.1 compile - jar CDDL 1.1
GPL2 w/ CPE - -
-org.eclipse.milo:bsd-core:0.6.15 -compile -- -jar -Eclipse Public License - v 2.0
-org.eclipse.milo:bsd-generator:0.6.15 +org.eclipse.milo:bsd-core:0.6.16 compile - jar Eclipse Public License - v 2.0
-org.eclipse.milo:sdk-client:0.6.15 +org.eclipse.milo:bsd-generator:0.6.16 compile - jar Eclipse Public License - v 2.0
-org.eclipse.milo:sdk-core:0.6.15 +org.eclipse.milo:sdk-client:0.6.16 compile - jar Eclipse Public License - v 2.0
-org.eclipse.milo:sdk-server:0.6.15 +org.eclipse.milo:sdk-core:0.6.16 compile - jar Eclipse Public License - v 2.0
-org.eclipse.milo:stack-client:0.6.15 +org.eclipse.milo:sdk-server:0.6.16 compile - jar Eclipse Public License - v 2.0
-org.eclipse.milo:stack-core:0.6.15 +org.eclipse.milo:stack-client:0.6.16 compile - jar Eclipse Public License - v 2.0
-org.eclipse.milo:stack-server:0.6.15 -test +org.eclipse.milo:stack-core:0.6.16 +compile - jar Eclipse Public License - v 2.0
+org.eclipse.milo:stack-server:0.6.16 +test +- +jar +Eclipse Public License - v 2.0 + +
org.glassfish.jaxb:jaxb-runtime:2.3.6 compile - jar Eclipse Distribution License - v 1.0 - +
org.glassfish.jaxb:txw2:2.3.6 compile - jar Eclipse Distribution License - v 1.0 - +
-com.prosysopc.ua:prosys-opc-ua-sdk-client-server:5.2.8-159 +com.prosysopc.ua:prosys-opc-ua-sdk-client-server:5.4.0-201 compile - jar - - +
-de.fraunhofer.iosb.ilt.faaast.service:filestorage-memory:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:filestorage-memory:1.3.0 test - jar Apache Software License, Version 2.0 - +
-de.fraunhofer.iosb.ilt.faaast.service:messagebus-internal:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:messagebus-internal:1.3.0 test - jar Apache Software License, Version 2.0 - +
org.apache.httpcomponents:httpcore:4.4.13 compile - jar Apache Software License, Version 2.0 - +
org.apache.httpcomponents:httpcore-nio:4.4.12 compile - jar Apache Software License, Version 2.0 + +
+org.bouncycastle:bcpkix-jdk15to18:1.82 +compile +- +jar +Bouncy Castle Licence
-org.bouncycastle:bcpkix-jdk15to18:1.78.1 +org.bouncycastle:bcprov-jdk15to18:1.82 compile - jar Bouncy Castle Licence
-org.bouncycastle:bcprov-jdk15to18:1.79 +org.bouncycastle:bcutil-jdk15to18:1.82 compile - jar Bouncy Castle Licence
-org.bouncycastle:bcutil-jdk15to18:1.78.1 -compile +com.github.tomakehurst:wiremock-jre8-standalone:2.35.2 +test - jar -Bouncy Castle Licence +Apache Software License, Version 2.0
-ch.qos.logback:logback-classic:1.5.12 +de.fraunhofer.iosb.ilt.faaast.service:assetconnection-http:1.3.0 compile - jar -Eclipse Public License - v 1.0
GNU Lesser General Public License +Apache Software License, Version 2.0
-ch.qos.logback:logback-core:1.5.12 +de.fraunhofer.iosb.ilt.faaast.service:assetconnection-mqtt:1.3.0 compile - jar -Eclipse Public License - v 1.0
GNU Lesser General Public License +Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:assetconnection-http:1.2.0 -compile +de.fraunhofer.iosb.ilt.faaast.service:endpoint-http:1.3.0 +test - jar Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:assetconnection-mqtt:1.2.0 -compile +de.fraunhofer.iosb.ilt.faaast.service:messagebus-mqtt:1.3.0 +test - jar Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:assetconnection-opcua:1.2.0 +ch.qos.logback:logback-classic:1.5.20 +compile +- +jar +Eclipse Public License - v 1.0
GNU Lesser General Public License + +
+ch.qos.logback:logback-core:1.5.20 +compile +- +jar +Eclipse Public License - v 1.0
GNU Lesser General Public License + +
+de.fraunhofer.iosb.ilt.faaast.service:assetconnection-opcua:1.3.0 compile - jar Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:endpoint-http:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:endpoint-opcua:1.3.0 compile - jar Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:endpoint-opcua:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:filestorage-filesystem:1.3.0 compile - jar Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:filestorage-filesystem:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:persistence-file:1.3.0 compile - jar Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:persistence-file:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:persistence-mongo:1.3.0 compile - jar Apache Software License, Version 2.0
-de.fraunhofer.iosb.ilt.faaast.service:persistence-mongo:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:submodeltemplate-asset-interfaces-mapping-configuration:1.3.0 compile - jar Apache Software License, Version 2.0
-net.bytebuddy:byte-buddy:1.15.1 +net.bytebuddy:byte-buddy:1.17.4 test - jar Apache Software License, Version 2.0
-net.bytebuddy:byte-buddy-agent:1.15.1 +net.bytebuddy:byte-buddy-agent:1.17.4 test - jar Apache Software License, Version 2.0
-org.codehaus.janino:commons-compiler:3.1.11 +org.codehaus.janino:commons-compiler:3.1.12 compile - jar BSD 3-Clause
-org.codehaus.janino:janino:3.1.11 +org.codehaus.janino:janino:3.1.12 compile - jar BSD 3-Clause
-uk.org.webcompere:system-stubs-core:2.1.7 +uk.org.webcompere:system-stubs-core:2.1.8 test - jar The MIT License (MIT)
-de.fraunhofer.iosb.ilt.faaast.service:starter:1.2.0 +de.fraunhofer.iosb.ilt.faaast.service:starter:1.3.0 compile - jar Apache Software License, Version 2.0

Third-parties detail with no license

-

Listing of Third-parties with no license.

-

com.prosysopc.ua:prosys-opc-ua-sdk-client-server:5.2.8-159

+

Listing of Third-parties with no license.

+

com.prosysopc.ua:prosys-opc-ua-sdk-client-server:5.4.0-201

- + @@ -1851,15 +1935,15 @@

org.hamcrest:hamcrest-core:1.3

-
Status
 No license for this dependency
GroupId:ArtifactId:Versioncom.prosysopc.ua:prosys-opc-ua-sdk-client-server:5.2.8-159
com.prosysopc.ua:prosys-opc-ua-sdk-client-server:5.4.0-201
Scope compile
jar
License(s)BSD License 2.0
Back to top
-

org.slf4j:slf4j-api:2.0.16

+BSD License 2.0Back to top
+

org.slf4j:slf4j-api:2.0.17

- + @@ -1891,15 +1975,15 @@

com.google.code.findbugs:jsr305:3.0.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.slf4j:slf4j-api:2.0.16
org.slf4j:slf4j-api:2.0.17
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.google.errorprone:error_prone_annotations:2.28.0

+Apache Software License, Version 2.0Back to top
+

com.google.errorprone:error_prone_annotations:2.41.0

- + @@ -1911,15 +1995,15 @@

com.google.errorprone:error_prone_annotations:2.28.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.google.errorprone:error_prone_annotations:2.28.0
com.google.errorprone:error_prone_annotations:2.41.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.google.guava:failureaccess:1.0.2

+Apache Software License, Version 2.0Back to top
+

com.google.guava:failureaccess:1.0.3

- + @@ -1928,18 +2012,18 @@

com.google.guava:failureaccess:1.0.2

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.google.guava:failureaccess:1.0.2
com.google.guava:failureaccess:1.0.3
Scope compile
Typebundle
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.google.guava:guava:33.3.1-jre

+Apache Software License, Version 2.0Back to top
+

com.google.guava:guava:33.5.0-jre

- + @@ -1971,15 +2055,15 @@

com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava<

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.google.guava:guava:33.3.1-jre
com.google.guava:guava:33.5.0-jre
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.google.j2objc:j2objc-annotations:3.0.0

+Apache Software License, Version 2.0Back to top
+

com.google.j2objc:j2objc-annotations:3.1

- + @@ -1991,15 +2075,15 @@

com.google.j2objc:j2objc-annotations:3.0.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.google.j2objc:j2objc-annotations:3.0.0
com.google.j2objc:j2objc-annotations:3.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.puppycrawl.tools:checkstyle:10.20.2

+Apache Software License, Version 2.0Back to top
+

com.puppycrawl.tools:checkstyle:12.1.1

- + @@ -2011,15 +2095,15 @@

com.puppycrawl.tools:checkstyle:10.20.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.puppycrawl.tools:checkstyle:10.20.2
com.puppycrawl.tools:checkstyle:12.1.1
Scope compile
jar
License(s)LGPL-2.1-or-later
Back to top
-

commons-beanutils:commons-beanutils:1.9.4

+LGPL-2.1-or-laterBack to top
+

commons-beanutils:commons-beanutils:1.11.0

- + @@ -2031,26 +2115,6 @@

commons-beanutils:commons-beanutils:1.9.4

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncommons-beanutils:commons-beanutils:1.9.4
commons-beanutils:commons-beanutils:1.11.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

commons-codec:commons-codec:1.15

- - - - - - - - - - - - - - - - - -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncommons-codec:commons-codec:1.15
Scoperuntime
Classifier
Typejar
License(s) Apache Software License, Version 2.0
Back to top

commons-collections:commons-collections:3.2.2

@@ -2091,15 +2155,15 @@

commons-logging:commons-logging:1.2

-
jar
License(s)Apache Software License, Version 2.0
Back to top
-

info.picocli:picocli:4.7.6

+Apache Software License, Version 2.0Back to top
+

info.picocli:picocli:4.7.7

- + @@ -2111,15 +2175,15 @@

info.picocli:picocli:4.7.6

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioninfo.picocli:picocli:4.7.6
info.picocli:picocli:4.7.7
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

net.sf.saxon:Saxon-HE:12.5

+Apache Software License, Version 2.0Back to top
+

net.sf.saxon:Saxon-HE:12.9

- + @@ -2231,18 +2295,18 @@

org.apache.httpcomponents:httpcore:4.4.14

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.sf.saxon:Saxon-HE:12.5
net.sf.saxon:Saxon-HE:12.9
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.httpcomponents.client5:httpclient5:5.1.3

+Apache Software License, Version 2.0Back to top
+

org.apache.maven.doxia:doxia-core:1.12.0

- + - + @@ -2251,18 +2315,18 @@

org.apache.httpcomponents.client5:httpclient5:5.1.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.client5:httpclient5:5.1.3
org.apache.maven.doxia:doxia-core:1.12.0
Scoperuntime
compile
Classifier
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.httpcomponents.core5:httpcore5:5.1.3

+Apache Software License, Version 2.0Back to top
+

org.apache.maven.doxia:doxia-logging-api:1.12.0

- + - + @@ -2271,18 +2335,18 @@

org.apache.httpcomponents.core5:httpcore5:5.1.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.core5:httpcore5:5.1.3
org.apache.maven.doxia:doxia-logging-api:1.12.0
Scoperuntime
compile
Classifier
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.httpcomponents.core5:httpcore5-h2:5.1.3

+Apache Software License, Version 2.0Back to top
+

org.apache.maven.doxia:doxia-module-xdoc:1.12.0

- + - + @@ -2291,15 +2355,15 @@

org.apache.httpcomponents.core5:httpcore5-h2:5.1.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.core5:httpcore5-h2:5.1.3
org.apache.maven.doxia:doxia-module-xdoc:1.12.0
Scoperuntime
compile
Classifier
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.maven.doxia:doxia-core:1.12.0

+Apache Software License, Version 2.0Back to top
+

org.apache.maven.doxia:doxia-sink-api:1.12.0

- + @@ -2311,15 +2375,15 @@

org.apache.maven.doxia:doxia-core:1.12.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.maven.doxia:doxia-core:1.12.0
org.apache.maven.doxia:doxia-sink-api:1.12.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.maven.doxia:doxia-logging-api:1.12.0

+Apache Software License, Version 2.0Back to top
+

org.apache.xbean:xbean-reflect:3.7

- + @@ -2328,18 +2392,18 @@

org.apache.maven.doxia:doxia-logging-api:1.12.0

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.maven.doxia:doxia-logging-api:1.12.0
org.apache.xbean:xbean-reflect:3.7
Scope compile
Typejar
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.maven.doxia:doxia-module-xdoc:1.12.0

+Apache Software License, Version 2.0Back to top
+

org.checkerframework:checker-qual:3.51.1

- + @@ -2351,15 +2415,35 @@

org.apache.maven.doxia:doxia-module-xdoc:1.12.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.maven.doxia:doxia-module-xdoc:1.12.0
org.checkerframework:checker-qual:3.51.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.maven.doxia:doxia-sink-api:1.12.0

+The MIT License (MIT)Back to top
+

org.codehaus.plexus:plexus-classworlds:2.6.0

- + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.maven.doxia:doxia-sink-api:1.12.0
org.codehaus.plexus:plexus-classworlds:2.6.0
Scopecompile
Classifier
Typebundle
License(s)Apache Software License, Version 2.0
Back to top
+

org.codehaus.plexus:plexus-component-annotations:2.1.0

+ + + + + + + @@ -2371,15 +2455,15 @@

org.apache.maven.doxia:doxia-sink-api:1.12.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.plexus:plexus-component-annotations:2.1.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.xbean:xbean-reflect:3.7

+Apache Software License, Version 2.0Back to top
+

org.codehaus.plexus:plexus-container-default:2.1.0

- + @@ -2388,18 +2472,18 @@

org.apache.xbean:xbean-reflect:3.7

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.xbean:xbean-reflect:3.7
org.codehaus.plexus:plexus-container-default:2.1.0
Scope compile
Typebundle
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.checkerframework:checker-qual:3.48.2

+Apache Software License, Version 2.0Back to top
+

org.codehaus.plexus:plexus-utils:3.3.0

- + @@ -2411,15 +2495,15 @@

org.checkerframework:checker-qual:3.48.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.checkerframework:checker-qual:3.48.2
org.codehaus.plexus:plexus-utils:3.3.0
Scope compile
jar
License(s)The MIT License (MIT)
Back to top
-

org.codehaus.plexus:plexus-classworlds:2.6.0

+Apache Software License, Version 2.0Back to top
+

org.javassist:javassist:3.28.0-GA

- + @@ -2431,15 +2515,15 @@

org.codehaus.plexus:plexus-classworlds:2.6.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.plexus:plexus-classworlds:2.6.0
org.javassist:javassist:3.28.0-GA
Scope compile
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

org.codehaus.plexus:plexus-component-annotations:2.1.0

+Apache Software License, Version 2.0
LGPL 2.1
MPL 1.1Back to top
+

org.jspecify:jspecify:1.0.0

- + @@ -2451,15 +2535,15 @@

org.codehaus.plexus:plexus-component-annotations:2.1.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.plexus:plexus-component-annotations:2.1.0
org.jspecify:jspecify:1.0.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.codehaus.plexus:plexus-container-default:2.1.0

+Apache Software License, Version 2.0Back to top
+

org.reflections:reflections:0.10.2

- + @@ -2471,15 +2555,15 @@

org.codehaus.plexus:plexus-container-default:2.1.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.plexus:plexus-container-default:2.1.0
org.reflections:reflections:0.10.2
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.codehaus.plexus:plexus-utils:3.3.0

+Apache Software License, Version 2.0
WTFPLBack to top
+

org.xmlresolver:xmlresolver:5.3.3

- + @@ -2491,15 +2575,15 @@

org.codehaus.plexus:plexus-utils:3.3.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.plexus:plexus-utils:3.3.0
org.xmlresolver:xmlresolver:5.3.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.javassist:javassist:3.28.0-GA

+Apache Software License, Version 2.0Back to top
+

com.apicatalog:titanium-jcs:1.1.1

- + @@ -2508,18 +2592,18 @@

org.javassist:javassist:3.28.0-GA

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.javassist:javassist:3.28.0-GA
com.apicatalog:titanium-jcs:1.1.1
Scope compile
Typebundle
jar
License(s)Apache Software License, Version 2.0
LGPL 2.1
MPL 1.1
Back to top
-

org.reflections:reflections:0.10.2

+Apache Software License, Version 2.0Back to top
+

com.apicatalog:titanium-json-ld:1.7.0

- + @@ -2531,15 +2615,15 @@

org.reflections:reflections:0.10.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.reflections:reflections:0.10.2
com.apicatalog:titanium-json-ld:1.7.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
WTFPL
Back to top
-

org.xmlresolver:xmlresolver:5.2.2

+Apache Software License, Version 2.0Back to top
+

com.apicatalog:titanium-rdf-api:1.0.0

- + @@ -2551,15 +2635,15 @@

org.xmlresolver:xmlresolver:5.2.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.xmlresolver:xmlresolver:5.2.2
com.apicatalog:titanium-rdf-api:1.0.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.apicatalog:titanium-json-ld:1.4.1

+Apache Software License, Version 2.0Back to top
+

com.apicatalog:titanium-rdf-n-quads:1.0.2

- + @@ -2571,15 +2655,15 @@

com.apicatalog:titanium-json-ld:1.4.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.apicatalog:titanium-json-ld:1.4.1
com.apicatalog:titanium-rdf-n-quads:1.0.2
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.ethlo.time:itu:1.10.2

+Apache Software License, Version 2.0Back to top
+

com.ethlo.time:itu:1.10.3

- + @@ -2591,15 +2675,15 @@

com.ethlo.time:itu:1.10.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.ethlo.time:itu:1.10.2
com.ethlo.time:itu:1.10.3
Scope compile
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.jackson.core:jackson-annotations:2.18.2

+Apache Software License, Version 2.0Back to top
+

com.fasterxml.jackson.core:jackson-annotations:2.20

- + @@ -2611,15 +2695,15 @@

com.fasterxml.jackson.core:jackson-annotations:2.18.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.jackson.core:jackson-annotations:2.18.2
com.fasterxml.jackson.core:jackson-annotations:2.20
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.jackson.core:jackson-core:2.18.2

+Apache Software License, Version 2.0Back to top
+

com.fasterxml.jackson.core:jackson-core:2.20.0

- + @@ -2631,15 +2715,15 @@

com.fasterxml.jackson.core:jackson-core:2.18.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.jackson.core:jackson-core:2.18.2
com.fasterxml.jackson.core:jackson-core:2.20.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.jackson.core:jackson-databind:2.18.2

+Apache Software License, Version 2.0Back to top
+

com.fasterxml.jackson.core:jackson-databind:2.20.0

- + @@ -2651,15 +2735,15 @@

com.fasterxml.jackson.core:jackson-databind:2.18.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.jackson.core:jackson-databind:2.18.2
com.fasterxml.jackson.core:jackson-databind:2.20.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.1

+Apache Software License, Version 2.0Back to top
+

com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.0

- + @@ -2671,15 +2755,15 @@

com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.1
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1

+Apache Software License, Version 2.0Back to top
+

com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.0

- + @@ -2691,15 +2775,15 @@

com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.woodstox:woodstox-core:7.0.0

+Apache Software License, Version 2.0Back to top
+

com.fasterxml.woodstox:woodstox-core:7.1.1

- + @@ -2731,15 +2815,15 @@

com.github.andrewoma.dexx:collection:0.7

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.woodstox:woodstox-core:7.0.0
com.fasterxml.woodstox:woodstox-core:7.1.1
Scope compile
jar
License(s)The MIT License (MIT)
Back to top
-

com.github.ben-manes.caffeine:caffeine:3.1.8

+The MIT License (MIT)Back to top
+

com.github.ben-manes.caffeine:caffeine:3.2.2

- + @@ -2851,15 +2935,35 @@

com.github.virtuald:curvesapi:1.08

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.github.ben-manes.caffeine:caffeine:3.1.8
com.github.ben-manes.caffeine:caffeine:3.2.2
Scope compile
jar
License(s)BSD License
Back to top
-

com.google.code.gson:gson:2.11.0

+BSD LicenseBack to top
+

com.google.code.findbugs:jsr305:2.0.1

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.google.code.findbugs:jsr305:2.0.1
Scopecompile
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

com.google.code.gson:gson:2.13.2

- + @@ -2871,15 +2975,15 @@

com.google.code.gson:gson:2.11.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.google.code.gson:gson:2.11.0
com.google.code.gson:gson:2.13.2
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.google.protobuf:protobuf-java:4.29.1

+Apache Software License, Version 2.0Back to top
+

com.google.protobuf:protobuf-java:4.33.0

- + @@ -2891,15 +2995,15 @@

com.google.protobuf:protobuf-java:4.29.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.google.protobuf:protobuf-java:4.29.1
com.google.protobuf:protobuf-java:4.33.0
Scope compile
jar
License(s)BSD 3-Clause
Back to top
-

com.networknt:json-schema-validator:1.5.3

+BSD 3-ClauseBack to top
+

com.networknt:json-schema-validator:1.5.8

- + @@ -2951,15 +3055,15 @@

com.zaxxer:SparseBitSet:1.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.networknt:json-schema-validator:1.5.3
com.networknt:json-schema-validator:1.5.8
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

commons-codec:commons-codec:1.17.1

+Apache Software License, Version 2.0Back to top
+

commons-codec:commons-codec:1.19.0

- + @@ -2971,15 +3075,15 @@

commons-codec:commons-codec:1.17.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncommons-codec:commons-codec:1.17.1
commons-codec:commons-codec:1.19.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

commons-io:commons-io:2.16.1

+Apache Software License, Version 2.0Back to top
+

commons-io:commons-io:2.20.0

- + @@ -2991,15 +3095,15 @@

commons-io:commons-io:2.16.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncommons-io:commons-io:2.16.1
commons-io:commons-io:2.20.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.github.classgraph:classgraph:4.8.179

+Apache Software License, Version 2.0Back to top
+

io.github.classgraph:classgraph:4.8.184

- + @@ -3011,15 +3115,15 @@

io.github.classgraph:classgraph:4.8.179

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.github.classgraph:classgraph:4.8.179
io.github.classgraph:classgraph:4.8.184
Scope compile
jar
License(s)The MIT License (MIT)
Back to top
-

jakarta.activation:jakarta.activation-api:2.1.3

+The MIT License (MIT)Back to top
+

jakarta.activation:jakarta.activation-api:2.1.4

- + @@ -3031,15 +3135,15 @@

jakarta.activation:jakarta.activation-api:2.1.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionjakarta.activation:jakarta.activation-api:2.1.3
jakarta.activation:jakarta.activation-api:2.1.4
Scope compile
jar
License(s)Eclipse Distribution License - v 1.0
Back to top
-

jakarta.xml.bind:jakarta.xml.bind-api:4.0.2

+Eclipse Distribution License - v 1.0Back to top
+

jakarta.xml.bind:jakarta.xml.bind-api:4.0.4

- + @@ -3071,15 +3175,15 @@

org.apache.commons:commons-collections4:4.4

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionjakarta.xml.bind:jakarta.xml.bind-api:4.0.2
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.commons:commons-compress:1.27.1

+Apache Software License, Version 2.0Back to top
+

org.apache.commons:commons-compress:1.28.0

- + @@ -3091,15 +3195,15 @@

org.apache.commons:commons-compress:1.27.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.commons:commons-compress:1.27.1
org.apache.commons:commons-compress:1.28.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.commons:commons-csv:1.12.0

+Apache Software License, Version 2.0Back to top
+

org.apache.commons:commons-csv:1.14.1

- + @@ -3111,15 +3215,15 @@

org.apache.commons:commons-csv:1.12.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.commons:commons-csv:1.12.0
org.apache.commons:commons-csv:1.14.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.commons:commons-lang3:3.17.0

+Apache Software License, Version 2.0Back to top
+

org.apache.commons:commons-lang3:3.19.0

- + @@ -3151,15 +3255,15 @@

org.apache.commons:commons-math3:3.6.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.commons:commons-lang3:3.17.0
org.apache.commons:commons-lang3:3.19.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.jena:jena-arq:5.2.0

+Apache Software License, Version 2.0Back to top
+

org.apache.jena:jena-arq:5.6.0

- + @@ -3171,15 +3275,15 @@

org.apache.jena:jena-arq:5.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.jena:jena-arq:5.2.0
org.apache.jena:jena-arq:5.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.jena:jena-base:5.2.0

+Apache Software License, Version 2.0Back to top
+

org.apache.jena:jena-base:5.6.0

- + @@ -3191,15 +3295,15 @@

org.apache.jena:jena-base:5.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.jena:jena-base:5.2.0
org.apache.jena:jena-base:5.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.jena:jena-core:5.2.0

+Apache Software License, Version 2.0Back to top
+

org.apache.jena:jena-core:5.6.0

- + @@ -3211,15 +3315,15 @@

org.apache.jena:jena-core:5.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.jena:jena-core:5.2.0
org.apache.jena:jena-core:5.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.jena:jena-iri:5.2.0

+Apache Software License, Version 2.0Back to top
+

org.apache.jena:jena-iri:5.6.0

- + @@ -3231,15 +3335,15 @@

org.apache.jena:jena-iri:5.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.jena:jena-iri:5.2.0
org.apache.jena:jena-iri:5.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.jena:jena-shacl:5.2.0

+Apache Software License, Version 2.0Back to top
+

org.apache.jena:jena-iri3986:5.6.0

- + @@ -3251,15 +3355,15 @@

org.apache.jena:jena-shacl:5.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.jena:jena-shacl:5.2.0
org.apache.jena:jena-iri3986:5.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.logging.log4j:log4j-api:2.23.1

+Apache Software License, Version 2.0Back to top
+

org.apache.jena:jena-langtag:5.6.0

- + @@ -3271,15 +3375,15 @@

org.apache.logging.log4j:log4j-api:2.23.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.logging.log4j:log4j-api:2.23.1
org.apache.jena:jena-langtag:5.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.poi:poi:5.3.0

+Apache Software License, Version 2.0Back to top
+

org.apache.jena:jena-shacl:5.6.0

- + @@ -3291,15 +3395,15 @@

org.apache.poi:poi:5.3.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.poi:poi:5.3.0
org.apache.jena:jena-shacl:5.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.poi:poi-ooxml:5.3.0

+Apache Software License, Version 2.0Back to top
+

org.apache.logging.log4j:log4j-api:2.24.3

- + @@ -3311,15 +3415,15 @@

org.apache.poi:poi-ooxml:5.3.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.poi:poi-ooxml:5.3.0
org.apache.logging.log4j:log4j-api:2.24.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.poi:poi-ooxml-lite:5.3.0

+Apache Software License, Version 2.0Back to top
+

org.apache.poi:poi:5.4.1

- + @@ -3331,15 +3435,15 @@

org.apache.poi:poi-ooxml-lite:5.3.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.poi:poi-ooxml-lite:5.3.0
org.apache.poi:poi:5.4.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.thrift:libthrift:0.21.0

+Apache Software License, Version 2.0Back to top
+

org.apache.poi:poi-ooxml:5.4.1

- + @@ -3351,15 +3455,15 @@

org.apache.thrift:libthrift:0.21.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.thrift:libthrift:0.21.0
org.apache.poi:poi-ooxml:5.4.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.xmlbeans:xmlbeans:5.2.1

+Apache Software License, Version 2.0Back to top
+

org.apache.poi:poi-ooxml-lite:5.4.1

- + @@ -3371,15 +3475,15 @@

org.apache.xmlbeans:xmlbeans:5.2.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.xmlbeans:xmlbeans:5.2.1
org.apache.poi:poi-ooxml-lite:5.4.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.checkerframework:checker-qual:3.43.0

+Apache Software License, Version 2.0Back to top
+

org.apache.thrift:libthrift:0.22.0

- + @@ -3391,15 +3495,15 @@

org.checkerframework:checker-qual:3.43.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.checkerframework:checker-qual:3.43.0
org.apache.thrift:libthrift:0.22.0
Scope compile
jar
License(s)The MIT License (MIT)
Back to top
-

org.codehaus.woodstox:stax2-api:4.2.2

+Apache Software License, Version 2.0Back to top
+

org.apache.xmlbeans:xmlbeans:5.3.0

- + @@ -3408,18 +3512,18 @@

org.codehaus.woodstox:stax2-api:4.2.2

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.woodstox:stax2-api:4.2.2
org.apache.xmlbeans:xmlbeans:5.3.0
Scope compile
Typebundle
jar
License(s)The BSD 2-Clause License
Back to top
-

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-aasx:1.0.3

+Apache Software License, Version 2.0Back to top
+

org.codehaus.woodstox:stax2-api:4.2.2

- + @@ -3428,18 +3532,18 @@

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-aasx:1.0.3

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.digitaltwin.aas4j:aas4j-dataformat-aasx:1.0.3
org.codehaus.woodstox:stax2-api:4.2.2
Scope compile
Typejar
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-core:1.0.3

+The BSD 2-Clause LicenseBack to top
+

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-aasx:1.0.5

- + @@ -3451,15 +3555,15 @@

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-core:1.0.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.digitaltwin.aas4j:aas4j-dataformat-core:1.0.3
org.eclipse.digitaltwin.aas4j:aas4j-dataformat-aasx:1.0.5
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-json:1.0.3

+Apache Software License, Version 2.0Back to top
+

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-core:1.0.5

- + @@ -3471,15 +3575,15 @@

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-json:1.0.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.digitaltwin.aas4j:aas4j-dataformat-json:1.0.3
org.eclipse.digitaltwin.aas4j:aas4j-dataformat-core:1.0.5
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-xml:1.0.3

+Apache Software License, Version 2.0Back to top
+

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-json:1.0.5

- + @@ -3491,15 +3595,15 @@

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-xml:1.0.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.digitaltwin.aas4j:aas4j-dataformat-xml:1.0.3
org.eclipse.digitaltwin.aas4j:aas4j-dataformat-json:1.0.5
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.eclipse.digitaltwin.aas4j:aas4j-model:1.0.3

+Apache Software License, Version 2.0Back to top
+

org.eclipse.digitaltwin.aas4j:aas4j-dataformat-xml:1.0.5

- + @@ -3511,15 +3615,15 @@

org.eclipse.digitaltwin.aas4j:aas4j-model:1.0.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.digitaltwin.aas4j:aas4j-model:1.0.3
org.eclipse.digitaltwin.aas4j:aas4j-dataformat-xml:1.0.5
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.glassfish:jakarta.json:2.0.1

+Apache Software License, Version 2.0Back to top
+

org.eclipse.digitaltwin.aas4j:aas4j-model:1.0.5

- + @@ -3528,18 +3632,18 @@

org.glassfish:jakarta.json:2.0.1

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.glassfish:jakarta.json:2.0.1
org.eclipse.digitaltwin.aas4j:aas4j-model:1.0.5
Scope compile
Typebundle
jar
License(s)Eclipse Public License 2.0
GNU General Public License, version 2 with the GNU Classpath Exception
Back to top
-

org.roaringbitmap:RoaringBitmap:1.3.0

+Apache Software License, Version 2.0Back to top
+

org.glassfish:jakarta.json:2.0.1

- + @@ -3548,18 +3652,18 @@

org.roaringbitmap:RoaringBitmap:1.3.0

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.roaringbitmap:RoaringBitmap:1.3.0
org.glassfish:jakarta.json:2.0.1
Scope compile
Typejar
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

org.skyscreamer:jsonassert:1.5.3

+Eclipse Public License 2.0
GNU General Public License, version 2 with the GNU Classpath ExceptionBack to top
+

org.roaringbitmap:RoaringBitmap:1.3.0

- + @@ -3571,15 +3675,15 @@

org.skyscreamer:jsonassert:1.5.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.skyscreamer:jsonassert:1.5.3
org.roaringbitmap:RoaringBitmap:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.slf4j:jcl-over-slf4j:2.0.16

+Apache Software License, Version 2.0Back to top
+

org.skyscreamer:jsonassert:1.5.3

- + @@ -3591,18 +3695,18 @@

org.slf4j:jcl-over-slf4j:2.0.16

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.slf4j:jcl-over-slf4j:2.0.16
org.skyscreamer:jsonassert:1.5.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.slf4j:slf4j-simple:2.0.16

+Apache Software License, Version 2.0Back to top
+

org.slf4j:jcl-over-slf4j:2.0.17

- + - + @@ -3611,35 +3715,35 @@

org.slf4j:slf4j-simple:2.0.16

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.slf4j:slf4j-simple:2.0.16
org.slf4j:jcl-over-slf4j:2.0.17
Scopetest
compile
Classifier
jar
License(s)The MIT License (MIT)
Back to top
-

org.yaml:snakeyaml:2.3

+Apache Software License, Version 2.0Back to top
+

org.slf4j:slf4j-simple:2.0.17

- + - + - + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.yaml:snakeyaml:2.3
org.slf4j:slf4j-simple:2.0.17
Scopecompile
test
Classifier
Typebundle
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.woodstox:woodstox-core:7.1.0

+The MIT License (MIT)Back to top
+

org.yaml:snakeyaml:2.4

- + @@ -3751,38 +3855,18 @@

commons-fileupload:commons-fileupload:1.4

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.woodstox:woodstox-core:7.1.0
org.yaml:snakeyaml:2.4
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

commons-io:commons-io:2.18.0

- - - - - - - - - - - - - - - - - - -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncommons-io:commons-io:2.18.0
Scopecompile
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:model:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:model:1.3.0

- + - + @@ -3811,15 +3895,15 @@

javax.servlet:javax.servlet-api:3.1.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:model:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:model:1.3.0
Scopecompile
test
Classifier
jar
License(s)CDDL + GPLv2 with classpath exception
Back to top
-

net.bytebuddy:byte-buddy:1.15.4

+CDDL + GPLv2 with classpath exceptionBack to top
+

net.bytebuddy:byte-buddy:1.17.7

- + @@ -3831,15 +3915,15 @@

net.bytebuddy:byte-buddy:1.15.4

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.bytebuddy:byte-buddy:1.15.4
net.bytebuddy:byte-buddy:1.17.7
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

net.bytebuddy:byte-buddy-agent:1.15.4

+Apache Software License, Version 2.0Back to top
+

net.bytebuddy:byte-buddy-agent:1.17.7

- + @@ -3931,15 +4015,75 @@

net.sf.jopt-simple:jopt-simple:5.0.4

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.bytebuddy:byte-buddy-agent:1.15.4
net.bytebuddy:byte-buddy-agent:1.17.7
Scope test
jar
License(s)The MIT License (MIT)
Back to top
-

org.apache.logging.log4j:log4j-api:2.24.2

+The MIT License (MIT)Back to top
+

org.apache.httpcomponents.client5:httpclient5:5.1.3

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.client5:httpclient5:5.1.3
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

org.apache.httpcomponents.core5:httpcore5:5.1.3

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.core5:httpcore5:5.1.3
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

org.apache.httpcomponents.core5:httpcore5-h2:5.1.3

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.core5:httpcore5-h2:5.1.3
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

org.apache.logging.log4j:log4j-api:2.25.2

- + @@ -3951,15 +4095,15 @@

org.apache.logging.log4j:log4j-api:2.24.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.logging.log4j:log4j-api:2.24.2
org.apache.logging.log4j:log4j-api:2.25.2
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.logging.log4j:log4j-to-slf4j:2.24.2

+Apache Software License, Version 2.0Back to top
+

org.apache.logging.log4j:log4j-to-slf4j:2.25.2

- + @@ -3971,15 +4115,15 @@

org.apache.logging.log4j:log4j-to-slf4j:2.24.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.logging.log4j:log4j-to-slf4j:2.24.2
org.apache.logging.log4j:log4j-to-slf4j:2.25.2
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.bouncycastle:bcpkix-jdk18on:1.79

+Apache Software License, Version 2.0Back to top
+

org.bouncycastle:bcpkix-jdk18on:1.82

- + @@ -3991,15 +4135,15 @@

org.bouncycastle:bcpkix-jdk18on:1.79

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.bouncycastle:bcpkix-jdk18on:1.79
org.bouncycastle:bcpkix-jdk18on:1.82
Scope compile
jar
License(s)Bouncy Castle Licence
Back to top
-

org.bouncycastle:bcprov-jdk18on:1.79

+Bouncy Castle LicenceBack to top
+

org.bouncycastle:bcprov-jdk18on:1.82

- + @@ -4011,15 +4155,15 @@

org.bouncycastle:bcprov-jdk18on:1.79

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.bouncycastle:bcprov-jdk18on:1.79
org.bouncycastle:bcprov-jdk18on:1.82
Scope compile
jar
License(s)Bouncy Castle Licence
Back to top
-

org.bouncycastle:bcutil-jdk18on:1.79

+Bouncy Castle LicenceBack to top
+

org.bouncycastle:bcutil-jdk18on:1.82

- + @@ -4471,15 +4615,15 @@

org.eclipse.jetty.http2:http2-server:9.4.49.v20220914

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.bouncycastle:bcutil-jdk18on:1.79
org.bouncycastle:bcutil-jdk18on:1.82
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 1.0
Back to top
-

org.mockito:mockito-core:5.14.2

+Apache Software License, Version 2.0
Eclipse Public License - Version 1.0Back to top
+

org.mockito:mockito-core:5.20.0

- + @@ -4591,15 +4735,15 @@

org.xmlunit:xmlunit-placeholders:2.9.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.mockito:mockito-core:5.14.2
org.mockito:mockito-core:5.20.0
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2

+Apache Software License, Version 2.0Back to top
+

com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.0

- + @@ -4611,15 +4755,15 @@

com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.0
Scope compile
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:core:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:core:1.3.0

- + @@ -4631,15 +4775,15 @@

de.fraunhofer.iosb.ilt.faaast.service:core:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:core:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:core:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.github.curious-odd-man:rgxgen:2.0

+Apache Software License, Version 2.0Back to top
+

com.github.curious-odd-man:rgxgen:3.1

- + @@ -4651,15 +4795,15 @@

com.github.curious-odd-man:rgxgen:2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.github.curious-odd-man:rgxgen:2.0
com.github.curious-odd-man:rgxgen:3.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

commons-fileupload:commons-fileupload:1.5

+Apache Software License, Version 2.0Back to top
+

commons-fileupload:commons-fileupload:1.6.0

- + @@ -4671,15 +4815,15 @@

commons-fileupload:commons-fileupload:1.5

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncommons-fileupload:commons-fileupload:1.5
commons-fileupload:commons-fileupload:1.6.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:dataformat-json:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:dataformat-json:1.3.0

- + @@ -4731,15 +4875,15 @@

jakarta.servlet:jakarta.servlet-api:6.0.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:dataformat-json:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:dataformat-json:1.3.0
Scope compile
jar
License(s)Eclipse Public License - Version 2.0
GPL2 w/ CPE
Back to top
-

org.apache.httpcomponents.client5:httpclient5:5.4.1

+Eclipse Public License - Version 2.0
GPL2 w/ CPEBack to top
+

org.apache.httpcomponents.client5:httpclient5:5.5.1

- + @@ -4751,15 +4895,15 @@

org.apache.httpcomponents.client5:httpclient5:5.4.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.client5:httpclient5:5.4.1
org.apache.httpcomponents.client5:httpclient5:5.5.1
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.httpcomponents.core5:httpcore5:5.3.1

+Apache Software License, Version 2.0Back to top
+

org.apache.httpcomponents.core5:httpcore5:5.3.6

- + @@ -4771,15 +4915,15 @@

org.apache.httpcomponents.core5:httpcore5:5.3.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.core5:httpcore5:5.3.1
org.apache.httpcomponents.core5:httpcore5:5.3.6
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.apache.httpcomponents.core5:httpcore5-h2:5.3.1

+Apache Software License, Version 2.0Back to top
+

org.apache.httpcomponents.core5:httpcore5-h2:5.3.6

- + @@ -4791,15 +4935,15 @@

org.apache.httpcomponents.core5:httpcore5-h2:5.3.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.apache.httpcomponents.core5:httpcore5-h2:5.3.1
org.apache.httpcomponents.core5:httpcore5-h2:5.3.6
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.eclipse.jetty:jetty-alpn-client:12.0.15

+Apache Software License, Version 2.0Back to top
+

org.eclipse.jetty:jetty-alpn-client:12.1.3

- + @@ -4811,15 +4955,15 @@

org.eclipse.jetty:jetty-alpn-client:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-alpn-client:12.0.15
org.eclipse.jetty:jetty-alpn-client:12.1.3
Scope test
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty:jetty-client:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty:jetty-client:12.1.3

- + @@ -4831,15 +4975,15 @@

org.eclipse.jetty:jetty-client:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-client:12.0.15
org.eclipse.jetty:jetty-client:12.1.3
Scope test
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty:jetty-http:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty:jetty-http:12.1.3

- + @@ -4851,15 +4995,15 @@

org.eclipse.jetty:jetty-http:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-http:12.0.15
org.eclipse.jetty:jetty-http:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty:jetty-io:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty:jetty-io:12.1.3

- + @@ -4871,15 +5015,15 @@

org.eclipse.jetty:jetty-io:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-io:12.0.15
org.eclipse.jetty:jetty-io:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty:jetty-security:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty:jetty-security:12.1.3

- + @@ -4891,15 +5035,15 @@

org.eclipse.jetty:jetty-security:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-security:12.0.15
org.eclipse.jetty:jetty-security:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty:jetty-server:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty:jetty-server:12.1.3

- + @@ -4911,15 +5055,15 @@

org.eclipse.jetty:jetty-server:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-server:12.0.15
org.eclipse.jetty:jetty-server:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty:jetty-session:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty:jetty-session:12.1.3

- + @@ -4931,15 +5075,15 @@

org.eclipse.jetty:jetty-session:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-session:12.0.15
org.eclipse.jetty:jetty-session:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty:jetty-util:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty:jetty-util:12.1.3

- + @@ -4951,15 +5095,55 @@

org.eclipse.jetty:jetty-util:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty:jetty-util:12.0.15
org.eclipse.jetty:jetty-util:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty.compression:jetty-compression-common:12.1.3

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty.compression:jetty-compression-common:12.1.3
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
+

org.eclipse.jetty.compression:jetty-compression-gzip:12.1.3

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty.compression:jetty-compression-gzip:12.1.3
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
+

org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.3

- + @@ -4971,15 +5155,15 @@

org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.15
org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 2.0
Back to top
-

org.eclipse.jetty.ee10:jetty-ee10-servlets:12.0.15

+Apache Software License, Version 2.0
Eclipse Public License - Version 2.0Back to top
+

org.eclipse.jetty.ee10:jetty-ee10-servlets:12.1.3

- + @@ -5031,15 +5215,55 @@

ch.qos.reload4j:reload4j:1.2.22

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.jetty.ee10:jetty-ee10-servlets:12.0.15
org.eclipse.jetty.ee10:jetty-ee10-servlets:12.1.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.librato.metrics:metrics-librato:5.1.4

+Apache Software License, Version 2.0Back to top
+

com.zaxxer:HikariCP:2.4.7

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.zaxxer:HikariCP:2.4.7
Scopecompile
Classifier
Typebundle
License(s)Apache Software License, Version 2.0
Back to top
+

commons-codec:commons-codec:1.18.0

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncommons-codec:commons-codec:1.18.0
Scopecompile
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

de.fraunhofer.iosb.io.moquette:moquette-broker:0.18.3

- + @@ -5051,15 +5275,15 @@

com.librato.metrics:metrics-librato:5.1.4

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.librato.metrics:metrics-librato:5.1.4
de.fraunhofer.iosb.io.moquette:moquette-broker:0.18.3
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

com.zaxxer:HikariCP:2.4.7

+Apache Software License, Version 2.0
Eclipse Public License - Version 1.0Back to top
+

io.netty:netty-buffer:4.2.7.Final

- + @@ -5068,18 +5292,18 @@

com.zaxxer:HikariCP:2.4.7

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.zaxxer:HikariCP:2.4.7
io.netty:netty-buffer:4.2.7.Final
Scope compile
Typebundle
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.io.moquette:moquette-broker:0.15.2

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-codec:4.2.7.Final

- + @@ -5091,15 +5315,15 @@

de.fraunhofer.iosb.io.moquette:moquette-broker:0.15.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.io.moquette:moquette-broker:0.15.2
io.netty:netty-codec:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Eclipse Public License - Version 1.0
Back to top
-

io.dropwizard.metrics:metrics-core:3.2.2

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-codec-base:4.2.7.Final

- + @@ -5108,18 +5332,18 @@

io.dropwizard.metrics:metrics-core:3.2.2

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.dropwizard.metrics:metrics-core:3.2.2
io.netty:netty-codec-base:4.2.7.Final
Scope compile
Typebundle
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.dropwizard.metrics:metrics-jvm:3.2.2

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-codec-compression:4.2.7.Final

- + @@ -5128,18 +5352,18 @@

io.dropwizard.metrics:metrics-jvm:3.2.2

- + -
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.dropwizard.metrics:metrics-jvm:3.2.2
io.netty:netty-codec-compression:4.2.7.Final
Scope compile
Typebundle
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-buffer:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-codec-http:4.2.7.Final

- + @@ -5151,15 +5375,15 @@

io.netty:netty-buffer:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-buffer:4.1.115.Final
io.netty:netty-codec-http:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-codec:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-codec-marshalling:4.2.7.Final

- + @@ -5171,15 +5395,15 @@

io.netty:netty-codec:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-codec:4.1.115.Final
io.netty:netty-codec-marshalling:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-codec-http:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-codec-mqtt:4.2.7.Final

- + @@ -5191,15 +5415,15 @@

io.netty:netty-codec-http:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-codec-http:4.1.115.Final
io.netty:netty-codec-mqtt:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-codec-mqtt:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-codec-protobuf:4.2.7.Final

- + @@ -5211,15 +5435,15 @@

io.netty:netty-codec-mqtt:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-codec-mqtt:4.1.115.Final
io.netty:netty-codec-protobuf:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-common:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-common:4.2.7.Final

- + @@ -5231,15 +5455,15 @@

io.netty:netty-common:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-common:4.1.115.Final
io.netty:netty-common:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-handler:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-handler:4.2.7.Final

- + @@ -5251,15 +5475,15 @@

io.netty:netty-handler:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-handler:4.1.115.Final
io.netty:netty-handler:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-resolver:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-resolver:4.2.7.Final

- + @@ -5271,15 +5495,15 @@

io.netty:netty-resolver:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-resolver:4.1.115.Final
io.netty:netty-resolver:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-transport:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-transport:4.2.7.Final

- + @@ -5291,15 +5515,15 @@

io.netty:netty-transport:4.1.115.Final

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-transport:4.1.115.Final
io.netty:netty-transport:4.2.7.Final
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

io.netty:netty-transport-native-unix-common:4.1.115.Final

+Apache Software License, Version 2.0Back to top
+

io.netty:netty-transport-native-unix-common:4.2.7.Final

- + @@ -5331,15 +5555,15 @@

org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionio.netty:netty-transport-native-unix-common:4.1.115.Final
io.netty:netty-transport-native-unix-common:4.2.7.Final
Scope compile
eclipse-plugin
License(s)Eclipse Public License - Version 2.0
Back to top
-

org.slf4j:slf4j-reload4j:2.0.7

+Eclipse Public License - Version 2.0Back to top
+

org.slf4j:slf4j-reload4j:2.0.17

- + @@ -5351,15 +5575,15 @@

org.slf4j:slf4j-reload4j:2.0.7

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.slf4j:slf4j-reload4j:2.0.7
org.slf4j:slf4j-reload4j:2.0.17
Scope runtime
jar
License(s)The MIT License (MIT)
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:persistence-memory:1.2.0

+The MIT License (MIT)Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:persistence-memory:1.3.0

- + @@ -5371,15 +5595,15 @@

de.fraunhofer.iosb.ilt.faaast.service:persistence-memory:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:persistence-memory:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:persistence-memory:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle:de.flapdoodle.os:1.9.0

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle:de.flapdoodle.os:1.9.2

- + @@ -5391,15 +5615,15 @@

de.flapdoodle:de.flapdoodle.os:1.9.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle:de.flapdoodle.os:1.9.0
de.flapdoodle:de.flapdoodle.os:1.9.2
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle:de.flapdoodle.os-api:1.7.0

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle:de.flapdoodle.os-api:1.7.1

- + @@ -5411,15 +5635,15 @@

de.flapdoodle:de.flapdoodle.os-api:1.7.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle:de.flapdoodle.os-api:1.7.0
de.flapdoodle:de.flapdoodle.os-api:1.7.1
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.18.1

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.21.0

- + @@ -5431,15 +5655,15 @@

de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.18.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle.embed:de.flapdoodle.embed.mongo:4.18.1
de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.21.0
Scope test
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle.embed:de.flapdoodle.embed.mongo.packageresolver:4.18.2

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle.embed:de.flapdoodle.embed.mongo.packageresolver:4.21.0

- + @@ -5451,15 +5675,15 @@

de.flapdoodle.embed:de.flapdoodle.embed.mongo.packageresolver:4.18.2

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle.embed:de.flapdoodle.embed.mongo.packageresolver:4.18.2
de.flapdoodle.embed:de.flapdoodle.embed.mongo.packageresolver:4.21.0
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle.embed:de.flapdoodle.embed.process:4.15.1

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle.embed:de.flapdoodle.embed.process:4.17.0

- + @@ -5471,15 +5695,15 @@

de.flapdoodle.embed:de.flapdoodle.embed.process:4.15.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle.embed:de.flapdoodle.embed.process:4.15.1
de.flapdoodle.embed:de.flapdoodle.embed.process:4.17.0
Scope test
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle.graph:de.flapdoodle.graph:1.3.3

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle.graph:de.flapdoodle.graph:1.3.4

- + @@ -5491,15 +5715,15 @@

de.flapdoodle.graph:de.flapdoodle.graph:1.3.3

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle.graph:de.flapdoodle.graph:1.3.3
de.flapdoodle.graph:de.flapdoodle.graph:1.3.4
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle.java8:de.flapdoodle.java8:1.7.0

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle.java8:de.flapdoodle.java8:1.7.2

- + @@ -5511,15 +5735,15 @@

de.flapdoodle.java8:de.flapdoodle.java8:1.7.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle.java8:de.flapdoodle.java8:1.7.0
de.flapdoodle.java8:de.flapdoodle.java8:1.7.2
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.flapdoodle.reverse:de.flapdoodle.reverse:1.8.1

+Apache Software License, Version 2.0Back to top
+

de.flapdoodle.reverse:de.flapdoodle.reverse:1.9.1

- + @@ -5531,15 +5755,15 @@

de.flapdoodle.reverse:de.flapdoodle.reverse:1.8.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.flapdoodle.reverse:de.flapdoodle.reverse:1.8.1
de.flapdoodle.reverse:de.flapdoodle.reverse:1.9.1
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

net.java.dev.jna:jna:5.15.0

+Apache Software License, Version 2.0Back to top
+

net.java.dev.jna:jna:5.17.0

- + @@ -5551,15 +5775,15 @@

net.java.dev.jna:jna:5.15.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.java.dev.jna:jna:5.15.0
net.java.dev.jna:jna:5.17.0
Scope test
jar
License(s)Apache Software License, Version 2.0
LGPL-2.1-or-later
Back to top
-

net.java.dev.jna:jna-platform:5.15.0

+Apache Software License, Version 2.0
LGPL-2.1-or-laterBack to top
+

net.java.dev.jna:jna-platform:5.17.0

- + @@ -5611,15 +5835,15 @@

org.jheaps:jheaps:0.11

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.java.dev.jna:jna-platform:5.15.0
net.java.dev.jna:jna-platform:5.17.0
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.mongodb:bson:5.2.1

+Apache Software License, Version 2.0Back to top
+

org.mongodb:bson:5.6.1

- + @@ -5631,15 +5855,15 @@

org.mongodb:bson:5.2.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.mongodb:bson:5.2.1
org.mongodb:bson:5.6.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.mongodb:bson-record-codec:5.2.1

+Apache Software License, Version 2.0Back to top
+

org.mongodb:bson-record-codec:5.6.1

- + @@ -5651,15 +5875,15 @@

org.mongodb:bson-record-codec:5.2.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.mongodb:bson-record-codec:5.2.1
org.mongodb:bson-record-codec:5.6.1
Scope runtime
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.mongodb:mongodb-driver-core:5.2.1

+Apache Software License, Version 2.0Back to top
+

org.mongodb:mongodb-driver-core:5.6.1

- + @@ -5671,15 +5895,15 @@

org.mongodb:mongodb-driver-core:5.2.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.mongodb:mongodb-driver-core:5.2.1
org.mongodb:mongodb-driver-core:5.6.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.mongodb:mongodb-driver-sync:5.2.1

+Apache Software License, Version 2.0Back to top
+

org.mongodb:mongodb-driver-sync:5.6.1

- + @@ -5711,15 +5935,35 @@

com.jayway.jsonpath:json-path:2.9.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.mongodb:mongodb-driver-sync:5.2.1
org.mongodb:mongodb-driver-sync:5.6.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

net.minidev:accessors-smart:2.5.1

+Apache Software License, Version 2.0Back to top
+

net.minidev:accessors-smart:2.6.0

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.minidev:accessors-smart:2.6.0
Scopecompile
Classifier
Typebundle
License(s)Apache Software License, Version 2.0
Back to top
+

net.minidev:json-smart:2.6.0

- + @@ -5731,15 +5975,15 @@

net.minidev:accessors-smart:2.5.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.minidev:accessors-smart:2.5.1
net.minidev:json-smart:2.6.0
Scope compile
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

net.minidev:json-smart:2.5.1

+Apache Software License, Version 2.0Back to top
+

net.thisptr:jackson-jq:1.6.0

- + @@ -5751,15 +5995,15 @@

net.minidev:json-smart:2.5.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.minidev:json-smart:2.5.1
net.thisptr:jackson-jq:1.6.0
Scope compile
bundle
License(s)Apache Software License, Version 2.0
Back to top
-

org.ow2.asm:asm:9.6

+Apache Software License, Version 2.0Back to top
+

org.jruby.jcodings:jcodings:1.0.63

- + @@ -5771,18 +6015,18 @@

org.ow2.asm:asm:9.6

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.ow2.asm:asm:9.6
org.jruby.jcodings:jcodings:1.0.63
Scope compile
jar
License(s)BSD 3-Clause
Back to top
-

com.github.valfirst:slf4j-test:3.0.1

+The MIT License (MIT)Back to top
+

org.jruby.joni:joni:2.2.6

- + - + @@ -5791,15 +6035,15 @@

com.github.valfirst:slf4j-test:3.0.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.github.valfirst:slf4j-test:3.0.1
org.jruby.joni:joni:2.2.6
Scopetest
compile
Classifier
jar
License(s)The MIT License (MIT)
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-common:1.2.0

+The MIT License (MIT)Back to top
+

org.ow2.asm:asm:9.7.1

- + @@ -5811,15 +6055,15 @@

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-common:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:assetconnection-common:1.2.0
org.ow2.asm:asm:9.7.1
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

net.bytebuddy:byte-buddy:1.15.10

+BSD 3-ClauseBack to top
+

com.github.valfirst:slf4j-test:3.0.3

- + @@ -5831,15 +6075,35 @@

net.bytebuddy:byte-buddy:1.15.10

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.bytebuddy:byte-buddy:1.15.10
com.github.valfirst:slf4j-test:3.0.3
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

nl.jqno.equalsverifier:equalsverifier:3.17.5

+The MIT License (MIT)Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-common:1.3.0

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:assetconnection-common:1.3.0
Scopecompile
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

nl.jqno.equalsverifier:equalsverifier:4.2

- + @@ -5851,15 +6115,15 @@

nl.jqno.equalsverifier:equalsverifier:3.17.5

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnl.jqno.equalsverifier:equalsverifier:3.17.5
nl.jqno.equalsverifier:equalsverifier:4.2
Scope test
pom
License(s)Apache Software License, Version 2.0
Back to top
-

org.awaitility:awaitility:4.2.2

+Apache Software License, Version 2.0Back to top
+

org.awaitility:awaitility:4.3.0

- + @@ -6051,15 +6315,15 @@

javax.xml.bind:jaxb-api:2.3.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.awaitility:awaitility:4.2.2
org.awaitility:awaitility:4.3.0
Scope test
jar
License(s)CDDL 1.1
GPL2 w/ CPE
Back to top
-

org.eclipse.milo:bsd-core:0.6.15

+CDDL 1.1
GPL2 w/ CPEBack to top
+

org.eclipse.milo:bsd-core:0.6.16

- + @@ -6071,15 +6335,15 @@

org.eclipse.milo:bsd-core:0.6.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:bsd-core:0.6.15
org.eclipse.milo:bsd-core:0.6.16
Scope compile
jar
License(s)Eclipse Public License - v 2.0
Back to top
-

org.eclipse.milo:bsd-generator:0.6.15

+Eclipse Public License - v 2.0Back to top
+

org.eclipse.milo:bsd-generator:0.6.16

- + @@ -6091,15 +6355,15 @@

org.eclipse.milo:bsd-generator:0.6.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:bsd-generator:0.6.15
org.eclipse.milo:bsd-generator:0.6.16
Scope compile
jar
License(s)Eclipse Public License - v 2.0
Back to top
-

org.eclipse.milo:sdk-client:0.6.15

+Eclipse Public License - v 2.0Back to top
+

org.eclipse.milo:sdk-client:0.6.16

- + @@ -6111,15 +6375,15 @@

org.eclipse.milo:sdk-client:0.6.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:sdk-client:0.6.15
org.eclipse.milo:sdk-client:0.6.16
Scope compile
jar
License(s)Eclipse Public License - v 2.0
Back to top
-

org.eclipse.milo:sdk-core:0.6.15

+Eclipse Public License - v 2.0Back to top
+

org.eclipse.milo:sdk-core:0.6.16

- + @@ -6131,15 +6395,15 @@

org.eclipse.milo:sdk-core:0.6.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:sdk-core:0.6.15
org.eclipse.milo:sdk-core:0.6.16
Scope compile
jar
License(s)Eclipse Public License - v 2.0
Back to top
-

org.eclipse.milo:sdk-server:0.6.15

+Eclipse Public License - v 2.0Back to top
+

org.eclipse.milo:sdk-server:0.6.16

- + @@ -6151,15 +6415,15 @@

org.eclipse.milo:sdk-server:0.6.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:sdk-server:0.6.15
org.eclipse.milo:sdk-server:0.6.16
Scope compile
jar
License(s)Eclipse Public License - v 2.0
Back to top
-

org.eclipse.milo:stack-client:0.6.15

+Eclipse Public License - v 2.0Back to top
+

org.eclipse.milo:stack-client:0.6.16

- + @@ -6171,15 +6435,15 @@

org.eclipse.milo:stack-client:0.6.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:stack-client:0.6.15
org.eclipse.milo:stack-client:0.6.16
Scope compile
jar
License(s)Eclipse Public License - v 2.0
Back to top
-

org.eclipse.milo:stack-core:0.6.15

+Eclipse Public License - v 2.0Back to top
+

org.eclipse.milo:stack-core:0.6.16

- + @@ -6191,15 +6455,15 @@

org.eclipse.milo:stack-core:0.6.15

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:stack-core:0.6.15
org.eclipse.milo:stack-core:0.6.16
Scope compile
jar
License(s)Eclipse Public License - v 2.0
Back to top
-

org.eclipse.milo:stack-server:0.6.15

+Eclipse Public License - v 2.0Back to top
+

org.eclipse.milo:stack-server:0.6.16

- + @@ -6251,15 +6515,15 @@

org.glassfish.jaxb:txw2:2.3.6

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.eclipse.milo:stack-server:0.6.15
org.eclipse.milo:stack-server:0.6.16
Scope test
jar
License(s)Eclipse Distribution License - v 1.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:filestorage-memory:1.2.0

+Eclipse Distribution License - v 1.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:filestorage-memory:1.3.0

- + @@ -6271,15 +6535,15 @@

de.fraunhofer.iosb.ilt.faaast.service:filestorage-memory:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:filestorage-memory:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:filestorage-memory:1.3.0
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:messagebus-internal:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:messagebus-internal:1.3.0

- + @@ -6331,15 +6595,15 @@

org.apache.httpcomponents:httpcore-nio:4.4.12

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:messagebus-internal:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:messagebus-internal:1.3.0
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.bouncycastle:bcpkix-jdk15to18:1.78.1

+Apache Software License, Version 2.0Back to top
+

org.bouncycastle:bcpkix-jdk15to18:1.82

- + @@ -6351,15 +6615,15 @@

org.bouncycastle:bcpkix-jdk15to18:1.78.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.bouncycastle:bcpkix-jdk15to18:1.78.1
org.bouncycastle:bcpkix-jdk15to18:1.82
Scope compile
jar
License(s)Bouncy Castle Licence
Back to top
-

org.bouncycastle:bcprov-jdk15to18:1.79

+Bouncy Castle LicenceBack to top
+

org.bouncycastle:bcprov-jdk15to18:1.82

- + @@ -6371,15 +6635,15 @@

org.bouncycastle:bcprov-jdk15to18:1.79

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.bouncycastle:bcprov-jdk15to18:1.79
org.bouncycastle:bcprov-jdk15to18:1.82
Scope compile
jar
License(s)Bouncy Castle Licence
Back to top
-

org.bouncycastle:bcutil-jdk15to18:1.78.1

+Bouncy Castle LicenceBack to top
+

org.bouncycastle:bcutil-jdk15to18:1.82

- + @@ -6391,15 +6655,35 @@

org.bouncycastle:bcutil-jdk15to18:1.78.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.bouncycastle:bcutil-jdk15to18:1.78.1
org.bouncycastle:bcutil-jdk15to18:1.82
Scope compile
jar
License(s)Bouncy Castle Licence
Back to top
-

ch.qos.logback:logback-classic:1.5.12

+Bouncy Castle LicenceBack to top
+

com.github.tomakehurst:wiremock-jre8-standalone:2.35.2

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versioncom.github.tomakehurst:wiremock-jre8-standalone:2.35.2
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-http:1.3.0

- + @@ -6411,15 +6695,15 @@

ch.qos.logback:logback-classic:1.5.12

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionch.qos.logback:logback-classic:1.5.12
de.fraunhofer.iosb.ilt.faaast.service:assetconnection-http:1.3.0
Scope compile
jar
License(s)Eclipse Public License - v 1.0
GNU Lesser General Public License
Back to top
-

ch.qos.logback:logback-core:1.5.12

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-mqtt:1.3.0

- + @@ -6431,15 +6715,55 @@

ch.qos.logback:logback-core:1.5.12

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionch.qos.logback:logback-core:1.5.12
de.fraunhofer.iosb.ilt.faaast.service:assetconnection-mqtt:1.3.0
Scope compile
jar
License(s)Eclipse Public License - v 1.0
GNU Lesser General Public License
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-http:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:endpoint-http:1.3.0

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:endpoint-http:1.3.0
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:messagebus-mqtt:1.3.0

+ + + + + + + + + + + + + + + + + + +
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:messagebus-mqtt:1.3.0
Scopetest
Classifier
Typejar
License(s)Apache Software License, Version 2.0
Back to top
+

ch.qos.logback:logback-classic:1.5.20

- + @@ -6451,15 +6775,15 @@

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-http:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:assetconnection-http:1.2.0
ch.qos.logback:logback-classic:1.5.20
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-mqtt:1.2.0

+Eclipse Public License - v 1.0
GNU Lesser General Public LicenseBack to top
+

ch.qos.logback:logback-core:1.5.20

- + @@ -6471,15 +6795,15 @@

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-mqtt:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:assetconnection-mqtt:1.2.0
ch.qos.logback:logback-core:1.5.20
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-opcua:1.2.0

+Eclipse Public License - v 1.0
GNU Lesser General Public LicenseBack to top
+

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-opcua:1.3.0

- + @@ -6491,15 +6815,15 @@

de.fraunhofer.iosb.ilt.faaast.service:assetconnection-opcua:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:assetconnection-opcua:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:assetconnection-opcua:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:endpoint-http:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:endpoint-opcua:1.3.0

- + @@ -6511,15 +6835,15 @@

de.fraunhofer.iosb.ilt.faaast.service:endpoint-http:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:endpoint-http:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:endpoint-opcua:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:endpoint-opcua:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:filestorage-filesystem:1.3.0

- + @@ -6531,15 +6855,15 @@

de.fraunhofer.iosb.ilt.faaast.service:endpoint-opcua:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:endpoint-opcua:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:filestorage-filesystem:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:filestorage-filesystem:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:persistence-file:1.3.0

- + @@ -6551,15 +6875,15 @@

de.fraunhofer.iosb.ilt.faaast.service:filestorage-filesystem:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:filestorage-filesystem:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:persistence-file:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:persistence-file:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:persistence-mongo:1.3.0

- + @@ -6571,15 +6895,15 @@

de.fraunhofer.iosb.ilt.faaast.service:persistence-file:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:persistence-file:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:persistence-mongo:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:persistence-mongo:1.2.0

+Apache Software License, Version 2.0Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:submodeltemplate-asset-interfaces-mapping-configuration:1.3.0

- + @@ -6591,15 +6915,15 @@

de.fraunhofer.iosb.ilt.faaast.service:persistence-mongo:1.2.0

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:persistence-mongo:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:submodeltemplate-asset-interfaces-mapping-configuration:1.3.0
Scope compile
jar
License(s)Apache Software License, Version 2.0
Back to top
-

net.bytebuddy:byte-buddy:1.15.1

+Apache Software License, Version 2.0Back to top
+

net.bytebuddy:byte-buddy:1.17.4

- + @@ -6611,15 +6935,15 @@

net.bytebuddy:byte-buddy:1.15.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.bytebuddy:byte-buddy:1.15.1
net.bytebuddy:byte-buddy:1.17.4
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

net.bytebuddy:byte-buddy-agent:1.15.1

+Apache Software License, Version 2.0Back to top
+

net.bytebuddy:byte-buddy-agent:1.17.4

- + @@ -6631,15 +6955,15 @@

net.bytebuddy:byte-buddy-agent:1.15.1

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionnet.bytebuddy:byte-buddy-agent:1.15.1
net.bytebuddy:byte-buddy-agent:1.17.4
Scope test
jar
License(s)Apache Software License, Version 2.0
Back to top
-

org.codehaus.janino:commons-compiler:3.1.11

+Apache Software License, Version 2.0Back to top
+

org.codehaus.janino:commons-compiler:3.1.12

- + @@ -6651,15 +6975,15 @@

org.codehaus.janino:commons-compiler:3.1.11

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.janino:commons-compiler:3.1.11
org.codehaus.janino:commons-compiler:3.1.12
Scope compile
jar
License(s)BSD 3-Clause
Back to top
-

org.codehaus.janino:janino:3.1.11

+BSD 3-ClauseBack to top
+

org.codehaus.janino:janino:3.1.12

- + @@ -6671,15 +6995,15 @@

org.codehaus.janino:janino:3.1.11

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionorg.codehaus.janino:janino:3.1.11
org.codehaus.janino:janino:3.1.12
Scope compile
jar
License(s)BSD 3-Clause
Back to top
-

uk.org.webcompere:system-stubs-core:2.1.7

+BSD 3-ClauseBack to top
+

uk.org.webcompere:system-stubs-core:2.1.8

- + @@ -6691,15 +7015,15 @@

uk.org.webcompere:system-stubs-core:2.1.7

-
Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionuk.org.webcompere:system-stubs-core:2.1.7
uk.org.webcompere:system-stubs-core:2.1.8
Scope test
jar
License(s)The MIT License (MIT)
Back to top
-

de.fraunhofer.iosb.ilt.faaast.service:starter:1.2.0

+The MIT License (MIT)Back to top
+

de.fraunhofer.iosb.ilt.faaast.service:starter:1.3.0

- + @@ -6718,7 +7042,7 @@

de.fraunhofer.iosb.ilt.faaast.service:starter:1.2.0

-

© 2022–2024 +

© 2022–2025 Fraunhofer IOSB

diff --git a/endpoint/http/pom.xml b/endpoint/http/pom.xml index 47beb7e0e..714b1b635 100644 --- a/endpoint/http/pom.xml +++ b/endpoint/http/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/endpoint/opcua/pom.xml b/endpoint/opcua/pom.xml index 0ff548697..a4730efa7 100644 --- a/endpoint/opcua/pom.xml +++ b/endpoint/opcua/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/examples/assetconnection-custom/pom.xml b/examples/assetconnection-custom/pom.xml index dc4666d5c..755400a2a 100644 --- a/examples/assetconnection-custom/pom.xml +++ b/examples/assetconnection-custom/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/filestorage/filesystem/pom.xml b/filestorage/filesystem/pom.xml index 3f03d3460..968628928 100644 --- a/filestorage/filesystem/pom.xml +++ b/filestorage/filesystem/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml filestorage-filesystem diff --git a/filestorage/memory/pom.xml b/filestorage/memory/pom.xml index b81119657..7557a6090 100644 --- a/filestorage/memory/pom.xml +++ b/filestorage/memory/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml filestorage-memory diff --git a/messagebus/internal/pom.xml b/messagebus/internal/pom.xml index 166327a8e..e7685acd3 100644 --- a/messagebus/internal/pom.xml +++ b/messagebus/internal/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml messagebus-internal diff --git a/messagebus/mqtt/pom.xml b/messagebus/mqtt/pom.xml index e9b4fff56..461f6a01a 100644 --- a/messagebus/mqtt/pom.xml +++ b/messagebus/mqtt/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml messagebus-mqtt diff --git a/model/pom.xml b/model/pom.xml index 56ec8258a..8ea21a6da 100644 --- a/model/pom.xml +++ b/model/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml model diff --git a/persistence/file/pom.xml b/persistence/file/pom.xml index 5149cc77a..bf47cf502 100644 --- a/persistence/file/pom.xml +++ b/persistence/file/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml persistence-file diff --git a/persistence/memory/pom.xml b/persistence/memory/pom.xml index cbe2e7b8a..10f4616fe 100644 --- a/persistence/memory/pom.xml +++ b/persistence/memory/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml persistence-memory diff --git a/persistence/mongo/pom.xml b/persistence/mongo/pom.xml index d55ebffbf..73d0c904d 100644 --- a/persistence/mongo/pom.xml +++ b/persistence/mongo/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml persistence-mongo diff --git a/pom.xml b/pom.xml index fa8f1cf7e..e682327dd 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 pom service An implementation of the Asset Administration Shell @@ -56,7 +56,7 @@ scm:git:git://github.com/FraunhoferIOSB/FAAAST-Service.git scm:git:ssh://github.com:FraunhoferIOSB/FAAAST-Service.git - HEAD + v1.3.0 https://github.com/FraunhoferIOSB/FAAAST-Service/tree/main diff --git a/starter/pom.xml b/starter/pom.xml index 1ff651427..6687d2236 100644 --- a/starter/pom.xml +++ b/starter/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml starter diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml b/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml index 601fddbca..d90d5e91c 100644 --- a/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml +++ b/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../../pom.xml submodeltemplate-asset-interfaces-mapping-configuration diff --git a/test/pom.xml b/test/pom.xml index 0d681ddf7..a397bbe7f 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml de.fraunhofer.iosb.ilt.faaast.service From 83decd68f5d8857ef3133d1e951a3967f3394f53 Mon Sep 17 00:00:00 2001 From: Michael Jacoby Date: Mon, 27 Oct 2025 16:18:22 +0100 Subject: [PATCH 049/108] Prepare for next development iteration --- README.md | 2 +- assetconnection/common/pom.xml | 2 +- assetconnection/http/pom.xml | 2 +- assetconnection/mqtt/pom.xml | 2 +- assetconnection/opcua/pom.xml | 2 +- checks/pom.xml | 2 +- core/pom.xml | 2 +- dataformat/json/pom.xml | 2 +- docs/source/basics/installation.md | 2 +- docs/source/other/release-notes.md | 2 ++ endpoint/http/pom.xml | 2 +- endpoint/opcua/pom.xml | 2 +- examples/assetconnection-custom/pom.xml | 2 +- filestorage/filesystem/pom.xml | 2 +- filestorage/memory/pom.xml | 2 +- messagebus/internal/pom.xml | 2 +- messagebus/mqtt/pom.xml | 2 +- model/pom.xml | 2 +- .../ilt/faaast/service/model/ServiceSpecificationProfile.java | 4 ++-- persistence/file/pom.xml | 2 +- persistence/memory/pom.xml | 2 +- persistence/mongo/pom.xml | 2 +- pom.xml | 4 ++-- starter/pom.xml | 2 +- .../asset-interfaces-mapping-configuration/pom.xml | 2 +- test/pom.xml | 2 +- 26 files changed, 29 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 2d4cc9a76..e288bc1e1 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The features of FA³ST Service include [Download latest RELEASE version (1.3.0)](https://repo1.maven.org/maven2/de/fraunhofer/iosb/ilt/faaast/service/starter/1.3.0/starter-1.3.0.jar) - +[Download latest SNAPSHOT version (1.4.0-SNAPSHOT)](https://purl.archive.org/faaast/service/snapshot/latest) ### As Maven Dependency ```xml diff --git a/assetconnection/common/pom.xml b/assetconnection/common/pom.xml index 3fa485aee..9525cbcdb 100644 --- a/assetconnection/common/pom.xml +++ b/assetconnection/common/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/assetconnection/http/pom.xml b/assetconnection/http/pom.xml index 4985e8009..33207f4aa 100644 --- a/assetconnection/http/pom.xml +++ b/assetconnection/http/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/assetconnection/mqtt/pom.xml b/assetconnection/mqtt/pom.xml index 2c62d6307..02c7c1b3b 100644 --- a/assetconnection/mqtt/pom.xml +++ b/assetconnection/mqtt/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/assetconnection/opcua/pom.xml b/assetconnection/opcua/pom.xml index 61a286076..0d520b418 100644 --- a/assetconnection/opcua/pom.xml +++ b/assetconnection/opcua/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/checks/pom.xml b/checks/pom.xml index 18ac041f5..981c579b2 100644 --- a/checks/pom.xml +++ b/checks/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/core/pom.xml b/core/pom.xml index ecd61d9e2..4979523ac 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/dataformat/json/pom.xml b/dataformat/json/pom.xml index 00a0a34fc..b78fe283e 100644 --- a/dataformat/json/pom.xml +++ b/dataformat/json/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml dataformat-json diff --git a/docs/source/basics/installation.md b/docs/source/basics/installation.md index a36f84418..2670606aa 100644 --- a/docs/source/basics/installation.md +++ b/docs/source/basics/installation.md @@ -10,7 +10,7 @@ {download}`Latest RELEASE version (1.3.0) ` - +{download}`Latest SNAPSHOT version (1.4.0-SNAPSHOT) ## Maven Dependency diff --git a/docs/source/other/release-notes.md b/docs/source/other/release-notes.md index bbb495735..f0638a016 100644 --- a/docs/source/other/release-notes.md +++ b/docs/source/other/release-notes.md @@ -1,4 +1,6 @@ # Release Notes + +## 1.4.0-SNAPSHOT (current development version) ## 1.3.0 **New Features & Major Changes** diff --git a/endpoint/http/pom.xml b/endpoint/http/pom.xml index 714b1b635..e95947c43 100644 --- a/endpoint/http/pom.xml +++ b/endpoint/http/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/endpoint/opcua/pom.xml b/endpoint/opcua/pom.xml index a4730efa7..e1c9cc383 100644 --- a/endpoint/opcua/pom.xml +++ b/endpoint/opcua/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/examples/assetconnection-custom/pom.xml b/examples/assetconnection-custom/pom.xml index 755400a2a..fb267e97a 100644 --- a/examples/assetconnection-custom/pom.xml +++ b/examples/assetconnection-custom/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml de.fraunhofer.iosb.ilt.faaast.service diff --git a/filestorage/filesystem/pom.xml b/filestorage/filesystem/pom.xml index 968628928..14542df0c 100644 --- a/filestorage/filesystem/pom.xml +++ b/filestorage/filesystem/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml filestorage-filesystem diff --git a/filestorage/memory/pom.xml b/filestorage/memory/pom.xml index 7557a6090..f2172a413 100644 --- a/filestorage/memory/pom.xml +++ b/filestorage/memory/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml filestorage-memory diff --git a/messagebus/internal/pom.xml b/messagebus/internal/pom.xml index e7685acd3..452331ff1 100644 --- a/messagebus/internal/pom.xml +++ b/messagebus/internal/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml messagebus-internal diff --git a/messagebus/mqtt/pom.xml b/messagebus/mqtt/pom.xml index 461f6a01a..8179cc6d3 100644 --- a/messagebus/mqtt/pom.xml +++ b/messagebus/mqtt/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml messagebus-mqtt diff --git a/model/pom.xml b/model/pom.xml index 8ea21a6da..de7ca6607 100644 --- a/model/pom.xml +++ b/model/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml model diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java index b73c63543..84a6085e3 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java @@ -451,11 +451,11 @@ public enum ServiceSpecificationProfile { GenerateSerializationByIdsRequest.class, GetSelfDescriptionRequest.class)), FAAAST_IMPORT( - "https://github.com/FraunhoferIOSB/FAAAST-Service/API/1/3/Import", + "https://github.com/FraunhoferIOSB/FAAAST-Service/API/1/4/Import", List.of(), List.of(ImportRequest.class)), FAAAST_RESET( - "https://github.com/FraunhoferIOSB/FAAAST-Service/API/1/3/Reset", + "https://github.com/FraunhoferIOSB/FAAAST-Service/API/1/4/Reset", List.of(), List.of(ResetRequest.class)); diff --git a/persistence/file/pom.xml b/persistence/file/pom.xml index bf47cf502..b4c9fdad0 100644 --- a/persistence/file/pom.xml +++ b/persistence/file/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml persistence-file diff --git a/persistence/memory/pom.xml b/persistence/memory/pom.xml index 10f4616fe..a4caa8f8c 100644 --- a/persistence/memory/pom.xml +++ b/persistence/memory/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml persistence-memory diff --git a/persistence/mongo/pom.xml b/persistence/mongo/pom.xml index 73d0c904d..0bf6b95a5 100644 --- a/persistence/mongo/pom.xml +++ b/persistence/mongo/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml persistence-mongo diff --git a/pom.xml b/pom.xml index e682327dd..395deded9 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT pom service An implementation of the Asset Administration Shell @@ -56,7 +56,7 @@ scm:git:git://github.com/FraunhoferIOSB/FAAAST-Service.git scm:git:ssh://github.com:FraunhoferIOSB/FAAAST-Service.git - v1.3.0 + HEAD https://github.com/FraunhoferIOSB/FAAAST-Service/tree/main diff --git a/starter/pom.xml b/starter/pom.xml index 6687d2236..5bc128f77 100644 --- a/starter/pom.xml +++ b/starter/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml starter diff --git a/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml b/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml index d90d5e91c..fc31b33b6 100644 --- a/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml +++ b/submodeltemplate/asset-interfaces-mapping-configuration/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../../pom.xml submodeltemplate-asset-interfaces-mapping-configuration diff --git a/test/pom.xml b/test/pom.xml index a397bbe7f..3d8832670 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.faaast.service service - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml de.fraunhofer.iosb.ilt.faaast.service From aa2596277e07cee348a4d93ce1a25c42305033ac Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 28 Oct 2025 15:13:26 +0100 Subject: [PATCH 050/108] add query evaluator and Query AAS endpoint, modify persistence --- .../service/persistence/Persistence.java | 16 + .../faaast/service/query/QueryEvaluator.java | 672 ++++++++++++++++++ ...setAdministrationShellsRequestHandler.java | 61 ++ ...ssetAdministrationShellsRequestMapper.java | 46 ++ .../http/security/filter/ApiGateway.java | 7 +- .../model/ServiceSpecificationProfile.java | 5 + ...QueryAssetAdministrationShellsRequest.java | 86 +++ ...ueryAssetAdministrationShellsResponse.java | 43 ++ .../persistence/file/PersistenceFile.java | 9 + .../memory/PersistenceInMemory.java | 24 + .../persistence/mongo/PersistenceMongo.java | 10 + pom.xml | 2 +- 12 files changed, 977 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAllAssetAdministrationShellsRequestHandler.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/aasrepository/QueryAssetAdministrationShellsResponse.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java index a1ac6dbf2..3f724748c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java @@ -24,6 +24,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceAlreadyExistsException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import java.util.Objects; @@ -233,6 +234,21 @@ public Page findAssetAdministrationShells(AssetAdminis throws PersistenceException; + /** + * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s by search criteria and query. + * + * @param criteria the search criteria + * @param modifier the modifier + * @param paging paging information + * @param query the query to be executed + * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s + * @throws PersistenceException if there was an error with the storage. + */ + public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + Query query) + throws PersistenceException; + + /** * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s by search criteria. * diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java new file mode 100644 index 000000000..0d8273208 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -0,0 +1,672 @@ +/* + * 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.ilt.faaast.service.query; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.MatchExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.StringValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; +import org.eclipse.digitaltwin.aas4j.v3.model.Key; +import org.eclipse.digitaltwin.aas4j.v3.model.LangStringTextType; +import org.eclipse.digitaltwin.aas4j.v3.model.MultiLanguageProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Evaluates queries sent to /query endpoints + */ +public class QueryEvaluator { + + private final Environment environment; + private static final Logger LOGGER = LoggerFactory.getLogger(QueryEvaluator.class); + + public QueryEvaluator(Environment environment) { + this.environment = environment; + } + + + /** + * Used to decice whether to filter out the Identifiable + * + * @param expr LogicalExpression of the query + * @param identifiable like AAS, Submodel or Concept-Description + * @return true if expression matches + */ + public boolean matches(LogicalExpression expr, Identifiable identifiable) { + if (!expr.get$and().isEmpty()) { + return expr.get$and().stream().allMatch(e -> matches(e, identifiable)); + } + if (!expr.get$or().isEmpty()) { + return expr.get$or().stream().anyMatch(e -> matches(e, identifiable)); + } + if (expr.get$not() != null) { + return !matches(expr.get$not(), identifiable); + } + if (!expr.get$match().isEmpty()) { + return evaluateMatch(expr.get$match(), identifiable); + } + if (!expr.get$eq().isEmpty()) { + List args = expr.get$eq(); + List left = evaluateValue(args.get(0), identifiable); + List right = evaluateValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "eq"))); + } + if (!expr.get$ne().isEmpty()) { + List args = expr.get$ne(); + List left = evaluateValue(args.get(0), identifiable); + List right = evaluateValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "ne"))); + } + if (!expr.get$gt().isEmpty()) { + List args = expr.get$gt(); + List left = evaluateValue(args.get(0), identifiable); + List right = evaluateValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "gt"))); + } + if (!expr.get$ge().isEmpty()) { + List args = expr.get$ge(); + List left = evaluateValue(args.get(0), identifiable); + List right = evaluateValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "ge"))); + } + if (!expr.get$lt().isEmpty()) { + List args = expr.get$lt(); + List left = evaluateValue(args.get(0), identifiable); + List right = evaluateValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "lt"))); + } + if (!expr.get$le().isEmpty()) { + List args = expr.get$le(); + List left = evaluateValue(args.get(0), identifiable); + List right = evaluateValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "le"))); + } + if (!expr.get$contains().isEmpty()) { + List args = expr.get$contains(); + List left = evaluateStringValue(args.get(0), identifiable); + List right = evaluateStringValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "contains"))); + } + if (!expr.get$startsWith().isEmpty()) { + List args = expr.get$startsWith(); + List left = evaluateStringValue(args.get(0), identifiable); + List right = evaluateStringValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "starts-with"))); + } + if (!expr.get$endsWith().isEmpty()) { + List args = expr.get$endsWith(); + List left = evaluateStringValue(args.get(0), identifiable); + List right = evaluateStringValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "ends-with"))); + } + if (!expr.get$regex().isEmpty()) { + List args = expr.get$regex(); + List left = evaluateStringValue(args.get(0), identifiable); + List right = evaluateStringValue(args.get(1), identifiable); + return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "regex"))); + } + if (expr.get$boolean() != null) { + return expr.get$boolean(); + } + return false; + } + + + private List evaluateValue(Value v, Identifiable identifiable) { + if (v.get$field() != null) { + return getFieldValues(v.get$field(), identifiable); + } + if (v.get$strVal() != null) { + return Collections.singletonList(v.get$strVal()); + } + if (v.get$numVal() != null) { + return Collections.singletonList(v.get$numVal()); + } + if (v.get$hexVal() != null) { + return Collections.singletonList(v.get$hexVal()); + } + if (v.get$dateTimeVal() != null) { + return Collections.singletonList(v.get$dateTimeVal()); + } + if (v.get$timeVal() != null) { + return Collections.singletonList(v.get$timeVal()); + } + if (v.get$boolean() != null) { + return Collections.singletonList(v.get$boolean()); + } + if (v.get$strCast() != null) { + List inner = evaluateValue(v.get$strCast(), identifiable); + return inner.stream().map(Object::toString).collect(Collectors.toList()); + } + if (v.get$numCast() != null) { + List inner = evaluateValue(v.get$numCast(), identifiable); + return inner.stream().map(i -> { + try { + return Double.parseDouble(i.toString()); + } + catch (NumberFormatException e) { + return 0.0; + } + }).collect(Collectors.toList()); + } + return null; + } + + + private List evaluateStringValue(StringValue sv, Identifiable identifiable) { + if (sv.get$field() != null) { + return getFieldValues(sv.get$field(), identifiable); + } + if (sv.get$strVal() != null) { + return Collections.singletonList(sv.get$strVal()); + } + if (sv.get$strCast() != null) { + List inner = evaluateValue(sv.get$strCast(), identifiable); + return inner.stream().map(Object::toString).collect(Collectors.toList()); + } + // Attribute not supported + LOGGER.error("Invalid string value."); + return null; + } + + + private boolean evaluateMatch(List matches, Identifiable identifiable) { + String commonPrefix = null; + Map suffixes = new HashMap<>(); + Map constants = new HashMap<>(); + String op = "eq"; // Assume eq for all + for (MatchExpression m: matches) { + if (m.get$eq() == null || m.get$eq().isEmpty()) { + LOGGER.error("Only $eq supported in match for now"); + return false; + } + Value left = m.get$eq().get(0); + Value right = m.get$eq().get(1); + if (left.get$field() == null) { + LOGGER.error("Left not field in match"); + return false; + } + String f = left.get$field(); + Object c = evaluateValue(right, identifiable).get(0); + int pos = f.indexOf("[]"); + if (pos == -1) { + LOGGER.error("No [] in field for match"); + return false; + } + String prefix = f.substring(0, pos); + if (commonPrefix == null) { + commonPrefix = prefix; + } + else if (!commonPrefix.equals(prefix)) { + LOGGER.error("Non-common prefix in match"); + } + String suffix = f.substring(pos + 2); + suffixes.put(f, suffix); + constants.put(f, c); + } + if (commonPrefix == null) { + return true; + } + // Handle different prefixes + if (commonPrefix.equals("$aas#assetInformation.specificAssetIds")) { + if (!(identifiable instanceof AssetAdministrationShell)) + return false; + AssetAdministrationShell aas = (AssetAdministrationShell) identifiable; + List list = aas.getAssetInformation().getSpecificAssetIds(); + for (SpecificAssetId item: list) { + boolean all = true; + for (String f: suffixes.keySet()) { + String suffix = suffixes.get(f); + Object value = getPropertyFromObject(item, suffix); + Object c = constants.get(f); + if (!compareObjects(value, c, op)) { + all = false; + break; + } + } + if (all) { + return true; + } + } + return false; + } + else if (commonPrefix.startsWith("$sme.")) { + // Basic support for $sme paths with [] + List sms = environment.getSubmodels(); + for (Submodel sm: sms) { + String path = commonPrefix.substring(5); // e.g., "ProductClassifications" + SubmodelElement listElem = getSubmodelElementByPath(sm, path); + if (listElem == null || !(listElem instanceof SubmodelElementList)) + continue; + SubmodelElementList smeList = (SubmodelElementList) listElem; + for (SubmodelElement item: smeList.getValue()) { + boolean all = true; + for (String f: suffixes.keySet()) { + String suffix = suffixes.get(f); + Object value = getPropertyFromSuffix(item, suffix); + Object c = constants.get(f); + if (!compareObjects(value, c, op)) { + all = false; + break; + } + } + if (all) { + return true; + } + } + } + return false; + } + LOGGER.error("Unsupported prefix for match: " + commonPrefix); + return false; + } + + + private List getFieldValues(String field, Identifiable identifiable) { + if (field.startsWith("$aas#")) { + if (!(identifiable instanceof AssetAdministrationShell)) + return Collections.emptyList(); + AssetAdministrationShell aas = (AssetAdministrationShell) identifiable; + String attr = field.substring(5); + // Handle AAS attributes + if (attr.equals("idShort")) { + return Collections.singletonList(aas.getIdShort()); + } + else if (attr.equals("id")) { + return Collections.singletonList(aas.getId()); + } + else if (attr.equals("assetInformation.assetKind")) { + return Collections.singletonList(aas.getAssetInformation().getAssetKind().name()); + } + else if (attr.equals("assetInformation.assetType")) { + return Collections.singletonList(aas.getAssetInformation().getAssetType()); + } + else if (attr.equals("assetInformation.globalAssetId")) { + String globalAssetId = aas.getAssetInformation().getGlobalAssetId(); + if (globalAssetId == null) + return Collections.emptyList(); + return Collections.singletonList(globalAssetId); + } + else if (attr.startsWith("assetInformation.specificAssetIds")) { + String remaining = attr.substring("assetInformation.specificAssetIds".length()); + boolean any = remaining.startsWith("[]"); + Integer index = null; + if (remaining.startsWith("[")) { + int end = remaining.indexOf("]"); + String idxStr = remaining.substring(1, end); + if (idxStr.isEmpty()) + any = true; + else + index = Integer.parseInt(idxStr); + remaining = remaining.substring(end + 1); + } + List sais = aas.getAssetInformation().getSpecificAssetIds(); + List values = new ArrayList<>(); + List targets = any || index == null ? sais + : (index < sais.size() && index >= 0 ? Collections.singletonList(sais.get(index)) : Collections.emptyList()); + for (SpecificAssetId sai: targets) { + values.add(getPropertyFromObject(sai, remaining)); + } + return values; + } + LOGGER.error("Unsupported AAS attribute: " + attr); + } + else if (field.startsWith("$sm#")) { + String attr = field.substring(4); + List sms = environment.getSubmodels(); + List values = new ArrayList<>(); + for (Submodel sm: sms) { + values.addAll(getSmAttrValues(sm, attr)); + } + return values; + } + else if (field.startsWith("$sme")) { + String pathPart = ""; + String attr = ""; + if (field.contains(".")) { + int hashPos = field.indexOf("#"); + pathPart = field.substring(field.indexOf(".") + 1, hashPos); + attr = field.substring(hashPos + 1); + } + else { + attr = field.substring(5); + } + if (!pathPart.isEmpty()) { + // Basic path support, no [] for now + List sms = environment.getSubmodels(); + List values = new ArrayList<>(); + for (Submodel sm: sms) { + SubmodelElement sme = getSubmodelElementByPath(sm, pathPart); + if (sme != null) { + values.addAll(getSmeAttrValues(sme, attr)); + } + } + return values; + } + else { + // Recursive all SME + Submodel sm = (Submodel) identifiable; + List smes = sm.getSubmodelElements(); + List values = new ArrayList<>(); + for (SubmodelElement sme: smes) { + values.addAll(getSmeAttrValues(sme, attr)); + } + return values; + } + } + else if (field.startsWith("$cd#")) { + if (!(identifiable instanceof ConceptDescription)) + return Collections.emptyList(); + ConceptDescription cd = (ConceptDescription) identifiable; + String attr = field.substring(4); + if (attr.equals("idShort")) { + return Collections.singletonList(cd.getIdShort()); + } + else if (attr.equals("id")) { + return Collections.singletonList(cd.getId()); + } + LOGGER.error("Unsupported CD attribute: " + attr); + } + LOGGER.error("Unsupported field: " + field); + return null; + } + + + private List getSmAttrValues(Submodel sm, String attr) { + if (attr.equals("idShort")) { + return Collections.singletonList(sm.getIdShort()); + } + else if (attr.equals("id")) { + return Collections.singletonList(sm.getId()); + } + else if (attr.equals("semanticId")) { + Reference ref = sm.getSemanticId(); + if (ref == null || ref.getKeys().isEmpty()) + return Collections.emptyList(); + return Collections.singletonList(ref.getKeys().get(0).getValue()); + } + else if (attr.startsWith("semanticId.keys")) { + Reference ref = sm.getSemanticId(); + if (ref == null) + return Collections.emptyList(); + //cut prefix + String remaining = attr.substring("semanticId.keys".length()); + //check if there is an index like [1] + boolean any = remaining.startsWith("[]"); + //set index + Integer index = null; + if (remaining.startsWith("[")) { + int end = remaining.indexOf("]"); + String idxStr = remaining.substring(1, end); + if (idxStr.isEmpty()) + any = true; + else + index = Integer.parseInt(idxStr); + remaining = remaining.substring(end + 1); + } + List keys = ref.getKeys(); + List values = new ArrayList<>(); + // set targets depending if any is true otherwise target index + List targets = any || index == null ? keys : (index < keys.size() && index >= 0 ? Collections.singletonList(keys.get(index)) : Collections.emptyList()); + for (Key key: targets) { + if (remaining.equals(".type")) { + values.add(key.getType().name()); + } + else if (remaining.equals(".value")) { + values.add(key.getValue()); + } + } + return values; + } + LOGGER.error("Unsupported SM attribute: " + attr); + return null; + } + + + private List getSmeAttrValues(SubmodelElement sme, String attr) { + if (attr.equals("idShort")) { + return Collections.singletonList(sme.getIdShort()); + } + else if (attr.equals("value")) { + if (sme instanceof Property) { + return Collections.singletonList(((Property) sme).getValue()); + } + return Collections.emptyList(); + } + else if (attr.equals("valueType")) { + if (sme instanceof Property) { + return Collections.singletonList(((Property) sme).getValueType().name()); + } + return Collections.emptyList(); + } + else if (attr.equals("language")) { + if (sme instanceof MultiLanguageProperty) { + return ((MultiLanguageProperty) sme).getValue().stream().map(LangStringTextType::getLanguage).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + else if (attr.equals("semanticId")) { + Reference ref = sme.getSemanticId(); + if (ref == null || ref.getKeys().isEmpty()) + return Collections.emptyList(); + return Collections.singletonList(ref.getKeys().get(0).getValue()); + } + else if (attr.startsWith("semanticId.keys")) { + Reference ref = sme.getSemanticId(); + if (ref == null) + return Collections.emptyList(); + String remaining = attr.substring("semanticId.keys".length()); + boolean any = remaining.startsWith("[]"); + Integer index = null; + if (remaining.startsWith("[")) { + int end = remaining.indexOf("]"); + String idxStr = remaining.substring(1, end); + if (idxStr.isEmpty()) + any = true; + else + index = Integer.parseInt(idxStr); + remaining = remaining.substring(end + 1); + } + List keys = ref.getKeys(); + List values = new ArrayList<>(); + List targets = any || index == null ? keys : (index < keys.size() && index >= 0 ? Collections.singletonList(keys.get(index)) : Collections.emptyList()); + for (Key key: targets) { + if (remaining.equals(".type")) { + values.add(key.getType().name()); + } + else if (remaining.equals(".value")) { + values.add(key.getValue()); + } + } + return values; + } + LOGGER.error("Unsupported SME attribute: " + attr); + return null; + } + + + private SubmodelElement getSubmodelElementByPath(Submodel sm, String path) { + SubmodelElement current = null; + for (String token: path.split("\\.")) { + current = sm.getSubmodelElements().stream() + .filter(e -> e.getIdShort().equals(token)) + .findFirst().orElse(null); + } + return current; + } + + + private Object getPropertyFromObject(Object item, String path) { + if (item instanceof SpecificAssetId) { + if (path.equals(".name")) { + return ((SpecificAssetId) item).getName(); + } + else if (path.equals(".value")) { + return ((SpecificAssetId) item).getValue(); + } + else if (path.startsWith(".externalSubjectId")) { + return ((SpecificAssetId) item).getExternalSubjectId(); + } + } + LOGGER.error("Unsupported property: " + path); + return null; + } + + + private Object getPropertyFromSuffix(SubmodelElement item, String suffix) { + // For suffixes like .ProductClassId#value + int hashPos = suffix.indexOf("#"); + if (hashPos == -1) { + LOGGER.error("No # in suffix for match"); + return null; + } + String subPath = suffix.substring(0, hashPos); + String attr = suffix.substring(hashPos + 1); + SubmodelElement subElem = getSubmodelElementByPathForItem(item, subPath); + if (subElem == null) + return null; + List values = getSmeAttrValues(subElem, attr); + return values.isEmpty() ? null : values.get(0); + } + + + private SubmodelElement getSubmodelElementByPathForItem(SubmodelElement item, String path) { + // Similar to getSubmodelElementByPath, but start from item + SubmodelElement current = null; + Submodel container = null; + if (path.startsWith(".")) + path = path.substring(1); + for (String token: path.split("\\.")) { + if (container == null) { + if (item.getIdShort().equals(token)) { + current = item; + } + else { + return null; + } + } + else { + current = container.getSubmodelElements().stream() + .filter(e -> e.getIdShort().equals(token)) + .findFirst().orElse(null); + } + if (current == null) + return null; + } + return current; + } + + + private boolean compareObjects(Object a, Object b, String op) { + if (a == null || b == null) { + switch (op) { + case "eq": + return a == b; + case "ne": + return a != b; + default: + return false; + } + } + try { + double d1 = a instanceof Number ? ((Number) a).doubleValue() : Double.parseDouble(a.toString()); + double d2 = b instanceof Number ? ((Number) b).doubleValue() : Double.parseDouble(b.toString()); + switch (op) { + case "eq": + return d1 == d2; + case "ne": + return d1 != d2; + case "gt": + return d1 > d2; + case "ge": + return d1 >= d2; + case "lt": + return d1 < d2; + case "le": + return d1 <= d2; + } + } + catch (NumberFormatException e) {} + boolean bool1, bool2; + try { + bool1 = a instanceof Boolean ? (Boolean) a : Boolean.parseBoolean(a.toString()); + bool2 = b instanceof Boolean ? (Boolean) b : Boolean.parseBoolean(b.toString()); + switch (op) { + case "eq": + return bool1 == bool2; + case "ne": + return bool1 != bool2; + default: + return false; + } + } + catch (Exception e) {} + String s1 = a.toString(); + String s2 = b.toString(); + int cmp = s1.compareTo(s2); + switch (op) { + case "eq": + return cmp == 0; + case "ne": + return cmp != 0; + case "gt": + return cmp > 0; + case "ge": + return cmp >= 0; + case "lt": + return cmp < 0; + case "le": + return cmp <= 0; + } + return false; + } + + + private boolean stringCompareObjects(Object a, Object b, String op) { + if (a == null || b == null) + return false; + String s1 = a.toString(); + String s2 = b.toString(); + switch (op) { + case "contains": + return s1.contains(s2); + case "starts-with": + return s1.startsWith(s2); + case "ends-with": + return s1.endsWith(s2); + case "regex": + return Pattern.compile(s2).matcher(s1).matches(); + } + return false; + } +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAllAssetAdministrationShellsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAllAssetAdministrationShellsRequestHandler.java new file mode 100644 index 000000000..ac05b6252 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAllAssetAdministrationShellsRequestHandler.java @@ -0,0 +1,61 @@ +/* + * 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.ilt.faaast.service.request.handler.aasrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.QueryAssetAdministrationShellsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.QueryAssetAdministrationShellsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.ElementReadEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; +import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractRequestHandler; +import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; +import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; + + +/** + * Class to handle a + * {@link de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.QueryAssetAdministrationShellsRequest} + * in the service and to send the corresponding response + * {@link de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.QueryAssetAdministrationShellsResponse}. + * Is responsible for communication with the persistence and sends the corresponding events to the message bus. + */ +public class QueryAllAssetAdministrationShellsRequestHandler extends AbstractRequestHandler { + + @Override + public QueryAssetAdministrationShellsResponse process(QueryAssetAdministrationShellsRequest request, RequestExecutionContext context) + throws MessageBusException, PersistenceException { + Page page = context.getPersistence().findAssetAdministrationShellsWithQuery( + AssetAdministrationShellSearchCriteria.NONE, + request.getOutputModifier(), + request.getPagingInfo(), + request.getQuery()); + if (!request.isInternal() && Objects.nonNull(page.getContent())) { + page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( + x -> context.getMessageBus().publish(ElementReadEventMessage.builder() + .element(x) + .value(x) + .build()))); + } + return QueryAssetAdministrationShellsResponse.builder() + .payload(page) + .success() + .build(); + } + +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java new file mode 100644 index 000000000..f17b57cfc --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java @@ -0,0 +1,46 @@ +/* + * 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.ilt.faaast.service.endpoint.http.request.mapper.aasrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.QueryAssetAdministrationShellsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import java.util.Map; + + +/** + * class to map HTTP-POST-Request path: query/shells. + */ +public class QueryAssetAdministrationShellsRequestMapper extends AbstractRequestMapper { + + private static final String PATTERN = "query/shells"; + + public QueryAssetAdministrationShellsRequestMapper(ServiceContext serviceContext) { + super(serviceContext, HttpMethod.POST, PATTERN); + } + + + @Override + public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { + return QueryAssetAdministrationShellsRequest.builder() + .query(parseBody(httpRequest, Query.class)) + .build(); + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index fd3fb8feb..0624dc890 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -69,6 +69,7 @@ public class ApiGateway { private Map aclList; private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; + private final String errorMessage = "Invalid ACL rule, skipping."; public ApiGateway(String aclFolder) { initializeAclList(aclFolder); @@ -356,7 +357,7 @@ private void initializeAclList(String aclFolder) { aclList.put(filePath, allRules); } catch (IOException e) { - LOGGER.error(abortMessage); + LOGGER.error(errorMessage); } } } @@ -382,7 +383,7 @@ private void monitorAclRules(String aclFolder) { monitorLoop(watchService, folderToWatch); } catch (IOException e) { - LOGGER.error(abortMessage); + LOGGER.error(errorMessage); } } @@ -419,7 +420,7 @@ private void monitorLoop(WatchService watchService, Path folderToWatch) { aclList.put(absolutePath, allRules); } catch (IOException e) { - LOGGER.error(abortMessage); + LOGGER.error(errorMessage); } LOGGER.info("Added new ACL rule."); } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java index b73c63543..fad6ae3d4 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java @@ -42,6 +42,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.GetAssetAdministrationShellByIdRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.PostAssetAdministrationShellRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.PutAssetAdministrationShellByIdRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.QueryAssetAdministrationShellsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasserialization.GenerateSerializationByIdsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.DeleteConceptDescriptionByIdRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetAllConceptDescriptionsByDataSpecificationReferenceRequest; @@ -108,6 +109,7 @@ public enum ServiceSpecificationProfile { GetAllSubmodelReferencesRequest.class, GetAssetAdministrationShellReferenceRequest.class, GetAssetAdministrationShellRequest.class, + QueryAssetAdministrationShellsRequest.class, GetAssetInformationRequest.class, GetThumbnailRequest.class, PostSubmodelReferenceRequest.class, @@ -150,6 +152,7 @@ public enum ServiceSpecificationProfile { GetAllSubmodelReferencesRequest.class, GetAssetAdministrationShellReferenceRequest.class, GetAssetAdministrationShellRequest.class, + QueryAssetAdministrationShellsRequest.class, GetAssetInformationRequest.class, GetThumbnailRequest.class, GetAllSubmodelElementsPathRequest.class, @@ -273,6 +276,7 @@ public enum ServiceSpecificationProfile { GetAllAssetAdministrationShellsByIdShortRequest.class, GetAllAssetAdministrationShellsReferenceRequest.class, GetAllAssetAdministrationShellsRequest.class, + QueryAssetAdministrationShellsRequest.class, GetAssetAdministrationShellByIdReferenceRequest.class, GetAssetAdministrationShellByIdRequest.class, PostAssetAdministrationShellRequest.class, @@ -340,6 +344,7 @@ public enum ServiceSpecificationProfile { GetAllAssetAdministrationShellsByIdShortRequest.class, GetAllAssetAdministrationShellsReferenceRequest.class, GetAllAssetAdministrationShellsRequest.class, + QueryAssetAdministrationShellsRequest.class, GetAssetAdministrationShellByIdReferenceRequest.class, GetAssetAdministrationShellByIdRequest.class, GetAllSubmodelReferencesRequest.class, diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java new file mode 100644 index 000000000..f4b742776 --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java @@ -0,0 +1,86 @@ +/* + * 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.ilt.faaast.service.model.api.request.aasrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifierAndPaging; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.QueryAssetAdministrationShellsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import java.util.Objects; + + +/** + * Request class for QueryAssetAdministrationShells requests. + */ +public class QueryAssetAdministrationShellsRequest extends AbstractRequestWithModifierAndPaging { + + private Query query; + + public Query getQuery() { + return query; + } + + + public void setQuery(Query query) { + this.query = query; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QueryAssetAdministrationShellsRequest that = (QueryAssetAdministrationShellsRequest) o; + return super.equals(that) + && Objects.equals(query, that.query); + } + + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), query); + } + + + public static Builder builder() { + return new Builder(); + } + + public abstract static class AbstractBuilder> extends Request.AbstractBuilder { + + public B query(Query value) { + getBuildingInstance().setQuery(value); + return getSelf(); + } + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected QueryAssetAdministrationShellsRequest newBuildingInstance() { + return new QueryAssetAdministrationShellsRequest(); + } + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/aasrepository/QueryAssetAdministrationShellsResponse.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/aasrepository/QueryAssetAdministrationShellsResponse.java new file mode 100644 index 000000000..0e017497a --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/aasrepository/QueryAssetAdministrationShellsResponse.java @@ -0,0 +1,43 @@ +/* + * 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.ilt.faaast.service.model.api.response.aasrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.AbstractPagedResponse; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; + + +/** + * Response class for QueryAssetAdministrationShells requests. + */ +public class QueryAssetAdministrationShellsResponse extends AbstractPagedResponse { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected QueryAssetAdministrationShellsResponse newBuildingInstance() { + return new QueryAssetAdministrationShellsResponse(); + } + } +} diff --git a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java index 12ccae8da..9c8518d67 100644 --- a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java +++ b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java @@ -37,6 +37,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; @@ -178,6 +179,14 @@ public Page findAssetAdministrationShells(AssetAdminis } + @Override + public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + Query query) + throws PersistenceException { + throw new PersistenceException("Query not supported."); + } + + @Override public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { return persistence.findSubmodels(criteria, modifier, paging); diff --git a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java index 851c2cb97..de0688eeb 100644 --- a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java +++ b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java @@ -31,6 +31,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementWalker; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementVisitor; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; @@ -39,6 +40,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.persistence.SubmodelElementSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.SubmodelSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.util.QueryModifierHelper; +import de.fraunhofer.iosb.ilt.faaast.service.query.QueryEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.util.CollectionHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ElementValueHelper; @@ -215,6 +217,28 @@ public Page findAssetAdministrationShells(AssetAdminis } + @Override + public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + Query query) { + Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); + Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); + Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); + + Stream result = environment.getAssetAdministrationShells().stream(); + if (criteria.isIdShortSet()) { + result = filterByIdShort(result, criteria.getIdShort()); + } + if (criteria.isAssetIdsSet()) { + result = filterByAssetIds(result, criteria.getAssetIds()); + } + QueryEvaluator evaluator = new QueryEvaluator(environment); + if (query != null) { + result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); + } + return preparePagedResult(result, modifier, paging); + } + + @Override public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); diff --git a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java index 4b6216c69..6933d776d 100644 --- a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java +++ b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java @@ -54,6 +54,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedModifierException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; @@ -256,6 +257,15 @@ public Page findAssetAdministrationShells(AssetAdminis } + @Override + public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + Query query) + throws PersistenceException { + throw new PersistenceException("Query not supported in mongoDB."); + + } + + @Override public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); diff --git a/pom.xml b/pom.xml index 3108aa612..5ddad3547 100644 --- a/pom.xml +++ b/pom.xml @@ -95,7 +95,7 @@ 2.6.0 1.5.3 2.9.0 - 1.2.1 + 1.2.2 4.13.2 0.22.1 1.5.20 From 5ddc4e56320f16cf7b4fdb881cb4eedc0142e204 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 08:02:21 +0100 Subject: [PATCH 051/108] bugfix: string claim parsing --- .../iosb/ilt/faaast/service/query/QueryEvaluator.java | 7 +++++-- .../service/endpoint/http/security/filter/ApiGateway.java | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index 0d8273208..7bd218f8d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -43,7 +43,7 @@ /** - * Evaluates queries sent to /query endpoints + * Evaluates queries sent to /query endpoints. */ public class QueryEvaluator { @@ -56,7 +56,7 @@ public QueryEvaluator(Environment environment) { /** - * Used to decice whether to filter out the Identifiable + * Used to decide whether to filter out the Identifiable. * * @param expr LogicalExpression of the query * @param identifiable like AAS, Submodel or Concept-Description @@ -485,8 +485,11 @@ else if (attr.startsWith("semanticId.keys")) { Reference ref = sme.getSemanticId(); if (ref == null) return Collections.emptyList(); + //cut prefix String remaining = attr.substring("semanticId.keys".length()); + //check if there is an index like [1] boolean any = remaining.startsWith("[]"); + //set index Integer index = null; if (remaining.startsWith("[")) { int end = remaining.indexOf("]"); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 0624dc890..72cc8be6f 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -190,9 +190,9 @@ private static boolean verifyAllClaims(Map claims, AccessPermissi .collect(Collectors.toList()); Map claimList = new HashMap<>(); for (String val: claimValues) { - Object claim = claims.get(val); + Claim claim = claims.get(val); if (claim != null) { - claimList.put(val, claim.toString()); + claimList.put(val, claim.asString()); } } return !claimValues.isEmpty() From 5bf8e5476d1bcc9a578c07dfbee2e03df9592014 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 08:12:44 +0100 Subject: [PATCH 052/108] extend persistence for query/submodels and query/concept-descriptions --- .../service/persistence/Persistence.java | 24 +++++++++++ .../persistence/file/PersistenceFile.java | 15 +++++-- .../memory/PersistenceInMemory.java | 41 +++++++++++++++++++ .../persistence/mongo/PersistenceMongo.java | 13 +++++- 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java index 3f724748c..a1f3e080b 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java @@ -260,6 +260,18 @@ public Page findAssetAdministrationShellsWithQuery(Ass */ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException; + /** + * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s by search criteria and query. + * + * @param criteria the search criteria + * @param modifier the modifier + * @param paging paging information + * @param query query to execute + * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s + * @throws PersistenceException if there was an error with the storage. + */ + public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException; + /** * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement}s by search criteria. @@ -286,6 +298,18 @@ public Page findSubmodelElements(SubmodelElementSearchCriteria */ public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException; + /** + * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s by search criteria and query. + * + * @param criteria the search criteria + * @param modifier the modifier + * @param paging paging information + * @param query query to execute + * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s + * @throws PersistenceException if there was an error with the storage. + */ + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException; + /** * Save an {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}. diff --git a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java index 9c8518d67..c703e756a 100644 --- a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java +++ b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java @@ -181,9 +181,8 @@ public Page findAssetAdministrationShells(AssetAdminis @Override public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, - Query query) - throws PersistenceException { - throw new PersistenceException("Query not supported."); + Query query) { + return persistence.findAssetAdministrationShellsWithQuery(criteria, modifier, paging, query); } @@ -192,6 +191,11 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi return persistence.findSubmodels(criteria, modifier, paging); } + @Override + public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + return persistence.findSubmodelsWithQuery(criteria, modifier, paging, query); + } + @Override public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws ResourceNotFoundException { @@ -204,6 +208,11 @@ public Page findConceptDescriptions(ConceptDescriptionSearch return persistence.findConceptDescriptions(criteria, modifier, paging); } + @Override + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + return persistence.findConceptDescriptionsWithQuery(criteria, modifier, paging, query); + } + @Override public void save(AssetAdministrationShell assetAdministrationShell) { diff --git a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java index de0688eeb..483544232 100644 --- a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java +++ b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java @@ -257,6 +257,28 @@ public Page findConceptDescriptions(ConceptDescriptionSearch return preparePagedResult(result, modifier, paging); } + @Override + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); + Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); + Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); + Stream result = environment.getConceptDescriptions().stream(); + if (criteria.isIdShortSet()) { + result = filterByIdShort(result, criteria.getIdShort()); + } + if (criteria.isIsCaseOfSet()) { + result = filterByIsCaseOf(result, criteria.getIsCaseOf()); + } + if (criteria.isDataSpecificationSet()) { + result = filterByDataSpecification(result, criteria.getDataSpecification()); + } + QueryEvaluator evaluator = new QueryEvaluator(environment); + if (query != null) { + result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); + } + return preparePagedResult(result, modifier, paging); + } + @Override public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws ResourceNotFoundException { @@ -313,6 +335,25 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi return preparePagedResult(result, modifier, paging); } + @Override + public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); + Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); + Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); + Stream result = environment.getSubmodels().stream(); + if (criteria.isIdShortSet()) { + result = filterByIdShort(result, criteria.getIdShort()); + } + if (criteria.isSemanticIdSet()) { + result = filterBySemanticId(result, criteria.getSemanticId()); + } + QueryEvaluator evaluator = new QueryEvaluator(environment); + if (query != null) { + result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); + } + return preparePagedResult(result, modifier, paging); + } + @Override public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModifier modifier) throws ResourceNotFoundException { diff --git a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java index 6933d776d..f6c588cf0 100644 --- a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java +++ b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java @@ -261,8 +261,7 @@ public Page findAssetAdministrationShells(AssetAdminis public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { - throw new PersistenceException("Query not supported in mongoDB."); - + throw new PersistenceException("Query not supported with mongoDB."); } @@ -281,6 +280,11 @@ public Page findConceptDescriptions(ConceptDescriptionSearch return preparePagedResult(cdCollection, filter, paging, modifier, ConceptDescription.class); } + @Override + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + throw new PersistenceException("Query not supported with mongoDB."); + } + @Override public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException { @@ -295,6 +299,11 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi return preparePagedResult(submodelCollection, filter, paging, modifier, Submodel.class); } + @Override + public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + throw new PersistenceException("Query not supported with mongoDB."); + } + @Override public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) From 6fc277698cf167be81587f37029590d4721ce911 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 08:28:43 +0100 Subject: [PATCH 053/108] add request handlers mappers requests responses for query/submodels query/concept-descriptions --- .../service/persistence/Persistence.java | 5 +- ...etAdministrationShellsRequestHandler.java} | 2 +- ...ueryConceptDescriptionsRequestHandler.java | 61 +++++++++++++ .../QuerySubmodelsRequestHandler.java | 71 +++++++++++++++ ...QueryConceptDescriptionsRequestMapper.java | 46 ++++++++++ .../QuerySubmodelsRequestMapper.java | 46 ++++++++++ .../QueryConceptDescriptionsRequest.java | 86 +++++++++++++++++++ .../QuerySubmodelsRequest.java | 86 +++++++++++++++++++ .../QueryConceptDescriptionsResponse.java | 43 ++++++++++ .../QuerySubmodelsResponse.java | 43 ++++++++++ .../persistence/file/PersistenceFile.java | 5 +- .../memory/PersistenceInMemory.java | 5 +- .../persistence/mongo/PersistenceMongo.java | 5 +- 13 files changed, 499 insertions(+), 5 deletions(-) rename core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/{QueryAllAssetAdministrationShellsRequestHandler.java => QueryAssetAdministrationShellsRequestHandler.java} (94%) create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java create mode 100644 core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/conceptdescription/QueryConceptDescriptionsResponse.java create mode 100644 model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/submodelrepository/QuerySubmodelsResponse.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java index a1f3e080b..5b8f8bd25 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java @@ -260,6 +260,7 @@ public Page findAssetAdministrationShellsWithQuery(Ass */ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException; + /** * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s by search criteria and query. * @@ -298,6 +299,7 @@ public Page findSubmodelElements(SubmodelElementSearchCriteria */ public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException; + /** * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s by search criteria and query. * @@ -308,7 +310,8 @@ public Page findSubmodelElements(SubmodelElementSearchCriteria * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s * @throws PersistenceException if there was an error with the storage. */ - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException; + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + throws PersistenceException; /** diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAllAssetAdministrationShellsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAssetAdministrationShellsRequestHandler.java similarity index 94% rename from core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAllAssetAdministrationShellsRequestHandler.java rename to core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAssetAdministrationShellsRequestHandler.java index ac05b6252..a1ef57b49 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAllAssetAdministrationShellsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAssetAdministrationShellsRequestHandler.java @@ -35,7 +35,7 @@ * {@link de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.QueryAssetAdministrationShellsResponse}. * Is responsible for communication with the persistence and sends the corresponding events to the message bus. */ -public class QueryAllAssetAdministrationShellsRequestHandler extends AbstractRequestHandler { +public class QueryAssetAdministrationShellsRequestHandler extends AbstractRequestHandler { @Override public QueryAssetAdministrationShellsResponse process(QueryAssetAdministrationShellsRequest request, RequestExecutionContext context) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java new file mode 100644 index 000000000..9988deeda --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java @@ -0,0 +1,61 @@ +/* + * 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.ilt.faaast.service.request.handler.conceptdescription; + +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.QueryConceptDescriptionsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.conceptdescription.QueryConceptDescriptionsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.ElementReadEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; +import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractRequestHandler; +import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; +import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; + + +/** + * Class to handle a + * {@link QueryConceptDescriptionsRequest} + * in the service and to send the corresponding response + * {@link QueryConceptDescriptionsResponse}. + * Is responsible for communication with the persistence and sends the corresponding events to the message bus. + */ +public class QueryConceptDescriptionsRequestHandler extends AbstractRequestHandler { + + @Override + public QueryConceptDescriptionsResponse process(QueryConceptDescriptionsRequest request, RequestExecutionContext context) + throws MessageBusException, PersistenceException { + Page page = context.getPersistence().findConceptDescriptionsWithQuery( + ConceptDescriptionSearchCriteria.NONE, + request.getOutputModifier(), + request.getPagingInfo(), + request.getQuery()); + if (!request.isInternal() && Objects.nonNull(page.getContent())) { + page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( + x -> context.getMessageBus().publish(ElementReadEventMessage.builder() + .element(x) + .value(x) + .build()))); + } + return QueryConceptDescriptionsResponse.builder() + .payload(page) + .success() + .build(); + } + +} diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java new file mode 100644 index 000000000..ea54a9c05 --- /dev/null +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java @@ -0,0 +1,71 @@ +/* + * 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.ilt.faaast.service.request.handler.submodelrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; +import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.QuerySubmodelsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.QuerySubmodelsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueMappingException; +import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.ElementReadEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.SubmodelSearchCriteria; +import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractRequestHandler; +import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.AasUtils; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; + + +/** + * Class to handle a + * {@link QuerySubmodelsRequest} + * in the service and to send the corresponding response + * {@link QuerySubmodelsResponse}. + * Is responsible for communication with the persistence and sends the corresponding events to the message bus. + */ +public class QuerySubmodelsRequestHandler extends AbstractRequestHandler { + + @Override + public QuerySubmodelsResponse process(QuerySubmodelsRequest request, RequestExecutionContext context) + throws MessageBusException, PersistenceException, ResourceNotAContainerElementException, ValueMappingException, ResourceNotFoundException, AssetConnectionException { + Page page = context.getPersistence().findSubmodelsWithQuery( + SubmodelSearchCriteria.NONE, + request.getOutputModifier(), + request.getPagingInfo(), + request.getQuery()); + if (Objects.nonNull(page.getContent())) { + for (Submodel submodel: page.getContent()) { + Reference reference = AasUtils.toReference(submodel); + syncWithAsset(reference, submodel.getSubmodelElements(), !request.isInternal(), context); + if (!request.isInternal()) { + context.getMessageBus().publish(ElementReadEventMessage.builder() + .element(reference) + .value(submodel) + .build()); + } + } + } + return QuerySubmodelsResponse.builder() + .payload(page) + .success() + .build(); + } + +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java new file mode 100644 index 000000000..f8750434f --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java @@ -0,0 +1,46 @@ +/* + * 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.ilt.faaast.service.endpoint.http.request.mapper.conceptdescription; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.QueryConceptDescriptionsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import java.util.Map; + + +/** + * class to map HTTP-POST-Request path: query/concept-descriptions. + */ +public class QueryConceptDescriptionsRequestMapper extends AbstractRequestMapper { + + private static final String PATTERN = "query/concept-descriptions"; + + public QueryConceptDescriptionsRequestMapper(ServiceContext serviceContext) { + super(serviceContext, HttpMethod.POST, PATTERN); + } + + + @Override + public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { + return QueryConceptDescriptionsRequest.builder() + .query(parseBody(httpRequest, Query.class)) + .build(); + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java new file mode 100644 index 000000000..59c398290 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java @@ -0,0 +1,46 @@ +/* + * 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.ilt.faaast.service.endpoint.http.request.mapper.submodelrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.QuerySubmodelsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import java.util.Map; + + +/** + * class to map HTTP-POST-Request path: query/submodels. + */ +public class QuerySubmodelsRequestMapper extends AbstractRequestMapper { + + private static final String PATTERN = "query/submodels"; + + public QuerySubmodelsRequestMapper(ServiceContext serviceContext) { + super(serviceContext, HttpMethod.POST, PATTERN); + } + + + @Override + public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { + return QuerySubmodelsRequest.builder() + .query(parseBody(httpRequest, Query.class)) + .build(); + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java new file mode 100644 index 000000000..93253d426 --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java @@ -0,0 +1,86 @@ +/* + * 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.ilt.faaast.service.model.api.request.conceptdescription; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifierAndPaging; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.conceptdescription.QueryConceptDescriptionsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import java.util.Objects; + + +/** + * Request class for QueryConceptDescriptions requests. + */ +public class QueryConceptDescriptionsRequest extends AbstractRequestWithModifierAndPaging { + + private Query query; + + public Query getQuery() { + return query; + } + + + public void setQuery(Query query) { + this.query = query; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QueryConceptDescriptionsRequest that = (QueryConceptDescriptionsRequest) o; + return super.equals(that) + && Objects.equals(query, that.query); + } + + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), query); + } + + + public static Builder builder() { + return new Builder(); + } + + public abstract static class AbstractBuilder> extends Request.AbstractBuilder { + + public B query(Query value) { + getBuildingInstance().setQuery(value); + return getSelf(); + } + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected QueryConceptDescriptionsRequest newBuildingInstance() { + return new QueryConceptDescriptionsRequest(); + } + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java new file mode 100644 index 000000000..b1737ab8e --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java @@ -0,0 +1,86 @@ +/* + * 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.ilt.faaast.service.model.api.request.submodelrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifierAndPaging; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.QuerySubmodelsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import java.util.Objects; + + +/** + * Request class for QuerySubmodels requests. + */ +public class QuerySubmodelsRequest extends AbstractRequestWithModifierAndPaging { + + private Query query; + + public Query getQuery() { + return query; + } + + + public void setQuery(Query query) { + this.query = query; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QuerySubmodelsRequest that = (QuerySubmodelsRequest) o; + return super.equals(that) + && Objects.equals(query, that.query); + } + + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), query); + } + + + public static Builder builder() { + return new Builder(); + } + + public abstract static class AbstractBuilder> extends Request.AbstractBuilder { + + public B query(Query value) { + getBuildingInstance().setQuery(value); + return getSelf(); + } + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected QuerySubmodelsRequest newBuildingInstance() { + return new QuerySubmodelsRequest(); + } + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/conceptdescription/QueryConceptDescriptionsResponse.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/conceptdescription/QueryConceptDescriptionsResponse.java new file mode 100644 index 000000000..11e5f0bba --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/conceptdescription/QueryConceptDescriptionsResponse.java @@ -0,0 +1,43 @@ +/* + * 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.ilt.faaast.service.model.api.response.conceptdescription; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.AbstractPagedResponse; +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; + + +/** + * Response class for QueryConceptDescriptions requests. + */ +public class QueryConceptDescriptionsResponse extends AbstractPagedResponse { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected QueryConceptDescriptionsResponse newBuildingInstance() { + return new QueryConceptDescriptionsResponse(); + } + } +} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/submodelrepository/QuerySubmodelsResponse.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/submodelrepository/QuerySubmodelsResponse.java new file mode 100644 index 000000000..e2ec5da5e --- /dev/null +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/response/submodelrepository/QuerySubmodelsResponse.java @@ -0,0 +1,43 @@ +/* + * 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.ilt.faaast.service.model.api.response.submodelrepository; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.AbstractPagedResponse; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; + + +/** + * Response class for QuerySubmodels requests. + */ +public class QuerySubmodelsResponse extends AbstractPagedResponse { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + + @Override + protected Builder getSelf() { + return this; + } + + + @Override + protected QuerySubmodelsResponse newBuildingInstance() { + return new QuerySubmodelsResponse(); + } + } +} diff --git a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java index c703e756a..dfdcd87c3 100644 --- a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java +++ b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java @@ -191,6 +191,7 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi return persistence.findSubmodels(criteria, modifier, paging); } + @Override public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { return persistence.findSubmodelsWithQuery(criteria, modifier, paging, query); @@ -208,8 +209,10 @@ public Page findConceptDescriptions(ConceptDescriptionSearch return persistence.findConceptDescriptions(criteria, modifier, paging); } + @Override - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + throws PersistenceException { return persistence.findConceptDescriptionsWithQuery(criteria, modifier, paging, query); } diff --git a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java index 483544232..173b935d2 100644 --- a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java +++ b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java @@ -257,8 +257,10 @@ public Page findConceptDescriptions(ConceptDescriptionSearch return preparePagedResult(result, modifier, paging); } + @Override - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + throws PersistenceException { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -335,6 +337,7 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi return preparePagedResult(result, modifier, paging); } + @Override public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); diff --git a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java index f6c588cf0..6e463a94c 100644 --- a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java +++ b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java @@ -280,8 +280,10 @@ public Page findConceptDescriptions(ConceptDescriptionSearch return preparePagedResult(cdCollection, filter, paging, modifier, ConceptDescription.class); } + @Override - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + throws PersistenceException { throw new PersistenceException("Query not supported with mongoDB."); } @@ -299,6 +301,7 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi return preparePagedResult(submodelCollection, filter, paging, modifier, Submodel.class); } + @Override public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { throw new PersistenceException("Query not supported with mongoDB."); From 19cc04a16532a335a560dcb6e6062a95bfe4322e Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 08:34:47 +0100 Subject: [PATCH 054/108] extend servicespecification for queries --- .../faaast/service/model/ServiceSpecificationProfile.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java index fad6ae3d4..4bbb918e1 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/ServiceSpecificationProfile.java @@ -52,6 +52,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetConceptDescriptionByIdRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.PostConceptDescriptionRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.PutConceptDescriptionByIdRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.QueryConceptDescriptionsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.description.GetSelfDescriptionRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.proprietary.ImportRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.proprietary.ResetRequest; @@ -88,6 +89,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.PatchSubmodelByIdRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.PostSubmodelRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.PutSubmodelByIdRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.QuerySubmodelsRequest; import java.util.Arrays; import java.util.List; @@ -297,6 +299,7 @@ public enum ServiceSpecificationProfile { GetAllSubmodelsBySemanticIdRequest.class, GetAllSubmodelsReferenceRequest.class, GetAllSubmodelsRequest.class, + QuerySubmodelsRequest.class, GetSubmodelByIdReferenceRequest.class, GetSubmodelByIdRequest.class, PatchSubmodelByIdRequest.class, @@ -356,6 +359,7 @@ public enum ServiceSpecificationProfile { GetAllSubmodelsBySemanticIdRequest.class, GetAllSubmodelsReferenceRequest.class, GetAllSubmodelsRequest.class, + QuerySubmodelsRequest.class, GetSubmodelByIdReferenceRequest.class, GetSubmodelByIdRequest.class, GetAllSubmodelElementsPathRequest.class, @@ -382,6 +386,7 @@ public enum ServiceSpecificationProfile { GetAllSubmodelsBySemanticIdRequest.class, GetAllSubmodelsReferenceRequest.class, GetAllSubmodelsRequest.class, + QuerySubmodelsRequest.class, GetSubmodelByIdReferenceRequest.class, GetSubmodelByIdRequest.class, PatchSubmodelByIdRequest.class, @@ -425,6 +430,7 @@ public enum ServiceSpecificationProfile { GetAllSubmodelsBySemanticIdRequest.class, GetAllSubmodelsReferenceRequest.class, GetAllSubmodelsRequest.class, + QuerySubmodelsRequest.class, GetSubmodelByIdReferenceRequest.class, GetSubmodelByIdRequest.class, GetAllSubmodelElementsPathRequest.class, @@ -450,6 +456,7 @@ public enum ServiceSpecificationProfile { GetAllConceptDescriptionsByIdShortRequest.class, GetAllConceptDescriptionsByIsCaseOfRequest.class, GetAllConceptDescriptionsRequest.class, + QueryConceptDescriptionsRequest.class, GetConceptDescriptionByIdRequest.class, PostConceptDescriptionRequest.class, PutConceptDescriptionByIdRequest.class, From 3dc65721bb355f19c9e008e6dd8ba7ef49f5bbff Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 09:14:16 +0100 Subject: [PATCH 055/108] handle values as string --- .../faaast/service/query/QueryEvaluator.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index 7bd218f8d..9a9b37b3c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -247,7 +247,7 @@ else if (!commonPrefix.equals(prefix)) { boolean all = true; for (String f: suffixes.keySet()) { String suffix = suffixes.get(f); - Object value = getPropertyFromObject(item, suffix); + String value = getPropertyFromObject(item, suffix); Object c = constants.get(f); if (!compareObjects(value, c, op)) { all = false; @@ -402,7 +402,7 @@ else if (attr.equals("id")) { } - private List getSmAttrValues(Submodel sm, String attr) { + private List getSmAttrValues(Submodel sm, String attr) { if (attr.equals("idShort")) { return Collections.singletonList(sm.getIdShort()); } @@ -435,7 +435,7 @@ else if (attr.startsWith("semanticId.keys")) { remaining = remaining.substring(end + 1); } List keys = ref.getKeys(); - List values = new ArrayList<>(); + List values = new ArrayList<>(); // set targets depending if any is true otherwise target index List targets = any || index == null ? keys : (index < keys.size() && index >= 0 ? Collections.singletonList(keys.get(index)) : Collections.emptyList()); for (Key key: targets) { @@ -453,7 +453,7 @@ else if (remaining.equals(".value")) { } - private List getSmeAttrValues(SubmodelElement sme, String attr) { + private List getSmeAttrValues(SubmodelElement sme, String attr) { if (attr.equals("idShort")) { return Collections.singletonList(sme.getIdShort()); } @@ -501,7 +501,7 @@ else if (attr.startsWith("semanticId.keys")) { remaining = remaining.substring(end + 1); } List keys = ref.getKeys(); - List values = new ArrayList<>(); + List values = new ArrayList<>(); List targets = any || index == null ? keys : (index < keys.size() && index >= 0 ? Collections.singletonList(keys.get(index)) : Collections.emptyList()); for (Key key: targets) { if (remaining.equals(".type")) { @@ -520,6 +520,7 @@ else if (remaining.equals(".value")) { private SubmodelElement getSubmodelElementByPath(Submodel sm, String path) { SubmodelElement current = null; + //TODO for (String token: path.split("\\.")) { current = sm.getSubmodelElements().stream() .filter(e -> e.getIdShort().equals(token)) @@ -529,7 +530,7 @@ private SubmodelElement getSubmodelElementByPath(Submodel sm, String path) { } - private Object getPropertyFromObject(Object item, String path) { + private String getPropertyFromObject(Object item, String path) { if (item instanceof SpecificAssetId) { if (path.equals(".name")) { return ((SpecificAssetId) item).getName(); @@ -538,7 +539,7 @@ else if (path.equals(".value")) { return ((SpecificAssetId) item).getValue(); } else if (path.startsWith(".externalSubjectId")) { - return ((SpecificAssetId) item).getExternalSubjectId(); + return ((SpecificAssetId) item).getExternalSubjectId().toString(); } } LOGGER.error("Unsupported property: " + path); @@ -546,7 +547,7 @@ else if (path.startsWith(".externalSubjectId")) { } - private Object getPropertyFromSuffix(SubmodelElement item, String suffix) { + private String getPropertyFromSuffix(SubmodelElement item, String suffix) { // For suffixes like .ProductClassId#value int hashPos = suffix.indexOf("#"); if (hashPos == -1) { @@ -558,7 +559,7 @@ private Object getPropertyFromSuffix(SubmodelElement item, String suffix) { SubmodelElement subElem = getSubmodelElementByPathForItem(item, subPath); if (subElem == null) return null; - List values = getSmeAttrValues(subElem, attr); + List values = getSmeAttrValues(subElem, attr); return values.isEmpty() ? null : values.get(0); } From 2de4a52ed61d4bcf33a4593ef85f7b5a4294e5cf Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 09:16:03 +0100 Subject: [PATCH 056/108] remove comments --- .../iosb/ilt/faaast/service/query/QueryEvaluator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index 9a9b37b3c..e1d4a3efd 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -520,7 +520,6 @@ else if (remaining.equals(".value")) { private SubmodelElement getSubmodelElementByPath(Submodel sm, String path) { SubmodelElement current = null; - //TODO for (String token: path.split("\\.")) { current = sm.getSubmodelElements().stream() .filter(e -> e.getIdShort().equals(token)) @@ -565,7 +564,6 @@ private String getPropertyFromSuffix(SubmodelElement item, String suffix) { private SubmodelElement getSubmodelElementByPathForItem(SubmodelElement item, String path) { - // Similar to getSubmodelElementByPath, but start from item SubmodelElement current = null; Submodel container = null; if (path.startsWith(".")) From eb2c0e46cc3d87afbecea87f88bd41e9b24ba79c Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 12:52:39 +0100 Subject: [PATCH 057/108] add evaluator test --- .../faaast/service/query/QueryEvaluator.java | 275 +++++---- .../service/query/QueryEvaluatorTest.java | 560 ++++++++++++++++++ 2 files changed, 735 insertions(+), 100 deletions(-) create mode 100644 core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index e1d4a3efd..7fbac8bc5 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -19,10 +19,9 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.StringValue; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -37,6 +36,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -199,40 +199,96 @@ private List evaluateStringValue(StringValue sv, Identifiable identifiab return null; } + private static class Condition { + String suffix; + String op; + List rightVals; + + Condition(String suffix, String op, List rightVals) { + this.suffix = suffix; + this.op = op; + this.rightVals = rightVals; + } + } private boolean evaluateMatch(List matches, Identifiable identifiable) { String commonPrefix = null; - Map suffixes = new HashMap<>(); - Map constants = new HashMap<>(); - String op = "eq"; // Assume eq for all + List itemConditions = new ArrayList<>(); + for (MatchExpression m: matches) { - if (m.get$eq() == null || m.get$eq().isEmpty()) { - LOGGER.error("Only $eq supported in match for now"); + String op; + List args = null; + Value left = null; + Value right = null; + if (m.get$eq() != null && !m.get$eq().isEmpty()) { + op = "eq"; + args = m.get$eq(); + } + else if (m.get$ne() != null && !m.get$ne().isEmpty()) { + op = "ne"; + args = m.get$ne(); + } + else if (m.get$gt() != null && !m.get$gt().isEmpty()) { + op = "gt"; + args = m.get$gt(); + } + else if (m.get$ge() != null && !m.get$ge().isEmpty()) { + op = "ge"; + args = m.get$ge(); + } + else if (m.get$lt() != null && !m.get$lt().isEmpty()) { + op = "lt"; + args = m.get$lt(); + } + else if (m.get$le() != null && !m.get$le().isEmpty()) { + op = "le"; + args = m.get$le(); + } + else { + op = null; + LOGGER.error("Unsupported operator in match"); return false; } - Value left = m.get$eq().get(0); - Value right = m.get$eq().get(1); + left = args.get(0); + right = args.get(1); if (left.get$field() == null) { LOGGER.error("Left not field in match"); return false; } String f = left.get$field(); - Object c = evaluateValue(right, identifiable).get(0); + List rightVals = evaluateValue(right, identifiable); int pos = f.indexOf("[]"); if (pos == -1) { - LOGGER.error("No [] in field for match"); - return false; + if (f.startsWith("$sme#")) { + // Treat as top-level SME list condition + String prefix = "$sme"; + if (commonPrefix != null && !commonPrefix.equals(prefix)) { + LOGGER.error("Non-common prefix in match"); + return false; + } + commonPrefix = prefix; + String suffix = f.substring(5); // "#attr" + itemConditions.add(new Condition(suffix, op, rightVals)); + } + else { + // Parent condition + List leftVals = evaluateValue(left, identifiable); + boolean condMatch = leftVals.stream().anyMatch(l -> rightVals.stream().anyMatch(r -> compareObjects(l, r, op))); + if (!condMatch) { + return false; + } + } } - String prefix = f.substring(0, pos); - if (commonPrefix == null) { + else { + String prefix = f.substring(0, pos); + if (commonPrefix != null && !commonPrefix.equals(prefix)) { + LOGGER.error("Non-common prefix in match"); + return false; + } commonPrefix = prefix; + String suffix = f.substring(pos + 2); + itemConditions.add(new Condition(suffix, op, rightVals)); } - else if (!commonPrefix.equals(prefix)) { - LOGGER.error("Non-common prefix in match"); - } - String suffix = f.substring(pos + 2); - suffixes.put(f, suffix); - constants.put(f, c); } if (commonPrefix == null) { return true; @@ -245,11 +301,11 @@ else if (!commonPrefix.equals(prefix)) { List list = aas.getAssetInformation().getSpecificAssetIds(); for (SpecificAssetId item: list) { boolean all = true; - for (String f: suffixes.keySet()) { - String suffix = suffixes.get(f); - String value = getPropertyFromObject(item, suffix); - Object c = constants.get(f); - if (!compareObjects(value, c, op)) { + for (Condition cond: itemConditions) { + String valStr = getPropertyFromObject(item, cond.suffix); + List leftVals = valStr == null ? Collections.emptyList() : Collections.singletonList(valStr); + boolean condMatch = leftVals.stream().anyMatch(l -> cond.rightVals.stream().anyMatch(r -> compareObjects(l, r, cond.op))); + if (!condMatch) { all = false; break; } @@ -260,30 +316,52 @@ else if (!commonPrefix.equals(prefix)) { } return false; } - else if (commonPrefix.startsWith("$sme.")) { - // Basic support for $sme paths with [] - List sms = environment.getSubmodels(); - for (Submodel sm: sms) { - String path = commonPrefix.substring(5); // e.g., "ProductClassifications" - SubmodelElement listElem = getSubmodelElementByPath(sm, path); - if (listElem == null || !(listElem instanceof SubmodelElementList)) - continue; - SubmodelElementList smeList = (SubmodelElementList) listElem; - for (SubmodelElement item: smeList.getValue()) { - boolean all = true; - for (String f: suffixes.keySet()) { - String suffix = suffixes.get(f); - Object value = getPropertyFromSuffix(item, suffix); - Object c = constants.get(f); - if (!compareObjects(value, c, op)) { - all = false; - break; - } + else if (commonPrefix.equals("$sme")) { + if (!(identifiable instanceof Submodel)) { + return false; + } + Submodel sm = (Submodel) identifiable; + List list = sm.getSubmodelElements(); + for (SubmodelElement item: list) { + boolean all = true; + for (Condition cond: itemConditions) { + List leftVals = getPropertyFromSuffix(item, cond.suffix); + boolean condMatch = leftVals.stream().anyMatch(l -> cond.rightVals.stream().anyMatch(r -> compareObjects(l, r, cond.op))); + if (!condMatch) { + all = false; + break; } - if (all) { - return true; + } + if (all) { + return true; + } + } + return false; + } + else if (commonPrefix.startsWith("$sme.")) { + if (!(identifiable instanceof Submodel)) { + return false; + } + Submodel sm = (Submodel) identifiable; + String path = commonPrefix.substring(5); + SubmodelElement listElem = getSubmodelElementByPath(sm, path); + if (listElem == null || !(listElem instanceof SubmodelElementList)) { + return false; + } + SubmodelElementList smeList = (SubmodelElementList) listElem; + for (SubmodelElement item: smeList.getValue()) { + boolean all = true; + for (Condition cond: itemConditions) { + List leftVals = getPropertyFromSuffix(item, cond.suffix); + boolean condMatch = leftVals.stream().anyMatch(l -> cond.rightVals.stream().anyMatch(r -> compareObjects(l, r, cond.op))); + if (!condMatch) { + all = false; + break; } } + if (all) { + return true; + } } return false; } @@ -342,47 +420,42 @@ else if (attr.startsWith("assetInformation.specificAssetIds")) { LOGGER.error("Unsupported AAS attribute: " + attr); } else if (field.startsWith("$sm#")) { + if (!(identifiable instanceof Submodel)) + return Collections.emptyList(); + Submodel sm = (Submodel) identifiable; String attr = field.substring(4); - List sms = environment.getSubmodels(); - List values = new ArrayList<>(); - for (Submodel sm: sms) { - values.addAll(getSmAttrValues(sm, attr)); - } - return values; + return new ArrayList<>(getSmAttrValues(sm, attr)); } else if (field.startsWith("$sme")) { + if (!(identifiable instanceof Submodel)) + return Collections.emptyList(); + Submodel sm = (Submodel) identifiable; String pathPart = ""; String attr = ""; if (field.contains(".")) { - int hashPos = field.indexOf("#"); + int hashPos = field.indexOf("#", field.indexOf(".")); pathPart = field.substring(field.indexOf(".") + 1, hashPos); attr = field.substring(hashPos + 1); } else { attr = field.substring(5); } + List values = new ArrayList<>(); if (!pathPart.isEmpty()) { // Basic path support, no [] for now - List sms = environment.getSubmodels(); - List values = new ArrayList<>(); - for (Submodel sm: sms) { - SubmodelElement sme = getSubmodelElementByPath(sm, pathPart); - if (sme != null) { - values.addAll(getSmeAttrValues(sme, attr)); - } + SubmodelElement sme = getSubmodelElementByPath(sm, pathPart); + if (sme != null) { + values.addAll(getSmeAttrValues(sme, attr)); } - return values; } else { // Recursive all SME - Submodel sm = (Submodel) identifiable; List smes = sm.getSubmodelElements(); - List values = new ArrayList<>(); for (SubmodelElement sme: smes) { values.addAll(getSmeAttrValues(sme, attr)); } - return values; } + return values; } else if (field.startsWith("$cd#")) { if (!(identifiable instanceof ConceptDescription)) @@ -546,44 +619,46 @@ else if (path.startsWith(".externalSubjectId")) { } - private String getPropertyFromSuffix(SubmodelElement item, String suffix) { - // For suffixes like .ProductClassId#value + private List getPropertyFromSuffix(SubmodelElement item, String suffix) { + if (suffix.startsWith(".")) { + suffix = suffix.substring(1); + } + String subPath = ""; + String attr = suffix; int hashPos = suffix.indexOf("#"); - if (hashPos == -1) { - LOGGER.error("No # in suffix for match"); - return null; + if (hashPos != -1) { + subPath = suffix.substring(0, hashPos); + attr = suffix.substring(hashPos + 1); } - String subPath = suffix.substring(0, hashPos); - String attr = suffix.substring(hashPos + 1); SubmodelElement subElem = getSubmodelElementByPathForItem(item, subPath); - if (subElem == null) - return null; + if (subElem == null) { + return Collections.emptyList(); + } List values = getSmeAttrValues(subElem, attr); - return values.isEmpty() ? null : values.get(0); + return new ArrayList<>(values); } private SubmodelElement getSubmodelElementByPathForItem(SubmodelElement item, String path) { - SubmodelElement current = null; - Submodel container = null; - if (path.startsWith(".")) - path = path.substring(1); - for (String token: path.split("\\.")) { - if (container == null) { - if (item.getIdShort().equals(token)) { - current = item; - } - else { - return null; - } - } - else { - current = container.getSubmodelElements().stream() - .filter(e -> e.getIdShort().equals(token)) - .findFirst().orElse(null); + SubmodelElement current = item; + if (path.isEmpty()) { + return current; + } + List tokens = Arrays.asList(path.split("\\.")); + if (!tokens.isEmpty() && tokens.get(0).equals(current.getIdShort())) { + tokens = tokens.subList(1, tokens.size()); + } + for (String token: tokens) { + if (!(current instanceof SubmodelElementCollection)) { + return null; } - if (current == null) + SubmodelElementCollection container = (SubmodelElementCollection) current; + current = container.getValue().stream() + .filter(e -> e.getIdShort().equals(token)) + .findFirst().orElse(null); + if (current == null) { return null; + } } return current; } @@ -619,10 +694,13 @@ private boolean compareObjects(Object a, Object b, String op) { } } catch (NumberFormatException e) {} - boolean bool1, bool2; - try { - bool1 = a instanceof Boolean ? (Boolean) a : Boolean.parseBoolean(a.toString()); - bool2 = b instanceof Boolean ? (Boolean) b : Boolean.parseBoolean(b.toString()); + String sa = a.toString(); + String sb = b.toString(); + boolean isBoolA = sa.trim().equalsIgnoreCase("true") || sa.trim().equalsIgnoreCase("false"); + boolean isBoolB = sb.trim().equalsIgnoreCase("true") || sb.trim().equalsIgnoreCase("false"); + if (isBoolA && isBoolB) { + boolean bool1 = Boolean.parseBoolean(sa); + boolean bool2 = Boolean.parseBoolean(sb); switch (op) { case "eq": return bool1 == bool2; @@ -632,10 +710,7 @@ private boolean compareObjects(Object a, Object b, String op) { return false; } } - catch (Exception e) {} - String s1 = a.toString(); - String s2 = b.toString(); - int cmp = s1.compareTo(s2); + int cmp = sa.compareTo(sb); switch (op) { case "eq": return cmp == 0; diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java new file mode 100644 index 000000000..f46108c8c --- /dev/null +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java @@ -0,0 +1,560 @@ +/* + * 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.ilt.faaast.service.query; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetKind; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultMultiLanguageProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSpecificAssetId; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; +import org.junit.Test; + + +/** + * Unit tests for {@link QueryEvaluator}. + */ +public class QueryEvaluatorTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private Environment createTestEnvironmentForSimpleEq(boolean matching) { + Submodel submodel = new DefaultSubmodel.Builder() + .id("https://example.com/submodel/1") + .idShort("TestSubmodel") + .build(); + + AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder() + .id("https://example.com/aas/1") + .idShort(matching ? "TestType" : "NonMatching") + .assetInformation(new DefaultAssetInformation.Builder() + .assetKind(AssetKind.INSTANCE) + .assetType("TestType") + .build()) + .build(); + + return new DefaultEnvironment.Builder() + .assetAdministrationShells(aas) + .submodels(submodel) + .build(); + } + + + private Environment createTestEnvironmentForDocumentsMatch(boolean matching) { + List documentsItems = new ArrayList<>(); + SubmodelElementCollection docItem = new DefaultSubmodelElementCollection.Builder() + .idShort("Doc1") + .value(new DefaultSubmodelElementCollection.Builder() + .idShort("DocumentClassification") + .value(new DefaultProperty.Builder() + .idShort("Class") + .value(matching ? "03-01" : "NonMatching") + .valueType(DataTypeDefXsd.STRING) + .build()) + .build()) + .value(new DefaultSubmodelElementCollection.Builder() + .idShort("DocumentVersion") + .value(new DefaultMultiLanguageProperty.Builder() + .idShort("SMLLanguages") + .value(new DefaultLangStringTextType.Builder() + .language("nl") + .text("Dutch text") + .build()) + .build()) + .build()) + .build(); + documentsItems.add(docItem); + + SubmodelElementList documents = new DefaultSubmodelElementList.Builder() + .idShort("Documents") + .value(documentsItems) + .build(); + + Submodel submodel = new DefaultSubmodel.Builder() + .id("https://example.com/submodel/2") + .idShort("TestSubmodel") + .submodelElements(documents) + .build(); + + AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder() + .id("https://example.com/aas/2") + .idShort("TestAAS") + .assetInformation(new DefaultAssetInformation.Builder() + .assetKind(AssetKind.INSTANCE) + .build()) + .build(); + + return new DefaultEnvironment.Builder() + .assetAdministrationShells(aas) + .submodels(submodel) + .build(); + } + + + private Environment createTestEnvironmentForAndMatch(boolean matching) { + List productClassItems = new ArrayList<>(); + Property productClassId = new DefaultProperty.Builder() + .idShort("ProductClassId") + .value(matching ? "27-37-09-05" : "NonMatching") + .valueType(DataTypeDefXsd.STRING) + .build(); + productClassItems.add(productClassId); + + SubmodelElementList productClassifications = new DefaultSubmodelElementList.Builder() + .idShort("ProductClassifications") + .value(productClassItems) + .build(); + + Property someProperty = new DefaultProperty.Builder() + .idShort("SomeProperty") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("0173-1#02-BAF016#006") + .build()) + .build()) + .value(matching ? "50" : "150") // For < 100 + .valueType(DataTypeDefXsd.INT) + .build(); + + Submodel submodel = new DefaultSubmodel.Builder() + .id("https://example.com/submodel/3") + .idShort("TechnicalData") + .submodelElements(productClassifications) + .submodelElements(someProperty) + .build(); + + AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder() + .id("https://example.com/aas/3") + .idShort("TestAAS") + .assetInformation(new DefaultAssetInformation.Builder() + .assetKind(AssetKind.INSTANCE) + .build()) + .build(); + + return new DefaultEnvironment.Builder() + .assetAdministrationShells(aas) + .submodels(submodel) + .build(); + } + + + private Environment createTestEnvironmentForOrMatch(boolean matching) { + List specificAssetIds = new ArrayList<>(); + specificAssetIds.add(new DefaultSpecificAssetId.Builder() + .name("supplierId") + .value(matching ? "aas-1" : "NonMatching") + .build()); + specificAssetIds.add(new DefaultSpecificAssetId.Builder() + .name("customerId") + .value(matching ? "aas-2" : "NonMatching") + .build()); + + AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder() + .id("https://example.com/aas/4") + .idShort("TestAAS") + .assetInformation(new DefaultAssetInformation.Builder() + .assetKind(AssetKind.INSTANCE) + .specificAssetIds(specificAssetIds) + .build()) + .build(); + + Submodel submodel = new DefaultSubmodel.Builder() + .id("https://example.com/submodel/4") + .idShort("TestSubmodel") + .build(); + + return new DefaultEnvironment.Builder() + .assetAdministrationShells(aas) + .submodels(submodel) + .build(); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void simpleEq_withMatchingFields() throws Exception { + String json = """ + { + "$condition": { + "$eq": [ + { "$field": "$aas#idShort" }, + { + "$field": + "$aas#assetInformation.assetType" + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForSimpleEq(true); + QueryEvaluator evaluator = new QueryEvaluator(env); + AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); + boolean result = evaluator.matches(query.get$condition(), aas); + assertTrue(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void simpleEq_withNonMatchingFields() throws Exception { + String json = """ + { + "$condition": { + "$eq": [ + { "$field": "$aas#idShort" }, + { + "$field": + "$aas#assetInformation.assetType" + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForSimpleEq(false); + QueryEvaluator evaluator = new QueryEvaluator(env); + AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); + boolean result = evaluator.matches(query.get$condition(), aas); + assertFalse(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void documentsMatch_withMatchingValues() throws Exception { + String json = """ + { + "$condition": { + "$match": [ + { "$eq": [ + { "$field": "$sme.Documents[].DocumentClassification.Class#value" }, + { "$strVal": "03-01" } + ] + }, + { "$eq": [ + { "$field": "$sme.Documents[].DocumentVersion.SMLLanguages#language" }, + { "$strVal": "nl" } + ] + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForDocumentsMatch(true); + QueryEvaluator evaluator = new QueryEvaluator(env); + Submodel submodel = env.getSubmodels().get(0); + boolean result = evaluator.matches(query.get$condition(), submodel); + assertTrue(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void documentsMatch_withNonMatchingValues() throws Exception { + String json = """ + { + "$condition": { + "$match": [ + { "$eq": [ + { "$field": "$sme.Documents[].DocumentClassification.Class#value" }, + { "$strVal": "03-01" } + ] + }, + { "$eq": [ + { "$field": "$sme.Documents[].DocumentVersion.SMLLanguages#language" }, + { "$strVal": "nl" } + ] + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForDocumentsMatch(false); + QueryEvaluator evaluator = new QueryEvaluator(env); + Submodel submodel = env.getSubmodels().get(0); + boolean result = evaluator.matches(query.get$condition(), submodel); + assertFalse(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void andMatch_withMatchingConditions() throws Exception { + String json = """ + { + "$condition": { + "$and": [ + { "$match": [ + { "$eq": [ + { "$field": "$sm#idShort" }, + { "$strVal": "TechnicalData" } + ] + }, + { "$eq": [ + { + "$field": + "$sme.ProductClassifications[].ProductClassId#value" + }, + { "$strVal": "27-37-09-05" } + ] + } + ] + }, + { "$match": [ + { + "$eq": [ + { "$field": "$sm#idShort" }, + { "$strVal": "TechnicalData" } + ] + }, + { + "$eq": [ + { "$field": "$sme#semanticId" }, + { "$strVal": "0173-1#02-BAF016#006" } + ] + }, + { + "$lt": [ + { "$field": "$sme#value" }, + { "$numVal": 100 } + ] + } + ] + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForAndMatch(true); + QueryEvaluator evaluator = new QueryEvaluator(env); + Submodel submodel = env.getSubmodels().get(0); + boolean result = evaluator.matches(query.get$condition(), submodel); + assertTrue(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void andMatch_withNonMatchingConditions() throws Exception { + String json = """ + { + "$condition": { + "$and": [ + { "$match": [ + { "$eq": [ + { "$field": "$sm#idShort" }, + { "$strVal": "TechnicalData" } + ] + }, + { "$eq": [ + { + "$field": + "$sme.ProductClassifications[].ProductClassId#value" + }, + { "$strVal": "27-37-09-05" } + ] + } + ] + }, + { "$match": [ + { + "$eq": [ + { "$field": "$sm#idShort" }, + { "$strVal": "TechnicalData" } + ] + }, + { + "$eq": [ + { "$field": "$sme#semanticId" }, + { "$strVal": "0173-1#02-BAF016#006" } + ] + }, + { + "$lt": [ + { "$field": "$sme#value" }, + { "$numVal": 100 } + ] + } + ] + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForAndMatch(false); + QueryEvaluator evaluator = new QueryEvaluator(env); + Submodel submodel = env.getSubmodels().get(0); + boolean result = evaluator.matches(query.get$condition(), submodel); + assertFalse(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void orMatch_withMatchingSpecificAssetIds() throws Exception { + String json = """ + { + "$condition": { + "$or": [ + { + "$match": [ + { "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].name" }, + { "$strVal": "supplierId" } + ] + }, + { "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].value" }, + { "$strVal": "aas-1" } + ] + } + ] + }, + { + "$match": [ + { + "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].name" }, + { "$strVal": "customerId" } + ] + }, + { + "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].value" }, + { "$strVal": "aas-2" } + ] + } + ] + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForOrMatch(true); + QueryEvaluator evaluator = new QueryEvaluator(env); + AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); + boolean result = evaluator.matches(query.get$condition(), aas); + assertTrue(result); + } + + + /* ------------------------------------------------------------------ */ + @Test + public void orMatch_withNonMatchingSpecificAssetIds() throws Exception { + String json = """ + { + "$condition": { + "$or": [ + { + "$match": [ + { "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].name" }, + { "$strVal": "supplierId" } + ] + }, + { "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].value" }, + { "$strVal": "aas-1" } + ] + } + ] + }, + { + "$match": [ + { + "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].name" }, + { "$strVal": "customerId" } + ] + }, + { + "$eq": [ + { "$field": "$aas#assetInformation.specificAssetIds[].value" }, + { "$strVal": "aas-2" } + ] + } + ] + } + ] + } + } + """; + + Query query = MAPPER.readValue( + json, new TypeReference<>() {}); + + Environment env = createTestEnvironmentForOrMatch(false); + QueryEvaluator evaluator = new QueryEvaluator(env); + AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); + boolean result = evaluator.matches(query.get$condition(), aas); + assertFalse(result); + } + +} From 6180cfded1bae6fb393438b43667e74b0ac3aaf8 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Wed, 29 Oct 2025 13:12:16 +0100 Subject: [PATCH 058/108] spotless --- .../java/de/fraunhofer/iosb/ilt/faaast/service/Service.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 2244be6eb..48dd1d65d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -36,10 +36,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.SubscriptionId; -import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.SubscriptionInfo; -import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementCreateEventMessage; -import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementDeleteEventMessage; -import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementUpdateEventMessage; import de.fraunhofer.iosb.ilt.faaast.service.model.serialization.DataFormat; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; @@ -65,9 +61,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.Operation; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; -import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; import org.slf4j.Logger; From 8c4d8d038b0135e8622e765e1843100f487a7af4 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 10:37:58 +0100 Subject: [PATCH 059/108] refactoring --- .../faaast/service/query/QueryEvaluator.java | 998 ++++++++++-------- 1 file changed, 555 insertions(+), 443 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index 7fbac8bc5..ec2e42e8d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -47,604 +48,654 @@ */ public class QueryEvaluator { - private final Environment environment; private static final Logger LOGGER = LoggerFactory.getLogger(QueryEvaluator.class); + private static final String PREFIX_AAS = "$aas#"; + private static final String PREFIX_SM = "$sm#"; + private static final String PREFIX_SME = "$sme"; + private static final String PREFIX_CD = "$cd#"; + private final Environment environment; public QueryEvaluator(Environment environment) { this.environment = environment; } + private enum Operator { + EQ, + NE, + GT, + GE, + LT, + LE, + CONTAINS, + STARTS_WITH, + ENDS_WITH, + REGEX; + + boolean isStringOp() { + return this == CONTAINS || this == STARTS_WITH || this == ENDS_WITH || this == REGEX; + } + } /** * Used to decide whether to filter out the Identifiable. * - * @param expr LogicalExpression of the query - * @param identifiable like AAS, Submodel or Concept-Description - * @return true if expression matches + * @param expr logical expression (tree) + * @param identifiable AAS | Submodel | ConceptDescription + * @return true if expression matches the identifiable + * */ public boolean matches(LogicalExpression expr, Identifiable identifiable) { - if (!expr.get$and().isEmpty()) { + if (expr == null || identifiable == null) { + return false; + } + if (expr.get$boolean() != null) { + return expr.get$boolean(); + } + + if (expr.get$and() != null && !expr.get$and().isEmpty()) { return expr.get$and().stream().allMatch(e -> matches(e, identifiable)); } - if (!expr.get$or().isEmpty()) { + if (expr.get$or() != null && !expr.get$or().isEmpty()) { return expr.get$or().stream().anyMatch(e -> matches(e, identifiable)); } if (expr.get$not() != null) { return !matches(expr.get$not(), identifiable); } - if (!expr.get$match().isEmpty()) { + + // $match + if (expr.get$match() != null && !expr.get$match().isEmpty()) { return evaluateMatch(expr.get$match(), identifiable); } - if (!expr.get$eq().isEmpty()) { - List args = expr.get$eq(); - List left = evaluateValue(args.get(0), identifiable); - List right = evaluateValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "eq"))); - } - if (!expr.get$ne().isEmpty()) { - List args = expr.get$ne(); - List left = evaluateValue(args.get(0), identifiable); - List right = evaluateValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "ne"))); - } - if (!expr.get$gt().isEmpty()) { - List args = expr.get$gt(); - List left = evaluateValue(args.get(0), identifiable); - List right = evaluateValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "gt"))); - } - if (!expr.get$ge().isEmpty()) { - List args = expr.get$ge(); - List left = evaluateValue(args.get(0), identifiable); - List right = evaluateValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "ge"))); - } - if (!expr.get$lt().isEmpty()) { - List args = expr.get$lt(); - List left = evaluateValue(args.get(0), identifiable); - List right = evaluateValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "lt"))); - } - if (!expr.get$le().isEmpty()) { - List args = expr.get$le(); - List left = evaluateValue(args.get(0), identifiable); - List right = evaluateValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> compareObjects(l, r, "le"))); - } - if (!expr.get$contains().isEmpty()) { - List args = expr.get$contains(); - List left = evaluateStringValue(args.get(0), identifiable); - List right = evaluateStringValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "contains"))); - } - if (!expr.get$startsWith().isEmpty()) { - List args = expr.get$startsWith(); - List left = evaluateStringValue(args.get(0), identifiable); - List right = evaluateStringValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "starts-with"))); - } - if (!expr.get$endsWith().isEmpty()) { - List args = expr.get$endsWith(); - List left = evaluateStringValue(args.get(0), identifiable); - List right = evaluateStringValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "ends-with"))); - } - if (!expr.get$regex().isEmpty()) { - List args = expr.get$regex(); - List left = evaluateStringValue(args.get(0), identifiable); - List right = evaluateStringValue(args.get(1), identifiable); - return left.stream().anyMatch(l -> right.stream().anyMatch(r -> stringCompareObjects(l, r, "regex"))); + + // Binary + if (expr.get$eq() != null && !expr.get$eq().isEmpty()) + return evaluateBinary(expr.get$eq(), identifiable, Operator.EQ); + if (expr.get$ne() != null && !expr.get$ne().isEmpty()) + return evaluateBinary(expr.get$ne(), identifiable, Operator.NE); + if (expr.get$gt() != null && !expr.get$gt().isEmpty()) + return evaluateBinary(expr.get$gt(), identifiable, Operator.GT); + if (expr.get$ge() != null && !expr.get$ge().isEmpty()) + return evaluateBinary(expr.get$ge(), identifiable, Operator.GE); + if (expr.get$lt() != null && !expr.get$lt().isEmpty()) + return evaluateBinary(expr.get$lt(), identifiable, Operator.LT); + if (expr.get$le() != null && !expr.get$le().isEmpty()) + return evaluateBinary(expr.get$le(), identifiable, Operator.LE); + + // String binary operators + if (expr.get$contains() != null && !expr.get$contains().isEmpty()) + return evaluateStringBinary(expr.get$contains(), identifiable, Operator.CONTAINS); + if (expr.get$startsWith() != null && !expr.get$startsWith().isEmpty()) + return evaluateStringBinary(expr.get$startsWith(), identifiable, Operator.STARTS_WITH); + if (expr.get$endsWith() != null && !expr.get$endsWith().isEmpty()) + return evaluateStringBinary(expr.get$endsWith(), identifiable, Operator.ENDS_WITH); + if (expr.get$regex() != null && !expr.get$regex().isEmpty()) + return evaluateStringBinary(expr.get$regex(), identifiable, Operator.REGEX); + + return false; + } + + + private boolean evaluateBinary(List args, Identifiable identifiable, Operator op) { + if (args.size() < 2) { + LOGGER.error("Operator {} requires two arguments", op); + return false; } - if (expr.get$boolean() != null) { - return expr.get$boolean(); + List left = evaluateValue(args.get(0), identifiable); + List right = evaluateValue(args.get(1), identifiable); + return anyPairMatches(left, right, op); + } + + + private boolean evaluateStringBinary(List args, Identifiable identifiable, Operator op) { + if (args.size() < 2) { + LOGGER.error("String operator {} requires two arguments", op); + return false; + } + List left = evaluateStringValue(args.get(0), identifiable); + List right = evaluateStringValue(args.get(1), identifiable); + return anyPairMatches(left, right, op); + } + + + private boolean anyPairMatches(List left, List right, Operator op) { + if (left == null || right == null) { + return false; + } + for (Object l: left) { + for (Object r: right) { + if (compare(l, r, op)) { + return true; + } + } } return false; } private List evaluateValue(Value v, Identifiable identifiable) { + if (v == null) + return Collections.emptyList(); + if (v.get$field() != null) { - return getFieldValues(v.get$field(), identifiable); + return nonNull(getFieldValues(v.get$field(), identifiable)); } - if (v.get$strVal() != null) { + if (v.get$strVal() != null) return Collections.singletonList(v.get$strVal()); - } - if (v.get$numVal() != null) { + if (v.get$numVal() != null) return Collections.singletonList(v.get$numVal()); - } - if (v.get$hexVal() != null) { + if (v.get$hexVal() != null) return Collections.singletonList(v.get$hexVal()); - } - if (v.get$dateTimeVal() != null) { + if (v.get$dateTimeVal() != null) return Collections.singletonList(v.get$dateTimeVal()); - } - if (v.get$timeVal() != null) { + if (v.get$timeVal() != null) return Collections.singletonList(v.get$timeVal()); - } - if (v.get$boolean() != null) { + if (v.get$boolean() != null) return Collections.singletonList(v.get$boolean()); - } if (v.get$strCast() != null) { - List inner = evaluateValue(v.get$strCast(), identifiable); - return inner.stream().map(Object::toString).collect(Collectors.toList()); + return evaluateValue(v.get$strCast(), identifiable).stream().map(String::valueOf).collect(Collectors.toList()); } if (v.get$numCast() != null) { - List inner = evaluateValue(v.get$numCast(), identifiable); - return inner.stream().map(i -> { - try { - return Double.parseDouble(i.toString()); - } - catch (NumberFormatException e) { - return 0.0; - } - }).collect(Collectors.toList()); + return evaluateValue(v.get$numCast(), identifiable).stream().map(String::valueOf).map(this::parseDoubleOrNull).filter(Objects::nonNull).collect(Collectors.toList()); } - return null; + return Collections.emptyList(); } private List evaluateStringValue(StringValue sv, Identifiable identifiable) { + if (sv == null) + return Collections.emptyList(); if (sv.get$field() != null) { - return getFieldValues(sv.get$field(), identifiable); + return nonNull(getFieldValues(sv.get$field(), identifiable)); } if (sv.get$strVal() != null) { return Collections.singletonList(sv.get$strVal()); } if (sv.get$strCast() != null) { - List inner = evaluateValue(sv.get$strCast(), identifiable); - return inner.stream().map(Object::toString).collect(Collectors.toList()); + return evaluateValue(sv.get$strCast(), identifiable).stream().map(String::valueOf).collect(Collectors.toList()); } - // Attribute not supported - LOGGER.error("Invalid string value."); - return null; + LOGGER.error("Invalid string value: {}", sv); + return Collections.emptyList(); } - private static class Condition { - String suffix; - String op; - List rightVals; - Condition(String suffix, String op, List rightVals) { + private Double parseDoubleOrNull(String s) { + try { + return Double.valueOf(s); + } + catch (NumberFormatException e) { + return null; + } + } + + private static final class Condition { + final String suffix; // e.g., ".name", "Sub.Path#value" + final Operator op; + final List rightVals; + + Condition(String suffix, Operator op, List rightVals) { this.suffix = suffix; this.op = op; - this.rightVals = rightVals; + this.rightVals = rightVals != null ? rightVals : Collections.emptyList(); + } + } + + private static final class MatchOp { + final Operator op; + final List args; + + MatchOp(Operator op, List args) { + this.op = op; + this.args = args; } } private boolean evaluateMatch(List matches, Identifiable identifiable) { + if (matches == null || matches.isEmpty()) { + return true; + } + String commonPrefix = null; List itemConditions = new ArrayList<>(); + // Evaluate/collect each match clause for (MatchExpression m: matches) { - String op; - List args = null; - Value left = null; - Value right = null; - if (m.get$eq() != null && !m.get$eq().isEmpty()) { - op = "eq"; - args = m.get$eq(); - } - else if (m.get$ne() != null && !m.get$ne().isEmpty()) { - op = "ne"; - args = m.get$ne(); - } - else if (m.get$gt() != null && !m.get$gt().isEmpty()) { - op = "gt"; - args = m.get$gt(); - } - else if (m.get$ge() != null && !m.get$ge().isEmpty()) { - op = "ge"; - args = m.get$ge(); - } - else if (m.get$lt() != null && !m.get$lt().isEmpty()) { - op = "lt"; - args = m.get$lt(); - } - else if (m.get$le() != null && !m.get$le().isEmpty()) { - op = "le"; - args = m.get$le(); - } - else { - op = null; + MatchOp mo = getMatchOp(m); + if (mo == null) { LOGGER.error("Unsupported operator in match"); return false; } - left = args.get(0); - right = args.get(1); + + Value left = mo.args.get(0); + Value right = mo.args.get(1); if (left.get$field() == null) { - LOGGER.error("Left not field in match"); + LOGGER.error("Left side in $match must be a field: {}", left); return false; } - String f = left.get$field(); + + String field = left.get$field(); List rightVals = evaluateValue(right, identifiable); - int pos = f.indexOf("[]"); - if (pos == -1) { - if (f.startsWith("$sme#")) { - // Treat as top-level SME list condition - String prefix = "$sme"; + + int listMarker = field.indexOf("[]"); + if (listMarker == -1) { + if (field.startsWith(PREFIX_SME + "#")) { + String prefix = PREFIX_SME; if (commonPrefix != null && !commonPrefix.equals(prefix)) { - LOGGER.error("Non-common prefix in match"); + LOGGER.error("Non-common prefix in match: {} vs {}", commonPrefix, prefix); return false; } commonPrefix = prefix; - String suffix = f.substring(5); // "#attr" - itemConditions.add(new Condition(suffix, op, rightVals)); + String suffix = field.substring((PREFIX_SME + "#").length()); + itemConditions.add(new Condition(suffix, mo.op, rightVals)); } else { - // Parent condition List leftVals = evaluateValue(left, identifiable); - boolean condMatch = leftVals.stream().anyMatch(l -> rightVals.stream().anyMatch(r -> compareObjects(l, r, op))); - if (!condMatch) { + if (!anyPairMatches(leftVals, rightVals, mo.op)) { return false; } } } else { - String prefix = f.substring(0, pos); + String prefix = field.substring(0, listMarker); if (commonPrefix != null && !commonPrefix.equals(prefix)) { - LOGGER.error("Non-common prefix in match"); + LOGGER.error("Non-common prefix in match: {} vs {}", commonPrefix, prefix); return false; } commonPrefix = prefix; - String suffix = f.substring(pos + 2); - itemConditions.add(new Condition(suffix, op, rightVals)); + String suffix = field.substring(listMarker + 2); + itemConditions.add(new Condition(suffix, mo.op, rightVals)); } } + + // parent conditions: all matched if (commonPrefix == null) { return true; } - // Handle different prefixes - if (commonPrefix.equals("$aas#assetInformation.specificAssetIds")) { - if (!(identifiable instanceof AssetAdministrationShell)) - return false; - AssetAdministrationShell aas = (AssetAdministrationShell) identifiable; - List list = aas.getAssetInformation().getSpecificAssetIds(); - for (SpecificAssetId item: list) { - boolean all = true; - for (Condition cond: itemConditions) { - String valStr = getPropertyFromObject(item, cond.suffix); - List leftVals = valStr == null ? Collections.emptyList() : Collections.singletonList(valStr); - boolean condMatch = leftVals.stream().anyMatch(l -> cond.rightVals.stream().anyMatch(r -> compareObjects(l, r, cond.op))); - if (!condMatch) { - all = false; - break; + + // Evaluate list items depending on prefix + switch (commonPrefix) { + case "$aas#assetInformation.specificAssetIds": + if (!(identifiable instanceof AssetAdministrationShell)) + return false; + AssetAdministrationShell aas = (AssetAdministrationShell) identifiable; + if (aas.getAssetInformation() == null || aas.getAssetInformation().getSpecificAssetIds() == null) + return false; + + for (SpecificAssetId item: aas.getAssetInformation().getSpecificAssetIds()) { + if (allItemConditionsMatch(itemConditions, cond -> { + String s = getPropertyFromObject(item, cond.suffix); + return s == null ? Collections.emptyList() : Collections.singletonList(s); + })) { + return true; } } - if (all) { - return true; - } - } - return false; - } - else if (commonPrefix.equals("$sme")) { - if (!(identifiable instanceof Submodel)) { return false; - } - Submodel sm = (Submodel) identifiable; - List list = sm.getSubmodelElements(); - for (SubmodelElement item: list) { - boolean all = true; - for (Condition cond: itemConditions) { - List leftVals = getPropertyFromSuffix(item, cond.suffix); - boolean condMatch = leftVals.stream().anyMatch(l -> cond.rightVals.stream().anyMatch(r -> compareObjects(l, r, cond.op))); - if (!condMatch) { - all = false; - break; + + case PREFIX_SME: + if (!(identifiable instanceof Submodel)) + return false; + Submodel sm = (Submodel) identifiable; + List topLevel = sm.getSubmodelElements(); + if (topLevel == null) + return false; + + for (SubmodelElement item: topLevel) { + if (allItemConditionsMatch(itemConditions, cond -> getPropertyFromSuffix(item, cond.suffix))) { + return true; } } - if (all) { - return true; - } - } - return false; - } - else if (commonPrefix.startsWith("$sme.")) { - if (!(identifiable instanceof Submodel)) { - return false; - } - Submodel sm = (Submodel) identifiable; - String path = commonPrefix.substring(5); - SubmodelElement listElem = getSubmodelElementByPath(sm, path); - if (listElem == null || !(listElem instanceof SubmodelElementList)) { return false; - } - SubmodelElementList smeList = (SubmodelElementList) listElem; - for (SubmodelElement item: smeList.getValue()) { - boolean all = true; - for (Condition cond: itemConditions) { - List leftVals = getPropertyFromSuffix(item, cond.suffix); - boolean condMatch = leftVals.stream().anyMatch(l -> cond.rightVals.stream().anyMatch(r -> compareObjects(l, r, cond.op))); - if (!condMatch) { - all = false; - break; + + default: + if (commonPrefix.startsWith(PREFIX_SME + ".")) { + if (!(identifiable instanceof Submodel)) + return false; + Submodel sm2 = (Submodel) identifiable; + String path = commonPrefix.substring((PREFIX_SME + ".").length()); + SubmodelElement listElem = getSubmodelElementByPath(sm2, path); + if (!(listElem instanceof SubmodelElementList)) + return false; + + List items = ((SubmodelElementList) listElem).getValue(); + if (items == null) + return false; + + for (SubmodelElement item: items) { + if (allItemConditionsMatch(itemConditions, cond -> getPropertyFromSuffix(item, cond.suffix))) { + return true; + } } + return false; } - if (all) { - return true; - } + LOGGER.error("Unsupported prefix for $match: {}", commonPrefix); + return false; + } + } + + + private MatchOp getMatchOp(MatchExpression m) { + if (m.get$eq() != null && !m.get$eq().isEmpty()) + return new MatchOp(Operator.EQ, m.get$eq()); + if (m.get$ne() != null && !m.get$ne().isEmpty()) + return new MatchOp(Operator.NE, m.get$ne()); + if (m.get$gt() != null && !m.get$gt().isEmpty()) + return new MatchOp(Operator.GT, m.get$gt()); + if (m.get$ge() != null && !m.get$ge().isEmpty()) + return new MatchOp(Operator.GE, m.get$ge()); + if (m.get$lt() != null && !m.get$lt().isEmpty()) + return new MatchOp(Operator.LT, m.get$lt()); + if (m.get$le() != null && !m.get$le().isEmpty()) + return new MatchOp(Operator.LE, m.get$le()); + return null; + } + + + private boolean allItemConditionsMatch(List conditions, + java.util.function.Function> leftExtractor) { + for (Condition cond: conditions) { + List leftVals = nonNull(leftExtractor.apply(cond)); + if (!anyPairMatches(leftVals, cond.rightVals, cond.op)) { + return false; } - return false; } - LOGGER.error("Unsupported prefix for match: " + commonPrefix); - return false; + return true; } private List getFieldValues(String field, Identifiable identifiable) { - if (field.startsWith("$aas#")) { + if (field == null || identifiable == null) + return Collections.emptyList(); + + if (field.startsWith(PREFIX_AAS)) { if (!(identifiable instanceof AssetAdministrationShell)) return Collections.emptyList(); - AssetAdministrationShell aas = (AssetAdministrationShell) identifiable; - String attr = field.substring(5); - // Handle AAS attributes - if (attr.equals("idShort")) { - return Collections.singletonList(aas.getIdShort()); - } - else if (attr.equals("id")) { - return Collections.singletonList(aas.getId()); - } - else if (attr.equals("assetInformation.assetKind")) { - return Collections.singletonList(aas.getAssetInformation().getAssetKind().name()); - } - else if (attr.equals("assetInformation.assetType")) { - return Collections.singletonList(aas.getAssetInformation().getAssetType()); - } - else if (attr.equals("assetInformation.globalAssetId")) { - String globalAssetId = aas.getAssetInformation().getGlobalAssetId(); - if (globalAssetId == null) - return Collections.emptyList(); - return Collections.singletonList(globalAssetId); - } - else if (attr.startsWith("assetInformation.specificAssetIds")) { - String remaining = attr.substring("assetInformation.specificAssetIds".length()); - boolean any = remaining.startsWith("[]"); - Integer index = null; - if (remaining.startsWith("[")) { - int end = remaining.indexOf("]"); - String idxStr = remaining.substring(1, end); - if (idxStr.isEmpty()) - any = true; - else - index = Integer.parseInt(idxStr); - remaining = remaining.substring(end + 1); - } - List sais = aas.getAssetInformation().getSpecificAssetIds(); - List values = new ArrayList<>(); - List targets = any || index == null ? sais - : (index < sais.size() && index >= 0 ? Collections.singletonList(sais.get(index)) : Collections.emptyList()); - for (SpecificAssetId sai: targets) { - values.add(getPropertyFromObject(sai, remaining)); - } - return values; - } - LOGGER.error("Unsupported AAS attribute: " + attr); + return getAasFieldValues((AssetAdministrationShell) identifiable, field.substring(PREFIX_AAS.length())); } - else if (field.startsWith("$sm#")) { + + if (field.startsWith(PREFIX_SM)) { if (!(identifiable instanceof Submodel)) return Collections.emptyList(); - Submodel sm = (Submodel) identifiable; - String attr = field.substring(4); - return new ArrayList<>(getSmAttrValues(sm, attr)); + return new ArrayList<>(getSmAttrValues((Submodel) identifiable, field.substring(PREFIX_SM.length()))); } - else if (field.startsWith("$sme")) { + + if (field.startsWith(PREFIX_SME)) { if (!(identifiable instanceof Submodel)) return Collections.emptyList(); Submodel sm = (Submodel) identifiable; + String pathPart = ""; - String attr = ""; + String attr; if (field.contains(".")) { int hashPos = field.indexOf("#", field.indexOf(".")); pathPart = field.substring(field.indexOf(".") + 1, hashPos); attr = field.substring(hashPos + 1); } else { - attr = field.substring(5); + attr = field.substring((PREFIX_SME + "#").length()); } + List values = new ArrayList<>(); if (!pathPart.isEmpty()) { - // Basic path support, no [] for now SubmodelElement sme = getSubmodelElementByPath(sm, pathPart); if (sme != null) { values.addAll(getSmeAttrValues(sme, attr)); } } else { - // Recursive all SME List smes = sm.getSubmodelElements(); - for (SubmodelElement sme: smes) { - values.addAll(getSmeAttrValues(sme, attr)); + if (smes != null) { + for (SubmodelElement sme: smes) { + values.addAll(getSmeAttrValues(sme, attr)); + } } } return values; } - else if (field.startsWith("$cd#")) { + + if (field.startsWith(PREFIX_CD)) { if (!(identifiable instanceof ConceptDescription)) return Collections.emptyList(); ConceptDescription cd = (ConceptDescription) identifiable; - String attr = field.substring(4); - if (attr.equals("idShort")) { + String attr = field.substring(PREFIX_CD.length()); + if ("idShort".equals(attr)) return Collections.singletonList(cd.getIdShort()); - } - else if (attr.equals("id")) { + if ("id".equals(attr)) return Collections.singletonList(cd.getId()); + LOGGER.error("Unsupported CD attribute: {}", attr); + return Collections.emptyList(); + } + + LOGGER.error("Unsupported field: {}", field); + return Collections.emptyList(); + } + + + private List getAasFieldValues(AssetAdministrationShell aas, String attr) { + if ("idShort".equals(attr)) + return Collections.singletonList(aas.getIdShort()); + if ("id".equals(attr)) + return Collections.singletonList(aas.getId()); + + if ("assetInformation.assetKind".equals(attr)) { + return aas.getAssetInformation() == null || aas.getAssetInformation().getAssetKind() == null + ? Collections.emptyList() + : Collections.singletonList(aas.getAssetInformation().getAssetKind().name()); + } + if ("assetInformation.assetType".equals(attr)) { + return aas.getAssetInformation() == null + ? Collections.emptyList() + : Collections.singletonList(aas.getAssetInformation().getAssetType()); + } + if ("assetInformation.globalAssetId".equals(attr)) { + if (aas.getAssetInformation() == null) + return Collections.emptyList(); + String globalAssetId = aas.getAssetInformation().getGlobalAssetId(); + return globalAssetId == null ? Collections.emptyList() : Collections.singletonList(globalAssetId); + } + + if (attr.startsWith("assetInformation.specificAssetIds")) { + if (aas.getAssetInformation() == null || aas.getAssetInformation().getSpecificAssetIds() == null) { + return Collections.emptyList(); + } + String remaining = attr.substring("assetInformation.specificAssetIds".length()); + IndexSpec spec = parseIndexSuffix(remaining); + + List sais = aas.getAssetInformation().getSpecificAssetIds(); + List targets = selectTargets(sais, spec); + + List values = new ArrayList<>(); + for (SpecificAssetId sai: targets) { + values.add(getPropertyFromObject(sai, spec.remaining)); } - LOGGER.error("Unsupported CD attribute: " + attr); + return values; } - LOGGER.error("Unsupported field: " + field); - return null; + + LOGGER.error("Unsupported AAS attribute: {}", attr); + return Collections.emptyList(); } private List getSmAttrValues(Submodel sm, String attr) { - if (attr.equals("idShort")) { + if ("idShort".equals(attr)) return Collections.singletonList(sm.getIdShort()); - } - else if (attr.equals("id")) { + if ("id".equals(attr)) return Collections.singletonList(sm.getId()); - } - else if (attr.equals("semanticId")) { + + if ("semanticId".equals(attr)) { Reference ref = sm.getSemanticId(); - if (ref == null || ref.getKeys().isEmpty()) + if (ref == null || ref.getKeys() == null || ref.getKeys().isEmpty()) return Collections.emptyList(); return Collections.singletonList(ref.getKeys().get(0).getValue()); } - else if (attr.startsWith("semanticId.keys")) { + + if (attr.startsWith("semanticId.keys")) { Reference ref = sm.getSemanticId(); - if (ref == null) + if (ref == null || ref.getKeys() == null) return Collections.emptyList(); - //cut prefix + String remaining = attr.substring("semanticId.keys".length()); - //check if there is an index like [1] - boolean any = remaining.startsWith("[]"); - //set index - Integer index = null; - if (remaining.startsWith("[")) { - int end = remaining.indexOf("]"); - String idxStr = remaining.substring(1, end); - if (idxStr.isEmpty()) - any = true; - else - index = Integer.parseInt(idxStr); - remaining = remaining.substring(end + 1); - } - List keys = ref.getKeys(); - List values = new ArrayList<>(); - // set targets depending if any is true otherwise target index - List targets = any || index == null ? keys : (index < keys.size() && index >= 0 ? Collections.singletonList(keys.get(index)) : Collections.emptyList()); + IndexSpec spec = parseIndexSuffix(remaining); + + List targets = selectTargets(ref.getKeys(), spec); + List out = new ArrayList<>(); for (Key key: targets) { - if (remaining.equals(".type")) { - values.add(key.getType().name()); - } - else if (remaining.equals(".value")) { - values.add(key.getValue()); - } + if (".type".equals(spec.remaining)) + out.add(key.getType().name()); + else if (".value".equals(spec.remaining)) + out.add(key.getValue()); } - return values; + return out; } - LOGGER.error("Unsupported SM attribute: " + attr); - return null; + + LOGGER.error("Unsupported SM attribute: {}", attr); + return Collections.emptyList(); } private List getSmeAttrValues(SubmodelElement sme, String attr) { - if (attr.equals("idShort")) { + if (sme == null || attr == null) + return Collections.emptyList(); + + if ("idShort".equals(attr)) { return Collections.singletonList(sme.getIdShort()); } - else if (attr.equals("value")) { + if ("value".equals(attr)) { if (sme instanceof Property) { return Collections.singletonList(((Property) sme).getValue()); } return Collections.emptyList(); } - else if (attr.equals("valueType")) { - if (sme instanceof Property) { + if ("valueType".equals(attr)) { + if (sme instanceof Property && ((Property) sme).getValueType() != null) { return Collections.singletonList(((Property) sme).getValueType().name()); } return Collections.emptyList(); } - else if (attr.equals("language")) { + if ("language".equals(attr)) { if (sme instanceof MultiLanguageProperty) { - return ((MultiLanguageProperty) sme).getValue().stream().map(LangStringTextType::getLanguage).collect(Collectors.toList()); + List values = ((MultiLanguageProperty) sme).getValue(); + if (values == null) + return Collections.emptyList(); + return values.stream().filter(Objects::nonNull).map(LangStringTextType::getLanguage).collect(Collectors.toList()); } return Collections.emptyList(); } - else if (attr.equals("semanticId")) { + if ("semanticId".equals(attr)) { Reference ref = sme.getSemanticId(); - if (ref == null || ref.getKeys().isEmpty()) + if (ref == null || ref.getKeys() == null || ref.getKeys().isEmpty()) return Collections.emptyList(); return Collections.singletonList(ref.getKeys().get(0).getValue()); } - else if (attr.startsWith("semanticId.keys")) { + if (attr.startsWith("semanticId.keys")) { Reference ref = sme.getSemanticId(); - if (ref == null) + if (ref == null || ref.getKeys() == null) return Collections.emptyList(); - //cut prefix + String remaining = attr.substring("semanticId.keys".length()); - //check if there is an index like [1] - boolean any = remaining.startsWith("[]"); - //set index - Integer index = null; - if (remaining.startsWith("[")) { - int end = remaining.indexOf("]"); - String idxStr = remaining.substring(1, end); - if (idxStr.isEmpty()) - any = true; - else - index = Integer.parseInt(idxStr); - remaining = remaining.substring(end + 1); - } - List keys = ref.getKeys(); - List values = new ArrayList<>(); - List targets = any || index == null ? keys : (index < keys.size() && index >= 0 ? Collections.singletonList(keys.get(index)) : Collections.emptyList()); + IndexSpec spec = parseIndexSuffix(remaining); + + List targets = selectTargets(ref.getKeys(), spec); + List out = new ArrayList<>(); for (Key key: targets) { - if (remaining.equals(".type")) { - values.add(key.getType().name()); - } - else if (remaining.equals(".value")) { - values.add(key.getValue()); - } + if (".type".equals(spec.remaining)) + out.add(key.getType().name()); + else if (".value".equals(spec.remaining)) + out.add(key.getValue()); } - return values; + return out; } - LOGGER.error("Unsupported SME attribute: " + attr); - return null; + + LOGGER.error("Unsupported SME attribute: {}", attr); + return Collections.emptyList(); } + /** + * Resolve a submodel element by dot-separated path. + */ private SubmodelElement getSubmodelElementByPath(Submodel sm, String path) { + if (sm == null || path == null || path.isEmpty()) + return null; + + String[] tokens = path.split("\\."); SubmodelElement current = null; - for (String token: path.split("\\.")) { - current = sm.getSubmodelElements().stream() - .filter(e -> e.getIdShort().equals(token)) - .findFirst().orElse(null); + + for (int i = 0; i < tokens.length; i++) { + String token = tokens[i]; + if (i == 0) { + current = findByIdShort(sm.getSubmodelElements(), token); + } + else { + if (current instanceof SubmodelElementCollection) { + current = findByIdShort(((SubmodelElementCollection) current).getValue(), token); + } + else if (current instanceof SubmodelElementList) { + current = findByIdShort(((SubmodelElementList) current).getValue(), token); + } + else { + return null; + } + } + if (current == null) + return null; } return current; } + private SubmodelElement findByIdShort(List elements, String idShort) { + if (elements == null || idShort == null) + return null; + return elements.stream().filter(e -> idShort.equals(e.getIdShort())).findFirst().orElse(null); + } + + private String getPropertyFromObject(Object item, String path) { - if (item instanceof SpecificAssetId) { - if (path.equals(".name")) { - return ((SpecificAssetId) item).getName(); - } - else if (path.equals(".value")) { - return ((SpecificAssetId) item).getValue(); - } - else if (path.startsWith(".externalSubjectId")) { - return ((SpecificAssetId) item).getExternalSubjectId().toString(); - } + if (!(item instanceof SpecificAssetId) || path == null) { + LOGGER.error("Unsupported property {} for object {}", path, item); + return null; + } + SpecificAssetId sai = (SpecificAssetId) item; + switch (path) { + case ".name": + return sai.getName(); + case ".value": + return sai.getValue(); + default: + if (path.startsWith(".externalSubjectId") && sai.getExternalSubjectId() != null) { + return String.valueOf(sai.getExternalSubjectId()); + } } - LOGGER.error("Unsupported property: " + path); + LOGGER.error("Unsupported property: {}", path); return null; } private List getPropertyFromSuffix(SubmodelElement item, String suffix) { - if (suffix.startsWith(".")) { - suffix = suffix.substring(1); - } + if (item == null || suffix == null) + return Collections.emptyList(); + + String normalized = suffix.startsWith(".") ? suffix.substring(1) : suffix; String subPath = ""; - String attr = suffix; - int hashPos = suffix.indexOf("#"); + String attr = normalized; + int hashPos = normalized.indexOf('#'); if (hashPos != -1) { - subPath = suffix.substring(0, hashPos); - attr = suffix.substring(hashPos + 1); + subPath = normalized.substring(0, hashPos); + attr = normalized.substring(hashPos + 1); } - SubmodelElement subElem = getSubmodelElementByPathForItem(item, subPath); - if (subElem == null) { + SubmodelElement target = getSubmodelElementByPathForItem(item, subPath); + if (target == null) { return Collections.emptyList(); } - List values = getSmeAttrValues(subElem, attr); - return new ArrayList<>(values); + return new ArrayList<>(getSmeAttrValues(target, attr)); } private SubmodelElement getSubmodelElementByPathForItem(SubmodelElement item, String path) { - SubmodelElement current = item; - if (path.isEmpty()) { - return current; - } + if (item == null || path == null || path.isEmpty()) + return item; + List tokens = Arrays.asList(path.split("\\.")); + SubmodelElement current = item; if (!tokens.isEmpty() && tokens.get(0).equals(current.getIdShort())) { tokens = tokens.subList(1, tokens.size()); } @@ -652,98 +703,159 @@ private SubmodelElement getSubmodelElementByPathForItem(SubmodelElement item, St if (!(current instanceof SubmodelElementCollection)) { return null; } - SubmodelElementCollection container = (SubmodelElementCollection) current; - current = container.getValue().stream() - .filter(e -> e.getIdShort().equals(token)) - .findFirst().orElse(null); - if (current == null) { + current = findByIdShort(((SubmodelElementCollection) current).getValue(), token); + if (current == null) return null; - } } return current; } - private boolean compareObjects(Object a, Object b, String op) { + private boolean compare(Object a, Object b, Operator op) { + if (op == null) + return false; + + if (op.isStringOp()) { + return compareStrings(a, b, op); + } + return compareGeneral(a, b, op); + } + + + private boolean compareStrings(Object a, Object b, Operator op) { + if (a == null || b == null) + return false; + String left = String.valueOf(a); + String right = String.valueOf(b); + + switch (op) { + case CONTAINS: + return left.contains(right); + case STARTS_WITH: + return left.startsWith(right); + case ENDS_WITH: + return left.endsWith(right); + case REGEX: + return Pattern.compile(right).matcher(left).matches(); + default: + return false; + } + } + + + private boolean compareGeneral(Object a, Object b, Operator op) { if (a == null || b == null) { - switch (op) { - case "eq": - return a == b; - case "ne": - return a != b; - default: - return false; - } + return (op == Operator.EQ) ? a == b + : (op == Operator.NE) && a != b; } + try { - double d1 = a instanceof Number ? ((Number) a).doubleValue() : Double.parseDouble(a.toString()); - double d2 = b instanceof Number ? ((Number) b).doubleValue() : Double.parseDouble(b.toString()); + double d1 = (a instanceof Number) ? ((Number) a).doubleValue() : Double.parseDouble(String.valueOf(a)); + double d2 = (b instanceof Number) ? ((Number) b).doubleValue() : Double.parseDouble(String.valueOf(b)); switch (op) { - case "eq": + case EQ: return d1 == d2; - case "ne": + case NE: return d1 != d2; - case "gt": + case GT: return d1 > d2; - case "ge": + case GE: return d1 >= d2; - case "lt": + case LT: return d1 < d2; - case "le": + case LE: return d1 <= d2; - } - } - catch (NumberFormatException e) {} - String sa = a.toString(); - String sb = b.toString(); - boolean isBoolA = sa.trim().equalsIgnoreCase("true") || sa.trim().equalsIgnoreCase("false"); - boolean isBoolB = sb.trim().equalsIgnoreCase("true") || sb.trim().equalsIgnoreCase("false"); - if (isBoolA && isBoolB) { - boolean bool1 = Boolean.parseBoolean(sa); - boolean bool2 = Boolean.parseBoolean(sb); - switch (op) { - case "eq": - return bool1 == bool2; - case "ne": - return bool1 != bool2; default: return false; } } + catch (NumberFormatException ignored) { + // left blank intentionally + } + + // boolean + String sa = String.valueOf(a).trim(); + String sb = String.valueOf(b).trim(); + boolean ba = "true".equalsIgnoreCase(sa) || "false".equalsIgnoreCase(sa); + boolean bb = "true".equalsIgnoreCase(sb) || "false".equalsIgnoreCase(sb); + if (ba && bb) { + boolean b1 = Boolean.parseBoolean(sa); + boolean b2 = Boolean.parseBoolean(sb); + return (op == Operator.EQ) ? (b1 == b2) + : (op == Operator.NE) && (b1 != b2); + } + + // string int cmp = sa.compareTo(sb); switch (op) { - case "eq": + case EQ: return cmp == 0; - case "ne": + case NE: return cmp != 0; - case "gt": + case GT: return cmp > 0; - case "ge": + case GE: return cmp >= 0; - case "lt": + case LT: return cmp < 0; - case "le": + case LE: return cmp <= 0; + default: + return false; } - return false; } - private boolean stringCompareObjects(Object a, Object b, String op) { - if (a == null || b == null) - return false; - String s1 = a.toString(); - String s2 = b.toString(); - switch (op) { - case "contains": - return s1.contains(s2); - case "starts-with": - return s1.startsWith(s2); - case "ends-with": - return s1.endsWith(s2); - case "regex": - return Pattern.compile(s2).matcher(s1).matches(); + private static List nonNull(List in) { + return in != null ? in : Collections.emptyList(); + } + + /** + * @param remaining remaining suffix (e.g., ".name") + */ + private record IndexSpec(boolean any, Integer index, String remaining) {} + + private IndexSpec parseIndexSuffix(String s) { + if (s == null || s.isEmpty()) { + return new IndexSpec(true, null, ""); } - return false; + String rem = s; + boolean any = false; + Integer idx = null; + + if (rem.startsWith("[]")) { + any = true; + rem = rem.substring(2); + } + else if (rem.startsWith("[")) { + int end = rem.indexOf(']'); + if (end > 1) { + String idxStr = rem.substring(1, end); + if (idxStr.isEmpty()) { + any = true; + } + else { + try { + idx = Integer.parseInt(idxStr); + } + catch (NumberFormatException e) { + LOGGER.error("Invalid index in path: {}", s); + return new IndexSpec(true, null, rem.substring(end + 1)); + } + } + rem = rem.substring(end + 1); + } + } + return new IndexSpec(any, idx, rem); + } + + + private List selectTargets(List list, IndexSpec spec) { + if (list == null || list.isEmpty()) + return Collections.emptyList(); + if (spec.any || spec.index == null) + return list; + int i = spec.index; + return (i >= 0 && i < list.size()) ? Collections.singletonList(list.get(i)) : Collections.emptyList(); } } From b21b9255e217f159cc0a56b5124a97810a1c5898 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 10:47:24 +0100 Subject: [PATCH 060/108] sonarcloud fixes --- .../http/security/filter/ApiGateway.java | 81 ++++++++++++------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 72cc8be6f..e69dc7d42 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -54,6 +54,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.slf4j.LoggerFactory; @@ -78,18 +79,22 @@ public ApiGateway(String aclFolder) { /** + * * Checks if the user is authorized to receive the response of the request. * * @param request the HttpRequest * @return true if authorized and ACL exists + * */ public boolean isAuthorized(HttpServletRequest request) { String token = request.getHeader("Authorization"); - if (java.util.Objects.isNull(token)) { + if (token == null) { return AuthServer.filterRules(this.aclList, null, request); } else { - token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; + if (token.startsWith(BEARER_KWD + " ")) { + token = token.substring(BEARER_KWD.length() + 1).trim(); + } DecodedJWT jwt = JWT.decode(token); return AuthServer.filterRules(this.aclList, jwt.getClaims(), request); } @@ -97,11 +102,14 @@ public boolean isAuthorized(HttpServletRequest request) { /** + * * Filters out AAS that the user is not authorized for. * * @param request the HttpRequest * @param response the ApiResponse + * * @return the ApiResponse with only allowed AAS + * */ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { response.getPayload().getContent() @@ -114,11 +122,14 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS /** + * * Filters out Submodels that the user is not authorized for. * * @param request the HttpRequest * @param response the ApiResponse + * * @return the ApiResponse with only allowed Submodels + * */ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { response.getPayload().getContent() @@ -135,7 +146,9 @@ private Map extractClaims(HttpServletRequest request) { if (token == null) { return null; } - token = token.startsWith("Bearer ") ? token.substring("Bearer ".length()).trim() : token; + if (token.startsWith(BEARER_KWD + " ")) { + token = token.substring(BEARER_KWD.length() + 1).trim(); + } try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaims(); @@ -146,24 +159,32 @@ private Map extractClaims(HttpServletRequest request) { } /** + * * Simple whitelist AuthServer implementation that supports ANONYMOUS access, * claims with simple eq formulas and route authorization. + * * Access must be explicitly defined, otherwise it is blocked. + * */ public static class AuthServer { private static final String apiPrefix = "/api/v3.0/"; /** + * * Check all rules that explicitly allows the request. * If a rule exists after all filters, true is returned * + * + * * @param claims * @param request + * * @return + * */ private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { String requestPath = request.getRequestURI(); - String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(9) : requestPath; + String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(apiPrefix.length()) : requestPath; String method = request.getMethod(); List relevantRules = aclList.values().stream() .filter(a -> a.getRules().stream() @@ -186,7 +207,7 @@ private static boolean verifyAllClaims(Map claims, AccessPermissi List claimValues = getAttributes(acl, allAccess).stream() .filter(attr -> attr.getGlobal() == null) .map(AttributeItem::getClaim) - .filter(java.util.Objects::nonNull) + .filter(Objects::nonNull) .collect(Collectors.toList()); Map claimList = new HashMap<>(); for (String val: claimValues) { @@ -197,9 +218,7 @@ private static boolean verifyAllClaims(Map claims, AccessPermissi } return !claimValues.isEmpty() && claimValues.stream() - .allMatch(value -> { - return evaluateFormula(getFormula(rule, allAccess), claimList); - }); + .allMatch(value -> evaluateFormula(getFormula(rule, allAccess), claimList)); } @@ -209,23 +228,19 @@ private static boolean evaluateFormula(LogicalExpression formula, for (var c: claims.entrySet()) { ctx.put("CLAIM:" + c.getKey(), c.getValue()); } - //ctx.put("CLAIM:" + claimName, claimValue); ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); // $GLOBAL → UTCNOW return FormulaEvaluator.evaluate(formula, ctx); } private static boolean evaluateRights(List aclRights, String method, String path) { - // We need the path to check if the request is an operation invocation (EXECUTE) String requiredRight = isOperationRequest(method, path) ? "EXECUTE" : getRequiredRight(method); - return aclRights.contains(RightsEnum.ALL) || aclRights.contains(RightsEnum.valueOf(requiredRight)); } private static boolean isOperationRequest(String method, String path) { - // Requirements for an operation request according to FAAAST docs: - // Method: POST, URL suffix: /invoke, /invoke-async, /invoke/$value, /invoke-async/$value + // Requirements: POST and URL suffix: invoke, invoke-async, invoke/$value, invoke-async/$value String cleanPath; String[] pathParts = path.split("/"); @@ -236,7 +251,7 @@ private static boolean isOperationRequest(String method, String path) { cleanPath = pathParts[pathParts.length - 1]; } - return POST.name().equals(method) && ("/invoke".equals(cleanPath) || "invoke-async".equals(path)); + return POST.name().equals(method) && ("invoke".equals(cleanPath) || "invoke-async".equals(cleanPath)); } @@ -257,14 +272,14 @@ private static boolean checkIdentifiable(String path, String identifiable) { return false; } - if (identifiable.equals("(Submodel)*")) { + if ("(Submodel)*".equals(identifiable)) { return true; } else if (identifiable.startsWith("(Submodel)")) { String id = identifiable.substring(10); return path.contains(EncodingHelper.base64Encode(id)); } - if (identifiable.equals("(AssetAdministrationShell)*")) { + if ("(AssetAdministrationShell)*".equals(identifiable)) { return true; } else if (identifiable.startsWith("(AssetAdministrationShell)")) { @@ -280,7 +295,7 @@ private static boolean checkDescriptor(String path, String descriptor) { if (!path.startsWith("/shell-descriptors")) { return false; } - if (descriptor.equals("(aasDesc)*")) { + if ("(aasDesc)*".equals(descriptor)) { return true; } else if (descriptor.startsWith("(aasDesc)")) { @@ -292,7 +307,7 @@ else if (descriptor.startsWith("(smDesc)")) { if (!path.startsWith("/submodel-descriptors")) { return false; } - if (descriptor.equals("(smDesc)*")) { + if ("(smDesc)*".equals(descriptor)) { return true; } else if (descriptor.startsWith("(smDesc)")) { @@ -312,7 +327,9 @@ && getAttributes(acl, allAccess) != null && getObjects(rule, allAccess) != null && getObjects(rule, allAccess).stream().anyMatch(attr -> { if (attr.getRoute() != null) { - return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); + // route is a list of allowed routes; support wildcard "*" + List routes = attr.getRoute(); + return routes.contains("*") || routes.contains(path); } else if (attr.getIdentifiable() != null) { return checkIdentifiable(path, attr.getIdentifiable()); @@ -393,12 +410,14 @@ private void monitorLoop(WatchService watchService, Path folderToWatch) { ObjectMapper mapper = new ObjectMapper(); Thread monitoringThread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { - WatchKey watchKey = null; + WatchKey watchKey; try { watchKey = watchService.take(); } catch (InterruptedException e) { - LOGGER.error(abortMessage); + Thread.currentThread().interrupt(); // restore interrupt status + LOGGER.warn("ACL monitoring thread interrupted", e); + break; // exit loop } for (WatchEvent event: watchKey.pollEvents()) { WatchEvent.Kind kind = event.kind(); @@ -430,10 +449,9 @@ else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { } } } - // Reset the key to receive further watch events boolean valid = watchKey.reset(); if (!valid) { - System.out.println("WatchKey no longer valid; exiting."); + LOGGER.info("WatchKey no longer valid; exiting."); break; } } @@ -447,7 +465,9 @@ private static Acl getAcl(AccessPermissionRule rule, AllAccessPermissionRules al return rule.getAcl(); } else if (rule.getUseacl() != null) { - Optional acl = allAccess.getDefacls().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + Optional acl = allAccess.getDefacls().stream() + .filter(a -> Objects.equals(a.getName(), rule.getUseacl())) + .findAny(); if (acl.isPresent()) { return acl.get().getAcl(); } @@ -466,7 +486,8 @@ private static List getAttributes(Acl acl, AllAccessPermissionRul return acl.getAttributes(); } else if (acl.getUseattributes() != null) { - Optional attribute = allAccess.getDefattributes().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))) + Optional attribute = allAccess.getDefattributes().stream() + .filter(a -> Objects.equals(a.getName(), acl.getUseattributes())) .findAny(); if (attribute.isPresent()) { return attribute.get().getAttributes(); @@ -486,7 +507,9 @@ private static LogicalExpression getFormula(AccessPermissionRule rule, AllAccess return rule.getFormula(); } else if (rule.getUseformula() != null) { - Optional formula = allAccess.getDefformulas().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + Optional formula = allAccess.getDefformulas().stream() + .filter(a -> Objects.equals(a.getName(), rule.getUseformula())) + .findAny(); if (formula.isPresent()) { return formula.get().getFormula(); } @@ -505,12 +528,14 @@ private static List getObjects(AccessPermissionRule rule, AllAccessP return rule.getObjects(); } else if (rule.getUseobjects() != null) { - Optional objects = allAccess.getDefobjects().stream().filter(a -> (a.getName() == null ? a.getName() == null : a.getName().equals(a.getName()))).findAny(); + Optional objects = allAccess.getDefobjects().stream() + .filter(a -> Objects.equals(a.getName(), rule.getUseobjects())) + .findAny(); if (objects.isPresent()) { return objects.get().getObjects(); } else { - throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseformula()); + throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseobjects()); } } else { From 5f684be57c18231ef5788a72a054a1aabfc77c3b Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 11:34:09 +0100 Subject: [PATCH 061/108] codacy fixes --- .../faaast/service/query/QueryEvaluator.java | 697 +++++++++++------- .../http/security/filter/ApiGateway.java | 5 +- 2 files changed, 424 insertions(+), 278 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index ec2e42e8d..d84adad02 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -59,7 +60,7 @@ public QueryEvaluator(Environment environment) { this.environment = environment; } - private enum Operator { + private enum ComparisonOperator { EQ, NE, GT, @@ -71,16 +72,38 @@ private enum Operator { ENDS_WITH, REGEX; - boolean isStringOp() { + boolean isStringOperator() { return this == CONTAINS || this == STARTS_WITH || this == ENDS_WITH || this == REGEX; } } + private enum ValueKind { + NONE, + FIELD, + STR, + NUM, + HEX, + DATETIME, + TIME, + BOOL, + STR_CAST, + NUM_CAST + } + + private enum StringValueKind { + NONE, + FIELD, + STR, + STR_CAST + } + /** + * * Used to decide whether to filter out the Identifiable. * * @param expr logical expression (tree) * @param identifiable AAS | Submodel | ConceptDescription + * * @return true if expression matches the identifiable * */ @@ -88,10 +111,13 @@ public boolean matches(LogicalExpression expr, Identifiable identifiable) { if (expr == null || identifiable == null) { return false; } + + // boolean if (expr.get$boolean() != null) { return expr.get$boolean(); } + // logical if (expr.get$and() != null && !expr.get$and().isEmpty()) { return expr.get$and().stream().allMatch(e -> matches(e, identifiable)); } @@ -102,68 +128,89 @@ public boolean matches(LogicalExpression expr, Identifiable identifiable) { return !matches(expr.get$not(), identifiable); } - // $match + // match operator if (expr.get$match() != null && !expr.get$match().isEmpty()) { return evaluateMatch(expr.get$match(), identifiable); } - // Binary - if (expr.get$eq() != null && !expr.get$eq().isEmpty()) - return evaluateBinary(expr.get$eq(), identifiable, Operator.EQ); - if (expr.get$ne() != null && !expr.get$ne().isEmpty()) - return evaluateBinary(expr.get$ne(), identifiable, Operator.NE); - if (expr.get$gt() != null && !expr.get$gt().isEmpty()) - return evaluateBinary(expr.get$gt(), identifiable, Operator.GT); - if (expr.get$ge() != null && !expr.get$ge().isEmpty()) - return evaluateBinary(expr.get$ge(), identifiable, Operator.GE); - if (expr.get$lt() != null && !expr.get$lt().isEmpty()) - return evaluateBinary(expr.get$lt(), identifiable, Operator.LT); - if (expr.get$le() != null && !expr.get$le().isEmpty()) - return evaluateBinary(expr.get$le(), identifiable, Operator.LE); - - // String binary operators - if (expr.get$contains() != null && !expr.get$contains().isEmpty()) - return evaluateStringBinary(expr.get$contains(), identifiable, Operator.CONTAINS); - if (expr.get$startsWith() != null && !expr.get$startsWith().isEmpty()) - return evaluateStringBinary(expr.get$startsWith(), identifiable, Operator.STARTS_WITH); - if (expr.get$endsWith() != null && !expr.get$endsWith().isEmpty()) - return evaluateStringBinary(expr.get$endsWith(), identifiable, Operator.ENDS_WITH); - if (expr.get$regex() != null && !expr.get$regex().isEmpty()) - return evaluateStringBinary(expr.get$regex(), identifiable, Operator.REGEX); + // numeric/boolean/string comparisons + boolean evaluated = evaluateFirstValueOperator(expr, identifiable); + if (evaluated) { + return true; + } + + // string binary operators + return evaluateFirstStringOperator(expr, identifiable); + } + + + private boolean evaluateFirstValueOperator(LogicalExpression expr, Identifiable identifiable) { + List> operations = Arrays.asList( + new OperationSpec<>(ComparisonOperator.EQ, expr::get$eq), + new OperationSpec<>(ComparisonOperator.NE, expr::get$ne), + new OperationSpec<>(ComparisonOperator.GT, expr::get$gt), + new OperationSpec<>(ComparisonOperator.GE, expr::get$ge), + new OperationSpec<>(ComparisonOperator.LT, expr::get$lt), + new OperationSpec<>(ComparisonOperator.LE, expr::get$le)); + for (OperationSpec spec: operations) { + List args = spec.argumentProvider.get(); + if (args != null && !args.isEmpty()) { + return evaluateBinaryComparison(args, identifiable, spec.operator); + } + } + return false; + } + + private boolean evaluateFirstStringOperator(LogicalExpression expr, Identifiable identifiable) { + List> operations = Arrays.asList( + new OperationSpec<>(ComparisonOperator.CONTAINS, expr::get$contains), + new OperationSpec<>(ComparisonOperator.STARTS_WITH, expr::get$startsWith), + new OperationSpec<>(ComparisonOperator.ENDS_WITH, expr::get$endsWith), + new OperationSpec<>(ComparisonOperator.REGEX, expr::get$regex)); + for (OperationSpec spec: operations) { + List args = spec.argumentProvider.get(); + if (args != null && !args.isEmpty()) { + return evaluateBinaryStringOperator(args, identifiable, spec.operator); + } + } return false; } + /** + * @param argumentProvider provides arguments for this operator + */ + private record OperationSpec(ComparisonOperator operator, Supplier> argumentProvider) {} - private boolean evaluateBinary(List args, Identifiable identifiable, Operator op) { + private boolean evaluateBinaryComparison(List args, Identifiable identifiable, ComparisonOperator operator) { if (args.size() < 2) { - LOGGER.error("Operator {} requires two arguments", op); + LOGGER.error("Operator {} requires two arguments", operator); return false; } List left = evaluateValue(args.get(0), identifiable); List right = evaluateValue(args.get(1), identifiable); - return anyPairMatches(left, right, op); + return anyPairSatisfies(left, right, operator); } - private boolean evaluateStringBinary(List args, Identifiable identifiable, Operator op) { + private boolean evaluateBinaryStringOperator(List args, Identifiable identifiable, ComparisonOperator operator) { if (args.size() < 2) { - LOGGER.error("String operator {} requires two arguments", op); + LOGGER.error("String operator {} requires two arguments", operator); return false; } List left = evaluateStringValue(args.get(0), identifiable); List right = evaluateStringValue(args.get(1), identifiable); - return anyPairMatches(left, right, op); + return anyPairSatisfies(left, right, operator); } - private boolean anyPairMatches(List left, List right, Operator op) { + private boolean anyPairSatisfies(List left, List right, ComparisonOperator operator) { if (left == null || right == null) { return false; } for (Object l: left) { for (Object r: right) { - if (compare(l, r, op)) { + if (compareValues(l, r, operator)) { return true; } } @@ -172,49 +219,89 @@ private boolean anyPairMatches(List left, List right, Operator o } - private List evaluateValue(Value v, Identifiable identifiable) { + private ValueKind determineValueKind(Value v) { if (v == null) - return Collections.emptyList(); - - if (v.get$field() != null) { - return nonNull(getFieldValues(v.get$field(), identifiable)); - } + return ValueKind.NONE; + if (v.get$field() != null) + return ValueKind.FIELD; if (v.get$strVal() != null) - return Collections.singletonList(v.get$strVal()); + return ValueKind.STR; if (v.get$numVal() != null) - return Collections.singletonList(v.get$numVal()); + return ValueKind.NUM; if (v.get$hexVal() != null) - return Collections.singletonList(v.get$hexVal()); + return ValueKind.HEX; if (v.get$dateTimeVal() != null) - return Collections.singletonList(v.get$dateTimeVal()); + return ValueKind.DATETIME; if (v.get$timeVal() != null) - return Collections.singletonList(v.get$timeVal()); + return ValueKind.TIME; if (v.get$boolean() != null) - return Collections.singletonList(v.get$boolean()); - if (v.get$strCast() != null) { - return evaluateValue(v.get$strCast(), identifiable).stream().map(String::valueOf).collect(Collectors.toList()); - } - if (v.get$numCast() != null) { - return evaluateValue(v.get$numCast(), identifiable).stream().map(String::valueOf).map(this::parseDoubleOrNull).filter(Objects::nonNull).collect(Collectors.toList()); - } - return Collections.emptyList(); + return ValueKind.BOOL; + if (v.get$strCast() != null) + return ValueKind.STR_CAST; + if (v.get$numCast() != null) + return ValueKind.NUM_CAST; + return ValueKind.NONE; } - private List evaluateStringValue(StringValue sv, Identifiable identifiable) { + private StringValueKind determineStringValueKind(StringValue sv) { if (sv == null) - return Collections.emptyList(); - if (sv.get$field() != null) { - return nonNull(getFieldValues(sv.get$field(), identifiable)); - } - if (sv.get$strVal() != null) { - return Collections.singletonList(sv.get$strVal()); + return StringValueKind.NONE; + if (sv.get$field() != null) + return StringValueKind.FIELD; + if (sv.get$strVal() != null) + return StringValueKind.STR; + if (sv.get$strCast() != null) + return StringValueKind.STR_CAST; + return StringValueKind.NONE; + } + + + private List evaluateValue(Value v, Identifiable identifiable) { + switch (determineValueKind(v)) { + case FIELD: + return nonNull(getFieldValues(v.get$field(), identifiable)); + case STR: + return Collections.singletonList(v.get$strVal()); + case NUM: + return Collections.singletonList(v.get$numVal()); + case HEX: + return Collections.singletonList(v.get$hexVal()); + case DATETIME: + return Collections.singletonList(v.get$dateTimeVal()); + case TIME: + return Collections.singletonList(v.get$timeVal()); + case BOOL: + return Collections.singletonList(v.get$boolean()); + case STR_CAST: + return evaluateValue(v.get$strCast(), identifiable).stream() + .map(String::valueOf).collect(Collectors.toList()); + case NUM_CAST: + return evaluateValue(v.get$numCast(), identifiable).stream() + .map(String::valueOf) + .map(this::parseDoubleOrNull) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + default: + return Collections.emptyList(); } - if (sv.get$strCast() != null) { - return evaluateValue(sv.get$strCast(), identifiable).stream().map(String::valueOf).collect(Collectors.toList()); + } + + + private List evaluateStringValue(StringValue sv, Identifiable identifiable) { + switch (determineStringValueKind(sv)) { + case FIELD: + return nonNull(getFieldValues(sv.get$field(), identifiable)); + case STR: + return Collections.singletonList(sv.get$strVal()); + case STR_CAST: + return evaluateValue(sv.get$strCast(), identifiable).stream() + .map(String::valueOf) + .collect(Collectors.toList()); + default: + LOGGER.error("Invalid string value: {}", sv); + return Collections.emptyList(); } - LOGGER.error("Invalid string value: {}", sv); - return Collections.emptyList(); } @@ -227,49 +314,87 @@ private Double parseDoubleOrNull(String s) { } } + + private Double toDouble(Object o) { + if (o instanceof Number) { + return ((Number) o).doubleValue(); + } + return parseDoubleOrNull(String.valueOf(o)); + } + private static final class Condition { final String suffix; // e.g., ".name", "Sub.Path#value" - final Operator op; + final ComparisonOperator operator; final List rightVals; - Condition(String suffix, Operator op, List rightVals) { + Condition(String suffix, ComparisonOperator operator, List rightVals) { this.suffix = suffix; - this.op = op; + this.operator = operator; this.rightVals = rightVals != null ? rightVals : Collections.emptyList(); } } - private static final class MatchOp { - final Operator op; + private static final class MatchOperation { + final ComparisonOperator operator; final List args; - MatchOp(Operator op, List args) { - this.op = op; + MatchOperation(ComparisonOperator operator, List args) { + this.operator = operator; this.args = args; } } + private static final class MatchEvaluationContext { + final String commonPrefix; + final List itemConditions; + final boolean directMismatch; + + MatchEvaluationContext(String commonPrefix, List itemConditions, boolean directMismatch) { + this.commonPrefix = commonPrefix; + this.itemConditions = itemConditions; + this.directMismatch = directMismatch; + } + } + private boolean evaluateMatch(List matches, Identifiable identifiable) { if (matches == null || matches.isEmpty()) { return true; } + MatchEvaluationContext ctx = buildMatchEvaluationContext(matches, identifiable); + if (ctx.directMismatch) { + return false; + } + if (ctx.commonPrefix == null) { + return true; + } + return evaluateListMatch(ctx.commonPrefix, ctx.itemConditions, identifiable); + } + + private MatchEvaluationContext buildMatchEvaluationContext(List matches, Identifiable identifiable) { String commonPrefix = null; List itemConditions = new ArrayList<>(); + boolean directMismatch = false; - // Evaluate/collect each match clause for (MatchExpression m: matches) { - MatchOp mo = getMatchOp(m); + MatchOperation mo = getMatchOperation(m); if (mo == null) { LOGGER.error("Unsupported operator in match"); - return false; + directMismatch = true; + break; + } + if (mo.args.size() < 2) { + LOGGER.error("$match operator {} requires two arguments", mo.operator); + directMismatch = true; + break; } Value left = mo.args.get(0); Value right = mo.args.get(1); if (left.get$field() == null) { LOGGER.error("Left side in $match must be a field: {}", left); - return false; + directMismatch = true; + break; } String field = left.get$field(); @@ -281,16 +406,19 @@ private boolean evaluateMatch(List matches, Identifiable identi String prefix = PREFIX_SME; if (commonPrefix != null && !commonPrefix.equals(prefix)) { LOGGER.error("Non-common prefix in match: {} vs {}", commonPrefix, prefix); - return false; + directMismatch = true; + break; } commonPrefix = prefix; String suffix = field.substring((PREFIX_SME + "#").length()); - itemConditions.add(new Condition(suffix, mo.op, rightVals)); + itemConditions.add(new Condition(suffix, mo.operator, rightVals)); } else { + // evaluate parent condition immediately List leftVals = evaluateValue(left, identifiable); - if (!anyPairMatches(leftVals, rightVals, mo.op)) { - return false; + if (!anyPairSatisfies(leftVals, rightVals, mo.operator)) { + directMismatch = true; + break; } } } @@ -298,20 +426,20 @@ private boolean evaluateMatch(List matches, Identifiable identi String prefix = field.substring(0, listMarker); if (commonPrefix != null && !commonPrefix.equals(prefix)) { LOGGER.error("Non-common prefix in match: {} vs {}", commonPrefix, prefix); - return false; + directMismatch = true; + break; } commonPrefix = prefix; String suffix = field.substring(listMarker + 2); - itemConditions.add(new Condition(suffix, mo.op, rightVals)); + itemConditions.add(new Condition(suffix, mo.operator, rightVals)); } } - // parent conditions: all matched - if (commonPrefix == null) { - return true; - } + return new MatchEvaluationContext(commonPrefix, itemConditions, directMismatch); + } - // Evaluate list items depending on prefix + + private boolean evaluateListMatch(String commonPrefix, List itemConditions, Identifiable identifiable) { switch (commonPrefix) { case "$aas#assetInformation.specificAssetIds": if (!(identifiable instanceof AssetAdministrationShell)) @@ -321,8 +449,8 @@ private boolean evaluateMatch(List matches, Identifiable identi return false; for (SpecificAssetId item: aas.getAssetInformation().getSpecificAssetIds()) { - if (allItemConditionsMatch(itemConditions, cond -> { - String s = getPropertyFromObject(item, cond.suffix); + if (doAllItemConditionsMatch(itemConditions, cond -> { + String s = getSpecificAssetIdAttribute(item, cond.suffix); return s == null ? Collections.emptyList() : Collections.singletonList(s); })) { return true; @@ -339,7 +467,7 @@ private boolean evaluateMatch(List matches, Identifiable identi return false; for (SubmodelElement item: topLevel) { - if (allItemConditionsMatch(itemConditions, cond -> getPropertyFromSuffix(item, cond.suffix))) { + if (doAllItemConditionsMatch(itemConditions, cond -> getPropertyValuesFromSuffix(item, cond.suffix))) { return true; } } @@ -360,7 +488,7 @@ private boolean evaluateMatch(List matches, Identifiable identi return false; for (SubmodelElement item: items) { - if (allItemConditionsMatch(itemConditions, cond -> getPropertyFromSuffix(item, cond.suffix))) { + if (doAllItemConditionsMatch(itemConditions, cond -> getPropertyValuesFromSuffix(item, cond.suffix))) { return true; } } @@ -372,28 +500,28 @@ private boolean evaluateMatch(List matches, Identifiable identi } - private MatchOp getMatchOp(MatchExpression m) { - if (m.get$eq() != null && !m.get$eq().isEmpty()) - return new MatchOp(Operator.EQ, m.get$eq()); - if (m.get$ne() != null && !m.get$ne().isEmpty()) - return new MatchOp(Operator.NE, m.get$ne()); - if (m.get$gt() != null && !m.get$gt().isEmpty()) - return new MatchOp(Operator.GT, m.get$gt()); - if (m.get$ge() != null && !m.get$ge().isEmpty()) - return new MatchOp(Operator.GE, m.get$ge()); - if (m.get$lt() != null && !m.get$lt().isEmpty()) - return new MatchOp(Operator.LT, m.get$lt()); - if (m.get$le() != null && !m.get$le().isEmpty()) - return new MatchOp(Operator.LE, m.get$le()); + private MatchOperation getMatchOperation(MatchExpression m) { + List candidates = Arrays.asList( + new MatchOperation(ComparisonOperator.EQ, m.get$eq()), + new MatchOperation(ComparisonOperator.NE, m.get$ne()), + new MatchOperation(ComparisonOperator.GT, m.get$gt()), + new MatchOperation(ComparisonOperator.GE, m.get$ge()), + new MatchOperation(ComparisonOperator.LT, m.get$lt()), + new MatchOperation(ComparisonOperator.LE, m.get$le())); + for (MatchOperation mo: candidates) { + if (mo.args != null && !mo.args.isEmpty()) { + return mo; + } + } return null; } - private boolean allItemConditionsMatch(List conditions, - java.util.function.Function> leftExtractor) { + private boolean doAllItemConditionsMatch(List conditions, + java.util.function.Function> leftValueExtractor) { for (Condition cond: conditions) { - List leftVals = nonNull(leftExtractor.apply(cond)); - if (!anyPairMatches(leftVals, cond.rightVals, cond.op)) { + List leftVals = nonNull(leftValueExtractor.apply(cond)); + if (!anyPairSatisfies(leftVals, cond.rightVals, cond.operator)) { return false; } } @@ -414,7 +542,7 @@ private List getFieldValues(String field, Identifiable identifiable) { if (field.startsWith(PREFIX_SM)) { if (!(identifiable instanceof Submodel)) return Collections.emptyList(); - return new ArrayList<>(getSmAttrValues((Submodel) identifiable, field.substring(PREFIX_SM.length()))); + return new ArrayList<>(getSubmodelAttributeValues((Submodel) identifiable, field.substring(PREFIX_SM.length()))); } if (field.startsWith(PREFIX_SME)) { @@ -437,14 +565,14 @@ private List getFieldValues(String field, Identifiable identifiable) { if (!pathPart.isEmpty()) { SubmodelElement sme = getSubmodelElementByPath(sm, pathPart); if (sme != null) { - values.addAll(getSmeAttrValues(sme, attr)); + values.addAll(getSubmodelElementAttributeValues(sme, attr)); } } else { List smes = sm.getSubmodelElements(); if (smes != null) { for (SubmodelElement sme: smes) { - values.addAll(getSmeAttrValues(sme, attr)); + values.addAll(getSubmodelElementAttributeValues(sme, attr)); } } } @@ -456,12 +584,15 @@ private List getFieldValues(String field, Identifiable identifiable) { return Collections.emptyList(); ConceptDescription cd = (ConceptDescription) identifiable; String attr = field.substring(PREFIX_CD.length()); - if ("idShort".equals(attr)) - return Collections.singletonList(cd.getIdShort()); - if ("id".equals(attr)) - return Collections.singletonList(cd.getId()); - LOGGER.error("Unsupported CD attribute: {}", attr); - return Collections.emptyList(); + switch (attr) { + case "idShort": + return Collections.singletonList(cd.getIdShort()); + case "id": + return Collections.singletonList(cd.getId()); + default: + LOGGER.error("Unsupported CD attribute: {}", attr); + return Collections.emptyList(); + } } LOGGER.error("Unsupported field: {}", field); @@ -470,147 +601,151 @@ private List getFieldValues(String field, Identifiable identifiable) { private List getAasFieldValues(AssetAdministrationShell aas, String attr) { - if ("idShort".equals(attr)) - return Collections.singletonList(aas.getIdShort()); - if ("id".equals(attr)) - return Collections.singletonList(aas.getId()); - - if ("assetInformation.assetKind".equals(attr)) { - return aas.getAssetInformation() == null || aas.getAssetInformation().getAssetKind() == null - ? Collections.emptyList() - : Collections.singletonList(aas.getAssetInformation().getAssetKind().name()); - } - if ("assetInformation.assetType".equals(attr)) { - return aas.getAssetInformation() == null - ? Collections.emptyList() - : Collections.singletonList(aas.getAssetInformation().getAssetType()); - } - if ("assetInformation.globalAssetId".equals(attr)) { - if (aas.getAssetInformation() == null) + switch (attr) { + case "idShort": + return Collections.singletonList(aas.getIdShort()); + case "id": + return Collections.singletonList(aas.getId()); + case "assetInformation.assetKind": + return (aas.getAssetInformation() == null || aas.getAssetInformation().getAssetKind() == null) + ? Collections.emptyList() + : Collections.singletonList(aas.getAssetInformation().getAssetKind().name()); + case "assetInformation.assetType": + return (aas.getAssetInformation() == null) + ? Collections.emptyList() + : Collections.singletonList(aas.getAssetInformation().getAssetType()); + case "assetInformation.globalAssetId": + if (aas.getAssetInformation() == null) + return Collections.emptyList(); + String globalAssetId = aas.getAssetInformation().getGlobalAssetId(); + return globalAssetId == null ? Collections.emptyList() : Collections.singletonList(globalAssetId); + default: + if (attr.startsWith("assetInformation.specificAssetIds")) { + if (aas.getAssetInformation() == null || aas.getAssetInformation().getSpecificAssetIds() == null) { + return Collections.emptyList(); + } + String remaining = attr.substring("assetInformation.specificAssetIds".length()); + IndexSelection indexSelection = parseIndexSelection(remaining); + List sais = aas.getAssetInformation().getSpecificAssetIds(); + List selectedItems = selectByIndex(sais, indexSelection); + List values = new ArrayList<>(); + for (SpecificAssetId sai: selectedItems) { + values.add(getSpecificAssetIdAttribute(sai, indexSelection.remainingSuffix)); + } + return values; + } + LOGGER.error("Unsupported AAS attribute: {}", attr); return Collections.emptyList(); - String globalAssetId = aas.getAssetInformation().getGlobalAssetId(); - return globalAssetId == null ? Collections.emptyList() : Collections.singletonList(globalAssetId); } - - if (attr.startsWith("assetInformation.specificAssetIds")) { - if (aas.getAssetInformation() == null || aas.getAssetInformation().getSpecificAssetIds() == null) { - return Collections.emptyList(); - } - String remaining = attr.substring("assetInformation.specificAssetIds".length()); - IndexSpec spec = parseIndexSuffix(remaining); - - List sais = aas.getAssetInformation().getSpecificAssetIds(); - List targets = selectTargets(sais, spec); - - List values = new ArrayList<>(); - for (SpecificAssetId sai: targets) { - values.add(getPropertyFromObject(sai, spec.remaining)); - } - return values; - } - - LOGGER.error("Unsupported AAS attribute: {}", attr); - return Collections.emptyList(); } - private List getSmAttrValues(Submodel sm, String attr) { - if ("idShort".equals(attr)) - return Collections.singletonList(sm.getIdShort()); - if ("id".equals(attr)) - return Collections.singletonList(sm.getId()); + private List getSubmodelAttributeValues(Submodel sm, String attr) { + switch (attr) { + case "idShort": + return Collections.singletonList(sm.getIdShort()); + case "id": + return Collections.singletonList(sm.getId()); + case "semanticId": { + Reference ref = sm.getSemanticId(); + if (ref == null || ref.getKeys() == null || ref.getKeys().isEmpty()) + return Collections.emptyList(); + return Collections.singletonList(ref.getKeys().get(0).getValue()); + } + default: + if (attr.startsWith("semanticId.keys")) { + Reference ref = sm.getSemanticId(); + if (ref == null || ref.getKeys() == null) + return Collections.emptyList(); - if ("semanticId".equals(attr)) { - Reference ref = sm.getSemanticId(); - if (ref == null || ref.getKeys() == null || ref.getKeys().isEmpty()) - return Collections.emptyList(); - return Collections.singletonList(ref.getKeys().get(0).getValue()); - } + String remaining = attr.substring("semanticId.keys".length()); + IndexSelection indexSelection = parseIndexSelection(remaining); - if (attr.startsWith("semanticId.keys")) { - Reference ref = sm.getSemanticId(); - if (ref == null || ref.getKeys() == null) + List selectedItems = selectByIndex(ref.getKeys(), indexSelection); + List results = extractKeyAttributeValues(selectedItems, indexSelection); + return results; + } + LOGGER.error("Unsupported SM attribute: {}", attr); return Collections.emptyList(); - - String remaining = attr.substring("semanticId.keys".length()); - IndexSpec spec = parseIndexSuffix(remaining); - - List targets = selectTargets(ref.getKeys(), spec); - List out = new ArrayList<>(); - for (Key key: targets) { - if (".type".equals(spec.remaining)) - out.add(key.getType().name()); - else if (".value".equals(spec.remaining)) - out.add(key.getValue()); - } - return out; } - - LOGGER.error("Unsupported SM attribute: {}", attr); - return Collections.emptyList(); } - private List getSmeAttrValues(SubmodelElement sme, String attr) { + private List getSubmodelElementAttributeValues(SubmodelElement sme, String attr) { if (sme == null || attr == null) return Collections.emptyList(); - if ("idShort".equals(attr)) { - return Collections.singletonList(sme.getIdShort()); - } - if ("value".equals(attr)) { - if (sme instanceof Property) { - return Collections.singletonList(((Property) sme).getValue()); - } - return Collections.emptyList(); - } - if ("valueType".equals(attr)) { - if (sme instanceof Property && ((Property) sme).getValueType() != null) { - return Collections.singletonList(((Property) sme).getValueType().name()); - } - return Collections.emptyList(); - } - if ("language".equals(attr)) { - if (sme instanceof MultiLanguageProperty) { - List values = ((MultiLanguageProperty) sme).getValue(); - if (values == null) + switch (attr) { + case "idShort": + return Collections.singletonList(sme.getIdShort()); + case "value": + if (sme instanceof Property) { + return Collections.singletonList(((Property) sme).getValue()); + } + return Collections.emptyList(); + case "valueType": + if (sme instanceof Property && ((Property) sme).getValueType() != null) { + return Collections.singletonList(((Property) sme).getValueType().name()); + } + return Collections.emptyList(); + case "language": + if (sme instanceof MultiLanguageProperty) { + List values = ((MultiLanguageProperty) sme).getValue(); + if (values == null) + return Collections.emptyList(); + return values.stream() + .filter(Objects::nonNull) + .map(LangStringTextType::getLanguage) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + case "semanticId": { + Reference ref = sme.getSemanticId(); + if (ref == null || ref.getKeys() == null || ref.getKeys().isEmpty()) return Collections.emptyList(); - return values.stream().filter(Objects::nonNull).map(LangStringTextType::getLanguage).collect(Collectors.toList()); + return Collections.singletonList(ref.getKeys().get(0).getValue()); } - return Collections.emptyList(); - } - if ("semanticId".equals(attr)) { - Reference ref = sme.getSemanticId(); - if (ref == null || ref.getKeys() == null || ref.getKeys().isEmpty()) + default: + if (attr.startsWith("semanticId.keys")) { + Reference ref = sme.getSemanticId(); + if (ref == null || ref.getKeys() == null) + return Collections.emptyList(); + + String remaining = attr.substring("semanticId.keys".length()); + IndexSelection indexSelection = parseIndexSelection(remaining); + + List selectedItems = selectByIndex(ref.getKeys(), indexSelection); + List results = extractKeyAttributeValues(selectedItems, indexSelection); + return results; + } + LOGGER.error("Unsupported SME attribute: {}", attr); return Collections.emptyList(); - return Collections.singletonList(ref.getKeys().get(0).getValue()); } - if (attr.startsWith("semanticId.keys")) { - Reference ref = sme.getSemanticId(); - if (ref == null || ref.getKeys() == null) - return Collections.emptyList(); + } - String remaining = attr.substring("semanticId.keys".length()); - IndexSpec spec = parseIndexSuffix(remaining); - List targets = selectTargets(ref.getKeys(), spec); - List out = new ArrayList<>(); - for (Key key: targets) { - if (".type".equals(spec.remaining)) - out.add(key.getType().name()); - else if (".value".equals(spec.remaining)) - out.add(key.getValue()); + private static List extractKeyAttributeValues(List selectedItems, IndexSelection selector) { + List results = new ArrayList<>(); + for (Key key: selectedItems) { + switch (selector.remainingSuffix) { + case ".type": + results.add(key.getType().name()); + break; + case ".value": + results.add(key.getValue()); + break; + default: + break; } - return out; } - - LOGGER.error("Unsupported SME attribute: {}", attr); - return Collections.emptyList(); + return results; } /** + * * Resolve a submodel element by dot-separated path. + * */ private SubmodelElement getSubmodelElementByPath(Submodel sm, String path) { if (sm == null || path == null || path.isEmpty()) @@ -649,7 +784,7 @@ private SubmodelElement findByIdShort(List elements, String idS } - private String getPropertyFromObject(Object item, String path) { + private String getSpecificAssetIdAttribute(Object item, String path) { if (!(item instanceof SpecificAssetId) || path == null) { LOGGER.error("Unsupported property {} for object {}", path, item); return null; @@ -670,7 +805,7 @@ private String getPropertyFromObject(Object item, String path) { } - private List getPropertyFromSuffix(SubmodelElement item, String suffix) { + private List getPropertyValuesFromSuffix(SubmodelElement item, String suffix) { if (item == null || suffix == null) return Collections.emptyList(); @@ -682,15 +817,15 @@ private List getPropertyFromSuffix(SubmodelElement item, String suffix) subPath = normalized.substring(0, hashPos); attr = normalized.substring(hashPos + 1); } - SubmodelElement target = getSubmodelElementByPathForItem(item, subPath); + SubmodelElement target = resolveRelativeSubmodelElementPath(item, subPath); if (target == null) { return Collections.emptyList(); } - return new ArrayList<>(getSmeAttrValues(target, attr)); + return new ArrayList<>(getSubmodelElementAttributeValues(target, attr)); } - private SubmodelElement getSubmodelElementByPathForItem(SubmodelElement item, String path) { + private SubmodelElement resolveRelativeSubmodelElementPath(SubmodelElement item, String path) { if (item == null || path == null || path.isEmpty()) return item; @@ -711,24 +846,24 @@ private SubmodelElement getSubmodelElementByPathForItem(SubmodelElement item, St } - private boolean compare(Object a, Object b, Operator op) { - if (op == null) + private boolean compareValues(Object a, Object b, ComparisonOperator operator) { + if (operator == null) return false; - if (op.isStringOp()) { - return compareStrings(a, b, op); + if (operator.isStringOperator()) { + return compareUsingStringOperator(a, b, operator); } - return compareGeneral(a, b, op); + return compareUsingGeneralComparison(a, b, operator); } - private boolean compareStrings(Object a, Object b, Operator op) { + private boolean compareUsingStringOperator(Object a, Object b, ComparisonOperator operator) { if (a == null || b == null) return false; String left = String.valueOf(a); String right = String.valueOf(b); - switch (op) { + switch (operator) { case CONTAINS: return left.contains(right); case STARTS_WITH: @@ -743,20 +878,22 @@ private boolean compareStrings(Object a, Object b, Operator op) { } - private boolean compareGeneral(Object a, Object b, Operator op) { + private boolean compareUsingGeneralComparison(Object a, Object b, ComparisonOperator operator) { if (a == null || b == null) { - return (op == Operator.EQ) ? a == b - : (op == Operator.NE) && a != b; + return (operator == ComparisonOperator.EQ) + ? Objects.equals(a, b) + : (operator == ComparisonOperator.NE) && !Objects.equals(a, b); } - try { - double d1 = (a instanceof Number) ? ((Number) a).doubleValue() : Double.parseDouble(String.valueOf(a)); - double d2 = (b instanceof Number) ? ((Number) b).doubleValue() : Double.parseDouble(String.valueOf(b)); - switch (op) { + // try numeric + Double d1 = toDouble(a); + Double d2 = toDouble(b); + if (d1 != null && d2 != null) { + switch (operator) { case EQ: - return d1 == d2; + return Double.compare(d1, d2) == 0; case NE: - return d1 != d2; + return Double.compare(d1, d2) != 0; case GT: return d1 > d2; case GE: @@ -769,25 +906,26 @@ private boolean compareGeneral(Object a, Object b, Operator op) { return false; } } - catch (NumberFormatException ignored) { - // left blank intentionally - } - // boolean + // try boolean String sa = String.valueOf(a).trim(); String sb = String.valueOf(b).trim(); - boolean ba = "true".equalsIgnoreCase(sa) || "false".equalsIgnoreCase(sa); - boolean bb = "true".equalsIgnoreCase(sb) || "false".equalsIgnoreCase(sb); - if (ba && bb) { - boolean b1 = Boolean.parseBoolean(sa); - boolean b2 = Boolean.parseBoolean(sb); - return (op == Operator.EQ) ? (b1 == b2) - : (op == Operator.NE) && (b1 != b2); + Boolean ba = parseBooleanStrict(sa); + Boolean bb = parseBooleanStrict(sb); + if (ba != null && bb != null) { + switch (operator) { + case EQ: + return Objects.equals(ba, bb); + case NE: + return !Objects.equals(ba, bb); + default: + return false; + } } - // string + // string comparison int cmp = sa.compareTo(sb); - switch (op) { + switch (operator) { case EQ: return cmp == 0; case NE: @@ -806,25 +944,34 @@ private boolean compareGeneral(Object a, Object b, Operator op) { } + private static Boolean parseBooleanStrict(String s) { + if ("true".equalsIgnoreCase(s)) + return Boolean.TRUE; + if ("false".equalsIgnoreCase(s)) + return Boolean.FALSE; + return null; + } + + private static List nonNull(List in) { return in != null ? in : Collections.emptyList(); } /** - * @param remaining remaining suffix (e.g., ".name") + * @param remainingSuffix remaining suffix (e.g., ".name") */ - private record IndexSpec(boolean any, Integer index, String remaining) {} + private record IndexSelection(boolean selectAll, Integer index, String remainingSuffix) {} - private IndexSpec parseIndexSuffix(String s) { + private IndexSelection parseIndexSelection(String s) { if (s == null || s.isEmpty()) { - return new IndexSpec(true, null, ""); + return new IndexSelection(true, null, ""); } String rem = s; - boolean any = false; + boolean selectAll = false; Integer idx = null; if (rem.startsWith("[]")) { - any = true; + selectAll = true; rem = rem.substring(2); } else if (rem.startsWith("[")) { @@ -832,7 +979,7 @@ else if (rem.startsWith("[")) { if (end > 1) { String idxStr = rem.substring(1, end); if (idxStr.isEmpty()) { - any = true; + selectAll = true; } else { try { @@ -840,22 +987,22 @@ else if (rem.startsWith("[")) { } catch (NumberFormatException e) { LOGGER.error("Invalid index in path: {}", s); - return new IndexSpec(true, null, rem.substring(end + 1)); + return new IndexSelection(true, null, rem.substring(end + 1)); } } rem = rem.substring(end + 1); } } - return new IndexSpec(any, idx, rem); + return new IndexSelection(selectAll, idx, rem); } - private List selectTargets(List list, IndexSpec spec) { + private List selectByIndex(List list, IndexSelection selector) { if (list == null || list.isEmpty()) return Collections.emptyList(); - if (spec.any || spec.index == null) + if (selector.selectAll || selector.index == null) return list; - int i = spec.index; + int i = selector.index; return (i >= 0 && i < list.size()) ? Collections.singletonList(list.get(i)) : Collections.emptyList(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index e69dc7d42..cffbb3d3c 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -327,9 +327,8 @@ && getAttributes(acl, allAccess) != null && getObjects(rule, allAccess) != null && getObjects(rule, allAccess).stream().anyMatch(attr -> { if (attr.getRoute() != null) { - // route is a list of allowed routes; support wildcard "*" - List routes = attr.getRoute(); - return routes.contains("*") || routes.contains(path); + String route = attr.getRoute(); + return route.contains("*") || route.contains(path); } else if (attr.getIdentifiable() != null) { return checkIdentifiable(path, attr.getIdentifiable()); From 5fe39bccd32b4bf605fcd1bc7b5433eef8da4f6a Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 11:44:20 +0100 Subject: [PATCH 062/108] checkstyle --- .../iosb/ilt/faaast/service/query/QueryEvaluator.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index d84adad02..d7eda41f8 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -98,12 +98,10 @@ private enum StringValueKind { } /** - * * Used to decide whether to filter out the Identifiable. * * @param expr logical expression (tree) * @param identifiable AAS | Submodel | ConceptDescription - * * @return true if expression matches the identifiable * */ @@ -743,9 +741,7 @@ private static List extractKeyAttributeValues(List selectedItems, I /** - * * Resolve a submodel element by dot-separated path. - * */ private SubmodelElement getSubmodelElementByPath(Submodel sm, String path) { if (sm == null || path == null || path.isEmpty()) From fdbea22455aad189526d177768355cdd3ce2d8bf Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 11:47:22 +0100 Subject: [PATCH 063/108] checkstyle apigateway --- .../http/security/filter/ApiGateway.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index cffbb3d3c..3caff903a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -79,12 +79,10 @@ public ApiGateway(String aclFolder) { /** - * * Checks if the user is authorized to receive the response of the request. * * @param request the HttpRequest * @return true if authorized and ACL exists - * */ public boolean isAuthorized(HttpServletRequest request) { String token = request.getHeader("Authorization"); @@ -102,14 +100,11 @@ public boolean isAuthorized(HttpServletRequest request) { /** - * * Filters out AAS that the user is not authorized for. * * @param request the HttpRequest * @param response the ApiResponse - * * @return the ApiResponse with only allowed AAS - * */ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { response.getPayload().getContent() @@ -122,14 +117,11 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS /** - * * Filters out Submodels that the user is not authorized for. * * @param request the HttpRequest * @param response the ApiResponse - * * @return the ApiResponse with only allowed Submodels - * */ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { response.getPayload().getContent() @@ -159,28 +151,20 @@ private Map extractClaims(HttpServletRequest request) { } /** - * * Simple whitelist AuthServer implementation that supports ANONYMOUS access, * claims with simple eq formulas and route authorization. - * * Access must be explicitly defined, otherwise it is blocked. - * */ public static class AuthServer { private static final String apiPrefix = "/api/v3.0/"; /** - * * Check all rules that explicitly allows the request. * If a rule exists after all filters, true is returned * - * - * * @param claims * @param request - * * @return - * */ private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { String requestPath = request.getRequestURI(); From 50ed54e9568192b0bf22d0141e947d4555e15568 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 12:55:57 +0100 Subject: [PATCH 064/108] revert route comparison --- .../service/endpoint/http/security/filter/ApiGateway.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 3caff903a..4b153cf3a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -311,8 +311,7 @@ && getAttributes(acl, allAccess) != null && getObjects(rule, allAccess) != null && getObjects(rule, allAccess).stream().anyMatch(attr -> { if (attr.getRoute() != null) { - String route = attr.getRoute(); - return route.contains("*") || route.contains(path); + return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); } else if (attr.getIdentifiable() != null) { return checkIdentifiable(path, attr.getIdentifiable()); From bb94811be1ce81aa35c53a0319998e4250ae2d94 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 12:58:00 +0100 Subject: [PATCH 065/108] fix equals with non-string --- .../service/endpoint/http/security/filter/ApiGateway.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 4b153cf3a..731a625c9 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -181,7 +181,7 @@ private static boolean filterRules(Map aclList, private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess) { Acl acl = getAcl(rule, allAccess); if (getAttributes(acl, allAccess).stream() - .anyMatch(attr -> "ANONYMOUS".equals(attr.getGlobal()) + .anyMatch(attr -> "ANONYMOUS".equals(attr.getGlobal().value()) && Boolean.TRUE.equals(getFormula(rule, allAccess).get$boolean()))) { return true; } @@ -323,7 +323,7 @@ else if (attr.getDescriptor() != null) { return false; } }) - && "ALLOW".equals(acl.getAccess()) + && "ALLOW".equals(acl.getAccess().value()) && evaluateRights(acl.getRights(), method, path) && verifyAllClaims(claims, rule, allAccess); } From 52f9ce38747c146901076b58fa4ca849cc7a2832 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 13:10:26 +0100 Subject: [PATCH 066/108] refactoring --- .../faaast/service/query/QueryEvaluator.java | 208 +++++++----------- .../service/query/QueryEvaluatorTest.java | 16 +- .../service/security/AccessRuleTest.java | 8 +- .../memory/PersistenceInMemory.java | 6 +- 4 files changed, 92 insertions(+), 146 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index d7eda41f8..7426f88e1 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -28,7 +28,6 @@ import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; -import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; import org.eclipse.digitaltwin.aas4j.v3.model.Key; import org.eclipse.digitaltwin.aas4j.v3.model.LangStringTextType; @@ -54,11 +53,8 @@ public class QueryEvaluator { private static final String PREFIX_SM = "$sm#"; private static final String PREFIX_SME = "$sme"; private static final String PREFIX_CD = "$cd#"; - private final Environment environment; - public QueryEvaluator(Environment environment) { - this.environment = environment; - } + public QueryEvaluator() {} private enum ComparisonOperator { EQ, @@ -256,50 +252,38 @@ private StringValueKind determineStringValueKind(StringValue sv) { private List evaluateValue(Value v, Identifiable identifiable) { - switch (determineValueKind(v)) { - case FIELD: - return nonNull(getFieldValues(v.get$field(), identifiable)); - case STR: - return Collections.singletonList(v.get$strVal()); - case NUM: - return Collections.singletonList(v.get$numVal()); - case HEX: - return Collections.singletonList(v.get$hexVal()); - case DATETIME: - return Collections.singletonList(v.get$dateTimeVal()); - case TIME: - return Collections.singletonList(v.get$timeVal()); - case BOOL: - return Collections.singletonList(v.get$boolean()); - case STR_CAST: - return evaluateValue(v.get$strCast(), identifiable).stream() - .map(String::valueOf).collect(Collectors.toList()); - case NUM_CAST: - return evaluateValue(v.get$numCast(), identifiable).stream() - .map(String::valueOf) - .map(this::parseDoubleOrNull) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - default: - return Collections.emptyList(); - } + return switch (determineValueKind(v)) { + case FIELD -> nonNull(getFieldValues(v.get$field(), identifiable)); + case STR -> Collections.singletonList(v.get$strVal()); + case NUM -> Collections.singletonList(v.get$numVal()); + case HEX -> Collections.singletonList(v.get$hexVal()); + case DATETIME -> Collections.singletonList(v.get$dateTimeVal()); + case TIME -> Collections.singletonList(v.get$timeVal()); + case BOOL -> Collections.singletonList(v.get$boolean()); + case STR_CAST -> evaluateValue(v.get$strCast(), identifiable).stream() + .map(String::valueOf).collect(Collectors.toList()); + case NUM_CAST -> evaluateValue(v.get$numCast(), identifiable).stream() + .map(String::valueOf) + .map(this::parseDoubleOrNull) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + default -> Collections.emptyList(); + }; } private List evaluateStringValue(StringValue sv, Identifiable identifiable) { - switch (determineStringValueKind(sv)) { - case FIELD: - return nonNull(getFieldValues(sv.get$field(), identifiable)); - case STR: - return Collections.singletonList(sv.get$strVal()); - case STR_CAST: - return evaluateValue(sv.get$strCast(), identifiable).stream() - .map(String::valueOf) - .collect(Collectors.toList()); - default: + return switch (determineStringValueKind(sv)) { + case FIELD -> nonNull(getFieldValues(sv.get$field(), identifiable)); + case STR -> Collections.singletonList(sv.get$strVal()); + case STR_CAST -> evaluateValue(sv.get$strCast(), identifiable).stream() + .map(String::valueOf) + .collect(Collectors.toList()); + default -> { LOGGER.error("Invalid string value: {}", sv); - return Collections.emptyList(); - } + yield Collections.emptyList(); + } + }; } @@ -440,9 +424,8 @@ private MatchEvaluationContext buildMatchEvaluationContext(List private boolean evaluateListMatch(String commonPrefix, List itemConditions, Identifiable identifiable) { switch (commonPrefix) { case "$aas#assetInformation.specificAssetIds": - if (!(identifiable instanceof AssetAdministrationShell)) + if (!(identifiable instanceof AssetAdministrationShell aas)) return false; - AssetAdministrationShell aas = (AssetAdministrationShell) identifiable; if (aas.getAssetInformation() == null || aas.getAssetInformation().getSpecificAssetIds() == null) return false; @@ -457,9 +440,8 @@ private boolean evaluateListMatch(String commonPrefix, List itemCondi return false; case PREFIX_SME: - if (!(identifiable instanceof Submodel)) + if (!(identifiable instanceof Submodel sm)) return false; - Submodel sm = (Submodel) identifiable; List topLevel = sm.getSubmodelElements(); if (topLevel == null) return false; @@ -473,9 +455,8 @@ private boolean evaluateListMatch(String commonPrefix, List itemCondi default: if (commonPrefix.startsWith(PREFIX_SME + ".")) { - if (!(identifiable instanceof Submodel)) + if (!(identifiable instanceof Submodel sm2)) return false; - Submodel sm2 = (Submodel) identifiable; String path = commonPrefix.substring((PREFIX_SME + ".").length()); SubmodelElement listElem = getSubmodelElementByPath(sm2, path); if (!(listElem instanceof SubmodelElementList)) @@ -544,9 +525,8 @@ private List getFieldValues(String field, Identifiable identifiable) { } if (field.startsWith(PREFIX_SME)) { - if (!(identifiable instanceof Submodel)) + if (!(identifiable instanceof Submodel sm)) return Collections.emptyList(); - Submodel sm = (Submodel) identifiable; String pathPart = ""; String attr; @@ -578,19 +558,17 @@ private List getFieldValues(String field, Identifiable identifiable) { } if (field.startsWith(PREFIX_CD)) { - if (!(identifiable instanceof ConceptDescription)) + if (!(identifiable instanceof ConceptDescription cd)) return Collections.emptyList(); - ConceptDescription cd = (ConceptDescription) identifiable; String attr = field.substring(PREFIX_CD.length()); - switch (attr) { - case "idShort": - return Collections.singletonList(cd.getIdShort()); - case "id": - return Collections.singletonList(cd.getId()); - default: + return switch (attr) { + case "idShort" -> Collections.singletonList(cd.getIdShort()); + case "id" -> Collections.singletonList(cd.getId()); + default -> { LOGGER.error("Unsupported CD attribute: {}", attr); - return Collections.emptyList(); - } + yield Collections.emptyList(); + } + }; } LOGGER.error("Unsupported field: {}", field); @@ -660,8 +638,7 @@ private List getSubmodelAttributeValues(Submodel sm, String attr) { IndexSelection indexSelection = parseIndexSelection(remaining); List selectedItems = selectByIndex(ref.getKeys(), indexSelection); - List results = extractKeyAttributeValues(selectedItems, indexSelection); - return results; + return extractKeyAttributeValues(selectedItems, indexSelection); } LOGGER.error("Unsupported SM attribute: {}", attr); return Collections.emptyList(); @@ -713,8 +690,7 @@ private List getSubmodelElementAttributeValues(SubmodelElement sme, Stri IndexSelection indexSelection = parseIndexSelection(remaining); List selectedItems = selectByIndex(ref.getKeys(), indexSelection); - List results = extractKeyAttributeValues(selectedItems, indexSelection); - return results; + return extractKeyAttributeValues(selectedItems, indexSelection); } LOGGER.error("Unsupported SME attribute: {}", attr); return Collections.emptyList(); @@ -781,11 +757,10 @@ private SubmodelElement findByIdShort(List elements, String idS private String getSpecificAssetIdAttribute(Object item, String path) { - if (!(item instanceof SpecificAssetId) || path == null) { + if (!(item instanceof SpecificAssetId sai) || path == null) { LOGGER.error("Unsupported property {} for object {}", path, item); return null; } - SpecificAssetId sai = (SpecificAssetId) item; switch (path) { case ".name": return sai.getName(); @@ -859,18 +834,13 @@ private boolean compareUsingStringOperator(Object a, Object b, ComparisonOperato String left = String.valueOf(a); String right = String.valueOf(b); - switch (operator) { - case CONTAINS: - return left.contains(right); - case STARTS_WITH: - return left.startsWith(right); - case ENDS_WITH: - return left.endsWith(right); - case REGEX: - return Pattern.compile(right).matcher(left).matches(); - default: - return false; - } + return switch (operator) { + case CONTAINS -> left.contains(right); + case STARTS_WITH -> left.startsWith(right); + case ENDS_WITH -> left.endsWith(right); + case REGEX -> Pattern.compile(right).matcher(left).matches(); + default -> false; + }; } @@ -885,22 +855,15 @@ private boolean compareUsingGeneralComparison(Object a, Object b, ComparisonOper Double d1 = toDouble(a); Double d2 = toDouble(b); if (d1 != null && d2 != null) { - switch (operator) { - case EQ: - return Double.compare(d1, d2) == 0; - case NE: - return Double.compare(d1, d2) != 0; - case GT: - return d1 > d2; - case GE: - return d1 >= d2; - case LT: - return d1 < d2; - case LE: - return d1 <= d2; - default: - return false; - } + return switch (operator) { + case EQ -> Double.compare(d1, d2) == 0; + case NE -> Double.compare(d1, d2) != 0; + case GT -> d1 > d2; + case GE -> d1 >= d2; + case LT -> d1 < d2; + case LE -> d1 <= d2; + default -> false; + }; } // try boolean @@ -909,34 +872,24 @@ private boolean compareUsingGeneralComparison(Object a, Object b, ComparisonOper Boolean ba = parseBooleanStrict(sa); Boolean bb = parseBooleanStrict(sb); if (ba != null && bb != null) { - switch (operator) { - case EQ: - return Objects.equals(ba, bb); - case NE: - return !Objects.equals(ba, bb); - default: - return false; - } + return switch (operator) { + case EQ -> Objects.equals(ba, bb); + case NE -> !Objects.equals(ba, bb); + default -> false; + }; } // string comparison int cmp = sa.compareTo(sb); - switch (operator) { - case EQ: - return cmp == 0; - case NE: - return cmp != 0; - case GT: - return cmp > 0; - case GE: - return cmp >= 0; - case LT: - return cmp < 0; - case LE: - return cmp <= 0; - default: - return false; - } + return switch (operator) { + case EQ -> cmp == 0; + case NE -> cmp != 0; + case GT -> cmp > 0; + case GE -> cmp >= 0; + case LT -> cmp < 0; + case LE -> cmp <= 0; + default -> false; + }; } @@ -974,17 +927,12 @@ else if (rem.startsWith("[")) { int end = rem.indexOf(']'); if (end > 1) { String idxStr = rem.substring(1, end); - if (idxStr.isEmpty()) { - selectAll = true; + try { + idx = Integer.parseInt(idxStr); } - else { - try { - idx = Integer.parseInt(idxStr); - } - catch (NumberFormatException e) { - LOGGER.error("Invalid index in path: {}", s); - return new IndexSelection(true, null, rem.substring(end + 1)); - } + catch (NumberFormatException e) { + LOGGER.error("Invalid index in path: {}", s); + return new IndexSelection(true, null, rem.substring(end + 1)); } rem = rem.substring(end + 1); } diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java index f46108c8c..bc9a0a6bb 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java @@ -231,7 +231,7 @@ public void simpleEq_withMatchingFields() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForSimpleEq(true); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); boolean result = evaluator.matches(query.get$condition(), aas); assertTrue(result); @@ -259,7 +259,7 @@ public void simpleEq_withNonMatchingFields() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForSimpleEq(false); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); boolean result = evaluator.matches(query.get$condition(), aas); assertFalse(result); @@ -292,7 +292,7 @@ public void documentsMatch_withMatchingValues() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForDocumentsMatch(true); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); Submodel submodel = env.getSubmodels().get(0); boolean result = evaluator.matches(query.get$condition(), submodel); assertTrue(result); @@ -325,7 +325,7 @@ public void documentsMatch_withNonMatchingValues() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForDocumentsMatch(false); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); Submodel submodel = env.getSubmodels().get(0); boolean result = evaluator.matches(query.get$condition(), submodel); assertFalse(result); @@ -385,7 +385,7 @@ public void andMatch_withMatchingConditions() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForAndMatch(true); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); Submodel submodel = env.getSubmodels().get(0); boolean result = evaluator.matches(query.get$condition(), submodel); assertTrue(result); @@ -445,7 +445,7 @@ public void andMatch_withNonMatchingConditions() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForAndMatch(false); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); Submodel submodel = env.getSubmodels().get(0); boolean result = evaluator.matches(query.get$condition(), submodel); assertFalse(result); @@ -498,7 +498,7 @@ public void orMatch_withMatchingSpecificAssetIds() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForOrMatch(true); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); boolean result = evaluator.matches(query.get$condition(), aas); assertTrue(result); @@ -551,7 +551,7 @@ public void orMatch_withNonMatchingSpecificAssetIds() throws Exception { json, new TypeReference<>() {}); Environment env = createTestEnvironmentForOrMatch(false); - QueryEvaluator evaluator = new QueryEvaluator(env); + QueryEvaluator evaluator = new QueryEvaluator(); AssetAdministrationShell aas = env.getAssetAdministrationShells().get(0); boolean result = evaluator.matches(query.get$condition(), aas); assertFalse(result); diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index 7b3edcd01..405e707dc 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -26,16 +26,12 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; -import org.junit.Before; +import java.util.Objects; import org.junit.Test; public class AccessRuleTest { - @Before - public void init() {} - - @Test public void testAllowAnonymousReadConstruction() { // Create ACL @@ -63,6 +59,7 @@ public void testAllowAnonymousReadConstruction() { // Wrap in AllAccessPermissionRules and the root AllAccessPermissionRules allRules = new AllAccessPermissionRules(); allRules.setRules(Arrays.asList(rule)); + assert !allRules.getRules().isEmpty(); } @@ -76,5 +73,6 @@ public void testParse() throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(inputStream); AllAccessPermissionRules allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); + assert Objects.nonNull(allRules); } } diff --git a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java index 173b935d2..a11e6fb80 100644 --- a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java +++ b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java @@ -231,7 +231,7 @@ public Page findAssetAdministrationShellsWithQuery(Ass if (criteria.isAssetIdsSet()) { result = filterByAssetIds(result, criteria.getAssetIds()); } - QueryEvaluator evaluator = new QueryEvaluator(environment); + QueryEvaluator evaluator = new QueryEvaluator(); if (query != null) { result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); } @@ -274,7 +274,7 @@ public Page findConceptDescriptionsWithQuery(ConceptDescript if (criteria.isDataSpecificationSet()) { result = filterByDataSpecification(result, criteria.getDataSpecification()); } - QueryEvaluator evaluator = new QueryEvaluator(environment); + QueryEvaluator evaluator = new QueryEvaluator(); if (query != null) { result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); } @@ -350,7 +350,7 @@ public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, Qu if (criteria.isSemanticIdSet()) { result = filterBySemanticId(result, criteria.getSemanticId()); } - QueryEvaluator evaluator = new QueryEvaluator(environment); + QueryEvaluator evaluator = new QueryEvaluator(); if (query != null) { result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); } From cf26fb9cd234daf39deae1dba54383e6ee0c0b08 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 13:20:56 +0100 Subject: [PATCH 067/108] fix json schema of idta --- .../service/security/AccessRuleTest.java | 6 ++- model/src/main/resources/schema.json | 40 ++----------------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index 405e707dc..f4352db6d 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -29,6 +29,8 @@ import java.util.Objects; import org.junit.Test; +import static org.junit.Assert.assertNotNull; + public class AccessRuleTest { @@ -59,7 +61,7 @@ public void testAllowAnonymousReadConstruction() { // Wrap in AllAccessPermissionRules and the root AllAccessPermissionRules allRules = new AllAccessPermissionRules(); allRules.setRules(Arrays.asList(rule)); - assert !allRules.getRules().isEmpty(); + assertNotNull(allRules.getRules()); } @@ -73,6 +75,6 @@ public void testParse() throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(inputStream); AllAccessPermissionRules allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); - assert Objects.nonNull(allRules); + assertNotNull(allRules); } } diff --git a/model/src/main/resources/schema.json b/model/src/main/resources/schema.json index e72079d53..9362a95cd 100644 --- a/model/src/main/resources/schema.json +++ b/model/src/main/resources/schema.json @@ -661,42 +661,10 @@ "type": "string" } }, - "oneOf": [ - { - "required": [ - "ACL" - ] - }, - { - "required": [ - "USEACL" - ] - } - ], - "oneOf": [ - { - "required": [ - "OBJECTS" - ] - }, - { - "required": [ - "USEOBJECTS" - ] - } - ], - "oneOf": [ - { - "required": [ - "FORMULA" - ] - }, - { - "required": [ - "USEFORMULA" - ] - } - ], + "allOf": [ + { "oneOf": [ { "required": ["ACL"] }, { "required": ["USEACL"] } ] }, + { "oneOf": [ { "required": ["OBJECTS"] }, { "required": ["USEOBJECTS"] } ] }, + { "oneOf": [ { "required": ["FORMULA"] }, { "required": ["USEFORMULA"] } ] } ], "additionalProperties": false } }, From 326765cc08ee1655ad57b4563efa24337efbaf95 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 30 Oct 2025 13:29:44 +0100 Subject: [PATCH 068/108] spotless --- .../iosb/ilt/faaast/service/security/AccessRuleTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java index f4352db6d..843e9ef6d 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/security/AccessRuleTest.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.security; +import static org.junit.Assert.assertNotNull; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; @@ -26,11 +28,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; -import java.util.Objects; import org.junit.Test; -import static org.junit.Assert.assertNotNull; - public class AccessRuleTest { From 5a629756cac3f0dc558007e24b6827ba905404ec Mon Sep 17 00:00:00 2001 From: Tino Bischoff Date: Thu, 30 Oct 2025 18:07:56 +0100 Subject: [PATCH 069/108] several fixes --- .../http/security/filter/ApiGateway.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 731a625c9..1dfcde9c8 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -52,10 +52,12 @@ import java.time.Clock; import java.time.LocalTime; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.slf4j.LoggerFactory; @@ -464,7 +466,7 @@ else if (rule.getUseacl() != null) { private static List getAttributes(Acl acl, AllAccessPermissionRules allAccess) { - if (acl.getAttributes() != null) { + if ((acl.getAttributes() != null) && (!acl.getAttributes().isEmpty())) { return acl.getAttributes(); } else if (acl.getUseattributes() != null) { @@ -506,18 +508,23 @@ else if (rule.getUseformula() != null) { private static List getObjects(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { - if (rule.getObjects() != null) { + if ((rule.getObjects() != null) && (!rule.getObjects().isEmpty())) { return rule.getObjects(); } else if (rule.getUseobjects() != null) { - Optional objects = allAccess.getDefobjects().stream() - .filter(a -> Objects.equals(a.getName(), rule.getUseobjects())) - .findAny(); - if (objects.isPresent()) { - return objects.get().getObjects(); + // We must collect all Defobjects in all Useobjects + List objectList = allAccess.getDefobjects().stream() + .filter(a -> rule.getUseobjects().contains(a.getName())) + .toList(); + if (objectList.isEmpty()) { + throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseobjects()); } else { - throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseobjects()); + Set retval = new HashSet<>(); + for (Defobject item: objectList) { + retval.addAll(item.getObjects()); + } + return retval.stream().toList(); } } else { From 7e0ba36456c0b5b0e9c13db8f3e20ee8eda80169 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 18 Nov 2025 12:58:11 +0100 Subject: [PATCH 070/108] Bugfix: do not execute request without auth --- .../endpoint/http/RequestHandlerServlet.java | 24 +++++++++++-------- .../security/filter/ApiGatewayFilterTest.java | 14 +++++------ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 1c5499ac5..3942f3762 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -133,27 +133,31 @@ private void executeAndSend(HttpServletRequest request, HttpServletResponse resp throw new InvalidRequestException("empty API request"); } checkRequestSupportedByProfiles(apiRequest); - de.fraunhofer.iosb.ilt.faaast.service.model.api.Response apiResponse = serviceContext.execute(endpoint, apiRequest); - if (Objects.isNull(apiResponse)) { - throw new ServletException("empty API response"); - } + de.fraunhofer.iosb.ilt.faaast.service.model.api.Response apiResponse = null; if (Objects.nonNull(apiGateway)) { String url = request.getRequestURI().replaceFirst(HttpEndpoint.getVersionPrefix(), ""); if ((url.equals("/shells") || url.equals("/shells/")) && request.getMethod().equals("GET")) { - GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) apiResponse; + GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) serviceContext.execute(endpoint, apiRequest);; apiResponse = apiGateway.filterAas(request, aasResponse); } else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getMethod().equals("GET")) { - GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) apiResponse; + GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest);; apiResponse = apiGateway.filterSubmodels(request, submodelsResponse); } + if (!apiGateway.isAuthorized(request)) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getRequestURI()))); + } else { - if (!apiGateway.isAuthorized(request)) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getRequestURI()))); - } + apiResponse = serviceContext.execute(endpoint, apiRequest); } } + else { + apiResponse = serviceContext.execute(endpoint, apiRequest); + } + if (Objects.isNull(apiResponse)) { + throw new ServletException("empty API response"); + } if (isSuccessful(apiResponse)) { responseMappingManager.map(apiRequest, apiResponse, response); } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index f5db06297..5681291b9 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -69,23 +71,21 @@ public void anonymousAccessDependsOnAclFile() throws Exception { HttpServletRequest request = req("GET", "/api/v3.0/submodels"); HttpServletResponse response = mockResponse(); - FilterChain filterChain = mockFilterChain(); + FilterChain filter = mockFilterChain(); - // TODO I suggest we build a filterChain and fail/succeed if filterChain.doFilter is called (i.e. the next filter) - //assertFalse(filter.doFilter(null, request)); - apiGateway.isAuthorized(request); + assertFalse(apiGateway.isAuthorized(request)); // Verify that request was blocked off - verify(filterChain, never()).doFilter(any(), any()); + verify(filter, never()).doFilter(any(), any()); Path rule = aclDir.resolve("allow.json"); Path tmpRule = aclDir.resolve("allow.json.tmp"); Files.writeString(tmpRule, ACL_JSON, StandardCharsets.UTF_8); Files.move(tmpRule, rule, StandardCopyOption.ATOMIC_MOVE); Thread.sleep(200); - //assertTrue(filter.isAuthorized(null, request)); + assertTrue(apiGateway.isAuthorized(request)); Files.delete(rule); Thread.sleep(200); - //assertFalse(filter.isAuthorized(null, request)); + assertFalse(apiGateway.isAuthorized(request)); } } From 2940d94f5fc0b5834b576d906a6fd0e161fcf88a Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 18 Nov 2025 13:06:00 +0100 Subject: [PATCH 071/108] Update docs --- docs/source/interfaces/endpoint.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/interfaces/endpoint.md b/docs/source/interfaces/endpoint.md index d4a628419..be9b06f93 100644 --- a/docs/source/interfaces/endpoint.md +++ b/docs/source/interfaces/endpoint.md @@ -41,8 +41,9 @@ All endpoint implementations share the following common configuration properties The HTTP Endpoint allows accessing data and execute operations within the FA³ST Service via REST-API. In accordance to the specification, only HTTPS is supported since AAS v3.0. -The HTTP Endpoint is based on the document [Details of the Asset Administration Shell - Part 2: Application Programming Interfaces v3.0](https://industrialdigitaltwin.org/en/content-hub/aasspecifications/specification-of-the-asset-administration-shell-part-1-metamodel-idta-number-01001-3-0) and the corresponding [OpenAPI documentation v3.0.1](https://app.swaggerhub.com/apis/Plattform_i40/Entire-API-Collection/V3.0.1). -This HTTP Endpoint also supports JWT Access Tokens and simple JSON Access Rules for Routes and Identifiables, as defined in [Part 4: Security (IDTA-01004)](https://admin-shell-io.github.io/aas-specs-antora/IDTA-01004/v3.0/index.html). +The HTTP Endpoint is based on the document [Details of the Asset Administration Shell - Part 2: Application Programming Interfaces v3.0](https://industrialdigitaltwin.org/en/content-hub/aasspecifications/specification-of-the-asset-administration-shell-part-2-application-programming-interfaces-idta-number-01002) and the corresponding [OpenAPI documentation v3.0.1](https://app.swaggerhub.com/apis/Plattform_i40/Entire-API-Collection). +Queries are supported if the chosen persistence implementation includes handling of queries. +This HTTP Endpoint also supports JWT Access Tokens and JSON Access Rules (excluding semanticIds) for Routes and Identifiables, as defined in [Part 4: Security (IDTA-01004)](https://industrialdigitaltwin.io/aas-specifications/IDTA-01004/v3.0.1/index.html). ### Configuration From ca36f48c3276834deab85372560980c227b581bc Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 18 Nov 2025 13:08:26 +0100 Subject: [PATCH 072/108] update docs --- docs/source/interfaces/endpoint.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/interfaces/endpoint.md b/docs/source/interfaces/endpoint.md index be9b06f93..1cab3dc5e 100644 --- a/docs/source/interfaces/endpoint.md +++ b/docs/source/interfaces/endpoint.md @@ -105,6 +105,7 @@ This HTTP Endpoint also supports JWT Access Tokens and JSON Access Rules (exclud FA³ST Service supports the verification of JWT Access Tokens and simple Access rules. If the configuration includes the `jwkProvider` URL, all HTTP API requests will be validated. To grant anonymous READ access, an Access rule must be defined and placed in the `aclFolder`: + ``` { "AllAccessPermissionRules": { From c21ebe796872dcfcffdcb628689b27502df9d208 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 18 Nov 2025 13:13:24 +0100 Subject: [PATCH 073/108] use records instead of class --- .../faaast/service/query/QueryEvaluator.java | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index 7426f88e1..cf2f3e9ee 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -304,38 +304,21 @@ private Double toDouble(Object o) { return parseDoubleOrNull(String.valueOf(o)); } - private static final class Condition { - final String suffix; // e.g., ".name", "Sub.Path#value" - final ComparisonOperator operator; - final List rightVals; - - Condition(String suffix, ComparisonOperator operator, List rightVals) { - this.suffix = suffix; - this.operator = operator; - this.rightVals = rightVals != null ? rightVals : Collections.emptyList(); + /** + * @param suffix e.g., ".name", "Sub.Path#value" + */ + private record Condition(String suffix, ComparisonOperator operator, List rightVals) { + private Condition(String suffix, ComparisonOperator operator, List rightVals) { + this.suffix = suffix; + this.operator = operator; + this.rightVals = rightVals != null ? rightVals : Collections.emptyList(); + } } - } - private static final class MatchOperation { - final ComparisonOperator operator; - final List args; - - MatchOperation(ComparisonOperator operator, List args) { - this.operator = operator; - this.args = args; - } + private record MatchOperation(ComparisonOperator operator, List args) { } - private static final class MatchEvaluationContext { - final String commonPrefix; - final List itemConditions; - final boolean directMismatch; - - MatchEvaluationContext(String commonPrefix, List itemConditions, boolean directMismatch) { - this.commonPrefix = commonPrefix; - this.itemConditions = itemConditions; - this.directMismatch = directMismatch; - } + private record MatchEvaluationContext(String commonPrefix, List itemConditions, boolean directMismatch) { } private boolean evaluateMatch(List matches, Identifiable identifiable) { From 0faff04896cfc0e6b1f2f911cb65b8a2083e526f Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 18 Nov 2025 13:13:58 +0100 Subject: [PATCH 074/108] spotless --- .../ilt/faaast/service/query/QueryEvaluator.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index cf2f3e9ee..fc11b9d7f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -308,18 +308,16 @@ private Double toDouble(Object o) { * @param suffix e.g., ".name", "Sub.Path#value" */ private record Condition(String suffix, ComparisonOperator operator, List rightVals) { - private Condition(String suffix, ComparisonOperator operator, List rightVals) { - this.suffix = suffix; - this.operator = operator; - this.rightVals = rightVals != null ? rightVals : Collections.emptyList(); - } + private Condition(String suffix, ComparisonOperator operator, List rightVals) { + this.suffix = suffix; + this.operator = operator; + this.rightVals = rightVals != null ? rightVals : Collections.emptyList(); } - - private record MatchOperation(ComparisonOperator operator, List args) { } - private record MatchEvaluationContext(String commonPrefix, List itemConditions, boolean directMismatch) { - } + private record MatchOperation(ComparisonOperator operator, List args) {} + + private record MatchEvaluationContext(String commonPrefix, List itemConditions, boolean directMismatch) {} private boolean evaluateMatch(List matches, Identifiable identifiable) { if (matches == null || matches.isEmpty()) { From c31fbb36a9701f30ddc21be3b2882bfacd04fa8d Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 20 Nov 2025 16:47:38 +0100 Subject: [PATCH 075/108] add semantic id filter to /submodels --- .../endpoint/http/RequestHandlerServlet.java | 2 +- .../http/security/filter/ApiGateway.java | 110 +++++++++--------- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 3942f3762..139d3b323 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -144,7 +144,7 @@ else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getM GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest);; apiResponse = apiGateway.filterSubmodels(request, submodelsResponse); } - if (!apiGateway.isAuthorized(request)) { + else if (!apiGateway.isAuthorized(request)) { doThrow(new UnauthorizedException( String.format("User not authorized '%s'", request.getRequestURI()))); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 1dfcde9c8..1d2b0572d 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -40,7 +40,6 @@ import jakarta.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -58,7 +57,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import org.slf4j.LoggerFactory; @@ -126,11 +124,19 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS * @return the ApiResponse with only allowed Submodels */ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { - response.getPayload().getContent() - .removeIf(submodel -> aclList.values().stream().noneMatch( - a -> a.getRules().stream() - .anyMatch(r -> AuthServer.evaluateRule(r, "/submodels/" + EncodingHelper.base64Encode(submodel.getId()), - request.getMethod(), extractClaims(request), a)))); + response.getPayload().getContent().removeIf(submodel -> { + String path = "/submodels/" + EncodingHelper.base64Encode(submodel.getId()); + String method = request.getMethod(); + Map claims = extractClaims(request); + + Map fieldCtx = new HashMap<>(); + if (submodel.getSemanticId() != null) { + fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); + } + + return aclList.values().stream() + .noneMatch(allAccess -> allAccess.getRules().stream().anyMatch(rule -> AuthServer.evaluateRule(rule, path, method, claims, allAccess, fieldCtx))); + }); return response; } @@ -164,9 +170,9 @@ public static class AuthServer { * Check all rules that explicitly allows the request. * If a rule exists after all filters, true is returned * - * @param claims - * @param request - * @return + * @param claims the claims found in the token + * @param request the request coming in + * @return true if there is a valid rule */ private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { String requestPath = request.getRequestURI(); @@ -175,47 +181,43 @@ private static boolean filterRules(Map aclList, List relevantRules = aclList.values().stream() .filter(a -> a.getRules().stream() .anyMatch(r -> evaluateRule(r, path, method, claims, a))) - .collect(Collectors.toList()); + .toList(); return !relevantRules.isEmpty(); } - private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess, Map fieldCtx) { Acl acl = getAcl(rule, allAccess); - if (getAttributes(acl, allAccess).stream() - .anyMatch(attr -> "ANONYMOUS".equals(attr.getGlobal().value()) - && Boolean.TRUE.equals(getFormula(rule, allAccess).get$boolean()))) { - return true; - } - if (claims == null) { - return false; - } - List claimValues = getAttributes(acl, allAccess).stream() + + boolean isAnonymous = getAttributes(acl, allAccess).stream() + .anyMatch(attr -> attr.getGlobal() != null && "ANONYMOUS".equals(attr.getGlobal().value())); + + List claimNames = getAttributes(acl, allAccess).stream() .filter(attr -> attr.getGlobal() == null) .map(AttributeItem::getClaim) .filter(Objects::nonNull) - .collect(Collectors.toList()); - Map claimList = new HashMap<>(); - for (String val: claimValues) { - Claim claim = claims.get(val); - if (claim != null) { - claimList.put(val, claim.asString()); - } - } - return !claimValues.isEmpty() - && claimValues.stream() - .allMatch(value -> evaluateFormula(getFormula(rule, allAccess), claimList)); - } - + .toList(); - private static boolean evaluateFormula(LogicalExpression formula, - Map claims) { + // Build context Map ctx = new HashMap<>(); - for (var c: claims.entrySet()) { - ctx.put("CLAIM:" + c.getKey(), c.getValue()); + if (claims != null) { + for (String name: claimNames) { + Claim c = claims.get(name); + if (c != null) { + ctx.put("CLAIM:" + name, c.asString()); + } + } + } + // Add $sm#semanticId + if (fieldCtx != null && !fieldCtx.isEmpty()) { + ctx.putAll(fieldCtx); + } + ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); + if (isAnonymous) { + return FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); } - ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); // $GLOBAL → UTCNOW - return FormulaEvaluator.evaluate(formula, ctx); + return !ctx.entrySet().stream().filter(e -> e.getKey().startsWith("CLAIM:")).toList().isEmpty() && + FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); } @@ -263,14 +265,14 @@ private static boolean checkIdentifiable(String path, String identifiable) { } else if (identifiable.startsWith("(Submodel)")) { String id = identifiable.substring(10); - return path.contains(EncodingHelper.base64Encode(id)); + return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); } if ("(AssetAdministrationShell)*".equals(identifiable)) { return true; } else if (identifiable.startsWith("(AssetAdministrationShell)")) { String id = identifiable.substring(26); - return path.contains(EncodingHelper.base64Encode(id)); + return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); } return false; } @@ -286,7 +288,7 @@ private static boolean checkDescriptor(String path, String descriptor) { } else if (descriptor.startsWith("(aasDesc)")) { String id = descriptor.substring(9); - return path.contains(EncodingHelper.base64UrlEncode(id)); + return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); } } else if (descriptor.startsWith("(smDesc)")) { @@ -298,7 +300,7 @@ else if (descriptor.startsWith("(smDesc)")) { } else if (descriptor.startsWith("(smDesc)")) { String id = descriptor.substring(8); - return path.contains(EncodingHelper.base64UrlEncode(id)); + return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); } } return false; @@ -306,11 +308,14 @@ else if (descriptor.startsWith("(smDesc)")) { private static boolean evaluateRule(AccessPermissionRule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { + return evaluateRule(rule, path, method, claims, allAccess, null); + } + + + private static boolean evaluateRule(AccessPermissionRule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess, + Map fieldCtx) { Acl acl = getAcl(rule, allAccess); - return acl != null - && getAttributes(acl, allAccess) != null - && acl.getRights() != null - && getObjects(rule, allAccess) != null + return acl != null && getAttributes(acl, allAccess) != null && acl.getRights() != null && getObjects(rule, allAccess) != null && getObjects(rule, allAccess).stream().anyMatch(attr -> { if (attr.getRoute() != null) { return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); @@ -324,10 +329,7 @@ else if (attr.getDescriptor() != null) { else { return false; } - }) - && "ALLOW".equals(acl.getAccess().value()) - && evaluateRights(acl.getRights(), method, path) - && verifyAllClaims(claims, rule, allAccess); + }) && "ALLOW".equals(acl.getAccess().value()) && evaluateRights(acl.getRights(), method, path) && verifyAllClaims(claims, rule, allAccess, fieldCtx); } } @@ -346,7 +348,7 @@ private void initializeAclList(String aclFolder) { for (File file: jsonFiles) { Path filePath = file.toPath(); try { - String jsonContent = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8); + String jsonContent = Files.readString(filePath); JsonNode rootNode = mapper.readTree(jsonContent); AllAccessPermissionRules allRules; if (rootNode.has("AllAccessPermissionRules")) { @@ -411,7 +413,7 @@ private void monitorLoop(WatchService watchService, Path folderToWatch) { if (filePath.toString().toLowerCase().endsWith(".json")) { if (kind == StandardWatchEventKinds.ENTRY_CREATE) { try { - String jsonContent = new String(Files.readAllBytes(absolutePath), StandardCharsets.UTF_8); + String jsonContent = Files.readString(absolutePath); JsonNode rootNode = mapper.readTree(jsonContent); AllAccessPermissionRules allRules; if (rootNode.has("AllAccessPermissionRules")) { From a2e54cbb8b35b54fd32018568c346144ce1c16e7 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 20 Nov 2025 17:27:20 +0100 Subject: [PATCH 076/108] add semanticID matching to /submodel/{submodelId} --- .../endpoint/http/RequestHandlerServlet.java | 9 ++++++ .../http/security/filter/ApiGateway.java | 28 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 139d3b323..a1afdbbe3 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -25,6 +25,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.GetAllSubmodelsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; @@ -144,6 +145,14 @@ else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getM GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest);; apiResponse = apiGateway.filterSubmodels(request, submodelsResponse); } + else if ((url.startsWith("/submodels/"))) { + GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest);; + if (!apiGateway.filterSubmodel(request, submodelResponse)) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getRequestURI()))); + } + apiResponse = submodelResponse; + } else if (!apiGateway.isAuthorized(request)) { doThrow(new UnauthorizedException( String.format("User not authorized '%s'", request.getRequestURI()))); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 1d2b0572d..54e2245aa 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -24,6 +24,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.GetAllSubmodelsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; @@ -57,6 +58,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.slf4j.LoggerFactory; @@ -141,6 +143,32 @@ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsRespo } + /** + * Filters out the Submodel that the user is not authorized for. + * + * @param request the HttpRequest + * @param response the ApiResponse + * @return true if user is authorized + */ + public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse response) { + Submodel submodel = response.getPayload(); + if (Objects.isNull(submodel)) { + return true; + } + String path = "/submodels/" + EncodingHelper.base64Encode(submodel.getId()); + String method = request.getMethod(); + Map claims = extractClaims(request); + + Map fieldCtx = new HashMap<>(); + if (submodel.getSemanticId() != null) { + fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); + } + + return aclList.values().stream() + .anyMatch(allAccess -> allAccess.getRules().stream().anyMatch(rule -> AuthServer.evaluateRule(rule, path, method, claims, allAccess, fieldCtx))); + } + + private Map extractClaims(HttpServletRequest request) { String token = request.getHeader("Authorization"); if (token == null) { From 94b14b91ed9aaaa410848925bc972c4b0ffd9ac9 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 20 Nov 2025 18:14:52 +0100 Subject: [PATCH 077/108] refactor into method --- .../endpoint/http/RequestHandlerServlet.java | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index a1afdbbe3..d17766136 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -136,30 +136,7 @@ private void executeAndSend(HttpServletRequest request, HttpServletResponse resp checkRequestSupportedByProfiles(apiRequest); de.fraunhofer.iosb.ilt.faaast.service.model.api.Response apiResponse = null; if (Objects.nonNull(apiGateway)) { - String url = request.getRequestURI().replaceFirst(HttpEndpoint.getVersionPrefix(), ""); - if ((url.equals("/shells") || url.equals("/shells/")) && request.getMethod().equals("GET")) { - GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) serviceContext.execute(endpoint, apiRequest);; - apiResponse = apiGateway.filterAas(request, aasResponse); - } - else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getMethod().equals("GET")) { - GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest);; - apiResponse = apiGateway.filterSubmodels(request, submodelsResponse); - } - else if ((url.startsWith("/submodels/"))) { - GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest);; - if (!apiGateway.filterSubmodel(request, submodelResponse)) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getRequestURI()))); - } - apiResponse = submodelResponse; - } - else if (!apiGateway.isAuthorized(request)) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getRequestURI()))); - } - else { - apiResponse = serviceContext.execute(endpoint, apiRequest); - } + apiResponse = handleResponseWithAcl(request, apiRequest); } else { apiResponse = serviceContext.execute(endpoint, apiRequest); @@ -187,4 +164,32 @@ private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model. .noneMatch(x -> Objects.equals(x, MessageTypeEnum.ERROR) || Objects.equals(x, MessageTypeEnum.EXCEPTION)); } + + private de.fraunhofer.iosb.ilt.faaast.service.model.api.Response handleResponseWithAcl(HttpServletRequest request, + de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) + throws ServletException { + String url = request.getRequestURI().replaceFirst(HttpEndpoint.getVersionPrefix(), ""); + if ((url.equals("/shells") || url.equals("/shells/")) && request.getMethod().equals("GET")) { + GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) serviceContext.execute(endpoint, apiRequest);; + return apiGateway.filterAas(request, aasResponse); + } + else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getMethod().equals("GET")) { + GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest);; + return apiGateway.filterSubmodels(request, submodelsResponse); + } + else if ((url.startsWith("/submodels/"))) { + GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest);; + if (!apiGateway.filterSubmodel(request, submodelResponse)) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getRequestURI()))); + } + return submodelResponse; + } + else if (!apiGateway.isAuthorized(request)) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getRequestURI()))); + } + return serviceContext.execute(endpoint, apiRequest); + } + } From 4adadd96f9cb47d94083881ff76ab32b226018ae Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Thu, 20 Nov 2025 18:50:30 +0100 Subject: [PATCH 078/108] url match submodel requests more closely, change docs --- docs/source/interfaces/endpoint.md | 3 ++- .../faaast/service/endpoint/http/RequestHandlerServlet.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/interfaces/endpoint.md b/docs/source/interfaces/endpoint.md index 1cab3dc5e..d4cf8355b 100644 --- a/docs/source/interfaces/endpoint.md +++ b/docs/source/interfaces/endpoint.md @@ -43,7 +43,8 @@ The HTTP Endpoint allows accessing data and execute operations within the FA³ST In accordance to the specification, only HTTPS is supported since AAS v3.0. The HTTP Endpoint is based on the document [Details of the Asset Administration Shell - Part 2: Application Programming Interfaces v3.0](https://industrialdigitaltwin.org/en/content-hub/aasspecifications/specification-of-the-asset-administration-shell-part-2-application-programming-interfaces-idta-number-01002) and the corresponding [OpenAPI documentation v3.0.1](https://app.swaggerhub.com/apis/Plattform_i40/Entire-API-Collection). Queries are supported if the chosen persistence implementation includes handling of queries. -This HTTP Endpoint also supports JWT Access Tokens and JSON Access Rules (excluding semanticIds) for Routes and Identifiables, as defined in [Part 4: Security (IDTA-01004)](https://industrialdigitaltwin.io/aas-specifications/IDTA-01004/v3.0.1/index.html). +This HTTP Endpoint also supports JWT Access Tokens and JSON Access Rules for Routes and Identifiables, as defined in [Part 4: Security (IDTA-01004)](https://industrialdigitaltwin.io/aas-specifications/IDTA-01004/v3.0.1/index.html). +Currently, semanticId fields in the rules are only validated for GET requests on /shells and /submodels, i.e. you can not grant POST access via a semanticId. Such access should be granted via Route/Identifiable. ### Configuration diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index d17766136..beac75e8c 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -177,7 +177,7 @@ else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getM GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest);; return apiGateway.filterSubmodels(request, submodelsResponse); } - else if ((url.startsWith("/submodels/"))) { + else if ((url.matches("^/submodels/[^/]+$")) && request.getMethod().equals("GET")) { GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest);; if (!apiGateway.filterSubmodel(request, submodelResponse)) { doThrow(new UnauthorizedException( From ffbb18e4fdd47ebe42fd60e815fdfe40f3a82805 Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 25 Nov 2025 09:50:06 +0100 Subject: [PATCH 079/108] merge conflicts --- .../ilt/faaast/service/endpoint/http/HttpEndpointConfig.java | 2 ++ pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java index 981534464..163b07c7b 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java @@ -379,6 +379,8 @@ public B hostname(String value) { public B jwkProvider(String value) { getBuildingInstance().setJwkProvider(value); } + + public B pathPrefix(String value) { getBuildingInstance().setPathPrefix(value); return getSelf(); diff --git a/pom.xml b/pom.xml index e1aaad8df..61898dbc4 100644 --- a/pom.xml +++ b/pom.xml @@ -95,10 +95,10 @@ 1.13 2.6.0 1.5.3 - 1.2.2 - 0.22.1 2.10.0 + 1.2.2 4.13.2 + 0.22.1 1.5.21 17 17 From 7a5b43e1d1fd6271ee6aba444550440127d45ddf Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Tue, 25 Nov 2025 09:56:51 +0100 Subject: [PATCH 080/108] add wait with awaitility in test file --- endpoint/http/pom.xml | 6 ++++++ .../faaast/service/endpoint/http/HttpEndpointConfig.java | 1 + .../service/endpoint/http/RequestHandlerServlet.java | 2 +- .../http/security/filter/ApiGatewayFilterTest.java | 9 +++++---- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/endpoint/http/pom.xml b/endpoint/http/pom.xml index 20b2bf5b6..007fbd748 100644 --- a/endpoint/http/pom.xml +++ b/endpoint/http/pom.xml @@ -95,6 +95,12 @@ ${org.apache.httpcomponents.client5.version} test + + org.awaitility + awaitility + ${awaitility.version} + test + org.eclipse.digitaltwin.aas4j aas4j-dataformat-core diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java index 163b07c7b..20c9af67a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java @@ -378,6 +378,7 @@ public B hostname(String value) { public B jwkProvider(String value) { getBuildingInstance().setJwkProvider(value); + return getSelf(); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 9e8c5cb73..61dcb0b61 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -168,7 +168,7 @@ private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model. private de.fraunhofer.iosb.ilt.faaast.service.model.api.Response handleResponseWithAcl(HttpServletRequest request, de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws ServletException { - String url = request.getRequestURI().replaceFirst(HttpEndpoint.getVersionPrefix(), ""); + String url = request.getRequestURI(); if ((url.equals("/shells") || url.equals("/shells/")) && request.getMethod().equals("GET")) { GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) serviceContext.execute(endpoint, apiRequest);; return apiGateway.filterAas(request, aasResponse); diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index 5681291b9..aa2a14699 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -14,6 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -81,11 +84,9 @@ public void anonymousAccessDependsOnAclFile() throws Exception { Files.writeString(tmpRule, ACL_JSON, StandardCharsets.UTF_8); Files.move(tmpRule, rule, StandardCopyOption.ATOMIC_MOVE); - Thread.sleep(200); - assertTrue(apiGateway.isAuthorized(request)); + await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertTrue(apiGateway.isAuthorized(request))); Files.delete(rule); - Thread.sleep(200); - assertFalse(apiGateway.isAuthorized(request)); + await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertFalse(apiGateway.isAuthorized(request))); } } From 02dfe6aaee108cfa14b3c8e5b22f48713a2fa813 Mon Sep 17 00:00:00 2001 From: fvolz Date: Wed, 11 Feb 2026 14:42:39 +0100 Subject: [PATCH 081/108] codacy --- .../iosb/ilt/faaast/service/Service.java | 8 - .../faaast/service/query/QueryEvaluator.java | 332 +++++++++++------- .../service/query/QueryEvaluatorTest.java | 24 +- .../http/security/FormulaEvaluator.java | 215 ++++++------ .../security/filter/ApiGatewayFilterTest.java | 5 +- .../filter/JwtAuthorizationFilterTest.java | 28 +- .../filter/JwtValidationFilterTest.java | 6 +- 7 files changed, 339 insertions(+), 279 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 48dd1d65d..d2e193ab3 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -35,7 +35,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.PagingInfo; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.SubscriptionId; import de.fraunhofer.iosb.ilt.faaast.service.model.serialization.DataFormat; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; @@ -74,8 +73,6 @@ public class Service implements ServiceContext { private static final Logger LOGGER = LoggerFactory.getLogger(Service.class); - private static final String VALUE_NULL = "value must not be null"; - private static final String ELEMENT_NULL = "element must not be null"; private final ServiceConfig config; private AssetConnectionManager assetConnectionManager; private List endpoints; @@ -87,8 +84,6 @@ public class Service implements ServiceContext { private RegistrySynchronization registrySynchronization; private RequestHandlerManager requestHandler; - private List submodelTemplateProcessors; - private List subscriptions; /** * Creates a new instance of {@link Service}. @@ -126,8 +121,6 @@ public Service(CoreConfig coreConfig, else { this.endpoints = endpoints; } - this.submodelTemplateProcessors = submodelTemplateProcessors; - this.subscriptions = new ArrayList<>(); this.config = ServiceConfig.builder() .core(coreConfig) .build(); @@ -156,7 +149,6 @@ public Service(ServiceConfig config) throws ConfigurationException, AssetConnectionException, PersistenceException, MessageBusException { Ensure.requireNonNull(config, "config must be non-null"); this.config = config; - this.subscriptions = new ArrayList<>(); init(); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index fc11b9d7f..3fbbfd528 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -23,6 +23,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -54,8 +56,6 @@ public class QueryEvaluator { private static final String PREFIX_SME = "$sme"; private static final String PREFIX_CD = "$cd#"; - public QueryEvaluator() {} - private enum ComparisonOperator { EQ, NE, @@ -93,6 +93,22 @@ private enum StringValueKind { STR_CAST } + private static final List VALUE_KIND_CHECKS = Arrays.asList( + new ValueKindCheck(ValueKind.FIELD, v -> v.get$field() != null), + new ValueKindCheck(ValueKind.STR, v -> v.get$strVal() != null), + new ValueKindCheck(ValueKind.NUM, v -> v.get$numVal() != null), + new ValueKindCheck(ValueKind.HEX, v -> v.get$hexVal() != null), + new ValueKindCheck(ValueKind.DATETIME, v -> v.get$dateTimeVal() != null), + new ValueKindCheck(ValueKind.TIME, v -> v.get$timeVal() != null), + new ValueKindCheck(ValueKind.BOOL, v -> v.get$boolean() != null), + new ValueKindCheck(ValueKind.STR_CAST, v -> v.get$strCast() != null), + new ValueKindCheck(ValueKind.NUM_CAST, v -> v.get$numCast() != null)); + + private static final List STRING_VALUE_KIND_CHECKS = Arrays.asList( + new StringValueKindCheck(StringValueKind.FIELD, v -> v.get$field() != null), + new StringValueKindCheck(StringValueKind.STR, v -> v.get$strVal() != null), + new StringValueKindCheck(StringValueKind.STR_CAST, v -> v.get$strCast() != null)); + /** * Used to decide whether to filter out the Identifiable. * @@ -106,12 +122,34 @@ public boolean matches(LogicalExpression expr, Identifiable identifiable) { return false; } - // boolean - if (expr.get$boolean() != null) { - return expr.get$boolean(); + Boolean directBoolean = evaluateBooleanLiteral(expr); + if (directBoolean != null) { + return directBoolean; + } + + Boolean logical = evaluateLogicalExpression(expr, identifiable); + if (logical != null) { + return logical; + } + + if (hasMatchExpression(expr)) { + return evaluateMatch(expr.get$match(), identifiable); } - // logical + if (evaluateFirstValueOperator(expr, identifiable)) { + return true; + } + + return evaluateFirstStringOperator(expr, identifiable); + } + + + private Boolean evaluateBooleanLiteral(LogicalExpression expr) { + return expr.get$boolean(); + } + + + private Boolean evaluateLogicalExpression(LogicalExpression expr, Identifiable identifiable) { if (expr.get$and() != null && !expr.get$and().isEmpty()) { return expr.get$and().stream().allMatch(e -> matches(e, identifiable)); } @@ -121,20 +159,12 @@ public boolean matches(LogicalExpression expr, Identifiable identifiable) { if (expr.get$not() != null) { return !matches(expr.get$not(), identifiable); } + return null; + } - // match operator - if (expr.get$match() != null && !expr.get$match().isEmpty()) { - return evaluateMatch(expr.get$match(), identifiable); - } - // numeric/boolean/string comparisons - boolean evaluated = evaluateFirstValueOperator(expr, identifiable); - if (evaluated) { - return true; - } - - // string binary operators - return evaluateFirstStringOperator(expr, identifiable); + private boolean hasMatchExpression(LogicalExpression expr) { + return expr.get$match() != null && !expr.get$match().isEmpty(); } @@ -212,41 +242,38 @@ private boolean anyPairSatisfies(List left, List right, Comparis return false; } + /** + * @param predicate checks if a value kind applies + */ + private record ValueKindCheck(ValueKind kind, Predicate predicate) {} + + /** + * @param predicate checks if a string value kind applies + */ + private record StringValueKindCheck(StringValueKind kind, Predicate predicate) {} private ValueKind determineValueKind(Value v) { - if (v == null) + if (v == null) { return ValueKind.NONE; - if (v.get$field() != null) - return ValueKind.FIELD; - if (v.get$strVal() != null) - return ValueKind.STR; - if (v.get$numVal() != null) - return ValueKind.NUM; - if (v.get$hexVal() != null) - return ValueKind.HEX; - if (v.get$dateTimeVal() != null) - return ValueKind.DATETIME; - if (v.get$timeVal() != null) - return ValueKind.TIME; - if (v.get$boolean() != null) - return ValueKind.BOOL; - if (v.get$strCast() != null) - return ValueKind.STR_CAST; - if (v.get$numCast() != null) - return ValueKind.NUM_CAST; + } + for (ValueKindCheck check: VALUE_KIND_CHECKS) { + if (check.predicate.test(v)) { + return check.kind; + } + } return ValueKind.NONE; } private StringValueKind determineStringValueKind(StringValue sv) { - if (sv == null) + if (sv == null) { return StringValueKind.NONE; - if (sv.get$field() != null) - return StringValueKind.FIELD; - if (sv.get$strVal() != null) - return StringValueKind.STR; - if (sv.get$strCast() != null) - return StringValueKind.STR_CAST; + } + for (StringValueKindCheck check: STRING_VALUE_KIND_CHECKS) { + if (check.predicate.test(sv)) { + return check.kind; + } + } return StringValueKind.NONE; } @@ -490,70 +517,100 @@ private boolean doAllItemConditionsMatch(List conditions, private List getFieldValues(String field, Identifiable identifiable) { - if (field == null || identifiable == null) + if (field == null || identifiable == null) { return Collections.emptyList(); + } - if (field.startsWith(PREFIX_AAS)) { - if (!(identifiable instanceof AssetAdministrationShell)) - return Collections.emptyList(); - return getAasFieldValues((AssetAdministrationShell) identifiable, field.substring(PREFIX_AAS.length())); + List values = resolveValuesForPrefix(field, PREFIX_AAS, identifiable, AssetAdministrationShell.class, + (aas, attr) -> getAasFieldValues(aas, attr)); + if (values != null) { + return values; } - if (field.startsWith(PREFIX_SM)) { - if (!(identifiable instanceof Submodel)) - return Collections.emptyList(); - return new ArrayList<>(getSubmodelAttributeValues((Submodel) identifiable, field.substring(PREFIX_SM.length()))); + values = resolveValuesForPrefix(field, PREFIX_SM, identifiable, Submodel.class, + (sm, attr) -> new ArrayList<>(getSubmodelAttributeValues(sm, attr))); + if (values != null) { + return values; } - if (field.startsWith(PREFIX_SME)) { - if (!(identifiable instanceof Submodel sm)) - return Collections.emptyList(); + values = resolveSmeFieldValues(field, identifiable); + if (values != null) { + return values; + } - String pathPart = ""; - String attr; - if (field.contains(".")) { - int hashPos = field.indexOf("#", field.indexOf(".")); - pathPart = field.substring(field.indexOf(".") + 1, hashPos); - attr = field.substring(hashPos + 1); - } - else { - attr = field.substring((PREFIX_SME + "#").length()); - } + values = resolveValuesForPrefix(field, PREFIX_CD, identifiable, ConceptDescription.class, + (cd, attr) -> getConceptDescriptionValues(cd, attr)); + if (values != null) { + return values; + } - List values = new ArrayList<>(); - if (!pathPart.isEmpty()) { - SubmodelElement sme = getSubmodelElementByPath(sm, pathPart); - if (sme != null) { - values.addAll(getSubmodelElementAttributeValues(sme, attr)); - } - } - else { - List smes = sm.getSubmodelElements(); - if (smes != null) { - for (SubmodelElement sme: smes) { - values.addAll(getSubmodelElementAttributeValues(sme, attr)); - } - } + LOGGER.error("Unsupported field: {}", field); + return Collections.emptyList(); + } + + + private List resolveValuesForPrefix(String field, + String prefix, + Identifiable identifiable, + Class type, + BiFunction> extractor) { + if (!field.startsWith(prefix)) { + return null; + } + if (!type.isInstance(identifiable)) { + return Collections.emptyList(); + } + return extractor.apply(type.cast(identifiable), field.substring(prefix.length())); + } + + + private List resolveSmeFieldValues(String field, Identifiable identifiable) { + if (!field.startsWith(PREFIX_SME)) { + return null; + } + if (!(identifiable instanceof Submodel sm)) { + return Collections.emptyList(); + } + + String pathPart = ""; + String attr; + if (field.contains(".")) { + int hashPos = field.indexOf("#", field.indexOf(".")); + pathPart = field.substring(field.indexOf(".") + 1, hashPos); + attr = field.substring(hashPos + 1); + } + else { + attr = field.substring((PREFIX_SME + "#").length()); + } + + List values = new ArrayList<>(); + if (!pathPart.isEmpty()) { + SubmodelElement sme = getSubmodelElementByPath(sm, pathPart); + if (sme != null) { + values.addAll(getSubmodelElementAttributeValues(sme, attr)); } return values; } - if (field.startsWith(PREFIX_CD)) { - if (!(identifiable instanceof ConceptDescription cd)) - return Collections.emptyList(); - String attr = field.substring(PREFIX_CD.length()); - return switch (attr) { - case "idShort" -> Collections.singletonList(cd.getIdShort()); - case "id" -> Collections.singletonList(cd.getId()); - default -> { - LOGGER.error("Unsupported CD attribute: {}", attr); - yield Collections.emptyList(); - } - }; + List smes = sm.getSubmodelElements(); + if (smes != null) { + for (SubmodelElement sme: smes) { + values.addAll(getSubmodelElementAttributeValues(sme, attr)); + } } + return values; + } - LOGGER.error("Unsupported field: {}", field); - return Collections.emptyList(); + + private List getConceptDescriptionValues(ConceptDescription cd, String attr) { + return switch (attr) { + case "idShort" -> Collections.singletonList(cd.getIdShort()); + case "id" -> Collections.singletonList(cd.getId()); + default -> { + LOGGER.error("Unsupported CD attribute: {}", attr); + yield Collections.emptyList(); + } + }; } @@ -826,41 +883,74 @@ private boolean compareUsingStringOperator(Object a, Object b, ComparisonOperato private boolean compareUsingGeneralComparison(Object a, Object b, ComparisonOperator operator) { - if (a == null || b == null) { - return (operator == ComparisonOperator.EQ) - ? Objects.equals(a, b) - : (operator == ComparisonOperator.NE) && !Objects.equals(a, b); + Boolean nullResult = compareNulls(a, b, operator); + if (nullResult != null) { + return nullResult; + } + + Boolean numericResult = compareNumbers(a, b, operator); + if (numericResult != null) { + return numericResult; } - // try numeric - Double d1 = toDouble(a); - Double d2 = toDouble(b); - if (d1 != null && d2 != null) { - return switch (operator) { - case EQ -> Double.compare(d1, d2) == 0; - case NE -> Double.compare(d1, d2) != 0; - case GT -> d1 > d2; - case GE -> d1 >= d2; - case LT -> d1 < d2; - case LE -> d1 <= d2; - default -> false; - }; - } - - // try boolean String sa = String.valueOf(a).trim(); String sb = String.valueOf(b).trim(); + Boolean booleanResult = compareBooleans(sa, sb, operator); + if (booleanResult != null) { + return booleanResult; + } + + return compareStrings(sa, sb, operator); + } + + + private Boolean compareNulls(Object a, Object b, ComparisonOperator operator) { + if (a != null && b != null) { + return null; + } + if (operator == ComparisonOperator.EQ) { + return Objects.equals(a, b); + } + if (operator == ComparisonOperator.NE) { + return !Objects.equals(a, b); + } + return false; + } + + + private Boolean compareNumbers(Object a, Object b, ComparisonOperator operator) { + Double d1 = toDouble(a); + Double d2 = toDouble(b); + if (d1 == null || d2 == null) { + return null; + } + return switch (operator) { + case EQ -> Double.compare(d1, d2) == 0; + case NE -> Double.compare(d1, d2) != 0; + case GT -> d1 > d2; + case GE -> d1 >= d2; + case LT -> d1 < d2; + case LE -> d1 <= d2; + default -> false; + }; + } + + + private Boolean compareBooleans(String sa, String sb, ComparisonOperator operator) { Boolean ba = parseBooleanStrict(sa); Boolean bb = parseBooleanStrict(sb); - if (ba != null && bb != null) { - return switch (operator) { - case EQ -> Objects.equals(ba, bb); - case NE -> !Objects.equals(ba, bb); - default -> false; - }; + if (ba == null || bb == null) { + return null; } + return switch (operator) { + case EQ -> Objects.equals(ba, bb); + case NE -> !Objects.equals(ba, bb); + default -> false; + }; + } + - // string comparison + private boolean compareStrings(String sa, String sb, ComparisonOperator operator) { int cmp = sa.compareTo(sb); return switch (operator) { case EQ -> cmp == 0; diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java index bc9a0a6bb..a4bf9985c 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluatorTest.java @@ -210,9 +210,8 @@ private Environment createTestEnvironmentForOrMatch(boolean matching) { } - /* ------------------------------------------------------------------ */ @Test - public void simpleEq_withMatchingFields() throws Exception { + public void simpleEqWithMatchingFields() throws Exception { String json = """ { "$condition": { @@ -238,9 +237,8 @@ public void simpleEq_withMatchingFields() throws Exception { } - /* ------------------------------------------------------------------ */ @Test - public void simpleEq_withNonMatchingFields() throws Exception { + public void simpleEqWithNonMatchingFields() throws Exception { String json = """ { "$condition": { @@ -266,9 +264,8 @@ public void simpleEq_withNonMatchingFields() throws Exception { } - /* ------------------------------------------------------------------ */ @Test - public void documentsMatch_withMatchingValues() throws Exception { + public void documentsMatchWithMatchingValues() throws Exception { String json = """ { "$condition": { @@ -299,9 +296,8 @@ public void documentsMatch_withMatchingValues() throws Exception { } - /* ------------------------------------------------------------------ */ @Test - public void documentsMatch_withNonMatchingValues() throws Exception { + public void documentsMatchWithNonMatchingValues() throws Exception { String json = """ { "$condition": { @@ -332,9 +328,8 @@ public void documentsMatch_withNonMatchingValues() throws Exception { } - /* ------------------------------------------------------------------ */ @Test - public void andMatch_withMatchingConditions() throws Exception { + public void andMatchWithMatchingConditions() throws Exception { String json = """ { "$condition": { @@ -392,9 +387,8 @@ public void andMatch_withMatchingConditions() throws Exception { } - /* ------------------------------------------------------------------ */ @Test - public void andMatch_withNonMatchingConditions() throws Exception { + public void andMatchWithNonMatchingConditions() throws Exception { String json = """ { "$condition": { @@ -452,9 +446,8 @@ public void andMatch_withNonMatchingConditions() throws Exception { } - /* ------------------------------------------------------------------ */ @Test - public void orMatch_withMatchingSpecificAssetIds() throws Exception { + public void orMatchWithMatchingSpecificAssetIds() throws Exception { String json = """ { "$condition": { @@ -505,9 +498,8 @@ public void orMatch_withMatchingSpecificAssetIds() throws Exception { } - /* ------------------------------------------------------------------ */ @Test - public void orMatch_withNonMatchingSpecificAssetIds() throws Exception { + public void orMatchWithNonMatchingSpecificAssetIds() throws Exception { String json = """ { "$condition": { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java index e98263e77..c0db81e52 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.IntPredicate; +import java.util.function.Supplier; import java.util.regex.Pattern; @@ -30,6 +32,20 @@ */ public final class FormulaEvaluator { + @FunctionalInterface + private interface ValueComparator { + boolean compare(Object left, Object right); + } + + @FunctionalInterface + private interface StringComparator { + boolean compare(String left, String right); + } + + private record ValueOperationSpec(String name, Supplier> operands, ValueComparator comparator) {} + + private record StringOperationSpec(String name, Supplier> operands, StringComparator comparator) {} + /** * Evaluates the given formula. * @@ -45,6 +61,33 @@ public static boolean evaluate(LogicalExpression formula, private static boolean eval(LogicalExpression node, Map ctx) { + Boolean logical = evaluateLogical(node, ctx); + if (logical != null) { + return logical; + } + + Boolean valueComparison = evaluateValueComparison(node, ctx); + if (valueComparison != null) { + return valueComparison; + } + + Boolean stringComparison = evaluateStringComparison(node, ctx); + if (stringComparison != null) { + return stringComparison; + } + + if (node.get$boolean() != null) { + return node.get$boolean(); + } + if (!node.get$match().isEmpty()) { + throw new UnsupportedOperationException("Operator $match not supported"); + } + throw new IllegalArgumentException("No supported operator found in node"); + } + + + private static Boolean evaluateLogical(LogicalExpression node, + Map ctx) { if (!node.get$and().isEmpty()) { for (LogicalExpression child: node.get$and()) { if (!eval(child, ctx)) { @@ -64,123 +107,71 @@ private static boolean eval(LogicalExpression node, if (node.get$not() != null) { return !eval(node.get$not(), ctx); } - if (!node.get$eq().isEmpty()) { - List ops = node.get$eq(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$eq requires exactly 2 operands"); - } - Object left = resolve(ops.get(0), ctx); - Object right = resolve(ops.get(1), ctx); - return Objects.equals(left, right); - } - if (!node.get$ne().isEmpty()) { - List ops = node.get$ne(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$ne requires exactly 2 operands"); - } - Object left = resolve(ops.get(0), ctx); - Object right = resolve(ops.get(1), ctx); - return !Objects.equals(left, right); - } - if (!node.get$gt().isEmpty()) { - List ops = node.get$gt(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$gt requires exactly 2 operands"); - } - Object lObj = resolve(ops.get(0), ctx); - Object rObj = resolve(ops.get(1), ctx); - if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { - throw new IllegalArgumentException("Operands are not comparable: " - + lObj + ", " + rObj); - } - int cmp = ((Comparable) lObj).compareTo(rObj); - return cmp > 0; - } - if (!node.get$ge().isEmpty()) { - List ops = node.get$ge(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$ge requires exactly 2 operands"); - } - Object lObj = resolve(ops.get(0), ctx); - Object rObj = resolve(ops.get(1), ctx); - if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { - throw new IllegalArgumentException("Operands are not comparable: " - + lObj + ", " + rObj); - } - int cmp = ((Comparable) lObj).compareTo(rObj); - return cmp >= 0; - } - if (!node.get$lt().isEmpty()) { - List ops = node.get$lt(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$lt requires exactly 2 operands"); - } - Object lObj = resolve(ops.get(0), ctx); - Object rObj = resolve(ops.get(1), ctx); - if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { - throw new IllegalArgumentException("Operands are not comparable: " - + lObj + ", " + rObj); - } - int cmp = ((Comparable) lObj).compareTo(rObj); - return cmp < 0; - } - if (!node.get$le().isEmpty()) { - List ops = node.get$le(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$le requires exactly 2 operands"); - } - Object lObj = resolve(ops.get(0), ctx); - Object rObj = resolve(ops.get(1), ctx); - if (!(lObj instanceof Comparable) || !(rObj instanceof Comparable)) { - throw new IllegalArgumentException("Operands are not comparable: " - + lObj + ", " + rObj); + return null; + } + + + private static Boolean evaluateValueComparison(LogicalExpression node, + Map ctx) { + List operations = List.of( + new ValueOperationSpec("$eq", node::get$eq, Objects::equals), + new ValueOperationSpec("$ne", node::get$ne, (left, right) -> !Objects.equals(left, right)), + new ValueOperationSpec("$gt", node::get$gt, (left, right) -> compareComparable(left, right, v -> v > 0)), + new ValueOperationSpec("$ge", node::get$ge, (left, right) -> compareComparable(left, right, v -> v >= 0)), + new ValueOperationSpec("$lt", node::get$lt, (left, right) -> compareComparable(left, right, v -> v < 0)), + new ValueOperationSpec("$le", node::get$le, (left, right) -> compareComparable(left, right, v -> v <= 0))); + + for (ValueOperationSpec spec: operations) { + List ops = spec.operands.get(); + if (!ops.isEmpty()) { + validateOperandCount(ops.size(), spec.name); + Object left = resolve(ops.get(0), ctx); + Object right = resolve(ops.get(1), ctx); + return spec.comparator.compare(left, right); } - int cmp = ((Comparable) lObj).compareTo(rObj); - return cmp <= 0; } - if (!node.get$contains().isEmpty()) { - List ops = node.get$contains(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$contains requires exactly 2 operands"); - } - String left = resolveString(ops.get(0), ctx); - String right = resolveString(ops.get(1), ctx); - return left != null && left.contains(right); - } - if (!node.get$startsWith().isEmpty()) { - List ops = node.get$startsWith(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$starts-with requires exactly 2 operands"); - } - String left = resolveString(ops.get(0), ctx); - String right = resolveString(ops.get(1), ctx); - return left != null && left.startsWith(right); - } - if (!node.get$endsWith().isEmpty()) { - List ops = node.get$endsWith(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$ends-with requires exactly 2 operands"); - } - String left = resolveString(ops.get(0), ctx); - String right = resolveString(ops.get(1), ctx); - return left != null && left.endsWith(right); - } - if (!node.get$regex().isEmpty()) { - List ops = node.get$regex(); - if (ops.size() != 2) { - throw new IllegalArgumentException("$regex requires exactly 2 operands"); + return null; + } + + + private static Boolean evaluateStringComparison(LogicalExpression node, + Map ctx) { + List operations = List.of( + new StringOperationSpec("$contains", node::get$contains, (left, right) -> left != null && left.contains(right)), + new StringOperationSpec("$starts-with", node::get$startsWith, (left, right) -> left != null && left.startsWith(right)), + new StringOperationSpec("$ends-with", node::get$endsWith, (left, right) -> left != null && left.endsWith(right)), + new StringOperationSpec("$regex", node::get$regex, (left, right) -> left != null && Pattern.matches(right, left))); + + for (StringOperationSpec spec: operations) { + List ops = spec.operands.get(); + if (!ops.isEmpty()) { + validateOperandCount(ops.size(), spec.name); + String left = resolveString(ops.get(0), ctx); + String right = resolveString(ops.get(1), ctx); + return spec.comparator.compare(left, right); } - String left = resolveString(ops.get(0), ctx); - String regex = resolveString(ops.get(1), ctx); - return left != null && Pattern.matches(regex, left); } - if (node.get$boolean() != null) { - return node.get$boolean(); - } - if (!node.get$match().isEmpty()) { - throw new UnsupportedOperationException("Operator $match not supported"); + return null; + } + + + private static void validateOperandCount(int count, + String operator) { + if (count != 2) { + throw new IllegalArgumentException(operator + " requires exactly 2 operands"); } - throw new IllegalArgumentException("No supported operator found in node"); + } + + + private static boolean compareComparable(Object left, + Object right, + IntPredicate predicate) { + if (!(left instanceof Comparable) || !(right instanceof Comparable)) { + throw new IllegalArgumentException("Operands are not comparable: " + + left + ", " + right); + } + int cmp = ((Comparable) left).compareTo(right); + return predicate.test(cmp); } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index aa2a14699..2cfbef939 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -27,7 +27,6 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -55,7 +54,6 @@ public class ApiGatewayFilterTest extends JwtAuthorizationFilterTest { @Rule public TemporaryFolder tmp = new TemporaryFolder(); - private Path aclDir; private ApiGateway apiGateway; private static HttpServletRequest req(String method, String uri) { @@ -69,11 +67,10 @@ private static HttpServletRequest req(String method, String uri) { @Test public void anonymousAccessDependsOnAclFile() throws Exception { - aclDir = tmp.newFolder("acl").toPath(); + Path aclDir = tmp.newFolder("acl").toPath(); apiGateway = new ApiGateway(aclDir.toString()); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); - HttpServletResponse response = mockResponse(); FilterChain filter = mockFilterChain(); assertFalse(apiGateway.isAuthorized(request)); diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java index 3b1a63493..4ce1c5a09 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java @@ -24,30 +24,31 @@ import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; import java.util.List; -import org.junit.Test; public class JwtAuthorizationFilterTest { - private JwtAuthorizationFilter testSubject = new JwtAuthorizationFilter() { - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { - // Intentionally left blank - } - }; - - @Test - public void testExtractAndDecodeJwt() { - // TODO + { + new JwtAuthorizationFilter() { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { + // Intentionally left blank + } + }; } + /** + * @Test + * public void testExtractAndDecodeJwt() { + * } + **/ protected FilterChain mockFilterChain() { return mock(FilterChain.class); } - protected static HttpServletRequest mockRequest(String method, String uri, String jwt) { + protected static HttpServletRequest mockRequest(String jwt) { HttpServletRequest r = mock(HttpServletRequest.class); //when(r.getMethod()).thenReturn(method); //when(r.getRequestURI()).thenReturn(uri); @@ -59,8 +60,7 @@ protected static HttpServletRequest mockRequest(String method, String uri, Strin protected static HttpServletResponse mockResponse() { - HttpServletResponse r = mock(HttpServletResponse.class); - return r; + return mock(HttpServletResponse.class); } } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java index 270b7190b..fea9e14de 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java @@ -37,8 +37,6 @@ public class JwtValidationFilterTest extends JwtAuthorizationFilterTest { - private JwtValidationFilter filter; - @Test public void jwtIsVerified() throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); @@ -62,9 +60,9 @@ public void jwtIsVerified() throws Exception { when(mockJwkProvider.get(kid)).thenReturn(jwk); - filter = new JwtValidationFilter(mockJwkProvider); + JwtValidationFilter filter = new JwtValidationFilter(mockJwkProvider); - HttpServletRequest request = mockRequest("GET", "/api/v3.0/submodels", jwt); + HttpServletRequest request = mockRequest(jwt); HttpServletResponse response = mockResponse(); FilterChain filterChain = mockFilterChain(); From f4808dc6286d819edc1b87b710004d77370aabfd Mon Sep 17 00:00:00 2001 From: fvolz Date: Wed, 11 Feb 2026 14:58:27 +0100 Subject: [PATCH 082/108] Update endpoint.md --- docs/source/interfaces/endpoint.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/interfaces/endpoint.md b/docs/source/interfaces/endpoint.md index 0ac224dc1..3dee814a6 100644 --- a/docs/source/interfaces/endpoint.md +++ b/docs/source/interfaces/endpoint.md @@ -143,15 +143,20 @@ If a `jwkProvider` is defined and no Access rules exist, all HTTP API requests w We recommend Ory Hydra as OAuth2 and OpenID Connect server, which can be used with the following curl requests to retrieve a JWT token. Create client_id: + ``` curl -s -X POST http://127.0.0.1:4444/oauth2/token -u '6c176008-9308-4603-a55a-9975bb3a93b6:MCXepEOPh3GLx.hPpdb.NRCCsz' -d 'grant_type=client_credentials' -d 'scope=openid'" ``` + Retrieve token: + ``` curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials" -d "scope=openid" -d "client_id=6c176008-9308-4603-a55a-9975bb3a93b6" -d "client_secret=MCXepEOPh3GLx.hPpdb.NRCCsz" http://127.0.0.1:4444/oauth2/token ``` + To grant READ access to this client_id, the following Access rule can be used: + ``` { "AllAccessPermissionRules": { @@ -193,6 +198,7 @@ To grant READ access to this client_id, the following Access rule can be used: To grant access to specific submodels, FA³ST Service supports Identifiables like `"IDENTIFIABLE": "(Submodel)https://example.com/ids/sm/5120_2111_9032_9005"` or `"IDENTIFIABLE": "(Submodel)*"`. Example: + ``` { "AllAccessPermissionRules": { From c5d97298c4066feaeecdbaadb60048ac7dd82d5b Mon Sep 17 00:00:00 2001 From: Friedrich Volz Date: Fri, 13 Feb 2026 11:02:33 +0100 Subject: [PATCH 083/108] change queries to require query keyword --- .../QueryAssetAdministrationShellsRequestMapper.java | 4 ++-- .../QueryConceptDescriptionsRequestMapper.java | 4 ++-- .../submodelrepository/QuerySubmodelsRequestMapper.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java index f17b57cfc..4e59da643 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java @@ -21,7 +21,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.QueryAssetAdministrationShellsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; @@ -40,7 +40,7 @@ public QueryAssetAdministrationShellsRequestMapper(ServiceContext serviceContext @Override public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { return QueryAssetAdministrationShellsRequest.builder() - .query(parseBody(httpRequest, Query.class)) + .query(parseBody(httpRequest, Schema.class).getQuery()) .build(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java index f8750434f..60e8872e7 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java @@ -21,7 +21,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.QueryConceptDescriptionsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; @@ -40,7 +40,7 @@ public QueryConceptDescriptionsRequestMapper(ServiceContext serviceContext) { @Override public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { return QueryConceptDescriptionsRequest.builder() - .query(parseBody(httpRequest, Query.class)) + .query(parseBody(httpRequest, Schema.class).getQuery()) .build(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java index 59c398290..a1799ec0b 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java @@ -21,7 +21,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.QuerySubmodelsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; @@ -40,7 +40,7 @@ public QuerySubmodelsRequestMapper(ServiceContext serviceContext) { @Override public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { return QuerySubmodelsRequest.builder() - .query(parseBody(httpRequest, Query.class)) + .query(parseBody(httpRequest, Schema.class).getQuery()) .build(); } } From be4c8ffbbc6d0a2700443cc747b6bcc730b5c782 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:41:24 +0200 Subject: [PATCH 084/108] feat: extract ACL file monitor service --- .../service/endpoint/http/HttpEndpoint.java | 15 +- .../endpoint/http/RequestHandlerServlet.java | 5 +- .../http/security/filter/ApiGateway.java | 161 ++---------------- .../filter/JwtAuthorizationFilter.java | 9 +- .../security/filter/JwtValidationFilter.java | 6 +- .../http/util/AclFileMonitoringHelper.java | 155 +++++++++++++++++ .../security/filter/ApiGatewayFilterTest.java | 3 +- 7 files changed, 196 insertions(+), 158 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index f70f0fc3e..efb38fb3e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -23,6 +23,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.JwtValidationFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.Interface; @@ -88,6 +89,7 @@ public HttpEndpointConfig asConfig() { private ServletContextHandler context; + @Override public void start() throws EndpointException { if (server != null && server.isStarted()) { @@ -102,10 +104,11 @@ public void start() throws EndpointException { context.setContextPath("/"); crossOriginHandler.setHandler(context); - RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext); - context.addServlet(handler, "/*"); + AclFileMonitoringHelper aclFileMonitoringHelper = null; if (Objects.nonNull(config.getJwkProvider())) { + aclFileMonitoringHelper = new AclFileMonitoringHelper(config.getAclFolder()); + URL jwkProviderUrl; try { jwkProviderUrl = new URL(config.getJwkProvider()); @@ -115,10 +118,14 @@ public void start() throws EndpointException { } JwkProvider jwkProvider = new UrlJwkProvider(jwkProviderUrl); - context.addFilter(new JwtValidationFilter(jwkProvider), - "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new JwtValidationFilter(jwkProvider), "*", EnumSet.allOf(DispatcherType.class)); } + + RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext, aclFileMonitoringHelper); + context.addServlet(handler, "/*"); + server.setErrorHandler(new HttpErrorHandler(config)); + try { server.start(); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 61dcb0b61..7214a8fa2 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -23,6 +23,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; @@ -58,7 +59,7 @@ public class RequestHandlerServlet extends HttpServlet { private final HttpJsonApiSerializer serializer; private final ApiGateway apiGateway; - public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext) { + public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext, AclFileMonitoringHelper aclFileMonitoringHelper) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); Ensure.requireNonNull(config, "config must be non-null"); Ensure.requireNonNull(serviceContext, "serviceContext must be non-null"); @@ -68,7 +69,7 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); - this.apiGateway = Objects.nonNull(config.getAclFolder()) ? new ApiGateway(config.getAclFolder()) : null; + this.apiGateway = Optional.ofNullable(aclFileMonitoringHelper).map(ApiGateway::new).orElse(null); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 54e2245aa..6ec5a436f 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -15,13 +15,13 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState.ANONYMOUS; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; @@ -39,18 +39,10 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardWatchEventKinds; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; + import java.time.Clock; import java.time.LocalTime; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -59,6 +51,7 @@ import java.util.Optional; import java.util.Set; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,16 +60,13 @@ */ public class ApiGateway { - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); private static final String BEARER_KWD = "Bearer"; - private Map aclList; - private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules.)"; - private final String errorMessage = "Invalid ACL rule, skipping."; + private final AclFileMonitoringHelper aclFileMonitoringHelper; - public ApiGateway(String aclFolder) { - initializeAclList(aclFolder); - monitorAclRules(aclFolder); + public ApiGateway(AclFileMonitoringHelper aclFileMonitoringHelper) { + this.aclFileMonitoringHelper = aclFileMonitoringHelper; } @@ -87,16 +77,11 @@ public ApiGateway(String aclFolder) { * @return true if authorized and ACL exists */ public boolean isAuthorized(HttpServletRequest request) { - String token = request.getHeader("Authorization"); - if (token == null) { - return AuthServer.filterRules(this.aclList, null, request); + if (ANONYMOUS == request.getAttribute("auth.state")) { + return AuthServer.filterRules(aclFileMonitoringHelper.getAll(), null, request); } else { - if (token.startsWith(BEARER_KWD + " ")) { - token = token.substring(BEARER_KWD.length() + 1).trim(); - } - DecodedJWT jwt = JWT.decode(token); - return AuthServer.filterRules(this.aclList, jwt.getClaims(), request); + return AuthServer.filterRules(aclFileMonitoringHelper.getAll(), (Map) request.getAttribute("claims"), request); } } @@ -110,7 +95,7 @@ public boolean isAuthorized(HttpServletRequest request) { */ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { response.getPayload().getContent() - .removeIf(aas -> aclList.values().stream() + .removeIf(aas -> aclFileMonitoringHelper.getAll().stream() .noneMatch(a -> a.getRules().stream() .anyMatch(r -> AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), request.getMethod(), extractClaims(request), a)))); @@ -136,7 +121,7 @@ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsRespo fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); } - return aclList.values().stream() + return aclFileMonitoringHelper.getAll().stream() .noneMatch(allAccess -> allAccess.getRules().stream().anyMatch(rule -> AuthServer.evaluateRule(rule, path, method, claims, allAccess, fieldCtx))); }); return response; @@ -164,7 +149,7 @@ public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse re fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); } - return aclList.values().stream() + return aclFileMonitoringHelper.getAll().stream() .anyMatch(allAccess -> allAccess.getRules().stream().anyMatch(rule -> AuthServer.evaluateRule(rule, path, method, claims, allAccess, fieldCtx))); } @@ -202,11 +187,11 @@ public static class AuthServer { * @param request the request coming in * @return true if there is a valid rule */ - private static boolean filterRules(Map aclList, Map claims, HttpServletRequest request) { + private static boolean filterRules(Collection aclList, Map claims, HttpServletRequest request) { String requestPath = request.getRequestURI(); String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(apiPrefix.length()) : requestPath; String method = request.getMethod(); - List relevantRules = aclList.values().stream() + List relevantRules = aclList.stream() .filter(a -> a.getRules().stream() .anyMatch(r -> evaluateRule(r, path, method, claims, a))) .toList(); @@ -361,118 +346,6 @@ else if (attr.getDescriptor() != null) { } } - private void initializeAclList(String aclFolder) { - this.aclList = new HashMap<>(); - if (aclFolder == null - || aclFolder.trim().isEmpty() - || !new File(aclFolder.trim()).isDirectory()) { - LOGGER.error(abortMessage); - return; - } - File folder = new File(aclFolder.trim()); - File[] jsonFiles = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); - ObjectMapper mapper = new ObjectMapper(); - if (jsonFiles != null) { - for (File file: jsonFiles) { - Path filePath = file.toPath(); - try { - String jsonContent = Files.readString(filePath); - JsonNode rootNode = mapper.readTree(jsonContent); - AllAccessPermissionRules allRules; - if (rootNode.has("AllAccessPermissionRules")) { - allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); - } - else { - allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); - } - aclList.put(filePath, allRules); - } - catch (IOException e) { - LOGGER.error(errorMessage); - } - } - } - } - - - private void monitorAclRules(String aclFolder) { - if (aclFolder == null - || aclFolder.trim().isEmpty() - || !new File(aclFolder.trim()).isDirectory()) { - LOGGER.error(abortMessage); - return; - } - Path folderToWatch = Paths.get(aclFolder); - WatchService watchService; - try { - watchService = FileSystems.getDefault().newWatchService(); - // Register the folder with the WatchService for CREATE and DELETE events - folderToWatch.register( - watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_DELETE); - monitorLoop(watchService, folderToWatch); - } - catch (IOException e) { - LOGGER.error(errorMessage); - } - - } - - - private void monitorLoop(WatchService watchService, Path folderToWatch) { - ObjectMapper mapper = new ObjectMapper(); - Thread monitoringThread = new Thread(() -> { - while (!Thread.currentThread().isInterrupted()) { - WatchKey watchKey; - try { - watchKey = watchService.take(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); // restore interrupt status - LOGGER.warn("ACL monitoring thread interrupted", e); - break; // exit loop - } - for (WatchEvent event: watchKey.pollEvents()) { - WatchEvent.Kind kind = event.kind(); - Path filePath = (Path) event.context(); - Path absolutePath = folderToWatch.resolve(filePath).toAbsolutePath(); - // Check if the file is a JSON file - if (filePath.toString().toLowerCase().endsWith(".json")) { - if (kind == StandardWatchEventKinds.ENTRY_CREATE) { - try { - String jsonContent = Files.readString(absolutePath); - JsonNode rootNode = mapper.readTree(jsonContent); - AllAccessPermissionRules allRules; - if (rootNode.has("AllAccessPermissionRules")) { - allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); - } - else { - allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); - } - aclList.put(absolutePath, allRules); - } - catch (IOException e) { - LOGGER.error(errorMessage); - } - LOGGER.info("Added new ACL rule."); - } - else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { - aclList.remove(absolutePath); - LOGGER.info("Removed ACL rule."); - } - } - } - boolean valid = watchKey.reset(); - if (!valid) { - LOGGER.info("WatchKey no longer valid; exiting."); - break; - } - } - }); - monitoringThread.start(); - } - private static Acl getAcl(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { if (rule.getAcl() != null) { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java index e51715de9..48d3e91a0 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java @@ -24,7 +24,8 @@ * Abstract filter for HTTP requests with JWT headers. */ public abstract class JwtAuthorizationFilter implements Filter { - private static final String BEARER_KWD = "Bearer"; + protected static final String AUTHORIZATION = "Authorization"; + private static final String BEARER = "Bearer"; /** * Extracts a JWT from an HTTP request by reading its Authorization header, @@ -35,14 +36,14 @@ public abstract class JwtAuthorizationFilter implements Filter { * @return The decoded JWT if present, else null */ protected DecodedJWT extractAndDecodeJwt(HttpServletRequest request) { - var authHeaderValue = request.getHeader("Authorization"); + var authHeaderValue = request.getHeader(AUTHORIZATION); - if (authHeaderValue == null || !authHeaderValue.startsWith(BEARER_KWD.concat(" "))) { + if (authHeaderValue == null || !authHeaderValue.startsWith(BEARER.concat(" "))) { return null; } // Remove "Bearer " - String token = authHeaderValue.substring(BEARER_KWD.length()).trim(); + String token = authHeaderValue.substring(BEARER.length()).trim(); return JWT.decode(token); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java index b879ba505..44821f176 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java @@ -43,7 +43,6 @@ */ public class JwtValidationFilter extends JwtAuthorizationFilter { - private static final String AUTHORIZATION_KWD = "Authorization"; private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JwtValidationFilter.class); private final JwkProvider jwkProvider; @@ -69,7 +68,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; // If multiple Authorization headers are present, attackers could maybe use one for auth and one for claims - var authHeaders = httpRequest.getHeaders(AUTHORIZATION_KWD); + var authHeaders = httpRequest.getHeaders(AUTHORIZATION); var authHeaderList = Collections.list(authHeaders); if (authHeaderList.size() > 1) { LOGGER.debug("Multiple authorization headers present! Not authorizing request."); @@ -77,7 +76,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo return; } - if (httpRequest.getHeader(AUTHORIZATION_KWD) == null) { + if (httpRequest.getHeader(AUTHORIZATION) == null) { // No JWT in request, anonymous requestor servletRequest.setAttribute("auth.state", AuthState.ANONYMOUS); filterChain.doFilter(servletRequest, servletResponse); @@ -93,6 +92,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo else { // Continue with the request as authenticated servletRequest.setAttribute("auth.state", AuthState.AUTHENTICATED); + servletRequest.setAttribute("claims", jwt.getClaims()); filterChain.doFilter(servletRequest, servletResponse); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java new file mode 100644 index 000000000..8595e83fa --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java @@ -0,0 +1,155 @@ +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + + +public class AclFileMonitoringHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(AclFileMonitoringHelper.class); + private final Map aclList; + + private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules."; + private final String errorMessage = "Invalid ACL rule, skipping."; + + + public AclFileMonitoringHelper(String aclFolder) { + this.aclList = new HashMap<>(); + initializeAclList(aclFolder); + monitorAclRules(aclFolder); + } + + + public Collection getAll() { + return aclList.values(); + } + + + private void initializeAclList(String aclFolder) { + if (aclFolder == null + || aclFolder.trim().isEmpty() + || !new File(aclFolder.trim()).isDirectory()) { + LOGGER.error(abortMessage); + return; + } + File folder = new File(aclFolder.trim()); + File[] jsonFiles = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); + ObjectMapper mapper = new ObjectMapper(); + if (jsonFiles != null) { + for (File file: jsonFiles) { + Path filePath = file.toPath(); + try { + String jsonContent = Files.readString(filePath); + JsonNode rootNode = mapper.readTree(jsonContent); + AllAccessPermissionRules allRules; + if (rootNode.has("AllAccessPermissionRules")) { + allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); + } + else { + allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); + } + aclList.put(filePath, allRules); + } + catch (IOException e) { + LOGGER.error(errorMessage); + } + } + } + } + + + private void monitorAclRules(String aclFolder) { + if (aclFolder == null + || aclFolder.trim().isEmpty() + || !new File(aclFolder.trim()).isDirectory()) { + LOGGER.error(abortMessage); + return; + } + Path folderToWatch = Paths.get(aclFolder); + WatchService watchService; + try { + watchService = FileSystems.getDefault().newWatchService(); + // Register the folder with the WatchService for CREATE and DELETE events + folderToWatch.register( + watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE); + monitorLoop(watchService, folderToWatch); + } + catch (IOException e) { + LOGGER.error(errorMessage); + } + } + + + private void monitorLoop(WatchService watchService, Path folderToWatch) { + ObjectMapper mapper = new ObjectMapper(); + Thread monitoringThread = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + WatchKey watchKey; + try { + watchKey = watchService.take(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); // restore interrupt status + LOGGER.warn("ACL monitoring thread interrupted", e); + break; // exit loop + } + for (WatchEvent event: watchKey.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + Path filePath = (Path) event.context(); + Path absolutePath = folderToWatch.resolve(filePath).toAbsolutePath(); + // Check if the file is a JSON file + if (filePath.toString().toLowerCase().endsWith(".json")) { + if (kind == StandardWatchEventKinds.ENTRY_CREATE) { + try { + String jsonContent = Files.readString(absolutePath); + JsonNode rootNode = mapper.readTree(jsonContent); + AllAccessPermissionRules allRules; + if (rootNode.has("AllAccessPermissionRules")) { + allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); + } + else { + allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); + } + aclList.put(absolutePath, allRules); + } + catch (IOException e) { + LOGGER.error(errorMessage); + } + LOGGER.info("Added new ACL rule."); + } + else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { + aclList.remove(absolutePath); + LOGGER.info("Removed ACL rule."); + } + } + } + boolean valid = watchKey.reset(); + if (!valid) { + LOGGER.info("WatchKey no longer valid; exiting."); + break; + } + } + }); + monitoringThread.start(); + } + + +} diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index 2cfbef939..da4052231 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; @@ -68,7 +69,7 @@ private static HttpServletRequest req(String method, String uri) { public void anonymousAccessDependsOnAclFile() throws Exception { Path aclDir = tmp.newFolder("acl").toPath(); - apiGateway = new ApiGateway(aclDir.toString()); + apiGateway = new ApiGateway(new AclFileMonitoringHelper(aclDir.toString())); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); FilterChain filter = mockFilterChain(); From 59c6cbe34e5022ecee3ea07018cfd999a7c56cdd Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:30:29 +0200 Subject: [PATCH 085/108] feat: create acl repository --- .../http/acl/repository/AclRepository.java | 26 +++ .../acl/repository/file/DirectoryWatcher.java | 118 +++++++++++++ .../file/DirectoryWatcherListener.java | 28 ++++ .../repository/file/FileAclRepository.java | 116 +++++++++++++ .../http/util/AclFileMonitoringHelper.java | 155 ------------------ 5 files changed, 288 insertions(+), 155 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java delete mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java new file mode 100644 index 000000000..d8ec81a95 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java @@ -0,0 +1,26 @@ +/* + * 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.ilt.faaast.service.endpoint.http.acl.repository; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; + + +/** + * Keeps an in-memory version of the aclFolder's rules. When access control rules are added/deleted/modified, updates + * its own state accordingly. + */ +public interface AclRepository { + AllAccessPermissionRules getAllAccessPermissionRules(); +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java new file mode 100644 index 000000000..c7ef20de8 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java @@ -0,0 +1,118 @@ +/* + * 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.ilt.faaast.service.endpoint.http.acl.repository.file; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +import java.io.IOException; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiConsumer; + + +public class DirectoryWatcher implements AutoCloseable { + + private final Path dir; + private final WatchService watchService; + private final List listeners = new CopyOnWriteArrayList<>(); + private final Thread workerThread; + private volatile boolean running = true; + + public DirectoryWatcher(Path dir) throws IOException { + this.dir = dir.toAbsolutePath().normalize(); + this.watchService = FileSystems.getDefault().newWatchService(); + this.dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + + workerThread = new Thread(this::processEvents, "DirectoryWatcher-" + dir); + workerThread.setDaemon(true); + workerThread.start(); + } + + + public void addListener(DirectoryWatcherListener listener) { + listeners.add(listener); + } + + + private void processEvents() { + try { + while (running) { + WatchKey key; + try { + key = watchService.take(); // blocks + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + catch (ClosedWatchServiceException e) { + break; + } + + for (WatchEvent event: key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + if (kind == StandardWatchEventKinds.OVERFLOW) { + continue; + } + + Path relative = (Path) event.context(); + Path full = dir.resolve(relative); + + if (kind == ENTRY_CREATE) { + notifyAbout(full, DirectoryWatcherListener::onFileCreated); + } + else if (kind == ENTRY_DELETE) { + notifyAbout(full, DirectoryWatcherListener::onFileDeleted); + } + else if (kind == ENTRY_MODIFY) { + notifyAbout(full, DirectoryWatcherListener::onFileModified); + } + } + + boolean valid = key.reset(); + if (!valid) { + break; + } + } + } + finally { + running = false; + } + } + + + @Override + public void close() throws IOException { + running = false; + watchService.close(); + workerThread.interrupt(); + } + + + private void notifyAbout(Path file, BiConsumer action) { + for (DirectoryWatcherListener l: listeners) { + action.accept(l, file); + } + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java new file mode 100644 index 000000000..34d976c81 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java @@ -0,0 +1,28 @@ +/* + * 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.ilt.faaast.service.endpoint.http.acl.repository.file; + +import java.nio.file.Path; + + +public interface DirectoryWatcherListener { + void onFileCreated(Path file); + + + void onFileDeleted(Path file); + + + void onFileModified(Path file); +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java new file mode 100644 index 000000000..92a324970 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java @@ -0,0 +1,116 @@ +/* + * 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.ilt.faaast.service.endpoint.http.acl.repository.file; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class FileAclRepository implements AclRepository, DirectoryWatcherListener { + private static final Logger LOGGER = LoggerFactory.getLogger(FileAclRepository.class); + private final Map aclList; + private final ObjectMapper mapper; + + private FileAclRepository() { + this.aclList = new HashMap<>(); + this.mapper = new ObjectMapper(); + } + + + /** + * Creates a FileAclRepository. + * + * @param aclFolder Directory where the observed access control rules live. + * @return New instance of a FileAclRepository. + * @throws EndpointException if initializing the ACL memory representation fails + */ + public static FileAclRepository createNewInstance(String aclFolder) throws EndpointException { + FileAclRepository instance = new FileAclRepository(); + try (DirectoryWatcher watcher = new DirectoryWatcher(Paths.get(aclFolder))) { + watcher.addListener(instance); + return instance; + } + catch (IOException e) { + throw new EndpointException(e); + } + } + + + /** + * Get all currently observed rules. + * + * @return All access control rules. + */ + public AllAccessPermissionRules getAllAccessPermissionRules() { + var rules = new AllAccessPermissionRules(); + for(AllAccessPermissionRules fileRules : aclList.values()) { + rules.setRules(fileRules.getRules()); + rules.setDefacls(fileRules.getDefacls()); + rules.setDefattributes(fileRules.getDefattributes()); + rules.setDefformulas(fileRules.getDefformulas()); + rules.setDefobjects(fileRules.getDefobjects()); + } + + return rules; + } + + + @Override + public void onFileCreated(Path path) { + LOGGER.debug("Added ACL {}", path); + aclList.put(path, readFile(path)); + } + + + @Override + public void onFileDeleted(Path path) { + LOGGER.debug("Removed ACL {}", path); + aclList.remove(path); + } + + + @Override + public void onFileModified(Path path) { + LOGGER.debug("Changed ACL {}", path); + aclList.put(path, readFile(path)); + } + + + private AllAccessPermissionRules readFile(Path path) { + try { + JsonNode rootNode = mapper.readTree(path.toFile()); + if (rootNode.has("AllAccessPermissionRules")) { + return mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); + } + else { + return mapper.readValue(rootNode.toString(), AllAccessPermissionRules.class); + } + } + catch (IOException e) { + throw new IllegalStateException(String.format("Could not parse latest addition to ACL folder: %s", path), e); + } + } + +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java deleted file mode 100644 index 8595e83fa..000000000 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AclFileMonitoringHelper.java +++ /dev/null @@ -1,155 +0,0 @@ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardWatchEventKinds; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - - -public class AclFileMonitoringHelper { - private static final Logger LOGGER = LoggerFactory.getLogger(AclFileMonitoringHelper.class); - private final Map aclList; - - private final String abortMessage = "Invalid ACL folder path, AAS Security will not enforce rules."; - private final String errorMessage = "Invalid ACL rule, skipping."; - - - public AclFileMonitoringHelper(String aclFolder) { - this.aclList = new HashMap<>(); - initializeAclList(aclFolder); - monitorAclRules(aclFolder); - } - - - public Collection getAll() { - return aclList.values(); - } - - - private void initializeAclList(String aclFolder) { - if (aclFolder == null - || aclFolder.trim().isEmpty() - || !new File(aclFolder.trim()).isDirectory()) { - LOGGER.error(abortMessage); - return; - } - File folder = new File(aclFolder.trim()); - File[] jsonFiles = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); - ObjectMapper mapper = new ObjectMapper(); - if (jsonFiles != null) { - for (File file: jsonFiles) { - Path filePath = file.toPath(); - try { - String jsonContent = Files.readString(filePath); - JsonNode rootNode = mapper.readTree(jsonContent); - AllAccessPermissionRules allRules; - if (rootNode.has("AllAccessPermissionRules")) { - allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); - } - else { - allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); - } - aclList.put(filePath, allRules); - } - catch (IOException e) { - LOGGER.error(errorMessage); - } - } - } - } - - - private void monitorAclRules(String aclFolder) { - if (aclFolder == null - || aclFolder.trim().isEmpty() - || !new File(aclFolder.trim()).isDirectory()) { - LOGGER.error(abortMessage); - return; - } - Path folderToWatch = Paths.get(aclFolder); - WatchService watchService; - try { - watchService = FileSystems.getDefault().newWatchService(); - // Register the folder with the WatchService for CREATE and DELETE events - folderToWatch.register( - watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_DELETE); - monitorLoop(watchService, folderToWatch); - } - catch (IOException e) { - LOGGER.error(errorMessage); - } - } - - - private void monitorLoop(WatchService watchService, Path folderToWatch) { - ObjectMapper mapper = new ObjectMapper(); - Thread monitoringThread = new Thread(() -> { - while (!Thread.currentThread().isInterrupted()) { - WatchKey watchKey; - try { - watchKey = watchService.take(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); // restore interrupt status - LOGGER.warn("ACL monitoring thread interrupted", e); - break; // exit loop - } - for (WatchEvent event: watchKey.pollEvents()) { - WatchEvent.Kind kind = event.kind(); - Path filePath = (Path) event.context(); - Path absolutePath = folderToWatch.resolve(filePath).toAbsolutePath(); - // Check if the file is a JSON file - if (filePath.toString().toLowerCase().endsWith(".json")) { - if (kind == StandardWatchEventKinds.ENTRY_CREATE) { - try { - String jsonContent = Files.readString(absolutePath); - JsonNode rootNode = mapper.readTree(jsonContent); - AllAccessPermissionRules allRules; - if (rootNode.has("AllAccessPermissionRules")) { - allRules = mapper.treeToValue(rootNode.get("AllAccessPermissionRules"), AllAccessPermissionRules.class); - } - else { - allRules = mapper.readValue(jsonContent, AllAccessPermissionRules.class); - } - aclList.put(absolutePath, allRules); - } - catch (IOException e) { - LOGGER.error(errorMessage); - } - LOGGER.info("Added new ACL rule."); - } - else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { - aclList.remove(absolutePath); - LOGGER.info("Removed ACL rule."); - } - } - } - boolean valid = watchKey.reset(); - if (!valid) { - LOGGER.info("WatchKey no longer valid; exiting."); - break; - } - } - }); - monitoringThread.start(); - } - - -} From 5277fbaf10ff60bca20505f3c67a82dec6f8adac Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:30:51 +0200 Subject: [PATCH 086/108] feat: several new filters --- .../http/security/auth/SharedAttributes.java | 18 ++ .../filter/AclRulesInceptionFilter.java | 36 ++++ .../http/security/filter/ApiGateway.java | 154 ++++++------------ .../security/filter/AttributeClaimFilter.java | 46 ++++++ .../security/filter/HttpMethodFilter.java | 63 +++++++ .../security/filter/JwtValidationFilter.java | 9 +- 6 files changed, 220 insertions(+), 106 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java new file mode 100644 index 000000000..ce6ade47b --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java @@ -0,0 +1,18 @@ +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth; + +public enum SharedAttributes { + AUTH_STATE("auth.state"), + CLAIMS("claims"), + ACL("acl"); + + private final String name; + + + public String getName() { + return name; + } + + SharedAttributes(String name) { + this.name = name; + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java new file mode 100644 index 000000000..b82ef5db4 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java @@ -0,0 +1,36 @@ +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; + +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; + + +/** + * Helper filter to inject the current ACL rules into a request. + */ +public class AclRulesInceptionFilter implements Filter { + + private final AclRepository aclRepository; + + + /** + * Class constructor. + * + * @param aclRepository Retrieval of ACL + */ + public AclRulesInceptionFilter(AclRepository aclRepository) { + this.aclRepository = aclRepository; + } + + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + // Intentionally empty + chain.doFilter(request, response); + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 6ec5a436f..b805d2f3a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -14,14 +14,11 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState.ANONYMOUS; - import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; @@ -39,10 +36,12 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.time.Clock; import java.time.LocalTime; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -50,9 +49,10 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState.ANONYMOUS; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.CLAIMS; /** @@ -61,12 +61,12 @@ public class ApiGateway { private static final Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); - private static final String BEARER_KWD = "Bearer"; - private final AclFileMonitoringHelper aclFileMonitoringHelper; + private final AclRepository aclRepository; + - public ApiGateway(AclFileMonitoringHelper aclFileMonitoringHelper) { - this.aclFileMonitoringHelper = aclFileMonitoringHelper; + public ApiGateway(AclRepository aclRepository) { + this.aclRepository = aclRepository; } @@ -77,12 +77,7 @@ public ApiGateway(AclFileMonitoringHelper aclFileMonitoringHelper) { * @return true if authorized and ACL exists */ public boolean isAuthorized(HttpServletRequest request) { - if (ANONYMOUS == request.getAttribute("auth.state")) { - return AuthServer.filterRules(aclFileMonitoringHelper.getAll(), null, request); - } - else { - return AuthServer.filterRules(aclFileMonitoringHelper.getAll(), (Map) request.getAttribute("claims"), request); - } + return AuthServer.filterRules(aclRepository.getAllAccessPermissionRules(), extractClaims(request), request); } @@ -94,11 +89,13 @@ public boolean isAuthorized(HttpServletRequest request) { * @return the ApiResponse with only allowed AAS */ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { + // remove AAS if none of the ALL_ACL have any rule that matches + response.getPayload().getContent() - .removeIf(aas -> aclFileMonitoringHelper.getAll().stream() - .noneMatch(a -> a.getRules().stream() - .anyMatch(r -> AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), - request.getMethod(), extractClaims(request), a)))); + .removeIf(aas -> aclRepository.getAllAccessPermissionRules().getRules() + .stream().noneMatch(r -> + AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), + extractClaims(request), aclRepository.getAllAccessPermissionRules()))); return response; } @@ -113,7 +110,6 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { response.getPayload().getContent().removeIf(submodel -> { String path = "/submodels/" + EncodingHelper.base64Encode(submodel.getId()); - String method = request.getMethod(); Map claims = extractClaims(request); Map fieldCtx = new HashMap<>(); @@ -121,8 +117,8 @@ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsRespo fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); } - return aclFileMonitoringHelper.getAll().stream() - .noneMatch(allAccess -> allAccess.getRules().stream().anyMatch(rule -> AuthServer.evaluateRule(rule, path, method, claims, allAccess, fieldCtx))); + return aclRepository.getAllAccessPermissionRules().getRules().stream() + .noneMatch(rule -> AuthServer.evaluateRule(rule, path, claims, aclRepository.getAllAccessPermissionRules(), fieldCtx)); }); return response; } @@ -141,7 +137,6 @@ public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse re return true; } String path = "/submodels/" + EncodingHelper.base64Encode(submodel.getId()); - String method = request.getMethod(); Map claims = extractClaims(request); Map fieldCtx = new HashMap<>(); @@ -149,53 +144,39 @@ public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse re fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); } - return aclFileMonitoringHelper.getAll().stream() - .anyMatch(allAccess -> allAccess.getRules().stream().anyMatch(rule -> AuthServer.evaluateRule(rule, path, method, claims, allAccess, fieldCtx))); + return aclRepository.getAllAccessPermissionRules().getRules().stream() + .anyMatch(rule -> AuthServer.evaluateRule(rule, path, claims, aclRepository.getAllAccessPermissionRules(), fieldCtx)); } + @SuppressWarnings("unchecked") private Map extractClaims(HttpServletRequest request) { - String token = request.getHeader("Authorization"); - if (token == null) { - return null; - } - if (token.startsWith(BEARER_KWD + " ")) { - token = token.substring(BEARER_KWD.length() + 1).trim(); - } - try { - DecodedJWT jwt = JWT.decode(token); - return jwt.getClaims(); - } - catch (com.auth0.jwt.exceptions.JWTDecodeException e) { - return null; - } + return (Map) request.getAttribute(CLAIMS.getName()); } + /** - * Simple whitelist AuthServer implementation that supports ANONYMOUS access, - * claims with simple eq formulas and route authorization. - * Access must be explicitly defined, otherwise it is blocked. + * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route authorization. Access must be explicitly defined, + * otherwise it is blocked. */ public static class AuthServer { private static final String apiPrefix = "/api/v3.0/"; + /** - * Check all rules that explicitly allows the request. - * If a rule exists after all filters, true is returned + * Check all rules that explicitly allows the request. If a rule exists after all filters, true is returned * * @param claims the claims found in the token * @param request the request coming in * @return true if there is a valid rule */ - private static boolean filterRules(Collection aclList, Map claims, HttpServletRequest request) { + private static boolean filterRules(AllAccessPermissionRules aclList, Map claims, HttpServletRequest request) { + // Filter ACL rules (Claims, Global Attributes, maybe ReferenceAttribute + String requestPath = request.getRequestURI(); String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(apiPrefix.length()) : requestPath; - String method = request.getMethod(); - List relevantRules = aclList.stream() - .filter(a -> a.getRules().stream() - .anyMatch(r -> evaluateRule(r, path, method, claims, a))) - .toList(); - return !relevantRules.isEmpty(); + aclList.getRules().removeIf(r -> !evaluateRule(r, path, claims, aclList)); + return !aclList.getRules().isEmpty(); } @@ -234,39 +215,6 @@ private static boolean verifyAllClaims(Map claims, AccessPermissi } - private static boolean evaluateRights(List aclRights, String method, String path) { - String requiredRight = isOperationRequest(method, path) ? "EXECUTE" : getRequiredRight(method); - return aclRights.contains(RightsEnum.ALL) || aclRights.contains(RightsEnum.valueOf(requiredRight)); - } - - - private static boolean isOperationRequest(String method, String path) { - // Requirements: POST and URL suffix: invoke, invoke-async, invoke/$value, invoke-async/$value - String cleanPath; - String[] pathParts = path.split("/"); - - if (pathParts.length > 1 && "$value".equals(pathParts[pathParts.length - 1])) { - cleanPath = pathParts[pathParts.length - 2]; - } - else { - cleanPath = pathParts[pathParts.length - 1]; - } - - return POST.name().equals(method) && ("invoke".equals(cleanPath) || "invoke-async".equals(cleanPath)); - } - - - private static String getRequiredRight(String method) { - return switch (method) { - case "GET" -> "READ"; - case "POST" -> "CREATE"; - case "PUT" -> "UPDATE"; - case "DELETE" -> "DELETE"; - default -> throw new IllegalArgumentException("Unsupported method: " + method); - }; - } - - private static boolean checkIdentifiable(String path, String identifiable) { //check submodel path if (!(path.startsWith("/submodels") || path.startsWith("/shells"))) { @@ -320,29 +268,29 @@ else if (descriptor.startsWith("(smDesc)")) { } - private static boolean evaluateRule(AccessPermissionRule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess) { - return evaluateRule(rule, path, method, claims, allAccess, null); + private static boolean evaluateRule(AccessPermissionRule rule, String path, Map claims, AllAccessPermissionRules allAccess) { + return evaluateRule(rule, path, claims, allAccess, null); } - private static boolean evaluateRule(AccessPermissionRule rule, String path, String method, Map claims, AllAccessPermissionRules allAccess, + private static boolean evaluateRule(AccessPermissionRule rule, String path, Map claims, AllAccessPermissionRules allAccess, Map fieldCtx) { Acl acl = getAcl(rule, allAccess); return acl != null && getAttributes(acl, allAccess) != null && acl.getRights() != null && getObjects(rule, allAccess) != null && getObjects(rule, allAccess).stream().anyMatch(attr -> { - if (attr.getRoute() != null) { - return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); - } - else if (attr.getIdentifiable() != null) { - return checkIdentifiable(path, attr.getIdentifiable()); - } - else if (attr.getDescriptor() != null) { - return checkDescriptor(path, attr.getDescriptor()); - } - else { - return false; - } - }) && "ALLOW".equals(acl.getAccess().value()) && evaluateRights(acl.getRights(), method, path) && verifyAllClaims(claims, rule, allAccess, fieldCtx); + if (attr.getRoute() != null) { + return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); + } + else if (attr.getIdentifiable() != null) { + return checkIdentifiable(path, attr.getIdentifiable()); + } + else if (attr.getDescriptor() != null) { + return checkDescriptor(path, attr.getDescriptor()); + } + else { + return false; + } + }) && "ALLOW".equals(acl.getAccess().value()) && verifyAllClaims(claims, rule, allAccess, fieldCtx); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java new file mode 100644 index 000000000..f149b1bc5 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java @@ -0,0 +1,46 @@ +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; + +import com.auth0.jwt.interfaces.Claim; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; +import java.util.Map; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.CLAIMS; + + +/** + * Filter all ACL for the Claims they require + */ +public class AttributeClaimFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { + Map claims = (Map) request.getAttribute(CLAIMS.getName()); + AllAccessPermissionRules filteredAcl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + + filteredAcl.getRules().removeIf(rule -> rule.getAcl().getAttributes().stream() + .anyMatch(attributeItem -> { + // claim, global and reference should be subtypes of AttributeItem... + if (attributeItem.getGlobal().equals(AttributeItem.Global.ANONYMOUS)) { + return false; + } + else if (attributeItem.getClaim() != null) { + return !claims.containsKey(attributeItem.getClaim()); + } + return false; + }) + ); + + request.setAttribute(ACL.getName(), filteredAcl); + + chain.doFilter(request, response); + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java new file mode 100644 index 000000000..c9048f70f --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java @@ -0,0 +1,63 @@ +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; + +import java.io.IOException; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + + +public class HttpMethodFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { + String method = ((HttpServletRequest) request).getMethod(); + + AllAccessPermissionRules filteredAcl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + + String requiredRight = isOperationRequest(method, ((HttpServletRequest) request).getContextPath()) ? "EXECUTE" : getRequiredRight(method); + + filteredAcl.getRules().removeIf( + rules -> rules.getAcl().getRights().contains(RightsEnum.ALL) || rules.getAcl().getRights().contains(RightsEnum.valueOf(requiredRight)) + ); + + request.setAttribute(ACL.getName(), filteredAcl); + chain.doFilter(request, response); + } + + + private static boolean isOperationRequest(String method, String path) { + // Requirements: POST and URL suffix: invoke, invoke-async, invoke/$value, invoke-async/$value + String cleanPath; + String[] pathParts = path.split("/"); + + if (pathParts.length > 1 && "$value".equals(pathParts[pathParts.length - 1])) { + cleanPath = pathParts[pathParts.length - 2]; + } + else { + cleanPath = pathParts[pathParts.length - 1]; + } + + return POST.name().equals(method) && ("invoke".equals(cleanPath) || "invoke-async".equals(cleanPath)); + } + + + private static String getRequiredRight(String method) { + return switch (method) { + case "GET" -> "READ"; + case "POST" -> "CREATE"; + case "PUT" -> "UPDATE"; + case "DELETE" -> "DELETE"; + default -> throw new IllegalArgumentException("Unsupported method: " + method); + }; + } + +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java index 44821f176..683f65b57 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java @@ -36,6 +36,9 @@ import java.util.Collections; import org.slf4j.LoggerFactory; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.AUTH_STATE; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.CLAIMS; + /** * Filters any incoming request by verifying its JWT if available. @@ -78,7 +81,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (httpRequest.getHeader(AUTHORIZATION) == null) { // No JWT in request, anonymous requestor - servletRequest.setAttribute("auth.state", AuthState.ANONYMOUS); + servletRequest.setAttribute(AUTH_STATE.getName(), AuthState.ANONYMOUS); filterChain.doFilter(servletRequest, servletResponse); return; } @@ -91,8 +94,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } else { // Continue with the request as authenticated - servletRequest.setAttribute("auth.state", AuthState.AUTHENTICATED); - servletRequest.setAttribute("claims", jwt.getClaims()); + servletRequest.setAttribute(AUTH_STATE.getName(), AuthState.AUTHENTICATED); + servletRequest.setAttribute(CLAIMS.getName(), jwt.getClaims()); filterChain.doFilter(servletRequest, servletResponse); } } From 42ad7d4f31995c93abea23b1f6b17684635f79a4 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:31:45 +0200 Subject: [PATCH 087/108] feat: adjust existing --- .../service/endpoint/http/HttpEndpoint.java | 15 +++-- .../endpoint/http/RequestHandlerServlet.java | 56 ++++++++++--------- .../security/filter/ApiGatewayFilterTest.java | 4 +- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index efb38fb3e..1fe83233b 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -22,8 +22,12 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateInformation; import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file.FileAclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AclRulesInceptionFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AttributeClaimFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.HttpMethodFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.JwtValidationFilter; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.Interface; @@ -104,10 +108,10 @@ public void start() throws EndpointException { context.setContextPath("/"); crossOriginHandler.setHandler(context); - AclFileMonitoringHelper aclFileMonitoringHelper = null; + AclRepository aclRepository = null; if (Objects.nonNull(config.getJwkProvider())) { - aclFileMonitoringHelper = new AclFileMonitoringHelper(config.getAclFolder()); + aclRepository = FileAclRepository.createNewInstance(config.getAclFolder()); URL jwkProviderUrl; try { @@ -119,9 +123,12 @@ public void start() throws EndpointException { JwkProvider jwkProvider = new UrlJwkProvider(jwkProviderUrl); context.addFilter(new JwtValidationFilter(jwkProvider), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclRulesInceptionFilter(aclRepository), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new HttpMethodFilter(), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AttributeClaimFilter(), "*", EnumSet.allOf(DispatcherType.class)); } - RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext, aclFileMonitoringHelper); + RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext, aclRepository); context.addServlet(handler, "/*"); server.setErrorHandler(new HttpErrorHandler(config)); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 7214a8fa2..35676be10 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; @@ -23,7 +24,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; @@ -35,19 +35,21 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.digitaltwin.aas4j.v3.model.Message; +import org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum; +import org.eclipse.jetty.server.Response; + import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum; -import org.eclipse.jetty.server.Response; /** - * HTTP handler that actually handles all requests to the endpoint by finding the matching request class, deserializing - * the request, executing it using the serviceContext and serializing the result. + * HTTP handler that actually handles all requests to the endpoint by finding the matching request class, deserializing the request, executing it using the serviceContext and + * serializing the result. */ public class RequestHandlerServlet extends HttpServlet { @@ -59,7 +61,8 @@ public class RequestHandlerServlet extends HttpServlet { private final HttpJsonApiSerializer serializer; private final ApiGateway apiGateway; - public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext, AclFileMonitoringHelper aclFileMonitoringHelper) { + + public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext, AclRepository aclRepository) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); Ensure.requireNonNull(config, "config must be non-null"); Ensure.requireNonNull(serviceContext, "serviceContext must be non-null"); @@ -69,7 +72,7 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); - this.apiGateway = Optional.ofNullable(aclFileMonitoringHelper).map(ApiGateway::new).orElse(null); + this.apiGateway = Optional.ofNullable(aclRepository).map(ApiGateway::new).orElse(null); } @@ -159,10 +162,10 @@ private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model. && response.getStatusCode().isSuccess() && Objects.nonNull(response.getResult()) && Optional.ofNullable(response.getResult().getMessages()) - .orElse(List.of()) - .stream() - .map(message -> message.getMessageType()) - .noneMatch(x -> Objects.equals(x, MessageTypeEnum.ERROR) || Objects.equals(x, MessageTypeEnum.EXCEPTION)); + .orElse(List.of()) + .stream() + .map(Message::getMessageType) + .noneMatch(x -> Objects.equals(x, MessageTypeEnum.ERROR) || Objects.equals(x, MessageTypeEnum.EXCEPTION)); } @@ -170,21 +173,24 @@ private de.fraunhofer.iosb.ilt.faaast.service.model.api.Response handleResponseW de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws ServletException { String url = request.getRequestURI(); - if ((url.equals("/shells") || url.equals("/shells/")) && request.getMethod().equals("GET")) { - GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) serviceContext.execute(endpoint, apiRequest);; - return apiGateway.filterAas(request, aasResponse); - } - else if ((url.equals("/submodels/") || url.equals("/submodels")) && request.getMethod().equals("GET")) { - GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest);; - return apiGateway.filterSubmodels(request, submodelsResponse); - } - else if ((url.matches("^/submodels/[^/]+$")) && request.getMethod().equals("GET")) { - GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest);; - if (!apiGateway.filterSubmodel(request, submodelResponse)) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getRequestURI()))); + + if (request.getMethod().equals("GET")) { + if ((url.equals("/shells") || url.equals("/shells/"))) { + GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) serviceContext.execute(endpoint, apiRequest); + return apiGateway.filterAas(request, aasResponse); + } + else if ((url.equals("/submodels") || url.equals("/submodels/"))) { + GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest); + return apiGateway.filterSubmodels(request, submodelsResponse); + } + else if ((url.matches("^/submodels/[A-Za-z][A-Za-z0-9]*(?:\\.[A-Za-z][A-Za-z0-9]*)*$"))) { + GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest); + if (!apiGateway.filterSubmodel(request, submodelResponse)) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getRequestURI()))); + } + return submodelResponse; } - return submodelResponse; } else if (!apiGateway.isAuthorized(request)) { doThrow(new UnauthorizedException( diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index da4052231..6df14054d 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AclFileMonitoringHelper; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file.FileAclRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; @@ -69,7 +69,7 @@ private static HttpServletRequest req(String method, String uri) { public void anonymousAccessDependsOnAclFile() throws Exception { Path aclDir = tmp.newFolder("acl").toPath(); - apiGateway = new ApiGateway(new AclFileMonitoringHelper(aclDir.toString())); + apiGateway = new ApiGateway(FileAclRepository.createNewInstance(aclDir.toString())); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); FilterChain filter = mockFilterChain(); From 8329e5ffd4307cc0c10c8a572e2ac0f71a5f8929 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:42:13 +0200 Subject: [PATCH 088/108] fix: spotless --- .../service/endpoint/http/HttpEndpoint.java | 1 - .../endpoint/http/RequestHandlerServlet.java | 19 +++-- .../http/acl/repository/AclRepository.java | 5 ++ .../acl/repository/file/DirectoryWatcher.java | 33 ++++++--- .../file/DirectoryWatcherListener.java | 25 ++++++- .../repository/file/FileAclRepository.java | 20 ++++-- .../http/security/auth/SharedAttributes.java | 29 +++++++- .../filter/AclRulesInceptionFilter.java | 16 ++++- .../http/security/filter/ApiGateway.java | 69 +++++++++---------- .../security/filter/AttributeClaimFilter.java | 32 ++++++--- .../security/filter/HttpMethodFilter.java | 27 ++++++-- .../security/filter/JwtValidationFilter.java | 8 --- .../security/filter/ApiGatewayFilterTest.java | 21 ++---- endpoint/http/src/test/resources/acl.json | 27 ++++++++ 14 files changed, 219 insertions(+), 113 deletions(-) create mode 100644 endpoint/http/src/test/resources/acl.json diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 1fe83233b..d93465a48 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -93,7 +93,6 @@ public HttpEndpointConfig asConfig() { private ServletContextHandler context; - @Override public void start() throws EndpointException { if (server != null && server.isStarted()) { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 35676be10..6a5f15a78 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -35,20 +35,20 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.digitaltwin.aas4j.v3.model.Message; -import org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum; -import org.eclipse.jetty.server.Response; - import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.model.Message; +import org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum; +import org.eclipse.jetty.server.Response; /** - * HTTP handler that actually handles all requests to the endpoint by finding the matching request class, deserializing the request, executing it using the serviceContext and + * HTTP handler that actually handles all requests to the endpoint by finding the matching request class, deserializing + * the request, executing it using the serviceContext and * serializing the result. */ public class RequestHandlerServlet extends HttpServlet { @@ -61,7 +61,6 @@ public class RequestHandlerServlet extends HttpServlet { private final HttpJsonApiSerializer serializer; private final ApiGateway apiGateway; - public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext, AclRepository aclRepository) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); Ensure.requireNonNull(config, "config must be non-null"); @@ -162,10 +161,10 @@ private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model. && response.getStatusCode().isSuccess() && Objects.nonNull(response.getResult()) && Optional.ofNullable(response.getResult().getMessages()) - .orElse(List.of()) - .stream() - .map(Message::getMessageType) - .noneMatch(x -> Objects.equals(x, MessageTypeEnum.ERROR) || Objects.equals(x, MessageTypeEnum.EXCEPTION)); + .orElse(List.of()) + .stream() + .map(Message::getMessageType) + .noneMatch(x -> Objects.equals(x, MessageTypeEnum.ERROR) || Objects.equals(x, MessageTypeEnum.EXCEPTION)); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java index d8ec81a95..709844b32 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java @@ -22,5 +22,10 @@ * its own state accordingly. */ public interface AclRepository { + /** + * Get the AllAccessPermissionRules state. For file-based repositories, this is the merged versions of all ACL files. + * + * @return The AllAccessPermissionRules. + */ AllAccessPermissionRules getAllAccessPermissionRules(); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java index c7ef20de8..4abdaa40d 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java @@ -31,25 +31,39 @@ import java.util.function.BiConsumer; -public class DirectoryWatcher implements AutoCloseable { +/** + * Keeps track of added, modified and deleted files within a file system directory. + */ +public class DirectoryWatcher { + private static final String DOT_JSON = ".json"; private final Path dir; private final WatchService watchService; private final List listeners = new CopyOnWriteArrayList<>(); - private final Thread workerThread; private volatile boolean running = true; + /** + * Class constructor. + * + * @param dir Directory to keep track of + * @throws IOException Registering WatchService on directory failed + */ public DirectoryWatcher(Path dir) throws IOException { this.dir = dir.toAbsolutePath().normalize(); this.watchService = FileSystems.getDefault().newWatchService(); this.dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); - workerThread = new Thread(this::processEvents, "DirectoryWatcher-" + dir); + Thread workerThread = new Thread(this::processEvents, "DirectoryWatcher-" + dir); workerThread.setDaemon(true); workerThread.start(); } + /** + * Add a listener to the watcher service. + * + * @param listener Listener to be added + */ public void addListener(DirectoryWatcherListener listener) { listeners.add(listener); } @@ -72,11 +86,12 @@ private void processEvents() { for (WatchEvent event: key.pollEvents()) { WatchEvent.Kind kind = event.kind(); - if (kind == StandardWatchEventKinds.OVERFLOW) { + Path relative = (Path) event.context(); + + if (kind == StandardWatchEventKinds.OVERFLOW || !isJsonFileEnding(relative)) { continue; } - Path relative = (Path) event.context(); Path full = dir.resolve(relative); if (kind == ENTRY_CREATE) { @@ -102,11 +117,9 @@ else if (kind == ENTRY_MODIFY) { } - @Override - public void close() throws IOException { - running = false; - watchService.close(); - workerThread.interrupt(); + private boolean isJsonFileEnding(Path path) { + String pathString = path.toString(); + return pathString.substring(pathString.length() - DOT_JSON.length()).equalsIgnoreCase(DOT_JSON); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java index 34d976c81..5d79802ee 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java @@ -17,12 +17,31 @@ import java.nio.file.Path; +/** + * Functionality to listen to a directory watcher, being notified of added, modified and deleted events. + */ public interface DirectoryWatcherListener { - void onFileCreated(Path file); + + /** + * Called when a file in a file system directory has been created. + * + * @param path The path to the file that has been created + */ + void onFileCreated(Path path); - void onFileDeleted(Path file); + /** + * Called when a file in a file system directory has been deleted. + * + * @param path The path to the file that has been deleted + */ + void onFileDeleted(Path path); - void onFileModified(Path file); + /** + * Called when a file in a file system directory has been modified. + * + * @param path The path to the file that has been modified + */ + void onFileModified(Path path); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java index 92a324970..deebebf13 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java @@ -22,12 +22,16 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * File implementation of an ACL Repository. Connected to a file system monitoring service. + */ public class FileAclRepository implements AclRepository, DirectoryWatcherListener { private static final Logger LOGGER = LoggerFactory.getLogger(FileAclRepository.class); private final Map aclList; @@ -48,7 +52,9 @@ private FileAclRepository() { */ public static FileAclRepository createNewInstance(String aclFolder) throws EndpointException { FileAclRepository instance = new FileAclRepository(); - try (DirectoryWatcher watcher = new DirectoryWatcher(Paths.get(aclFolder))) { + DirectoryWatcher watcher; + try { + watcher = new DirectoryWatcher(Paths.get(aclFolder)); watcher.addListener(instance); return instance; } @@ -65,12 +71,12 @@ public static FileAclRepository createNewInstance(String aclFolder) throws Endpo */ public AllAccessPermissionRules getAllAccessPermissionRules() { var rules = new AllAccessPermissionRules(); - for(AllAccessPermissionRules fileRules : aclList.values()) { - rules.setRules(fileRules.getRules()); - rules.setDefacls(fileRules.getDefacls()); - rules.setDefattributes(fileRules.getDefattributes()); - rules.setDefformulas(fileRules.getDefformulas()); - rules.setDefobjects(fileRules.getDefobjects()); + for (AllAccessPermissionRules fileRules: aclList.values()) { + rules.setRules(new ArrayList<>(fileRules.getRules())); + rules.setDefacls(new ArrayList<>(fileRules.getDefacls())); + rules.setDefattributes(new ArrayList<>(fileRules.getDefattributes())); + rules.setDefformulas(new ArrayList<>(fileRules.getDefformulas())); + rules.setDefobjects(new ArrayList<>(fileRules.getDefobjects())); } return rules; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java index ce6ade47b..a67b1976e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java @@ -1,17 +1,40 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.auth; +/** + * Shared attributes during servlet request filtering. + */ public enum SharedAttributes { - AUTH_STATE("auth.state"), - CLAIMS("claims"), + /** + * Shared AAS "All Access Control List" attribute. + */ ACL("acl"); private final String name; - + /** + * Serialization of an attribute. + * + * @return String version of an attribute. + */ public String getName() { return name; } + SharedAttributes(String name) { this.name = name; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java index b82ef5db4..9a444a447 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java @@ -1,3 +1,17 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; @@ -6,7 +20,6 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; - import java.io.IOException; @@ -17,7 +30,6 @@ public class AclRulesInceptionFilter implements Filter { private final AclRepository aclRepository; - /** * Class constructor. * diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index b805d2f3a..413888cdd 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -14,9 +14,10 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.JwtAuthorizationFilter.AUTHORIZATION; + import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; -import com.auth0.jwt.interfaces.DecodedJWT; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; @@ -33,13 +34,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defobject; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.time.Clock; import java.time.LocalTime; import java.util.HashMap; @@ -49,10 +45,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState.ANONYMOUS; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.CLAIMS; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -64,7 +59,6 @@ public class ApiGateway { private final AclRepository aclRepository; - public ApiGateway(AclRepository aclRepository) { this.aclRepository = aclRepository; } @@ -93,9 +87,8 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS response.getPayload().getContent() .removeIf(aas -> aclRepository.getAllAccessPermissionRules().getRules() - .stream().noneMatch(r -> - AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), - extractClaims(request), aclRepository.getAllAccessPermissionRules()))); + .stream().noneMatch(r -> AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), + extractClaims(request), aclRepository.getAllAccessPermissionRules()))); return response; } @@ -145,24 +138,31 @@ public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse re } return aclRepository.getAllAccessPermissionRules().getRules().stream() - .anyMatch(rule -> AuthServer.evaluateRule(rule, path, claims, aclRepository.getAllAccessPermissionRules(), fieldCtx)); + .anyMatch(rule -> AuthServer.evaluateRule(rule, path, claims, aclRepository.getAllAccessPermissionRules(), fieldCtx)); } - @SuppressWarnings("unchecked") private Map extractClaims(HttpServletRequest request) { - return (Map) request.getAttribute(CLAIMS.getName()); - } + var authHeaderValue = request.getHeader(AUTHORIZATION); + if (authHeaderValue == null || !authHeaderValue.startsWith("Bearer".concat(" "))) { + return null; + } + + // Remove "Bearer " + String token = authHeaderValue.substring("Bearer".length()).trim(); + + return JWT.decode(token).getClaims(); + } /** - * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route authorization. Access must be explicitly defined, + * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route + * authorization. Access must be explicitly defined, * otherwise it is blocked. */ public static class AuthServer { private static final String apiPrefix = "/api/v3.0/"; - /** * Check all rules that explicitly allows the request. If a rule exists after all filters, true is returned * @@ -171,8 +171,6 @@ public static class AuthServer { * @return true if there is a valid rule */ private static boolean filterRules(AllAccessPermissionRules aclList, Map claims, HttpServletRequest request) { - // Filter ACL rules (Claims, Global Attributes, maybe ReferenceAttribute - String requestPath = request.getRequestURI(); String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(apiPrefix.length()) : requestPath; aclList.getRules().removeIf(r -> !evaluateRule(r, path, claims, aclList)); @@ -278,23 +276,22 @@ private static boolean evaluateRule(AccessPermissionRule rule, String path, Map< Acl acl = getAcl(rule, allAccess); return acl != null && getAttributes(acl, allAccess) != null && acl.getRights() != null && getObjects(rule, allAccess) != null && getObjects(rule, allAccess).stream().anyMatch(attr -> { - if (attr.getRoute() != null) { - return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); - } - else if (attr.getIdentifiable() != null) { - return checkIdentifiable(path, attr.getIdentifiable()); - } - else if (attr.getDescriptor() != null) { - return checkDescriptor(path, attr.getDescriptor()); - } - else { - return false; - } - }) && "ALLOW".equals(acl.getAccess().value()) && verifyAllClaims(claims, rule, allAccess, fieldCtx); + if (attr.getRoute() != null) { + return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); + } + else if (attr.getIdentifiable() != null) { + return checkIdentifiable(path, attr.getIdentifiable()); + } + else if (attr.getDescriptor() != null) { + return checkDescriptor(path, attr.getDescriptor()); + } + else { + return false; + } + }) && "ALLOW".equals(acl.getAccess().value()) && verifyAllClaims(claims, rule, allAccess, fieldCtx); } } - private static Acl getAcl(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { if (rule.getAcl() != null) { return rule.getAcl(); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java index f149b1bc5..877b77c33 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java @@ -1,29 +1,42 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; -import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; - +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Map; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.CLAIMS; +import java.util.Optional; /** - * Filter all ACL for the Claims they require + * Filters applicable AAS ACL rules using the incoming request's bearer token claims. */ -public class AttributeClaimFilter implements Filter { +public class AttributeClaimFilter extends JwtAuthorizationFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { - Map claims = (Map) request.getAttribute(CLAIMS.getName()); + Map claims = Optional.ofNullable(extractAndDecodeJwt(((HttpServletRequest) request)).getClaims()).orElse(Map.of()); AllAccessPermissionRules filteredAcl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); filteredAcl.getRules().removeIf(rule -> rule.getAcl().getAttributes().stream() @@ -36,8 +49,7 @@ else if (attributeItem.getClaim() != null) { return !claims.containsKey(attributeItem.getClaim()); } return false; - }) - ); + })); request.setAttribute(ACL.getName(), filteredAcl); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java index c9048f70f..13bb979f2 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java @@ -1,5 +1,22 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import jakarta.servlet.Filter; @@ -8,13 +25,12 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; - import java.io.IOException; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; - +/** + * Filters applicable AAS ACL rules using the incoming request's HTTP method. + */ public class HttpMethodFilter implements Filter { @Override @@ -26,8 +42,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String requiredRight = isOperationRequest(method, ((HttpServletRequest) request).getContextPath()) ? "EXECUTE" : getRequiredRight(method); filteredAcl.getRules().removeIf( - rules -> rules.getAcl().getRights().contains(RightsEnum.ALL) || rules.getAcl().getRights().contains(RightsEnum.valueOf(requiredRight)) - ); + rules -> rules.getAcl().getRights().contains(RightsEnum.ALL) || rules.getAcl().getRights().contains(RightsEnum.valueOf(requiredRight))); request.setAttribute(ACL.getName(), filteredAcl); chain.doFilter(request, response); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java index 683f65b57..226dfc684 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java @@ -24,7 +24,6 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.AuthState; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; @@ -36,9 +35,6 @@ import java.util.Collections; import org.slf4j.LoggerFactory; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.AUTH_STATE; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.CLAIMS; - /** * Filters any incoming request by verifying its JWT if available. @@ -81,7 +77,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (httpRequest.getHeader(AUTHORIZATION) == null) { // No JWT in request, anonymous requestor - servletRequest.setAttribute(AUTH_STATE.getName(), AuthState.ANONYMOUS); filterChain.doFilter(servletRequest, servletResponse); return; } @@ -93,9 +88,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo respondForbidden(httpResponse); } else { - // Continue with the request as authenticated - servletRequest.setAttribute(AUTH_STATE.getName(), AuthState.AUTHENTICATED); - servletRequest.setAttribute(CLAIMS.getName(), jwt.getClaims()); filterChain.doFilter(servletRequest, servletResponse); } } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index 6df14054d..6b9cec66e 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -31,7 +31,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.Objects; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -39,20 +41,6 @@ public class ApiGatewayFilterTest extends JwtAuthorizationFilterTest { - private static final String ACL_JSON = "{\n" + - " \"AllAccessPermissionRules\": {\n" + - " \"rules\": [{\n" + - " \"ACL\": {\n" + - " \"ATTRIBUTES\": [{ \"GLOBAL\": \"ANONYMOUS\" }],\n" + - " \"RIGHTS\": [\"READ\"],\n" + - " \"ACCESS\": \"ALLOW\"\n" + - " },\n" + - " \"OBJECTS\": [{ \"ROUTE\": \"*\" }],\n" + - " \"FORMULA\": { \"$boolean\": true }\n" + - " }]\n" + - " }\n" + - "}"; - @Rule public TemporaryFolder tmp = new TemporaryFolder(); private ApiGateway apiGateway; @@ -67,7 +55,6 @@ private static HttpServletRequest req(String method, String uri) { @Test public void anonymousAccessDependsOnAclFile() throws Exception { - Path aclDir = tmp.newFolder("acl").toPath(); apiGateway = new ApiGateway(FileAclRepository.createNewInstance(aclDir.toString())); @@ -79,10 +66,10 @@ public void anonymousAccessDependsOnAclFile() throws Exception { verify(filter, never()).doFilter(any(), any()); Path rule = aclDir.resolve("allow.json"); Path tmpRule = aclDir.resolve("allow.json.tmp"); - Files.writeString(tmpRule, ACL_JSON, StandardCharsets.UTF_8); + Files.writeString(tmpRule, Files.readString(Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("acl.json")).toURI())), StandardCharsets.UTF_8); Files.move(tmpRule, rule, StandardCopyOption.ATOMIC_MOVE); - await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertTrue(apiGateway.isAuthorized(request))); + await().atMost(50000, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertTrue(apiGateway.isAuthorized(request))); Files.delete(rule); await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertFalse(apiGateway.isAuthorized(request))); diff --git a/endpoint/http/src/test/resources/acl.json b/endpoint/http/src/test/resources/acl.json new file mode 100644 index 000000000..3be206e9f --- /dev/null +++ b/endpoint/http/src/test/resources/acl.json @@ -0,0 +1,27 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "GLOBAL": "ANONYMOUS" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "ROUTE": "*" + } + ], + "FORMULA": { + "$boolean": true + } + } + ] + } +} From 9e47ad13d45301002dd58d389acab46a58ac6324 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:35:00 +0200 Subject: [PATCH 089/108] fix: minor merge issues --- .../submodelrepository/QuerySubmodelsRequestHandler.java | 2 +- .../QueryAssetAdministrationShellsRequestMapper.java | 2 +- .../QueryConceptDescriptionsRequestMapper.java | 2 +- .../submodelrepository/QuerySubmodelsRequestMapper.java | 2 +- .../endpoint/http/security/filter/HttpMethodFilter.java | 4 ++-- pom.xml | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java index ea54a9c05..368e71607 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java @@ -53,7 +53,7 @@ public QuerySubmodelsResponse process(QuerySubmodelsRequest request, RequestExec if (Objects.nonNull(page.getContent())) { for (Submodel submodel: page.getContent()) { Reference reference = AasUtils.toReference(submodel); - syncWithAsset(reference, submodel.getSubmodelElements(), !request.isInternal(), context); + context.getAssetConnectionManager().syncValueProvidersOnRead(reference, page, !request.isInternal()); if (!request.isInternal()) { context.getMessageBus().publish(ElementReadEventMessage.builder() .element(reference) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java index 4e59da643..c6aa3225f 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java @@ -15,12 +15,12 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.aasrepository; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.QueryAssetAdministrationShellsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; +import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java index 60e8872e7..6d11f0a74 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java @@ -15,12 +15,12 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.conceptdescription; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.QueryConceptDescriptionsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; +import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java index a1799ec0b..75d2b7da8 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java @@ -15,12 +15,12 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.submodelrepository; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.QuerySubmodelsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; +import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java index 13bb979f2..513cd7288 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java @@ -14,9 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpMethod.POST; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import jakarta.servlet.Filter; @@ -61,7 +61,7 @@ private static boolean isOperationRequest(String method, String path) { cleanPath = pathParts[pathParts.length - 1]; } - return POST.name().equals(method) && ("invoke".equals(cleanPath) || "invoke-async".equals(cleanPath)); + return HttpMethod.POST.name().equals(method) && ("invoke".equals(cleanPath) || "invoke-async".equals(cleanPath)); } diff --git a/pom.xml b/pom.xml index dcdc283dc..129be3b57 100644 --- a/pom.xml +++ b/pom.xml @@ -98,12 +98,12 @@ 2.6.0 1.5.3 2.10.0 - 1.2.2 3.0.0 + 1.2.2 4.13.2 0.22.1 - 1.5.22 2.26.0 + 1.5.22 1.5.33 17 17 From 55ce1849fff3d30a3dfca719751ec16e6cfcb350 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:45:44 +0200 Subject: [PATCH 090/108] fix: codacy --- .../faaast/service/endpoint/http/HttpEndpointConfig.java | 6 +++--- .../http/security/filter/AclRulesInceptionFilter.java | 4 +++- .../endpoint/http/security/FormulaEvaluatorTest.java | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java index 2fb921c27..64f1ee199 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpointConfig.java @@ -44,6 +44,7 @@ public static Builder builder() { return new Builder(); } + private String aclFolder; private CertificateConfig certificate; private boolean corsEnabled; private boolean corsAllowCredentials; @@ -53,16 +54,15 @@ public static Builder builder() { private String corsExposedHeaders; private long corsMaxAge; private String hostname; - private String pathPrefix; private boolean includeErrorDetails; + private String jwkProvider; + private String pathPrefix; private int port; private boolean sniEnabled; private boolean sslEnabled; private String subprotocol; private String subprotocolBody; private String subprotocolBodyEncoding; - private String jwkProvider; - private String aclFolder; public HttpEndpointConfig() { certificate = CertificateConfig.builder() diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java index 9a444a447..1e7df8e4e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java @@ -22,6 +22,8 @@ import jakarta.servlet.ServletResponse; import java.io.IOException; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + /** * Helper filter to inject the current ACL rules into a request. @@ -42,7 +44,7 @@ public AclRulesInceptionFilter(AclRepository aclRepository) { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - // Intentionally empty + request.setAttribute(ACL.getName(), aclRepository.getAllAccessPermissionRules()); chain.doFilter(request, response); } } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java index 8b9b22268..c6d0edb36 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java @@ -36,7 +36,7 @@ public class FormulaEvaluatorTest { /* ------------------------------------------------------------------ */ @Test - public void complexFormula_withMatchingClaims() throws Exception { + public void complexFormulaWithMatchingClaims() throws Exception { String json = """ { "$and": [ @@ -89,7 +89,7 @@ public void complexFormula_withMatchingClaims() throws Exception { /* ------------------------------------------------------------------ */ @Test - public void regexFormula_withNonMatchingEmail() throws Exception { + public void regexFormulaWithNonMatchingEmail() throws Exception { String json = """ { "$and": [ @@ -130,7 +130,7 @@ public void regexFormula_withNonMatchingEmail() throws Exception { /* ------------------------------------------------------------------ */ @Test - public void fullFormula_allConditionsMet() throws Exception { + public void fullFormulaAllConditionsMet() throws Exception { String json = """ { "$and": [ @@ -190,7 +190,7 @@ public void fullFormula_allConditionsMet() throws Exception { @Test - public void testFormula_ConditionsNotMet() throws JsonProcessingException { + public void testFormulaConditionsNotMet() throws JsonProcessingException { String json = """ { "$and": [ From e592706bb811f5d7f28cb695886470b33ca2d980 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:47:52 +0200 Subject: [PATCH 091/108] fix: spotless --- .../http/security/filter/AclRulesInceptionFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java index 1e7df8e4e..cd5798f0a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; @@ -22,8 +24,6 @@ import jakarta.servlet.ServletResponse; import java.io.IOException; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; - /** * Helper filter to inject the current ACL rules into a request. From 5025674f5bb0c32a7028bb8525116318f11672bf Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:58:24 +0200 Subject: [PATCH 092/108] feat: extract ACLHelper utility --- .../service/endpoint/http/HttpEndpoint.java | 30 ++-- .../endpoint/http/RequestHandlerServlet.java | 11 +- .../http/security/filter/ApiGateway.java | 144 +++--------------- .../AclAttributeFilter.java} | 21 +-- .../filter/pre/AclDisabledFilter.java | 43 ++++++ .../security/filter/pre/AclObjectsFilter.java | 134 ++++++++++++++++ .../AclRightsFilter.java} | 13 +- .../{ => pre}/AclRulesInceptionFilter.java | 2 +- .../{ => pre}/JwtAuthorizationFilter.java | 6 +- .../filter/{ => pre}/JwtValidationFilter.java | 2 +- .../http/util/AccessControlListHelper.java | 126 +++++++++++++++ .../security/filter/ApiGatewayFilterTest.java | 6 +- .../filter/JwtAuthorizationFilterTest.java | 1 + .../filter/JwtValidationFilterTest.java | 1 + 14 files changed, 373 insertions(+), 167 deletions(-) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/{AttributeClaimFilter.java => pre/AclAttributeFilter.java} (74%) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/{HttpMethodFilter.java => pre/AclRightsFilter.java} (84%) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/{ => pre}/AclRulesInceptionFilter.java (99%) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/{ => pre}/JwtAuthorizationFilter.java (93%) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/{ => pre}/JwtValidationFilter.java (99%) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AccessControlListHelper.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 6a101f6ed..00c740dcb 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -16,7 +16,6 @@ import static de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper.DEFAULT_ALIAS; -import com.auth0.jwk.JwkProvider; import com.auth0.jwk.UrlJwkProvider; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.certificate.CertificateData; @@ -24,12 +23,13 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file.FileAclRepository; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AclRulesInceptionFilter; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.AttributeClaimFilter; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.HttpMethodFilter; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.JwtValidationFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclAttributeFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclDisabledFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclObjectsFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRightsFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRulesInceptionFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtValidationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.Interface; @@ -124,11 +124,9 @@ public void start() throws EndpointException { context.setContextPath("/"); crossOriginHandler.setHandler(context); - AclRepository aclRepository = null; + RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext); if (Objects.nonNull(config.getJwkProvider())) { - aclRepository = FileAclRepository.createNewInstance(config.getAclFolder()); - URL jwkProviderUrl; try { jwkProviderUrl = new URL(config.getJwkProvider()); @@ -136,15 +134,15 @@ public void start() throws EndpointException { catch (MalformedURLException malformedJwkProviderUrl) { throw new EndpointException("Could not parse JWK provider URL", malformedJwkProviderUrl); } - JwkProvider jwkProvider = new UrlJwkProvider(jwkProviderUrl); - - context.addFilter(new JwtValidationFilter(jwkProvider), "*", EnumSet.allOf(DispatcherType.class)); - context.addFilter(new AclRulesInceptionFilter(aclRepository), "*", EnumSet.allOf(DispatcherType.class)); - context.addFilter(new HttpMethodFilter(), "*", EnumSet.allOf(DispatcherType.class)); - context.addFilter(new AttributeClaimFilter(), "*", EnumSet.allOf(DispatcherType.class)); + // Anonymous access filter, Identification Filter + context.addFilter(new JwtValidationFilter(new UrlJwkProvider(jwkProviderUrl)), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclRulesInceptionFilter(FileAclRepository.createNewInstance(config.getAclFolder())), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclDisabledFilter(), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclRightsFilter(), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclObjectsFilter(), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclAttributeFilter(), "*", EnumSet.allOf(DispatcherType.class)); } - RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext, aclRepository); context.addServlet(handler, "/*"); server.setErrorHandler(new HttpErrorHandler(config)); try { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 391df8f87..b75046e31 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -15,7 +15,6 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; @@ -61,7 +60,7 @@ public class RequestHandlerServlet extends HttpServlet { private final HttpJsonApiSerializer serializer; private final ApiGateway apiGateway; - public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext, AclRepository aclRepository) { + public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); Ensure.requireNonNull(config, "config must be non-null"); Ensure.requireNonNull(serviceContext, "serviceContext must be non-null"); @@ -71,7 +70,7 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); - this.apiGateway = Optional.ofNullable(aclRepository).map(ApiGateway::new).orElse(null); + this.apiGateway = Objects.nonNull(config.getJwkProvider()) ? new ApiGateway() : null; } @@ -182,7 +181,7 @@ else if ((url.equals("/submodels") || url.equals("/submodels/"))) { GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest); return apiGateway.filterSubmodels(request, submodelsResponse); } - else if ((url.matches("^/submodels/[A-Za-z][A-Za-z0-9]*(?:\\.[A-Za-z][A-Za-z0-9]*)*$"))) { + else if ((url.matches("^/submodels/[^/]+$"))) { GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest); if (!apiGateway.filterSubmodel(request, submodelResponse)) { doThrow(new UnauthorizedException( @@ -191,10 +190,12 @@ else if ((url.matches("^/submodels/[A-Za-z][A-Za-z0-9]*(?:\\.[A-Za-z][A-Za-z0-9] return submodelResponse; } } - else if (!apiGateway.isAuthorized(request)) { + + if (!apiGateway.isAuthorized(request)) { doThrow(new UnauthorizedException( String.format("User not authorized '%s'", request.getRequestURI()))); } + return serviceContext.execute(endpoint, apiRequest); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 413888cdd..7958f173a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -14,11 +14,16 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.JwtAuthorizationFilter.AUTHORIZATION; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getFormula; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; @@ -28,26 +33,15 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defacl; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defattribute; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defformula; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defobject; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; import java.time.Clock; import java.time.LocalTime; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.Set; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -55,15 +49,6 @@ */ public class ApiGateway { - private static final Logger LOGGER = LoggerFactory.getLogger(ApiGateway.class); - - private final AclRepository aclRepository; - - public ApiGateway(AclRepository aclRepository) { - this.aclRepository = aclRepository; - } - - /** * Checks if the user is authorized to receive the response of the request. * @@ -71,7 +56,7 @@ public ApiGateway(AclRepository aclRepository) { * @return true if authorized and ACL exists */ public boolean isAuthorized(HttpServletRequest request) { - return AuthServer.filterRules(aclRepository.getAllAccessPermissionRules(), extractClaims(request), request); + return AuthServer.filterRules((AllAccessPermissionRules) request.getAttribute(ACL.getName()), extractClaims(request), request); } @@ -84,11 +69,12 @@ public boolean isAuthorized(HttpServletRequest request) { */ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { // remove AAS if none of the ALL_ACL have any rule that matches + AllAccessPermissionRules allAccessPermissionRules = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); response.getPayload().getContent() - .removeIf(aas -> aclRepository.getAllAccessPermissionRules().getRules() + .removeIf(aas -> allAccessPermissionRules.getRules() .stream().noneMatch(r -> AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), - extractClaims(request), aclRepository.getAllAccessPermissionRules()))); + extractClaims(request), allAccessPermissionRules))); return response; } @@ -110,8 +96,8 @@ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsRespo fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); } - return aclRepository.getAllAccessPermissionRules().getRules().stream() - .noneMatch(rule -> AuthServer.evaluateRule(rule, path, claims, aclRepository.getAllAccessPermissionRules(), fieldCtx)); + return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() + .noneMatch(rule -> AuthServer.evaluateRule(rule, path, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); }); return response; } @@ -137,20 +123,20 @@ public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse re fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); } - return aclRepository.getAllAccessPermissionRules().getRules().stream() - .anyMatch(rule -> AuthServer.evaluateRule(rule, path, claims, aclRepository.getAllAccessPermissionRules(), fieldCtx)); + return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() + .anyMatch(rule -> AuthServer.evaluateRule(rule, path, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); } private Map extractClaims(HttpServletRequest request) { var authHeaderValue = request.getHeader(AUTHORIZATION); - if (authHeaderValue == null || !authHeaderValue.startsWith("Bearer".concat(" "))) { + if (authHeaderValue == null || !authHeaderValue.startsWith(BEARER.concat(" "))) { return null; } // Remove "Bearer " - String token = authHeaderValue.substring("Bearer".length()).trim(); + String token = authHeaderValue.substring(BEARER.length()).trim(); return JWT.decode(token).getClaims(); } @@ -161,19 +147,16 @@ private Map extractClaims(HttpServletRequest request) { * otherwise it is blocked. */ public static class AuthServer { - private static final String apiPrefix = "/api/v3.0/"; /** - * Check all rules that explicitly allows the request. If a rule exists after all filters, true is returned + * Check that at least one rule exists that allows access to the resource. * * @param claims the claims found in the token * @param request the request coming in * @return true if there is a valid rule */ private static boolean filterRules(AllAccessPermissionRules aclList, Map claims, HttpServletRequest request) { - String requestPath = request.getRequestURI(); - String path = requestPath.startsWith(apiPrefix) ? requestPath.substring(apiPrefix.length()) : requestPath; - aclList.getRules().removeIf(r -> !evaluateRule(r, path, claims, aclList)); + aclList.getRules().removeIf(r -> !evaluateRule(r, request.getRequestURI(), claims, aclList)); return !aclList.getRules().isEmpty(); } @@ -288,95 +271,8 @@ else if (attr.getDescriptor() != null) { else { return false; } - }) && "ALLOW".equals(acl.getAccess().value()) && verifyAllClaims(claims, rule, allAccess, fieldCtx); + }) && verifyAllClaims(claims, rule, allAccess, fieldCtx); } } - private static Acl getAcl(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { - if (rule.getAcl() != null) { - return rule.getAcl(); - } - else if (rule.getUseacl() != null) { - Optional acl = allAccess.getDefacls().stream() - .filter(a -> Objects.equals(a.getName(), rule.getUseacl())) - .findAny(); - if (acl.isPresent()) { - return acl.get().getAcl(); - } - else { - throw new IllegalArgumentException("DEFACL not found: " + rule.getUseacl()); - } - } - else { - throw new IllegalArgumentException("invalid rule: ACL or USEACL must be specified"); - } - } - - - private static List getAttributes(Acl acl, AllAccessPermissionRules allAccess) { - if ((acl.getAttributes() != null) && (!acl.getAttributes().isEmpty())) { - return acl.getAttributes(); - } - else if (acl.getUseattributes() != null) { - Optional attribute = allAccess.getDefattributes().stream() - .filter(a -> Objects.equals(a.getName(), acl.getUseattributes())) - .findAny(); - if (attribute.isPresent()) { - return attribute.get().getAttributes(); - } - else { - throw new IllegalArgumentException("DEFATTRIBUTES not found: " + acl.getUseattributes()); - } - } - else { - throw new IllegalArgumentException("invalid rule: ATTRIBUTES or USEATTRIBUTES must be specified"); - } - } - - - private static LogicalExpression getFormula(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { - if (rule.getFormula() != null) { - return rule.getFormula(); - } - else if (rule.getUseformula() != null) { - Optional formula = allAccess.getDefformulas().stream() - .filter(a -> Objects.equals(a.getName(), rule.getUseformula())) - .findAny(); - if (formula.isPresent()) { - return formula.get().getFormula(); - } - else { - throw new IllegalArgumentException("DEFFORMULA not found: " + rule.getUseformula()); - } - } - else { - throw new IllegalArgumentException("invalid rule: FORMULA or USEFORMULA must be specified"); - } - } - - - private static List getObjects(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { - if ((rule.getObjects() != null) && (!rule.getObjects().isEmpty())) { - return rule.getObjects(); - } - else if (rule.getUseobjects() != null) { - // We must collect all Defobjects in all Useobjects - List objectList = allAccess.getDefobjects().stream() - .filter(a -> rule.getUseobjects().contains(a.getName())) - .toList(); - if (objectList.isEmpty()) { - throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseobjects()); - } - else { - Set retval = new HashSet<>(); - for (Defobject item: objectList) { - retval.addAll(item.getObjects()); - } - return retval.stream().toList(); - } - } - else { - throw new IllegalArgumentException("invalid rule: OBJECTS or USEOBJECTS must be specified"); - } - } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java similarity index 74% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java index 877b77c33..b005be2aa 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AttributeClaimFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java @@ -12,9 +12,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; @@ -32,26 +33,26 @@ /** * Filters applicable AAS ACL rules using the incoming request's bearer token claims. */ -public class AttributeClaimFilter extends JwtAuthorizationFilter { +public class AclAttributeFilter extends JwtAuthorizationFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { Map claims = Optional.ofNullable(extractAndDecodeJwt(((HttpServletRequest) request)).getClaims()).orElse(Map.of()); - AllAccessPermissionRules filteredAcl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); - filteredAcl.getRules().removeIf(rule -> rule.getAcl().getAttributes().stream() - .anyMatch(attributeItem -> { + acl.getRules().removeIf(rule -> getAcl(rule, acl).getAttributes().stream() + .noneMatch(attributeItem -> { // claim, global and reference should be subtypes of AttributeItem... - if (attributeItem.getGlobal().equals(AttributeItem.Global.ANONYMOUS)) { - return false; + if (AttributeItem.Global.ANONYMOUS == attributeItem.getGlobal()) { + return true; } else if (attributeItem.getClaim() != null) { - return !claims.containsKey(attributeItem.getClaim()); + return claims.containsKey(attributeItem.getClaim()); } - return false; + return true; })); - request.setAttribute(ACL.getName(), filteredAcl); + request.setAttribute(ACL.getName(), acl); chain.doFilter(request, response); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java new file mode 100644 index 000000000..e534b15fd --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java @@ -0,0 +1,43 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter.pre; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import java.io.IOException; + + +/** + * Filters applicable AAS ACL rules using the rules' "Access" field. + */ +public class AclDisabledFilter implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + + acl.getRules().removeIf(rule -> getAcl(rule, acl).getAccess() == Acl.Access.DISABLED); + + request.setAttribute(ACL.getName(), acl); + chain.doFilter(request, response); + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java new file mode 100644 index 000000000..55654f169 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java @@ -0,0 +1,134 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter.pre; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + + +/** + * Filters applicable AAS ACL rules using the incoming request's path. + */ +public class AclObjectsFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String path = ((HttpServletRequest) request).getRequestURI(); + + AllAccessPermissionRules filteredAcl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + + List filteredRules = new ArrayList<>(); + + for (AccessPermissionRule rule: filteredAcl.getRules()) { + + boolean anyMatch = getObjects(rule, filteredAcl).stream().anyMatch(objectItem -> { + if (objectItem.getRoute() != null) { + String route = objectItem.getRoute(); + // Warning: potentially does not allow trailing slash at requests. + return route.contains("*") && path.startsWith(route.substring(0, route.indexOf("*"))) || route.equals(path); + } + else if (objectItem.getIdentifiable() != null) { + return checkIdentifiable(path, objectItem.getIdentifiable()); + } + else if (objectItem.getReferable() != null) { + return false; // TODO + } + else if (objectItem.getFragment() != null) { + return false; // TODO + } + else if (objectItem.getDescriptor() != null) { + return checkDescriptor(path, objectItem.getDescriptor()); + } + return false; + }); + + if (anyMatch) { + filteredRules.add(rule); + } + } + + filteredAcl.setRules(filteredRules); + + request.setAttribute(ACL.getName(), filteredAcl); + chain.doFilter(request, response); + } + + + private boolean checkIdentifiable(String path, String identifiable) { + if (!(path.startsWith("/submodels") || path.startsWith("/shells"))) { + return false; + } + + if ("(Submodel)*".equals(identifiable)) { + return true; + } + else if (identifiable.startsWith("(Submodel)")) { + String id = identifiable.substring(10); + return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); + } + if ("(AssetAdministrationShell)*".equals(identifiable)) { + return true; + } + else if (identifiable.startsWith("(AssetAdministrationShell)")) { + String id = identifiable.substring(26); + return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); + } + return false; + } + + + private static boolean checkDescriptor(String path, String descriptor) { + if (descriptor.startsWith("(aasDesc)")) { + if (!path.startsWith("/shell-descriptors")) { + return false; + } + if ("(aasDesc)*".equals(descriptor)) { + return true; + } + else if (descriptor.startsWith("(aasDesc)")) { + String id = descriptor.substring(9); + return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); + } + } + else if (descriptor.startsWith("(smDesc)")) { + if (!path.startsWith("/submodel-descriptors")) { + return false; + } + if ("(smDesc)*".equals(descriptor)) { + return true; + } + else if (descriptor.startsWith("(smDesc)")) { + String id = descriptor.substring(8); + return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); + } + } + return false; + } + +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java similarity index 84% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java index 513cd7288..a9c3ed029 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/HttpMethodFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java @@ -12,9 +12,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; @@ -31,20 +32,20 @@ /** * Filters applicable AAS ACL rules using the incoming request's HTTP method. */ -public class HttpMethodFilter implements Filter { +public class AclRightsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { String method = ((HttpServletRequest) request).getMethod(); - AllAccessPermissionRules filteredAcl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); String requiredRight = isOperationRequest(method, ((HttpServletRequest) request).getContextPath()) ? "EXECUTE" : getRequiredRight(method); - filteredAcl.getRules().removeIf( - rules -> rules.getAcl().getRights().contains(RightsEnum.ALL) || rules.getAcl().getRights().contains(RightsEnum.valueOf(requiredRight))); + acl.getRules().removeIf( + rule -> getAcl(rule, acl).getRights().contains(RightsEnum.ALL) || getAcl(rule, acl).getRights().contains(RightsEnum.valueOf(requiredRight))); - request.setAttribute(ACL.getName(), filteredAcl); + request.setAttribute(ACL.getName(), acl); chain.doFilter(request, response); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java similarity index 99% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java index cd5798f0a..b6996f280 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtAuthorizationFilter.java similarity index 93% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtAuthorizationFilter.java index 48d3e91a0..e0bc5dcd4 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtAuthorizationFilter.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; @@ -24,8 +24,8 @@ * Abstract filter for HTTP requests with JWT headers. */ public abstract class JwtAuthorizationFilter implements Filter { - protected static final String AUTHORIZATION = "Authorization"; - private static final String BEARER = "Bearer"; + public static final String AUTHORIZATION = "Authorization"; + public static final String BEARER = "Bearer"; /** * Extracts a JWT from an HTTP request by reading its Authorization header, diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtValidationFilter.java similarity index 99% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtValidationFilter.java index 226dfc684..23981e692 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtValidationFilter.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import com.auth0.jwk.InvalidPublicKeyException; import com.auth0.jwk.Jwk; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AccessControlListHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AccessControlListHelper.java new file mode 100644 index 000000000..d78569074 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AccessControlListHelper.java @@ -0,0 +1,126 @@ +/* + * 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.ilt.faaast.service.endpoint.http.util; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defacl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defattribute; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defformula; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defobject; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + + +public class AccessControlListHelper { + private AccessControlListHelper() {} + + + public static Acl getAcl(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + if (rule.getAcl() != null) { + return rule.getAcl(); + } + else if (rule.getUseacl() != null) { + Optional acl = allAccess.getDefacls().stream() + .filter(a -> Objects.equals(a.getName(), rule.getUseacl())) + .findAny(); + if (acl.isPresent()) { + return acl.get().getAcl(); + } + else { + throw new IllegalArgumentException("DEFACL not found: " + rule.getUseacl()); + } + } + else { + throw new IllegalArgumentException("invalid rule: ACL or USEACL must be specified"); + } + } + + + public static List getAttributes(Acl acl, AllAccessPermissionRules allAccess) { + if ((acl.getAttributes() != null) && (!acl.getAttributes().isEmpty())) { + return acl.getAttributes(); + } + else if (acl.getUseattributes() != null) { + Optional attribute = allAccess.getDefattributes().stream() + .filter(a -> Objects.equals(a.getName(), acl.getUseattributes())) + .findAny(); + if (attribute.isPresent()) { + return attribute.get().getAttributes(); + } + else { + throw new IllegalArgumentException("DEFATTRIBUTES not found: " + acl.getUseattributes()); + } + } + else { + throw new IllegalArgumentException("invalid rule: ATTRIBUTES or USEATTRIBUTES must be specified"); + } + } + + + public static LogicalExpression getFormula(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + if (rule.getFormula() != null) { + return rule.getFormula(); + } + else if (rule.getUseformula() != null) { + Optional formula = allAccess.getDefformulas().stream() + .filter(a -> Objects.equals(a.getName(), rule.getUseformula())) + .findAny(); + if (formula.isPresent()) { + return formula.get().getFormula(); + } + else { + throw new IllegalArgumentException("DEFFORMULA not found: " + rule.getUseformula()); + } + } + else { + throw new IllegalArgumentException("invalid rule: FORMULA or USEFORMULA must be specified"); + } + } + + + public static List getObjects(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + if ((rule.getObjects() != null) && (!rule.getObjects().isEmpty())) { + return rule.getObjects(); + } + else if (rule.getUseobjects() != null) { + // We must collect all Defobjects in all Useobjects + List objectList = allAccess.getDefobjects().stream() + .filter(a -> rule.getUseobjects().contains(a.getName())) + .toList(); + if (objectList.isEmpty()) { + throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseobjects()); + } + else { + Set retval = new HashSet<>(); + for (Defobject item: objectList) { + retval.addAll(item.getObjects()); + } + return retval.stream().toList(); + } + } + else { + throw new IllegalArgumentException("Invalid rule: OBJECTS or USEOBJECTS must be specified"); + } + } +} diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index 6b9cec66e..a2e37a4ea 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; @@ -25,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file.FileAclRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; @@ -56,9 +58,11 @@ private static HttpServletRequest req(String method, String uri) { @Test public void anonymousAccessDependsOnAclFile() throws Exception { Path aclDir = tmp.newFolder("acl").toPath(); - apiGateway = new ApiGateway(FileAclRepository.createNewInstance(aclDir.toString())); + AclRepository aclRepo = FileAclRepository.createNewInstance(aclDir.toString()); + apiGateway = new ApiGateway(); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); + request.setAttribute(ACL.getName(), aclRepo); FilterChain filter = mockFilterChain(); assertFalse(apiGateway.isAuthorized(request)); diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java index 4ce1c5a09..2d00133e0 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtAuthorizationFilterTest.java @@ -17,6 +17,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java index fea9e14de..88bbf60cd 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/JwtValidationFilterTest.java @@ -25,6 +25,7 @@ import com.auth0.jwk.UrlJwkProvider; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtValidationFilter; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; From 75b060bdbcf01488e746159a05dd58360c4d2dd1 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:17:19 +0200 Subject: [PATCH 093/108] feat: extract ACL validation --- .../service/endpoint/http/HttpEndpoint.java | 2 + .../http/security/filter/ApiGateway.java | 179 ++++++------------ .../filter/pre/AclValidationFilter.java | 37 ++++ 3 files changed, 93 insertions(+), 125 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 00c740dcb..5b67f3948 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -29,6 +29,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclObjectsFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRightsFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRulesInceptionFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclValidationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtValidationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; @@ -137,6 +138,7 @@ public void start() throws EndpointException { // Anonymous access filter, Identification Filter context.addFilter(new JwtValidationFilter(new UrlJwkProvider(jwkProviderUrl)), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclRulesInceptionFilter(FileAclRepository.createNewInstance(config.getAclFolder())), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclValidationFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclDisabledFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclRightsFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclObjectsFilter(), "*", EnumSet.allOf(DispatcherType.class)); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 7958f173a..266b649dc 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -14,14 +14,6 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getFormula; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; - import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; @@ -34,14 +26,23 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import jakarta.servlet.http.HttpServletRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; + import java.time.Clock; import java.time.LocalTime; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getFormula; /** @@ -56,7 +57,7 @@ public class ApiGateway { * @return true if authorized and ACL exists */ public boolean isAuthorized(HttpServletRequest request) { - return AuthServer.filterRules((AllAccessPermissionRules) request.getAttribute(ACL.getName()), extractClaims(request), request); + return AuthServer.filterRules((AllAccessPermissionRules) request.getAttribute(ACL.getName()), extractClaims(request)); } @@ -73,8 +74,7 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS response.getPayload().getContent() .removeIf(aas -> allAccessPermissionRules.getRules() - .stream().noneMatch(r -> AuthServer.evaluateRule(r, "/shells/" + EncodingHelper.base64Encode(aas.getId()), - extractClaims(request), allAccessPermissionRules))); + .stream().noneMatch(r -> AuthServer.evaluateRule(r, extractClaims(request), allAccessPermissionRules))); return response; } @@ -88,7 +88,6 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS */ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { response.getPayload().getContent().removeIf(submodel -> { - String path = "/submodels/" + EncodingHelper.base64Encode(submodel.getId()); Map claims = extractClaims(request); Map fieldCtx = new HashMap<>(); @@ -97,7 +96,7 @@ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsRespo } return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() - .noneMatch(rule -> AuthServer.evaluateRule(rule, path, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); + .noneMatch(rule -> AuthServer.evaluateRule(rule, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); }); return response; } @@ -115,16 +114,16 @@ public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse re if (Objects.isNull(submodel)) { return true; } - String path = "/submodels/" + EncodingHelper.base64Encode(submodel.getId()); Map claims = extractClaims(request); + AllAccessPermissionRules acl = ((AllAccessPermissionRules) request.getAttribute(ACL.getName())); Map fieldCtx = new HashMap<>(); if (submodel.getSemanticId() != null) { - fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); + fieldCtx.put("$sm#semanticId", Objects.requireNonNull(ReferenceHelper.getRoot(submodel.getSemanticId())).getValue()); } - return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() - .anyMatch(rule -> AuthServer.evaluateRule(rule, path, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); + return acl.getRules().stream() + .anyMatch(rule -> AuthServer.evaluateRule(rule, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); } @@ -141,9 +140,9 @@ private Map extractClaims(HttpServletRequest request) { return JWT.decode(token).getClaims(); } + /** - * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route - * authorization. Access must be explicitly defined, + * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route authorization. Access must be explicitly defined, * otherwise it is blocked. */ public static class AuthServer { @@ -151,128 +150,58 @@ public static class AuthServer { /** * Check that at least one rule exists that allows access to the resource. * + * @param aclList the applying ACL rules * @param claims the claims found in the token - * @param request the request coming in - * @return true if there is a valid rule + * @return true if there is an allowing rule */ - private static boolean filterRules(AllAccessPermissionRules aclList, Map claims, HttpServletRequest request) { - aclList.getRules().removeIf(r -> !evaluateRule(r, request.getRequestURI(), claims, aclList)); - return !aclList.getRules().isEmpty(); + private static boolean filterRules(AllAccessPermissionRules aclList, Map claims) { + return aclList.getRules().stream().anyMatch(r -> evaluateRule(r, claims, aclList)); } - private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess, Map fieldCtx) { - Acl acl = getAcl(rule, allAccess); - - boolean isAnonymous = getAttributes(acl, allAccess).stream() - .anyMatch(attr -> attr.getGlobal() != null && "ANONYMOUS".equals(attr.getGlobal().value())); + private static boolean evaluateRule(AccessPermissionRule rule, Map claims, AllAccessPermissionRules allAccess) { + return evaluateRule(rule, claims, allAccess, null); + } - List claimNames = getAttributes(acl, allAccess).stream() - .filter(attr -> attr.getGlobal() == null) - .map(AttributeItem::getClaim) - .filter(Objects::nonNull) - .toList(); - // Build context - Map ctx = new HashMap<>(); - if (claims != null) { - for (String name: claimNames) { - Claim c = claims.get(name); - if (c != null) { - ctx.put("CLAIM:" + name, c.asString()); - } - } - } - // Add $sm#semanticId - if (fieldCtx != null && !fieldCtx.isEmpty()) { - ctx.putAll(fieldCtx); - } - ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); - if (isAnonymous) { - return FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); - } - return !ctx.entrySet().stream().filter(e -> e.getKey().startsWith("CLAIM:")).toList().isEmpty() && - FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); + private static boolean evaluateRule(AccessPermissionRule rule, Map claims, AllAccessPermissionRules allAccess, + Map fieldCtx) { + return verifyAllClaims(claims, rule, allAccess, fieldCtx); } + } - private static boolean checkIdentifiable(String path, String identifiable) { - //check submodel path - if (!(path.startsWith("/submodels") || path.startsWith("/shells"))) { - return false; - } + private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess, Map fieldCtx) { + Acl acl = getAcl(rule, allAccess); - if ("(Submodel)*".equals(identifiable)) { - return true; - } - else if (identifiable.startsWith("(Submodel)")) { - String id = identifiable.substring(10); - return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); - } - if ("(AssetAdministrationShell)*".equals(identifiable)) { - return true; - } - else if (identifiable.startsWith("(AssetAdministrationShell)")) { - String id = identifiable.substring(26); - return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); - } - return false; - } + boolean isAnonymous = getAttributes(acl, allAccess).stream() + .anyMatch(attr -> attr.getGlobal() != null && "ANONYMOUS".equals(attr.getGlobal().value())); + List claimNames = getAttributes(acl, allAccess).stream() + .filter(attr -> attr.getGlobal() == null) + .map(AttributeItem::getClaim) + .filter(Objects::nonNull) + .toList(); - private static boolean checkDescriptor(String path, String descriptor) { - if (descriptor.startsWith("(aasDesc)")) { - if (!path.startsWith("/shell-descriptors")) { - return false; - } - if ("(aasDesc)*".equals(descriptor)) { - return true; - } - else if (descriptor.startsWith("(aasDesc)")) { - String id = descriptor.substring(9); - return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); - } - } - else if (descriptor.startsWith("(smDesc)")) { - if (!path.startsWith("/submodel-descriptors")) { - return false; - } - if ("(smDesc)*".equals(descriptor)) { - return true; - } - else if (descriptor.startsWith("(smDesc)")) { - String id = descriptor.substring(8); - return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); + // Build context + Map ctx = new HashMap<>(); + if (claims != null) { + for (String name: claimNames) { + Claim c = claims.get(name); + if (c != null) { + ctx.put("CLAIM:" + name, c.asString()); } } - return false; } - - - private static boolean evaluateRule(AccessPermissionRule rule, String path, Map claims, AllAccessPermissionRules allAccess) { - return evaluateRule(rule, path, claims, allAccess, null); + // Add $sm#semanticId + if (fieldCtx != null && !fieldCtx.isEmpty()) { + ctx.putAll(fieldCtx); } - - - private static boolean evaluateRule(AccessPermissionRule rule, String path, Map claims, AllAccessPermissionRules allAccess, - Map fieldCtx) { - Acl acl = getAcl(rule, allAccess); - return acl != null && getAttributes(acl, allAccess) != null && acl.getRights() != null && getObjects(rule, allAccess) != null - && getObjects(rule, allAccess).stream().anyMatch(attr -> { - if (attr.getRoute() != null) { - return "*".equals(attr.getRoute()) || attr.getRoute().contains(path); - } - else if (attr.getIdentifiable() != null) { - return checkIdentifiable(path, attr.getIdentifiable()); - } - else if (attr.getDescriptor() != null) { - return checkDescriptor(path, attr.getDescriptor()); - } - else { - return false; - } - }) && verifyAllClaims(claims, rule, allAccess, fieldCtx); + ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); + if (isAnonymous) { + return FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); } + return !ctx.entrySet().stream().filter(e -> e.getKey().startsWith("CLAIM:")).toList().isEmpty() && + FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); } - } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java new file mode 100644 index 000000000..5052b75d3 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java @@ -0,0 +1,37 @@ +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; + + +/** + * Might also be implemented as ACL validator when repository receives a new ACL. + */ +public class AclValidationFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + + acl.getRules().removeIf( + rule -> getAcl(rule, acl) == null || + getAttributes(getAcl(rule, acl), acl) == null || + getAcl(rule, acl).getRights() == null || + getObjects(rule, acl) == null); + + request.setAttribute(ACL.getName(), acl); + chain.doFilter(request, response); + } +} From d70774e2c73eaf1892202f24ac3abf1b9c077624 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:20:02 +0200 Subject: [PATCH 094/108] fix: spotless --- .../http/security/filter/ApiGateway.java | 23 ++++++++--------- .../filter/pre/AclValidationFilter.java | 25 ++++++++++++++----- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 266b649dc..cf464a2ae 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -14,6 +14,13 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getFormula; + import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; @@ -25,24 +32,15 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; -import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import jakarta.servlet.http.HttpServletRequest; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; - import java.time.Clock; import java.time.LocalTime; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getFormula; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; /** @@ -140,9 +138,9 @@ private Map extractClaims(HttpServletRequest request) { return JWT.decode(token).getClaims(); } - /** - * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route authorization. Access must be explicitly defined, + * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route + * authorization. Access must be explicitly defined, * otherwise it is blocked. */ public static class AuthServer { @@ -170,7 +168,6 @@ private static boolean evaluateRule(AccessPermissionRule rule, Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess, Map fieldCtx) { Acl acl = getAcl(rule, allAccess); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java index 5052b75d3..8a30058eb 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java @@ -1,19 +1,32 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter.pre; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; + import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; - import java.io.IOException; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; - /** * Might also be implemented as ACL validator when repository receives a new ACL. From cdb359c344383b82db8c9cb1f84c0dfa91cd77c0 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:50:22 +0200 Subject: [PATCH 095/108] feat: ACL Validator --- .../util/AccessControlListRulesValidator.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListRulesValidator.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListRulesValidator.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListRulesValidator.java new file mode 100644 index 000000000..750e2f4dc --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListRulesValidator.java @@ -0,0 +1,59 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.util; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getObjects; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Helper class to validate ACL rules. + */ +public class AccessControlListRulesValidator { + private static final Logger LOGGER = LoggerFactory.getLogger(AccessControlListRulesValidator.class); + + private AccessControlListRulesValidator() {} + + + /** + * Validates AccessPermissionRule. Checks if any of the required fields are null. Does not check semantic validity of + * the rule! + * + * @param rule Rule to check. + * @param allAccessPermissionRules Rule environment. + * @return If the Rule is valid, i.e. contains ACL, ATTRIBUTES, RIGHTS, OBJECTS, either directly or via (resolvable) + * USE*. + */ + public static boolean validate(AccessPermissionRule rule, AllAccessPermissionRules allAccessPermissionRules) { + Acl acl = getAcl(rule, allAccessPermissionRules); + try { + return acl != null && + getAttributes(acl, allAccessPermissionRules) != null && + acl.getRights() != null && + getObjects(rule, allAccessPermissionRules) != null; + } + catch (IllegalArgumentException illegalArgumentException) { + LOGGER.warn("Validation failed: {}", illegalArgumentException.getMessage(), illegalArgumentException); + return false; + } + } +} From 46009b443aa27ad14ae25b0594dedcce32881262 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:51:04 +0200 Subject: [PATCH 096/108] feat: restructure security source files --- .../service/endpoint/http/HttpEndpoint.java | 24 +++---- .../acl/repository/AclRepository.java | 2 +- .../acl/repository/file/DirectoryWatcher.java | 2 +- .../file/DirectoryWatcherListener.java | 2 +- .../repository/file/FileAclRepository.java | 27 +++++--- .../http/security/filter/ApiGateway.java | 6 +- .../filter/pre/AclAttributeFilter.java | 2 +- .../filter/pre/AclDisabledFilter.java | 2 +- .../security/filter/pre/AclObjectsFilter.java | 2 +- .../security/filter/pre/AclRightsFilter.java | 2 +- .../filter/pre/AclRulesInceptionFilter.java | 2 +- .../filter/pre/AclValidationFilter.java | 50 --------------- .../util/AccessControlListHelper.java | 63 ++++++++++++++++--- .../security/filter/ApiGatewayFilterTest.java | 4 +- 14 files changed, 98 insertions(+), 92 deletions(-) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/{ => security}/acl/repository/AclRepository.java (93%) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/{ => security}/acl/repository/file/DirectoryWatcher.java (98%) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/{ => security}/acl/repository/file/DirectoryWatcherListener.java (94%) rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/{ => security}/acl/repository/file/FileAclRepository.java (83%) delete mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/{ => security}/util/AccessControlListHelper.java (61%) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 5b67f3948..6555b693a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -23,13 +23,12 @@ import de.fraunhofer.iosb.ilt.faaast.service.certificate.util.KeyStoreHelper; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file.FileAclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file.FileAclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclAttributeFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclDisabledFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclObjectsFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRightsFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRulesInceptionFilter; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclValidationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtValidationFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; @@ -128,17 +127,8 @@ public void start() throws EndpointException { RequestHandlerServlet handler = new RequestHandlerServlet(this, config, serviceContext); if (Objects.nonNull(config.getJwkProvider())) { - URL jwkProviderUrl; - try { - jwkProviderUrl = new URL(config.getJwkProvider()); - } - catch (MalformedURLException malformedJwkProviderUrl) { - throw new EndpointException("Could not parse JWK provider URL", malformedJwkProviderUrl); - } - // Anonymous access filter, Identification Filter - context.addFilter(new JwtValidationFilter(new UrlJwkProvider(jwkProviderUrl)), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new JwtValidationFilter(new UrlJwkProvider(parseJwkProviderUrl(config.getJwkProvider()))), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclRulesInceptionFilter(FileAclRepository.createNewInstance(config.getAclFolder())), "*", EnumSet.allOf(DispatcherType.class)); - context.addFilter(new AclValidationFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclDisabledFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclRightsFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclObjectsFilter(), "*", EnumSet.allOf(DispatcherType.class)); @@ -163,6 +153,16 @@ public void init(CoreConfig coreConfig, HttpEndpointConfig config, ServiceContex } + private URL parseJwkProviderUrl(String urlString) throws EndpointException { + try { + return new URL(urlString); + } + catch (MalformedURLException malformedJwkProviderUrl) { + throw new EndpointException("Could not parse JWK provider URL", malformedJwkProviderUrl); + } + } + + private void configureHttpServer() throws EndpointException { HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSendServerVersion(false); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AclRepository.java similarity index 93% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AclRepository.java index 709844b32..a3fffda6f 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/AclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AclRepository.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/DirectoryWatcher.java similarity index 98% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/DirectoryWatcher.java index 4abdaa40d..24dc10ff0 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcher.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/DirectoryWatcher.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/DirectoryWatcherListener.java similarity index 94% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/DirectoryWatcherListener.java index 5d79802ee..0bd0fb0d7 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/DirectoryWatcherListener.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/DirectoryWatcherListener.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file; import java.nio.file.Path; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java similarity index 83% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java index deebebf13..8397842b2 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/acl/repository/file/FileAclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java @@ -12,11 +12,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListRulesValidator.validate; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AclRepository; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import java.io.IOException; @@ -85,22 +87,33 @@ public AllAccessPermissionRules getAllAccessPermissionRules() { @Override public void onFileCreated(Path path) { - LOGGER.debug("Added ACL {}", path); - aclList.put(path, readFile(path)); + LOGGER.debug("Adding ACL {}", path); + update(path); + } + + + private void update(Path path) { + AllAccessPermissionRules rules = readFile(path); + if (rules.getRules().stream().allMatch(rule -> validate(rule, rules))) { + aclList.put(path, rules); + } + else { + LOGGER.warn("Tried to load invalid ACL: {}.", path); + } } @Override public void onFileDeleted(Path path) { - LOGGER.debug("Removed ACL {}", path); + LOGGER.debug("Removing ACL {}", path); aclList.remove(path); } @Override public void onFileModified(Path path) { - LOGGER.debug("Changed ACL {}", path); - aclList.put(path, readFile(path)); + LOGGER.debug("Changing ACL {}", path); + update(path); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index cf464a2ae..eaeeff376 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -17,9 +17,9 @@ import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getFormula; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFormula; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java index b005be2aa..2a38c3117 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java @@ -15,7 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java index e534b15fd..d0d502565 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java @@ -15,7 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java index 55654f169..80eaddd5e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java @@ -15,7 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getObjects; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java index a9c3ed029..b01c5285b 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java @@ -15,7 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java index b6996f280..dcf775ab3 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java @@ -16,7 +16,7 @@ import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AclRepository; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java deleted file mode 100644 index 8a30058eb..000000000 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclValidationFilter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAcl; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getAttributes; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.AccessControlListHelper.getObjects; - -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import java.io.IOException; - - -/** - * Might also be implemented as ACL validator when repository receives a new ACL. - */ -public class AclValidationFilter implements Filter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - - AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); - - acl.getRules().removeIf( - rule -> getAcl(rule, acl) == null || - getAttributes(getAcl(rule, acl), acl) == null || - getAcl(rule, acl).getRights() == null || - getObjects(rule, acl) == null); - - request.setAttribute(ACL.getName(), acl); - chain.doFilter(request, response); - } -} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AccessControlListHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListHelper.java similarity index 61% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AccessControlListHelper.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListHelper.java index d78569074..f4655d9a9 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/AccessControlListHelper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListHelper.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; @@ -24,7 +24,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Defobject; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; - import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -32,10 +31,30 @@ import java.util.Set; +/** + * Helper class for working with AAS ACL. + */ public class AccessControlListHelper { + private static final String DEFACL = "DEFACL"; + private static final Object USEACL = "USEACL"; + private static final String DEFATTRIBUTES = "DEFATTRIBUTES"; + private static final Object USEATTRIBUTES = "USEATTRIBUTES"; + private static final String DEFFORMULA = "DEFFORUMLA"; + private static final Object USEFORUMLA = "USEFORUMLA"; + private static final String DEFOBJECTS = "DEFOBJECTS"; + private static final Object USEOBJECTS = "USEOBJECTS"; + private AccessControlListHelper() {} + /** + * returns the ACL definition of the AccessPermissionRule. If ACL is not directly defined, returns the DEFACL defined by + * USEACL. + * + * @param rule Rule to get ACL from + * @param allAccess Rule environment + * @return The ACL. + */ public static Acl getAcl(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { if (rule.getAcl() != null) { return rule.getAcl(); @@ -48,15 +67,23 @@ else if (rule.getUseacl() != null) { return acl.get().getAcl(); } else { - throw new IllegalArgumentException("DEFACL not found: " + rule.getUseacl()); + throw new IllegalArgumentException(String.format("%s not found for %s: %s", DEFACL, USEACL, rule.getUseacl())); } } else { - throw new IllegalArgumentException("invalid rule: ACL or USEACL must be specified"); + throw new IllegalArgumentException(String.format("Invalid rule: ACL or %s must be specified", USEACL)); } } + /** + * returns the ATTRIBUTES of the AccessPermissionRule. If ATTRIBUTES are not directly defined, returns the DEFATTRIBUTES + * defined by USEATTRIBUTES. + * + * @param acl ACL to get ATTRIBUTES from + * @param allAccess Rule environment + * @return The ATTRIBUTES. + */ public static List getAttributes(Acl acl, AllAccessPermissionRules allAccess) { if ((acl.getAttributes() != null) && (!acl.getAttributes().isEmpty())) { return acl.getAttributes(); @@ -69,15 +96,23 @@ else if (acl.getUseattributes() != null) { return attribute.get().getAttributes(); } else { - throw new IllegalArgumentException("DEFATTRIBUTES not found: " + acl.getUseattributes()); + throw new IllegalArgumentException(String.format("%s not found for %s: %s", DEFATTRIBUTES, USEATTRIBUTES, acl.getUseattributes())); } } else { - throw new IllegalArgumentException("invalid rule: ATTRIBUTES or USEATTRIBUTES must be specified"); + throw new IllegalArgumentException(String.format("Invalid rule: ATTRIBUTES or %s must be specified", USEATTRIBUTES)); } } + /** + * returns the FORMULA of the AccessPermissionRule. If FORMULA is not directly defined, returns the DEFFORMULA defined + * by USEFORMULA. + * + * @param rule Rule to get FORMULA from + * @param allAccess Rule environment + * @return The FORMULA. + */ public static LogicalExpression getFormula(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { if (rule.getFormula() != null) { return rule.getFormula(); @@ -90,15 +125,23 @@ else if (rule.getUseformula() != null) { return formula.get().getFormula(); } else { - throw new IllegalArgumentException("DEFFORMULA not found: " + rule.getUseformula()); + throw new IllegalArgumentException(String.format("%s not found for %s: %s", DEFFORMULA, USEFORUMLA, rule.getUseformula())); } } else { - throw new IllegalArgumentException("invalid rule: FORMULA or USEFORMULA must be specified"); + throw new IllegalArgumentException(String.format("Invalid rule: FORMULA or %s must be specified", USEFORUMLA)); } } + /** + * returns the OBJECTS of the AccessPermissionRule. If OBJECTS is not directly defined, returns the DEFOBJECTS defined + * by USEOBJECTS. + * + * @param rule Rule to get ObjectItems from + * @param allAccess Rule environment + * @return The OBJECTS. + */ public static List getObjects(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { if ((rule.getObjects() != null) && (!rule.getObjects().isEmpty())) { return rule.getObjects(); @@ -109,7 +152,7 @@ else if (rule.getUseobjects() != null) { .filter(a -> rule.getUseobjects().contains(a.getName())) .toList(); if (objectList.isEmpty()) { - throw new IllegalArgumentException("DEFOBJECTS not found: " + rule.getUseobjects()); + throw new IllegalArgumentException(String.format("%s not found for %s: %s", DEFOBJECTS, USEOBJECTS, rule.getUseobjects())); } else { Set retval = new HashSet<>(); @@ -120,7 +163,7 @@ else if (rule.getUseobjects() != null) { } } else { - throw new IllegalArgumentException("Invalid rule: OBJECTS or USEOBJECTS must be specified"); + throw new IllegalArgumentException(String.format("Invalid rule: OBJECTS or %s must be specified", USEOBJECTS)); } } } diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index a2e37a4ea..0f16f1883 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -26,8 +26,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.AclRepository; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.acl.repository.file.FileAclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file.FileAclRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; From 9417e8e3c09bd3d7658513ce3e5bbf3f83cef32b Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:48:56 +0200 Subject: [PATCH 097/108] fix: remove file handling from ApiGatewayTest --- .../repository/file/FileAclRepository.java | 6 +- .../http/security/filter/ApiGateway.java | 38 +--- .../security/filter/ApiGatewayFilterTest.java | 56 ++++-- .../resources/{acl.json => anonymous.json} | 0 endpoint/http/src/test/resources/bpn.json | 36 ++++ endpoint/http/src/test/resources/filter.json | 185 ++++++++++++++++++ .../http/src/test/resources/regexclaim.json | 64 ++++++ .../src/test/resources/semanticid_anon.json | 48 +++++ .../src/test/resources/semanticid_auth.json | 81 ++++++++ .../http/src/test/resources/submodel.json | 37 ++++ endpoint/http/src/test/resources/timed.json | 100 ++++++++++ endpoint/http/src/test/resources/usedef.json | 92 +++++++++ 12 files changed, 688 insertions(+), 55 deletions(-) rename endpoint/http/src/test/resources/{acl.json => anonymous.json} (100%) create mode 100644 endpoint/http/src/test/resources/bpn.json create mode 100644 endpoint/http/src/test/resources/filter.json create mode 100644 endpoint/http/src/test/resources/regexclaim.json create mode 100644 endpoint/http/src/test/resources/semanticid_anon.json create mode 100644 endpoint/http/src/test/resources/semanticid_auth.json create mode 100644 endpoint/http/src/test/resources/submodel.json create mode 100644 endpoint/http/src/test/resources/timed.json create mode 100644 endpoint/http/src/test/resources/usedef.json diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java index 8397842b2..48e0983ad 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java @@ -94,7 +94,7 @@ public void onFileCreated(Path path) { private void update(Path path) { AllAccessPermissionRules rules = readFile(path); - if (rules.getRules().stream().allMatch(rule -> validate(rule, rules))) { + if (rules != null && rules.getRules().stream().allMatch(rule -> validate(rule, rules))) { aclList.put(path, rules); } else { @@ -128,8 +128,8 @@ private AllAccessPermissionRules readFile(Path path) { } } catch (IOException e) { - throw new IllegalStateException(String.format("Could not parse latest addition to ACL folder: %s", path), e); + LOGGER.warn("Could not parse latest addition to ACL folder: {}", path, e); + return null; } } - } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index eaeeff376..0d8b8a474 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -55,7 +55,8 @@ public class ApiGateway { * @return true if authorized and ACL exists */ public boolean isAuthorized(HttpServletRequest request) { - return AuthServer.filterRules((AllAccessPermissionRules) request.getAttribute(ACL.getName()), extractClaims(request)); + return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() + .anyMatch(r -> verifyAllClaims(extractClaims(request), r, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), null)); } @@ -72,7 +73,7 @@ public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationS response.getPayload().getContent() .removeIf(aas -> allAccessPermissionRules.getRules() - .stream().noneMatch(r -> AuthServer.evaluateRule(r, extractClaims(request), allAccessPermissionRules))); + .stream().noneMatch(r -> verifyAllClaims(extractClaims(request), r, allAccessPermissionRules, null))); return response; } @@ -94,7 +95,7 @@ public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsRespo } return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() - .noneMatch(rule -> AuthServer.evaluateRule(rule, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); + .noneMatch(rule -> verifyAllClaims(claims, rule, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); }); return response; } @@ -121,7 +122,7 @@ public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse re } return acl.getRules().stream() - .anyMatch(rule -> AuthServer.evaluateRule(rule, claims, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); + .anyMatch(rule -> verifyAllClaims(claims, rule, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); } @@ -138,35 +139,6 @@ private Map extractClaims(HttpServletRequest request) { return JWT.decode(token).getClaims(); } - /** - * Simple whitelist AuthServer implementation that supports ANONYMOUS access, claims with simple eq formulas and route - * authorization. Access must be explicitly defined, - * otherwise it is blocked. - */ - public static class AuthServer { - - /** - * Check that at least one rule exists that allows access to the resource. - * - * @param aclList the applying ACL rules - * @param claims the claims found in the token - * @return true if there is an allowing rule - */ - private static boolean filterRules(AllAccessPermissionRules aclList, Map claims) { - return aclList.getRules().stream().anyMatch(r -> evaluateRule(r, claims, aclList)); - } - - - private static boolean evaluateRule(AccessPermissionRule rule, Map claims, AllAccessPermissionRules allAccess) { - return evaluateRule(rule, claims, allAccess, null); - } - - - private static boolean evaluateRule(AccessPermissionRule rule, Map claims, AllAccessPermissionRules allAccess, - Map fieldCtx) { - return verifyAllClaims(claims, rule, allAccess, fieldCtx); - } - } private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess, Map fieldCtx) { Acl acl = getAcl(rule, allAccess); diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index 0f16f1883..39f2e088f 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem.Global.ANONYMOUS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; @@ -26,16 +27,16 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AclRepository; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file.FileAclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.Objects; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -43,8 +44,6 @@ public class ApiGatewayFilterTest extends JwtAuthorizationFilterTest { - @Rule - public TemporaryFolder tmp = new TemporaryFolder(); private ApiGateway apiGateway; private static HttpServletRequest req(String method, String uri) { @@ -57,25 +56,44 @@ private static HttpServletRequest req(String method, String uri) { @Test public void anonymousAccessDependsOnAclFile() throws Exception { - Path aclDir = tmp.newFolder("acl").toPath(); - AclRepository aclRepo = FileAclRepository.createNewInstance(aclDir.toString()); apiGateway = new ApiGateway(); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); - request.setAttribute(ACL.getName(), aclRepo); + when(request.getAttribute(ACL.getName())).thenReturn(new AllAccessPermissionRules()); + FilterChain filter = mockFilterChain(); assertFalse(apiGateway.isAuthorized(request)); // Verify that request was blocked off verify(filter, never()).doFilter(any(), any()); - Path rule = aclDir.resolve("allow.json"); - Path tmpRule = aclDir.resolve("allow.json.tmp"); - Files.writeString(tmpRule, Files.readString(Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("acl.json")).toURI())), StandardCharsets.UTF_8); - Files.move(tmpRule, rule, StandardCopyOption.ATOMIC_MOVE); - await().atMost(50000, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertTrue(apiGateway.isAuthorized(request))); + AllAccessPermissionRules env = mockEnvironment(); + + when(request.getAttribute(ACL.getName())).thenReturn(env); + await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertTrue(apiGateway.isAuthorized(request))); - Files.delete(rule); + when(request.getAttribute(ACL.getName())).thenReturn(new AllAccessPermissionRules()); await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertFalse(apiGateway.isAuthorized(request))); } + + + private AllAccessPermissionRules mockEnvironment() { + var env = new AllAccessPermissionRules(); + AccessPermissionRule rule = new AccessPermissionRule(); + Acl acl = new Acl(); + AttributeItem attributeItem = new AttributeItem(); + attributeItem.setGlobal(ANONYMOUS); + acl.setAttributes(List.of(attributeItem)); + acl.setRights(List.of(RightsEnum.READ)); + acl.setAccess(Acl.Access.ALLOW); + rule.setAcl(acl); + ObjectItem object = new ObjectItem(); + object.setRoute("*"); + rule.setObjects(List.of(object)); + LogicalExpression formula = new LogicalExpression(); + formula.set$boolean(true); + rule.setFormula(formula); + env.setRules(List.of(rule)); + return env; + } } diff --git a/endpoint/http/src/test/resources/acl.json b/endpoint/http/src/test/resources/anonymous.json similarity index 100% rename from endpoint/http/src/test/resources/acl.json rename to endpoint/http/src/test/resources/anonymous.json diff --git a/endpoint/http/src/test/resources/bpn.json b/endpoint/http/src/test/resources/bpn.json new file mode 100644 index 000000000..d6f8b325c --- /dev/null +++ b/endpoint/http/src/test/resources/bpn.json @@ -0,0 +1,36 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "CLAIM": "BusinessPartnerNumber" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "ROUTE": "*" + } + ], + "FORMULA": { + "$eq": [ + { + "$attribute": { + "CLAIM": "BusinessPartnerNumber" + } + }, + { + "$strVal": "BPN1234" + } + ] + } + } + ] + } +} diff --git a/endpoint/http/src/test/resources/filter.json b/endpoint/http/src/test/resources/filter.json new file mode 100644 index 000000000..a21a8b3e9 --- /dev/null +++ b/endpoint/http/src/test/resources/filter.json @@ -0,0 +1,185 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "CLAIM": "BusinessPartnerNumber" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "DESCRIPTOR": "(aasdesc)*" + } + ], + "FORMULA": { + "$and": [ + { + "$eq": [ + { + "$attribute": { + "CLAIM": "BusinessPartnerNumber" + } + }, + { + "$strVal": "BPNL00000000000A" + } + ] + }, + { + "$match": [ + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].name" + }, + { + "$strVal": "manufacturerPartId" + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].value" + }, + { + "$strVal": "99991" + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].externalSubjectId" + }, + { + "$strVal": "PUBLIC_READABLE" + } + ] + } + ] + }, + { + "$match": [ + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].name" + }, + { + "$strVal": "customerPartId" + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].value" + }, + { + "$strVal": "ACME001" + } + ] + } + ] + } + ] + }, + "FILTER": { + "FRAGMENT": "$aasdesc#specificAssetIds[]", + "CONDITION": { + "$or": [ + { + "$match": [ + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].name" + }, + { + "$strVal": "manufacturerPartId" + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].value" + }, + { + "$strVal": "99991" + } + ] + } + ] + }, + { + "$match": [ + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].name" + }, + { + "$strVal": "customerPartId" + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].value" + }, + { + "$strVal": "ACME001" + } + ] + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].name" + }, + { + "$strVal": "partInstanceId" + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].externalSubjectId" + }, + { + "$attribute": { + "CLAIM": "BusinessPartnerNumber" + } + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#specificAssetIds[].externalSubjectId" + }, + { + "$strVal": "PUBLIC_READABLE" + } + ] + } + ] + } + } + } + ] + } +} diff --git a/endpoint/http/src/test/resources/regexclaim.json b/endpoint/http/src/test/resources/regexclaim.json new file mode 100644 index 000000000..41ff4cd1e --- /dev/null +++ b/endpoint/http/src/test/resources/regexclaim.json @@ -0,0 +1,64 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "CLAIM": "email" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "IDENTIFIABLE": "(Submodel)*" + } + ], + "FORMULA": { + "$and": [ + { + "$or": [ + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-Nameplate" + } + ] + }, + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-TechnicalData" + } + ] + } + ] + }, + { + "$regex": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "[\\w\\.]+@company\\.com" + } + ] + } + ] + } + } + ] + } +} diff --git a/endpoint/http/src/test/resources/semanticid_anon.json b/endpoint/http/src/test/resources/semanticid_anon.json new file mode 100644 index 000000000..2554a6b1b --- /dev/null +++ b/endpoint/http/src/test/resources/semanticid_anon.json @@ -0,0 +1,48 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "GLOBAL": "ANONYMOUS" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "ROUTE": "*" + } + ], + "FORMULA": { + "$or": [ + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-Nameplate" + } + ] + }, + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-TechnicalData" + } + ] + } + ] + } + } + ] + } +} diff --git a/endpoint/http/src/test/resources/semanticid_auth.json b/endpoint/http/src/test/resources/semanticid_auth.json new file mode 100644 index 000000000..0579860f2 --- /dev/null +++ b/endpoint/http/src/test/resources/semanticid_auth.json @@ -0,0 +1,81 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "CLAIM": "email" + } + ], + "RIGHTS": [ + "READ", + "UPDATE" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "IDENTIFIABLE": "(Submodel)*" + } + ], + "FORMULA": { + "$and": [ + { + "$or": [ + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-Nameplate" + } + ] + }, + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-TechnicalData" + } + ] + } + ] + }, + { + "$or": [ + { + "$eq": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "user1@company1.com" + } + ] + }, + { + "$eq": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "user2@company2.com" + } + ] + } + ] + } + ] + } + } + ] + } +} diff --git a/endpoint/http/src/test/resources/submodel.json b/endpoint/http/src/test/resources/submodel.json new file mode 100644 index 000000000..4b18defb4 --- /dev/null +++ b/endpoint/http/src/test/resources/submodel.json @@ -0,0 +1,37 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "CLAIM": "email" + } + ], + "RIGHTS": [ + "READ", + "UPDATE" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "IDENTIFIABLE": "(Submodel)https://submodel1.company1.com" + } + ], + "FORMULA": { + "$eq": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "user1@company1.com" + } + ] + } + } + ] + } +} diff --git a/endpoint/http/src/test/resources/timed.json b/endpoint/http/src/test/resources/timed.json new file mode 100644 index 000000000..bb4ef7c21 --- /dev/null +++ b/endpoint/http/src/test/resources/timed.json @@ -0,0 +1,100 @@ +{ + "AllAccessPermissionRules": { + "rules": [ + { + "ACL": { + "ATTRIBUTES": [ + { + "CLAIM": "companyName" + } + ], + "RIGHTS": [ + "READ" + ], + "ACCESS": "ALLOW" + }, + "OBJECTS": [ + { + "ROUTE": "*" + } + ], + "FORMULA": { + "$and": [ + { + "$or": [ + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-Nameplate" + } + ] + }, + { + "$eq": [ + { + "$field": "$sm#semanticId" + }, + { + "$strVal": "SemanticID-TechnicalData" + } + ] + } + ] + }, + { + "$eq": [ + { + "$attribute": { + "CLAIM": "companyName" + } + }, + { + "$strVal": "company1-name" + } + ] + }, + { + "$regex": [ + { + "$attribute": { + "REFERENCE": "(Submodel)*#Id" + } + }, + { + "$strVal": "^https://company1.com/.*$" + } + ] + }, + { + "$ge": [ + { + "$attribute": { + "GLOBAL": "UTCNOW" + } + }, + { + "$timeVal": "09:00" + } + ] + }, + { + "$le": [ + { + "$attribute": { + "GLOBAL": "UTCNOW" + } + }, + { + "$timeVal": "17:00" + } + ] + } + ] + } + } + ] + } +} diff --git a/endpoint/http/src/test/resources/usedef.json b/endpoint/http/src/test/resources/usedef.json new file mode 100644 index 000000000..f64443a12 --- /dev/null +++ b/endpoint/http/src/test/resources/usedef.json @@ -0,0 +1,92 @@ +{ + "AllAccessPermissionRules": { + "DEFACLS": [ + { + "name": "acl1", + "acl": { + "ATTRIBUTES": [ + { + "CLAIM": "email" + } + ], + "RIGHTS": [ + "READ", + "UPDATE" + ], + "ACCESS": "ALLOW" + } + } + ], + "DEFOBJECTS": [ + { + "name": "Properties", + "objects": [ + { + "REFERABLE": "(Submodel)https://s1.com, (Property)p1" + }, + { + "REFERABLE": "(Submodel)https://s1.com, (Property)p2" + } + ] + } + ], + "DEFFORMULAS": [ + { + "name": "allowSubjectGroup1", + "formula": { + "$and": [ + { + "$eq": [ + { + "$attribute": { + "GLOBAL": "UTCNOW" + } + }, + { + "$timeVal": "15:00" + } + ] + }, + { + "$or": [ + { + "$eq": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "user1@company1.com" + } + ] + }, + { + "$eq": [ + { + "$attribute": { + "CLAIM": "email" + } + }, + { + "$strVal": "user2@company2.com" + } + ] + } + ] + } + ] + } + } + ], + "rules": [ + { + "USEACL": "acl1", + "USEOBJECTS": [ + "Properties" + ], + "USEFORMULA": "allowSubjectGroup1" + } + ] + } +} From 4e83ab529ded5684909f5fe9355f234a03fe0977 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:01:49 +0200 Subject: [PATCH 098/108] fix: spotless --- .../endpoint/http/security/filter/ApiGatewayFilterTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index 39f2e088f..a0b953e24 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -37,9 +37,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import java.util.List; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; public class ApiGatewayFilterTest extends JwtAuthorizationFilterTest { From a16a00b705062d2742ac5892c0d7930f3e01a990 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 2 Jun 2026 17:15:03 +0200 Subject: [PATCH 099/108] feat: inject claims, global attrs via filters --- .../faaast/service/query/QueryEvaluator.java | 4 +- .../service/endpoint/http/HttpEndpoint.java | 2 + .../filter/pre/AclAttributeFilter.java | 1 - .../filter/pre/AclClaimInjectionFilter.java | 50 +++++ .../util/ExpressionInjectionHelper.java | 199 ++++++++++++++++++ 5 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index 3fbbfd528..008f8f618 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -522,7 +522,7 @@ private List getFieldValues(String field, Identifiable identifiable) { } List values = resolveValuesForPrefix(field, PREFIX_AAS, identifiable, AssetAdministrationShell.class, - (aas, attr) -> getAasFieldValues(aas, attr)); + this::getAasFieldValues); if (values != null) { return values; } @@ -539,7 +539,7 @@ private List getFieldValues(String field, Identifiable identifiable) { } values = resolveValuesForPrefix(field, PREFIX_CD, identifiable, ConceptDescription.class, - (cd, attr) -> getConceptDescriptionValues(cd, attr)); + this::getConceptDescriptionValues); if (values != null) { return values; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 6555b693a..65e8add4a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -25,6 +25,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file.FileAclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclAttributeFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclClaimInjectionFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclDisabledFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclObjectsFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRightsFilter; @@ -133,6 +134,7 @@ public void start() throws EndpointException { context.addFilter(new AclRightsFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclObjectsFilter(), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclAttributeFilter(), "*", EnumSet.allOf(DispatcherType.class)); + context.addFilter(new AclClaimInjectionFilter(), "*", EnumSet.allOf(DispatcherType.class)); } context.addServlet(handler, "/*"); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java index 2a38c3117..93eb52627 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java @@ -53,7 +53,6 @@ else if (attributeItem.getClaim() != null) { })); request.setAttribute(ACL.getName(), acl); - chain.doFilter(request, response); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java new file mode 100644 index 000000000..bfd029d96 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java @@ -0,0 +1,50 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter.pre; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFormula; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.ExpressionInjectionHelper.injectLogicalExpression; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.Map; +import java.util.stream.Collectors; + + +/** + * Inject claims into ACL formula. + */ +public class AclClaimInjectionFilter extends JwtAuthorizationFilter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + Map claims = extractAndDecodeJwt((HttpServletRequest) request).getClaims().entrySet().stream() + .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().asString())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + acl.getRules().forEach(rule -> injectLogicalExpression(getFormula(rule, acl), claims)); + + request.setAttribute(ACL.getName(), acl); + chain.doFilter(request, response); + } + +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java new file mode 100644 index 000000000..d33e8c09a --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java @@ -0,0 +1,199 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.util; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.MatchExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.StringValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; + +import java.time.Clock; +import java.time.LocalTime; +import java.util.Map; + + +/** + * Helps to inject request claims into a nested logical expression. + */ +public class ExpressionInjectionHelper { + + private ExpressionInjectionHelper() { + + } + + + /** + * Inject a set of claims and global attributes into a LogicalExpression ("formula"). + * + * @param formula The formula to inject into. + * @param claims The claims to inject. + */ + public static void injectLogicalExpression(LogicalExpression formula, Map claims) { + if (!formula.get$and().isEmpty()) { + // It is an AND expression + formula.get$and().forEach(op -> injectLogicalExpression(op, claims)); + } + else if (!formula.get$or().isEmpty()) { + // It is an OR expression + formula.get$or().forEach(op -> injectLogicalExpression(op, claims)); + } + else if (formula.get$not() != null) { + // It is an OR expression + injectLogicalExpression(formula.get$not(), claims); + } + else if (!formula.get$match().isEmpty()) { + // It is an OR expression + formula.get$match().forEach(op -> injectMatchExpression(op, claims)); + } + else if (!formula.get$eq().isEmpty()) { + formula.get$eq().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$ne().isEmpty()) { + formula.get$ne().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$gt().isEmpty()) { + formula.get$gt().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$ge().isEmpty()) { + formula.get$ge().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$lt().isEmpty()) { + formula.get$lt().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$le().isEmpty()) { + formula.get$le().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$contains().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + else if (!formula.get$startsWith().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + else if (!formula.get$endsWith().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + else if (!formula.get$regex().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + } + + + private static void injectMatchExpression(MatchExpression formula, Map claims) { + if (!formula.get$match().isEmpty()) { + // It is an AND expression + formula.get$match().forEach(op -> injectMatchExpression(op, claims)); + } + else if (!formula.get$eq().isEmpty()) { + formula.get$eq().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$ne().isEmpty()) { + formula.get$ne().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$gt().isEmpty()) { + formula.get$gt().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$ge().isEmpty()) { + formula.get$ge().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$lt().isEmpty()) { + formula.get$lt().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$le().isEmpty()) { + formula.get$le().forEach(val -> injectValue(val, claims)); + } + else if (!formula.get$contains().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + else if (!formula.get$startsWith().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + else if (!formula.get$endsWith().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + else if (!formula.get$regex().isEmpty()) { + formula.get$contains().forEach(val -> injectStringValue(val, claims)); + } + } + + + private static void injectStringValue(StringValue stringValue, Map claims) { + if (stringValue.get$attribute() != null) { + if (stringValue.get$attribute().getClaim() != null) { + stringValue.set$strVal(claims.get(stringValue.get$attribute().getClaim())); + } + } + else if (stringValue.get$strCast() != null) { + injectValue(stringValue.get$strCast(), claims); + return; + } + stringValue.set$attribute(null); + } + + + private static void injectValue(Value value, Map claims) { + if (value.get$attribute() == null) { + return; + } + if (value.get$attribute().getClaim() != null) { + value.set$strVal(claims.get(value.get$attribute().getClaim())); + } + else if (value.get$attribute().getGlobal() != null) { + AttributeItem.Global global = value.get$attribute().getGlobal(); + if (global == AttributeItem.Global.UTCNOW) { + // TODO might be a bug. Have to consider how ACL creator defined UTC w.r.t. format + value.set$timeVal(LocalTime.now(Clock.systemUTC()).toString()); + } + else if (global == AttributeItem.Global.LOCALNOW) { + // TODO see UTCNOW + value.set$timeVal(LocalTime.now().toString()); + } + else if (global == AttributeItem.Global.CLIENTNOW) { + value.set$timeVal(null); + // TODO how to get client time? + value.set$boolean(false); + } + else { + return; + } + } + else if (value.get$strCast() != null) { + injectValue(value.get$strCast(), claims); + return; + } + else if (value.get$numCast() != null) { + injectValue(value.get$numCast(), claims); + return; + } + else if (value.get$hexCast() != null) { + injectValue(value.get$hexCast(), claims); + return; + } + else if (value.get$boolCast() != null) { + injectValue(value.get$boolCast(), claims); + return; + } + else if (value.get$timeCast() != null) { + injectValue(value.get$timeCast(), claims); + return; + } + else if (value.get$dateTimeCast() != null) { + injectValue(value.get$dateTimeCast(), claims); + return; + } + value.set$attribute(null); + } + +} From 80e678eb38b58c51630bb324ac80e8bae116979f Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:25:56 +0200 Subject: [PATCH 100/108] feat: Abstract ACL filter --- .../filter/pre/AbstractAclFilter.java | 27 ++++++++++++ .../filter/pre/AclAttributeFilter.java | 44 ++++++++----------- .../filter/pre/AclClaimInjectionFilter.java | 23 ++++------ .../filter/pre/AclDisabledFilter.java | 22 +++------- .../security/filter/pre/AclObjectsFilter.java | 30 +++++-------- .../security/filter/pre/AclRightsFilter.java | 14 +++--- .../filter/pre/AclRulesInceptionFilter.java | 18 +++----- .../filter/pre/JwtValidationFilter.java | 8 ++-- 8 files changed, 85 insertions(+), 101 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java new file mode 100644 index 000000000..5b11d778b --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java @@ -0,0 +1,27 @@ +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; + +import java.io.IOException; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + + +public abstract class AbstractAclFilter extends JwtAuthorizationFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + request.setAttribute(ACL.getName(), doFilter((HttpServletRequest) request, acl)); + chain.doFilter(request, response); + } + + + protected abstract AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl); +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java index 93eb52627..d7b4f6357 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java @@ -14,18 +14,11 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; - import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import java.io.IOException; + import java.util.Map; import java.util.Optional; @@ -33,26 +26,27 @@ /** * Filters applicable AAS ACL rules using the incoming request's bearer token claims. */ -public class AclAttributeFilter extends JwtAuthorizationFilter { +public class AclAttributeFilter extends AbstractAclFilter { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { - Map claims = Optional.ofNullable(extractAndDecodeJwt(((HttpServletRequest) request)).getClaims()).orElse(Map.of()); - AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + protected AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + Map claims = Optional.ofNullable(extractAndDecodeJwt(request).getClaims()).orElse(Map.of()); - acl.getRules().removeIf(rule -> getAcl(rule, acl).getAttributes().stream() - .noneMatch(attributeItem -> { - // claim, global and reference should be subtypes of AttributeItem... - if (AttributeItem.Global.ANONYMOUS == attributeItem.getGlobal()) { - return true; - } - else if (attributeItem.getClaim() != null) { - return claims.containsKey(attributeItem.getClaim()); - } + acl.getRules().removeIf(rule -> { + for (AttributeItem item: rule.getAcl().getAttributes()) { + if (AttributeItem.Global.ANONYMOUS == item.getGlobal()) { + return false; + } + else if (item.getReference() != null) { return true; - })); - - request.setAttribute(ACL.getName(), acl); - chain.doFilter(request, response); + } + else if (item.getClaim() != null && claims.containsKey(item.getClaim())) { + continue; + } + return true; + } + return false; + }); + return acl; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java index bfd029d96..e0e7c8cfc 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java @@ -14,37 +14,30 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFormula; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.ExpressionInjectionHelper.injectLogicalExpression; - import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import java.io.IOException; + import java.util.AbstractMap; import java.util.Map; import java.util.stream.Collectors; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFormula; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.ExpressionInjectionHelper.injectLogicalExpression; + /** * Inject claims into ACL formula. */ -public class AclClaimInjectionFilter extends JwtAuthorizationFilter { +public class AclClaimInjectionFilter extends AbstractAclFilter { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); - Map claims = extractAndDecodeJwt((HttpServletRequest) request).getClaims().entrySet().stream() + public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + Map claims = extractAndDecodeJwt(request).getClaims().entrySet().stream() .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().asString())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); acl.getRules().forEach(rule -> injectLogicalExpression(getFormula(rule, acl), claims)); - request.setAttribute(ACL.getName(), acl); - chain.doFilter(request, response); + return acl; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java index d0d502565..32e176099 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java @@ -14,30 +14,20 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; - import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import java.io.IOException; +import jakarta.servlet.http.HttpServletRequest; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; /** * Filters applicable AAS ACL rules using the rules' "Access" field. */ -public class AclDisabledFilter implements Filter { +public class AclDisabledFilter extends AbstractAclFilter { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); - + public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { acl.getRules().removeIf(rule -> getAcl(rule, acl).getAccess() == Acl.Access.DISABLED); - - request.setAttribute(ACL.getName(), acl); - chain.doFilter(request, response); + return acl; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java index 80eaddd5e..379efb9fb 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java @@ -14,40 +14,31 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getObjects; - import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import java.io.IOException; + import java.util.ArrayList; import java.util.List; import java.util.Objects; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getObjects; + /** * Filters applicable AAS ACL rules using the incoming request's path. */ -public class AclObjectsFilter implements Filter { +public class AclObjectsFilter extends AbstractAclFilter { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - String path = ((HttpServletRequest) request).getRequestURI(); - - AllAccessPermissionRules filteredAcl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); - + public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + String path = (request).getRequestURI(); List filteredRules = new ArrayList<>(); - for (AccessPermissionRule rule: filteredAcl.getRules()) { + for (AccessPermissionRule rule: acl.getRules()) { - boolean anyMatch = getObjects(rule, filteredAcl).stream().anyMatch(objectItem -> { + boolean anyMatch = getObjects(rule, acl).stream().anyMatch(objectItem -> { if (objectItem.getRoute() != null) { String route = objectItem.getRoute(); // Warning: potentially does not allow trailing slash at requests. @@ -73,10 +64,9 @@ else if (objectItem.getDescriptor() != null) { } } - filteredAcl.setRules(filteredRules); + acl.setRules(filteredRules); - request.setAttribute(ACL.getName(), filteredAcl); - chain.doFilter(request, response); + return acl; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java index b01c5285b..099792b09 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java @@ -32,21 +32,17 @@ /** * Filters applicable AAS ACL rules using the incoming request's HTTP method. */ -public class AclRightsFilter implements Filter { +public class AclRightsFilter extends AbstractAclFilter { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { - String method = ((HttpServletRequest) request).getMethod(); - - AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); - - String requiredRight = isOperationRequest(method, ((HttpServletRequest) request).getContextPath()) ? "EXECUTE" : getRequiredRight(method); + public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + String method = request.getMethod(); + String requiredRight = isOperationRequest(method, request.getContextPath()) ? "EXECUTE" : getRequiredRight(method); acl.getRules().removeIf( rule -> getAcl(rule, acl).getRights().contains(RightsEnum.ALL) || getAcl(rule, acl).getRights().contains(RightsEnum.valueOf(requiredRight))); - request.setAttribute(ACL.getName(), acl); - chain.doFilter(request, response); + return acl; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java index dcf775ab3..a0effbc16 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java @@ -14,24 +14,19 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; - import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AclRepository; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import java.io.IOException; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import jakarta.servlet.http.HttpServletRequest; /** * Helper filter to inject the current ACL rules into a request. */ -public class AclRulesInceptionFilter implements Filter { +public class AclRulesInceptionFilter extends AbstractAclFilter { private final AclRepository aclRepository; + /** * Class constructor. * @@ -43,8 +38,7 @@ public AclRulesInceptionFilter(AclRepository aclRepository) { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - request.setAttribute(ACL.getName(), aclRepository.getAllAccessPermissionRules()); - chain.doFilter(request, response); + public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + return aclRepository.getAllAccessPermissionRules(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtValidationFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtValidationFilter.java index 23981e692..e2de249bd 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtValidationFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/JwtValidationFilter.java @@ -71,7 +71,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo var authHeaderList = Collections.list(authHeaders); if (authHeaderList.size() > 1) { LOGGER.debug("Multiple authorization headers present! Not authorizing request."); - respondForbidden(httpResponse); + respondUnauthorized(httpResponse); return; } @@ -85,7 +85,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo DecodedJWT jwt = extractAndDecodeJwt(httpRequest); if (jwt == null || !validateJWT(jwt)) { LOGGER.debug("Could not extract and validate JWT"); - respondForbidden(httpResponse); + respondUnauthorized(httpResponse); } else { filterChain.doFilter(servletRequest, servletResponse); @@ -93,8 +93,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } - private static void respondForbidden(HttpServletResponse httpResponse) throws IOException { - httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); + private static void respondUnauthorized(HttpServletResponse httpResponse) throws IOException { + httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpResponse.getWriter().write("Invalid token"); } From 3105ef9eba65372abef5eb1db8d53471b0f3972a Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:30:47 +0200 Subject: [PATCH 101/108] feat: Abstract ACL filter --- .../acl/repository/AbstractAclRepository.java | 90 +++++++++++++++++++ .../acl/repository/AclRepository.java | 30 ++++++- .../repository/file/FileAclRepository.java | 49 ++++------ .../filter/pre/AbstractAclFilter.java | 37 ++++++-- .../filter/pre/AclAttributeFilter.java | 7 +- .../filter/pre/AclClaimInjectionFilter.java | 13 ++- .../filter/pre/AclDisabledFilter.java | 8 +- .../security/filter/pre/AclObjectsFilter.java | 13 +-- .../security/filter/pre/AclRightsFilter.java | 20 ++--- .../filter/pre/AclRulesInceptionFilter.java | 9 +- .../util/AccessControlListHelper.java | 31 +++++++ 11 files changed, 223 insertions(+), 84 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java new file mode 100644 index 000000000..8ad921ad8 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java @@ -0,0 +1,90 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.acl.repository; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAttributes; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFilter; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFormula; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getObjects; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; +import java.util.ArrayList; +import java.util.List; + + +/** + * Keeps an in-memory version of the aclFolder's rules. When access control rules are added/deleted/modified, updates + * its own state accordingly. + */ +public abstract class AbstractAclRepository implements AclRepository { + + protected AllAccessPermissionRules allAccessPermissionRules; + + @Override + public List getAccessPermissionRules() { + return allAccessPermissionRules.getRules(); + } + + @Override + public final void addAndResolve(AllAccessPermissionRules acl) { + allAccessPermissionRules.getRules().addAll(acl.getRules()); + allAccessPermissionRules.getDefacls().addAll(acl.getDefacls()); + allAccessPermissionRules.getDefattributes().addAll(acl.getDefattributes()); + allAccessPermissionRules.getDefformulas().addAll(acl.getDefformulas()); + allAccessPermissionRules.getDefobjects().addAll(acl.getDefobjects()); + + allAccessPermissionRules = resolve(allAccessPermissionRules); + } + + @Override + public final void remove(AllAccessPermissionRules acl) { + allAccessPermissionRules.getRules().removeAll(acl.getRules()); + allAccessPermissionRules.getDefacls().removeAll(acl.getDefacls()); + allAccessPermissionRules.getDefattributes().removeAll(acl.getDefattributes()); + allAccessPermissionRules.getDefformulas().removeAll(acl.getDefformulas()); + allAccessPermissionRules.getDefobjects().removeAll(acl.getDefobjects()); + + allAccessPermissionRules = resolve(allAccessPermissionRules); + } + + + private AllAccessPermissionRules resolve(AllAccessPermissionRules unresolved) { + AllAccessPermissionRules resolved = new AllAccessPermissionRules(); + List resolvedRules = new ArrayList<>(); + for (AccessPermissionRule rule: unresolved.getRules()) { + AccessPermissionRule resolvedRule = new AccessPermissionRule(); + Acl acl = new Acl(); + acl.setAccess(rule.getAcl().getAccess()); + acl.setRights(rule.getAcl().getRights()); + acl.setAttributes(getAttributes(getAcl(rule, unresolved), unresolved)); + resolvedRule.setAcl(acl); + List objects = getObjects(rule, unresolved); + resolvedRule.setObjects(objects); + LogicalExpression formula = getFormula(rule, unresolved); + resolvedRule.setFormula(formula); + LogicalExpression filter = getFilter(rule, unresolved); + resolvedRule.setFilter(filter); + resolvedRules.add(resolvedRule); + } + + resolved.setRules(resolvedRules); + return resolved; + } +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AclRepository.java index a3fffda6f..2db805ede 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AclRepository.java @@ -14,8 +14,11 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import java.util.List; + /** * Keeps an in-memory version of the aclFolder's rules. When access control rules are added/deleted/modified, updates @@ -23,9 +26,28 @@ */ public interface AclRepository { /** - * Get the AllAccessPermissionRules state. For file-based repositories, this is the merged versions of all ACL files. - * - * @return The AllAccessPermissionRules. + * Get the fully resolved AccessPermissionRules, i.e. USE(ACL|ATTRIBUTE|...) are already replaced by their DEF* + * counterparts. For file-based repositories, this is the merged + * versions of all ACL files. + * + * @return All resolved AccessPermissionRules. + */ + List getAccessPermissionRules(); + + + /** + * Add an environment to the current ACL and resolve all DEF* into the AccessPermissionRule list. + * + * @param allAccessPermissionRules Rule environment to add. */ - AllAccessPermissionRules getAllAccessPermissionRules(); + void addAndResolve(AllAccessPermissionRules allAccessPermissionRules); + + + /** + * Remove an environment from the current ACL along with its DEF*. + * + * @param allAccessPermissionRules Rule environment to remove. + */ + void remove(AllAccessPermissionRules allAccessPermissionRules); + } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java index 48e0983ad..bb4751179 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/file/FileAclRepository.java @@ -18,13 +18,12 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AclRepository; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AbstractAclRepository; import de.fraunhofer.iosb.ilt.faaast.service.exception.EndpointException; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; @@ -34,7 +33,7 @@ /** * File implementation of an ACL Repository. Connected to a file system monitoring service. */ -public class FileAclRepository implements AclRepository, DirectoryWatcherListener { +public class FileAclRepository extends AbstractAclRepository implements DirectoryWatcherListener { private static final Logger LOGGER = LoggerFactory.getLogger(FileAclRepository.class); private final Map aclList; private final ObjectMapper mapper; @@ -66,25 +65,6 @@ public static FileAclRepository createNewInstance(String aclFolder) throws Endpo } - /** - * Get all currently observed rules. - * - * @return All access control rules. - */ - public AllAccessPermissionRules getAllAccessPermissionRules() { - var rules = new AllAccessPermissionRules(); - for (AllAccessPermissionRules fileRules: aclList.values()) { - rules.setRules(new ArrayList<>(fileRules.getRules())); - rules.setDefacls(new ArrayList<>(fileRules.getDefacls())); - rules.setDefattributes(new ArrayList<>(fileRules.getDefattributes())); - rules.setDefformulas(new ArrayList<>(fileRules.getDefformulas())); - rules.setDefobjects(new ArrayList<>(fileRules.getDefobjects())); - } - - return rules; - } - - @Override public void onFileCreated(Path path) { LOGGER.debug("Adding ACL {}", path); @@ -92,21 +72,10 @@ public void onFileCreated(Path path) { } - private void update(Path path) { - AllAccessPermissionRules rules = readFile(path); - if (rules != null && rules.getRules().stream().allMatch(rule -> validate(rule, rules))) { - aclList.put(path, rules); - } - else { - LOGGER.warn("Tried to load invalid ACL: {}.", path); - } - } - - @Override public void onFileDeleted(Path path) { LOGGER.debug("Removing ACL {}", path); - aclList.remove(path); + remove(aclList.get(path)); } @@ -117,6 +86,18 @@ public void onFileModified(Path path) { } + private void update(Path path) { + AllAccessPermissionRules acl = readFile(path); + if (acl != null && acl.getRules().stream().allMatch(rule -> validate(rule, acl))) { + aclList.put(path, acl); + addAndResolve(acl); + } + else { + LOGGER.warn("Tried to load invalid ACL: {}.", path); + } + } + + private AllAccessPermissionRules readFile(Path path) { try { JsonNode rootNode = mapper.readTree(path.toFile()); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java index 5b11d778b..737fdcc40 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java @@ -1,27 +1,52 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter.pre; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; - import java.io.IOException; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import java.util.List; +/** + * Contains common logic for extracting and storing the AAS ACL in a request. + */ public abstract class AbstractAclFilter extends JwtAuthorizationFilter implements Filter { + @SuppressWarnings("unchecked") @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - AllAccessPermissionRules acl = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); + List acl = ((List) request.getAttribute(ACL.getName())); request.setAttribute(ACL.getName(), doFilter((HttpServletRequest) request, acl)); chain.doFilter(request, response); } - protected abstract AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl); + /** + * Perform filtering of the ACL dependent on the HTTP request at hand. + * + * @param request The request to filter with + * @param acl The ACL to filter + * @return Filtered ACL list + */ + protected abstract List doFilter(HttpServletRequest request, List acl); } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java index d7b4f6357..b07af7652 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java @@ -15,10 +15,11 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import com.auth0.jwt.interfaces.Claim; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; import jakarta.servlet.http.HttpServletRequest; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -29,10 +30,10 @@ public class AclAttributeFilter extends AbstractAclFilter { @Override - protected AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + protected List doFilter(HttpServletRequest request, List acl) { Map claims = Optional.ofNullable(extractAndDecodeJwt(request).getClaims()).orElse(Map.of()); - acl.getRules().removeIf(rule -> { + acl.removeIf(rule -> { for (AttributeItem item: rule.getAcl().getAttributes()) { if (AttributeItem.Global.ANONYMOUS == item.getGlobal()) { return false; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java index e0e7c8cfc..fddd989f2 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java @@ -14,28 +14,27 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; -import jakarta.servlet.http.HttpServletRequest; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.ExpressionInjectionHelper.injectLogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import jakarta.servlet.http.HttpServletRequest; import java.util.AbstractMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFormula; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.ExpressionInjectionHelper.injectLogicalExpression; - /** * Inject claims into ACL formula. */ public class AclClaimInjectionFilter extends AbstractAclFilter { @Override - public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + protected List doFilter(HttpServletRequest request, List acl) { Map claims = extractAndDecodeJwt(request).getClaims().entrySet().stream() .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().asString())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - acl.getRules().forEach(rule -> injectLogicalExpression(getFormula(rule, acl), claims)); + acl.forEach(rule -> injectLogicalExpression(rule.getFormula(), claims)); return acl; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java index 32e176099..5ebce54f8 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java @@ -14,11 +14,11 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import jakarta.servlet.http.HttpServletRequest; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; +import java.util.List; /** @@ -26,8 +26,8 @@ */ public class AclDisabledFilter extends AbstractAclFilter { @Override - public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { - acl.getRules().removeIf(rule -> getAcl(rule, acl).getAccess() == Acl.Access.DISABLED); + protected List doFilter(HttpServletRequest request, List acl) { + acl.removeIf(rule -> rule.getAcl().getAccess() == Acl.Access.DISABLED); return acl; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java index 379efb9fb..72b805b58 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java @@ -15,7 +15,6 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; @@ -23,8 +22,6 @@ import java.util.List; import java.util.Objects; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getObjects; - /** * Filters applicable AAS ACL rules using the incoming request's path. @@ -32,13 +29,13 @@ public class AclObjectsFilter extends AbstractAclFilter { @Override - public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + protected List doFilter(HttpServletRequest request, List acl) { String path = (request).getRequestURI(); List filteredRules = new ArrayList<>(); - for (AccessPermissionRule rule: acl.getRules()) { + for (AccessPermissionRule rule: acl) { - boolean anyMatch = getObjects(rule, acl).stream().anyMatch(objectItem -> { + boolean anyMatch = rule.getObjects().stream().anyMatch(objectItem -> { if (objectItem.getRoute() != null) { String route = objectItem.getRoute(); // Warning: potentially does not allow trailing slash at requests. @@ -64,9 +61,7 @@ else if (objectItem.getDescriptor() != null) { } } - acl.setRules(filteredRules); - - return acl; + return filteredRules; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java index 099792b09..20c78bad1 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java @@ -14,19 +14,12 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; - import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import java.io.IOException; + +import java.util.List; /** @@ -35,12 +28,13 @@ public class AclRightsFilter extends AbstractAclFilter { @Override - public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { + protected List doFilter(HttpServletRequest request, List acl) { String method = request.getMethod(); String requiredRight = isOperationRequest(method, request.getContextPath()) ? "EXECUTE" : getRequiredRight(method); - acl.getRules().removeIf( - rule -> getAcl(rule, acl).getRights().contains(RightsEnum.ALL) || getAcl(rule, acl).getRights().contains(RightsEnum.valueOf(requiredRight))); + acl.removeIf( + rule -> rule.getAcl().getRights().contains(RightsEnum.ALL) || + rule.getAcl().getRights().contains(RightsEnum.valueOf(requiredRight))); return acl; } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java index a0effbc16..6738201f1 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java @@ -15,9 +15,11 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.AclRepository; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import jakarta.servlet.http.HttpServletRequest; +import java.util.List; + /** * Helper filter to inject the current ACL rules into a request. @@ -26,7 +28,6 @@ public class AclRulesInceptionFilter extends AbstractAclFilter { private final AclRepository aclRepository; - /** * Class constructor. * @@ -38,7 +39,7 @@ public AclRulesInceptionFilter(AclRepository aclRepository) { @Override - public AllAccessPermissionRules doFilter(HttpServletRequest request, AllAccessPermissionRules acl) { - return aclRepository.getAllAccessPermissionRules(); + protected List doFilter(HttpServletRequest request, List acl) { + return aclRepository.getAccessPermissionRules(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListHelper.java index f4655d9a9..06d523bd2 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListHelper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/AccessControlListHelper.java @@ -41,6 +41,8 @@ public class AccessControlListHelper { private static final Object USEATTRIBUTES = "USEATTRIBUTES"; private static final String DEFFORMULA = "DEFFORUMLA"; private static final Object USEFORUMLA = "USEFORUMLA"; + private static final String DEFFILTER = "DEFFILTER"; + private static final Object USEFILTER = "USEFILTER"; private static final String DEFOBJECTS = "DEFOBJECTS"; private static final Object USEOBJECTS = "USEOBJECTS"; @@ -134,6 +136,35 @@ else if (rule.getUseformula() != null) { } + /** + * returns the FILTER of the AccessPermissionRule. If FILTER is not directly defined, returns the DEFFORMULA defined + * by USEFILTER. + * + * @param rule Rule to get FILTER from + * @param allAccess Rule environment + * @return The FILTER. + */ + public static LogicalExpression getFilter(AccessPermissionRule rule, AllAccessPermissionRules allAccess) { + if (rule.getFilter() != null) { + return rule.getFilter(); + } + else if (rule.getUsefilter() != null) { + Optional formula = allAccess.getDefformulas().stream() + .filter(a -> Objects.equals(a.getName(), rule.getUsefilter())) + .findAny(); + if (formula.isPresent()) { + return formula.get().getFormula(); + } + else { + throw new IllegalArgumentException(String.format("%s not found for %s: %s", DEFFILTER, USEFILTER, rule.getUseformula())); + } + } + else { + throw new IllegalArgumentException(String.format("Invalid rule: FILTER or %s must be specified", USEFILTER)); + } + } + + /** * returns the OBJECTS of the AccessPermissionRule. If OBJECTS is not directly defined, returns the DEFOBJECTS defined * by USEOBJECTS. From af825bc5a5c4ddf141e623e308b3cab81e05e507 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:10:17 +0200 Subject: [PATCH 102/108] chore: clean up --- .../service/endpoint/http/HttpEndpoint.java | 10 +- .../endpoint/http/RequestHandlerServlet.java | 130 ++++++++++++---- .../acl/repository/AbstractAclRepository.java | 2 + .../http/security/filter/ApiGateway.java | 139 +----------------- .../security/filter/post/AclFilterFilter.java | 42 ++++++ ... => AclAttributeInjectionInterceptor.java} | 16 +- .../filter/pre/AclDisabledFilter.java | 2 +- .../util/ExpressionInjectionHelper.java | 13 +- .../endpoint/http/util/HttpHelper.java | 26 ++++ .../security/filter/ApiGatewayFilterTest.java | 16 +- 10 files changed, 201 insertions(+), 195 deletions(-) create mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/{AclClaimInjectionFilter.java => AclAttributeInjectionInterceptor.java} (72%) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java index 65e8add4a..49a66d61a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/HttpEndpoint.java @@ -25,7 +25,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.AbstractEndpoint; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.acl.repository.file.FileAclRepository; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclAttributeFilter; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclClaimInjectionFilter; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclAttributeInjectionInterceptor; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclDisabledFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclObjectsFilter; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.AclRightsFilter; @@ -130,11 +130,17 @@ public void start() throws EndpointException { if (Objects.nonNull(config.getJwkProvider())) { context.addFilter(new JwtValidationFilter(new UrlJwkProvider(parseJwkProviderUrl(config.getJwkProvider()))), "*", EnumSet.allOf(DispatcherType.class)); context.addFilter(new AclRulesInceptionFilter(FileAclRepository.createNewInstance(config.getAclFolder())), "*", EnumSet.allOf(DispatcherType.class)); + // Remove ACL that are DISABLED. context.addFilter(new AclDisabledFilter(), "*", EnumSet.allOf(DispatcherType.class)); + // Remove ACL that do not comply with the HTTP method of a request. context.addFilter(new AclRightsFilter(), "*", EnumSet.allOf(DispatcherType.class)); + // Remove ACL that do not comply with the HTTP path of a request. context.addFilter(new AclObjectsFilter(), "*", EnumSet.allOf(DispatcherType.class)); + // Remove ACL that do not comply with the JWT claims of a request. context.addFilter(new AclAttributeFilter(), "*", EnumSet.allOf(DispatcherType.class)); - context.addFilter(new AclClaimInjectionFilter(), "*", EnumSet.allOf(DispatcherType.class)); + + // Inject claims and global attributes into the remaining ACL rules. + context.addFilter(new AclAttributeInjectionInterceptor(), "*", EnumSet.allOf(DispatcherType.class)); } context.addServlet(handler, "/*"); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index b75046e31..bfc23700a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -14,34 +14,54 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.GET; + import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; +import de.fraunhofer.iosb.ilt.faaast.service.dataformat.SerializationException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.RequestMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; +import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.GetAllSubmodelsResponse; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aas.GetAssetAdministrationShellRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.GetAllAssetAdministrationShellsByIdShortRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.GetAllAssetAdministrationShellsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetAllConceptDescriptionsByIdShortRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetAllConceptDescriptionsRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetConceptDescriptionByIdRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.GetSubmodelRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.GetAllSubmodelsByIdShortRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.GetAllSubmodelsBySemanticIdRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.GetAllSubmodelsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedModifierException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.model.Key; import org.eclipse.digitaltwin.aas4j.v3.model.Message; import org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.jetty.server.Response; @@ -105,13 +125,75 @@ protected void service(HttpServletRequest request, HttpServletResponse response) x -> x, request::getHeader))) .build(); + try { + if (Objects.nonNull(apiGateway) && GET == httpRequest.getMethod()) { + modifyRequest(request, httpRequest); + } executeAndSend(request, response, requestMappingManager.map(httpRequest)); } catch (Exception e) { doThrow(e); } + } + + + private void modifyRequest(HttpServletRequest servletRequest, HttpRequest request) + throws SerializationException, InvalidRequestException { + var wouldHaveBeen = requestMappingManager.map(request); + List acl = (List) servletRequest.getAttribute(ACL.getName()); + if (acl.isEmpty()) { + throw new UnauthorizedException("unauthorized. TODO decide if 404 or 403"); + } + List formulas = acl.stream().map(AccessPermissionRule::getFormula).toList(); + LogicalExpression formulasOr = new LogicalExpression(); + formulasOr.set$or(formulas); + + if (wouldHaveBeen instanceof GetAllAssetAdministrationShellsRequest) { + request.setPath("query/shells"); + request.setBody(serializer.write(formulasOr)); + } + else if (wouldHaveBeen instanceof GetAllSubmodelsRequest) { + request.setPath("query/submodels"); + request.setBody(serializer.write(formulasOr)); + } + else if (wouldHaveBeen instanceof GetAllConceptDescriptionsRequest) { + request.setPath("query/concept-descriptions"); + request.setBody(serializer.write(formulasOr)); + } + + else if (wouldHaveBeen instanceof GetAssetAdministrationShellRequest getAssetAdministrationShellRequest) { + setQuery(request, "aas", "shells", "id", getAssetAdministrationShellRequest.getId(), formulasOr); + } + else if (wouldHaveBeen instanceof GetAllAssetAdministrationShellsByIdShortRequest getAllAssetAdministrationShellsByIdShortRequest) { + setQuery(request, "aas", "shells", "idShort", getAllAssetAdministrationShellsByIdShortRequest.getIdShort(), formulasOr); + } + + else if (wouldHaveBeen instanceof GetSubmodelRequest getSubmodelRequest) { + setQuery(request, "sm", "submodels", "id", getSubmodelRequest.getSubmodelId(), formulasOr); + } + else if (wouldHaveBeen instanceof GetAllSubmodelsByIdShortRequest getAllSubmodelsByIdShortRequest) { + setQuery(request, "sm", "submodels", "idShort", getAllSubmodelsByIdShortRequest.getIdShort(), formulasOr); + } + else if (wouldHaveBeen instanceof GetAllSubmodelsBySemanticIdRequest getAllSubmodelsBySemanticIdRequest) { + setQuery(request, "sm", "submodels", "semanticId", getSemanticIdString(getAllSubmodelsBySemanticIdRequest.getSemanticId()), formulasOr); + } + else if (wouldHaveBeen instanceof GetConceptDescriptionByIdRequest getConceptDescriptionByIdRequest) { + setQuery(request, "cd", "concept-descriptions", "id", getConceptDescriptionByIdRequest.getId(), formulasOr); + } + else if (wouldHaveBeen instanceof GetAllConceptDescriptionsByIdShortRequest getAllConceptDescriptionsByIdShortRequest) { + setQuery(request, "cd", "concept-descriptions", "idShort", getAllConceptDescriptionsByIdShortRequest.getIdShort(), formulasOr); + } + } + + + private void setQuery(HttpRequest request, String res, String resource, String pattern, String id, LogicalExpression formula) + throws SerializationException, UnsupportedModifierException { + request.setPath(String.format("query/%s", resource)); + LogicalExpression parentFormula = new LogicalExpression(); + parentFormula.set$and(List.of(formEq(String.format("$%s#%s", res, pattern), id), formula)); + request.setBody(serializer.write(parentFormula)); } @@ -170,28 +252,8 @@ private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model. private de.fraunhofer.iosb.ilt.faaast.service.model.api.Response handleResponseWithAcl(HttpServletRequest request, de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws ServletException { - String url = request.getRequestURI(); - - if (request.getMethod().equals("GET")) { - if ((url.equals("/shells") || url.equals("/shells/"))) { - GetAllAssetAdministrationShellsResponse aasResponse = (GetAllAssetAdministrationShellsResponse) serviceContext.execute(endpoint, apiRequest); - return apiGateway.filterAas(request, aasResponse); - } - else if ((url.equals("/submodels") || url.equals("/submodels/"))) { - GetAllSubmodelsResponse submodelsResponse = (GetAllSubmodelsResponse) serviceContext.execute(endpoint, apiRequest); - return apiGateway.filterSubmodels(request, submodelsResponse); - } - else if ((url.matches("^/submodels/[^/]+$"))) { - GetSubmodelResponse submodelResponse = (GetSubmodelResponse) serviceContext.execute(endpoint, apiRequest); - if (!apiGateway.filterSubmodel(request, submodelResponse)) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getRequestURI()))); - } - return submodelResponse; - } - } - - if (!apiGateway.isAuthorized(request)) { + List rules = ((List) request.getAttribute(ACL.getName())); + if (rules.stream().noneMatch(rule -> FormulaEvaluator.evaluate(rule.getFormula(), new HashMap<>()))) { doThrow(new UnauthorizedException( String.format("User not authorized '%s'", request.getRequestURI()))); } @@ -199,4 +261,22 @@ else if ((url.matches("^/submodels/[^/]+$"))) { return serviceContext.execute(endpoint, apiRequest); } + + private String getSemanticIdString(Reference semanticId) { + return Optional.ofNullable(ReferenceHelper.getRoot(semanticId)) + .map(Key::getValue) + .orElse(null); + } + + + private LogicalExpression formEq(String left, String right) { + LogicalExpression eqFormula = new LogicalExpression(); + Value smId = new Value(); + smId.set$strVal(left); + Value identifier = new Value(); + identifier.set$strVal(right); + + eqFormula.set$eq(List.of(smId, identifier)); + return eqFormula; + } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java index 8ad921ad8..2e03552cf 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/acl/repository/AbstractAclRepository.java @@ -42,6 +42,7 @@ public List getAccessPermissionRules() { return allAccessPermissionRules.getRules(); } + @Override public final void addAndResolve(AllAccessPermissionRules acl) { allAccessPermissionRules.getRules().addAll(acl.getRules()); @@ -53,6 +54,7 @@ public final void addAndResolve(AllAccessPermissionRules acl) { allAccessPermissionRules = resolve(allAccessPermissionRules); } + @Override public final void remove(AllAccessPermissionRules acl) { allAccessPermissionRules.getRules().removeAll(acl.getRules()); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java index 0d8b8a474..be07743fe 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java @@ -15,32 +15,12 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAcl; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getAttributes; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.AccessControlListHelper.getFormula; -import com.auth0.jwt.JWT; -import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.GetAllAssetAdministrationShellsResponse; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodel.GetSubmodelResponse; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.GetAllSubmodelsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; -import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import jakarta.servlet.http.HttpServletRequest; -import java.time.Clock; -import java.time.LocalTime; import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Objects; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; /** @@ -55,122 +35,7 @@ public class ApiGateway { * @return true if authorized and ACL exists */ public boolean isAuthorized(HttpServletRequest request) { - return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() - .anyMatch(r -> verifyAllClaims(extractClaims(request), r, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), null)); - } - - - /** - * Filters out AAS that the user is not authorized for. - * - * @param request the HttpRequest - * @param response the ApiResponse - * @return the ApiResponse with only allowed AAS - */ - public Response filterAas(HttpServletRequest request, GetAllAssetAdministrationShellsResponse response) { - // remove AAS if none of the ALL_ACL have any rule that matches - AllAccessPermissionRules allAccessPermissionRules = (AllAccessPermissionRules) request.getAttribute(ACL.getName()); - - response.getPayload().getContent() - .removeIf(aas -> allAccessPermissionRules.getRules() - .stream().noneMatch(r -> verifyAllClaims(extractClaims(request), r, allAccessPermissionRules, null))); - return response; - } - - - /** - * Filters out Submodels that the user is not authorized for. - * - * @param request the HttpRequest - * @param response the ApiResponse - * @return the ApiResponse with only allowed Submodels - */ - public Response filterSubmodels(HttpServletRequest request, GetAllSubmodelsResponse response) { - response.getPayload().getContent().removeIf(submodel -> { - Map claims = extractClaims(request); - - Map fieldCtx = new HashMap<>(); - if (submodel.getSemanticId() != null) { - fieldCtx.put("$sm#semanticId", submodel.getSemanticId().getKeys().get(0).getValue()); - } - - return ((AllAccessPermissionRules) request.getAttribute(ACL.getName())).getRules().stream() - .noneMatch(rule -> verifyAllClaims(claims, rule, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); - }); - return response; - } - - - /** - * Filters out the Submodel that the user is not authorized for. - * - * @param request the HttpRequest - * @param response the ApiResponse - * @return true if user is authorized - */ - public boolean filterSubmodel(HttpServletRequest request, GetSubmodelResponse response) { - Submodel submodel = response.getPayload(); - if (Objects.isNull(submodel)) { - return true; - } - Map claims = extractClaims(request); - AllAccessPermissionRules acl = ((AllAccessPermissionRules) request.getAttribute(ACL.getName())); - - Map fieldCtx = new HashMap<>(); - if (submodel.getSemanticId() != null) { - fieldCtx.put("$sm#semanticId", Objects.requireNonNull(ReferenceHelper.getRoot(submodel.getSemanticId())).getValue()); - } - - return acl.getRules().stream() - .anyMatch(rule -> verifyAllClaims(claims, rule, (AllAccessPermissionRules) request.getAttribute(ACL.getName()), fieldCtx)); - } - - - private Map extractClaims(HttpServletRequest request) { - var authHeaderValue = request.getHeader(AUTHORIZATION); - - if (authHeaderValue == null || !authHeaderValue.startsWith(BEARER.concat(" "))) { - return null; - } - - // Remove "Bearer " - String token = authHeaderValue.substring(BEARER.length()).trim(); - - return JWT.decode(token).getClaims(); - } - - - private static boolean verifyAllClaims(Map claims, AccessPermissionRule rule, AllAccessPermissionRules allAccess, Map fieldCtx) { - Acl acl = getAcl(rule, allAccess); - - boolean isAnonymous = getAttributes(acl, allAccess).stream() - .anyMatch(attr -> attr.getGlobal() != null && "ANONYMOUS".equals(attr.getGlobal().value())); - - List claimNames = getAttributes(acl, allAccess).stream() - .filter(attr -> attr.getGlobal() == null) - .map(AttributeItem::getClaim) - .filter(Objects::nonNull) - .toList(); - - // Build context - Map ctx = new HashMap<>(); - if (claims != null) { - for (String name: claimNames) { - Claim c = claims.get(name); - if (c != null) { - ctx.put("CLAIM:" + name, c.asString()); - } - } - } - // Add $sm#semanticId - if (fieldCtx != null && !fieldCtx.isEmpty()) { - ctx.putAll(fieldCtx); - } - ctx.put("UTCNOW", LocalTime.now(Clock.systemUTC())); - if (isAnonymous) { - return FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); - } - return !ctx.entrySet().stream().filter(e -> e.getKey().startsWith("CLAIM:")).toList().isEmpty() && - FormulaEvaluator.evaluate(getFormula(rule, allAccess), ctx); + return ((List) request.getAttribute(ACL.getName())).stream() + .anyMatch(rule -> FormulaEvaluator.evaluate(rule.getFormula(), new HashMap<>())); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java new file mode 100644 index 000000000..534b11803 --- /dev/null +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java @@ -0,0 +1,42 @@ +/* + * 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.ilt.faaast.service.endpoint.http.security.filter.post; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; + +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import java.io.IOException; +import java.util.List; + + +/** + * Applies post-persistence filtering of responses according to AAS Security Filter. + */ +public class AclFilterFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + doFilter(request, response, chain); + List acl = ((List) request.getAttribute(ACL.getName())); + + // TODO apply filters post-query + } + +} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeInjectionInterceptor.java similarity index 72% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeInjectionInterceptor.java index fddd989f2..8c8c448bc 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclClaimInjectionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeInjectionInterceptor.java @@ -15,28 +15,20 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util.ExpressionInjectionHelper.injectLogicalExpression; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper.extractClaims; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import jakarta.servlet.http.HttpServletRequest; -import java.util.AbstractMap; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; /** - * Inject claims into ACL formula. + * Inject claims and global attributes into the remaining ACL rules. */ -public class AclClaimInjectionFilter extends AbstractAclFilter { +public class AclAttributeInjectionInterceptor extends AbstractAclFilter { @Override protected List doFilter(HttpServletRequest request, List acl) { - Map claims = extractAndDecodeJwt(request).getClaims().entrySet().stream() - .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().asString())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - acl.forEach(rule -> injectLogicalExpression(rule.getFormula(), claims)); - + acl.forEach(rule -> injectLogicalExpression(rule.getFormula(), extractClaims(request))); return acl; } - } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java index 5ebce54f8..f8098461e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java @@ -27,7 +27,7 @@ public class AclDisabledFilter extends AbstractAclFilter { @Override protected List doFilter(HttpServletRequest request, List acl) { - acl.removeIf(rule -> rule.getAcl().getAccess() == Acl.Access.DISABLED); + acl.removeIf(rule -> Acl.Access.DISABLED == rule.getAcl().getAccess()); return acl; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java index d33e8c09a..474b56cae 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.util; +import com.auth0.jwt.interfaces.Claim; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.MatchExpression; @@ -41,7 +42,7 @@ private ExpressionInjectionHelper() { * @param formula The formula to inject into. * @param claims The claims to inject. */ - public static void injectLogicalExpression(LogicalExpression formula, Map claims) { + public static void injectLogicalExpression(LogicalExpression formula, Map claims) { if (!formula.get$and().isEmpty()) { // It is an AND expression formula.get$and().forEach(op -> injectLogicalExpression(op, claims)); @@ -91,7 +92,7 @@ else if (!formula.get$regex().isEmpty()) { } - private static void injectMatchExpression(MatchExpression formula, Map claims) { + private static void injectMatchExpression(MatchExpression formula, Map claims) { if (!formula.get$match().isEmpty()) { // It is an AND expression formula.get$match().forEach(op -> injectMatchExpression(op, claims)); @@ -129,10 +130,10 @@ else if (!formula.get$regex().isEmpty()) { } - private static void injectStringValue(StringValue stringValue, Map claims) { + private static void injectStringValue(StringValue stringValue, Map claims) { if (stringValue.get$attribute() != null) { if (stringValue.get$attribute().getClaim() != null) { - stringValue.set$strVal(claims.get(stringValue.get$attribute().getClaim())); + stringValue.set$strVal(claims.get(stringValue.get$attribute().getClaim()).asString()); } } else if (stringValue.get$strCast() != null) { @@ -143,12 +144,12 @@ else if (stringValue.get$strCast() != null) { } - private static void injectValue(Value value, Map claims) { + private static void injectValue(Value value, Map claims) { if (value.get$attribute() == null) { return; } if (value.get$attribute().getClaim() != null) { - value.set$strVal(claims.get(value.get$attribute().getClaim())); + value.set$strVal(claims.get(value.get$attribute().getClaim()).asString()); } else if (value.get$attribute().getGlobal() != null) { AttributeItem.Global global = value.get$attribute().getGlobal(); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/HttpHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/HttpHelper.java index 685428f1f..2325c81fd 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/HttpHelper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/util/HttpHelper.java @@ -14,6 +14,11 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.AUTHORIZATION; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre.JwtAuthorizationFilter.BEARER; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.Claim; import com.google.common.net.MediaType; import de.fraunhofer.iosb.ilt.faaast.service.dataformat.SerializationException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; @@ -23,6 +28,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedModifierException; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import de.fraunhofer.iosb.ilt.faaast.service.util.StringHelper; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.IllegalCharsetNameException; @@ -129,6 +135,26 @@ public static MessageTypeEnum messageTypeFromstatusCode(StatusCode statusCode) { } + /** + * Extract the claims out of an HTTP request with a bearer token. + * + * @param request The request containing a bearer token header. + * @return The claims contained in the bearer token of the request's header. + */ + public static Map extractClaims(HttpServletRequest request) { + var authHeaderValue = request.getHeader(AUTHORIZATION); + + if (authHeaderValue == null || !authHeaderValue.startsWith(BEARER.concat(" "))) { + return null; + } + + // Remove "Bearer " + String token = authHeaderValue.substring(BEARER.length()).trim(); + + return JWT.decode(token).getClaims(); + } + + /** * Sends a HTTP response with given statusCode and corresponding HTTP status code as message. * diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java index a0b953e24..612c87eed 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java @@ -21,10 +21,7 @@ import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; @@ -34,7 +31,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; -import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import java.util.List; import org.junit.Test; @@ -53,24 +49,20 @@ private static HttpServletRequest req(String method, String uri) { @Test - public void anonymousAccessDependsOnAclFile() throws Exception { + public void anonymousAccessDependsOnAclFile() { apiGateway = new ApiGateway(); HttpServletRequest request = req("GET", "/api/v3.0/submodels"); - when(request.getAttribute(ACL.getName())).thenReturn(new AllAccessPermissionRules()); - - FilterChain filter = mockFilterChain(); + when(request.getAttribute(ACL.getName())).thenReturn(List.of()); assertFalse(apiGateway.isAuthorized(request)); - // Verify that request was blocked off - verify(filter, never()).doFilter(any(), any()); AllAccessPermissionRules env = mockEnvironment(); - when(request.getAttribute(ACL.getName())).thenReturn(env); + when(request.getAttribute(ACL.getName())).thenReturn(env.getRules()); await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertTrue(apiGateway.isAuthorized(request))); - when(request.getAttribute(ACL.getName())).thenReturn(new AllAccessPermissionRules()); + when(request.getAttribute(ACL.getName())).thenReturn(List.of()); await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertFalse(apiGateway.isAuthorized(request))); } From b90c12628898c6f2102b0556a39e02de7a0e1db8 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 8 Jun 2026 10:59:27 +0200 Subject: [PATCH 103/108] feat: update structure --- .../http/security/auth/AuthState.java | 23 ----------- .../http/security/filter/ApiGateway.java | 41 ------------------- .../{auth => filter}/SharedAttributes.java | 2 +- .../security/filter/post/AclFilterFilter.java | 2 +- .../filter/pre/AbstractAclFilter.java | 28 ++++++++++--- 5 files changed, 25 insertions(+), 71 deletions(-) delete mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java delete mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java rename endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/{auth => filter}/SharedAttributes.java (98%) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java deleted file mode 100644 index 561d97e5b..000000000 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/AuthState.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth; - -/** - * Shows the current state of an incoming HTTP request. - */ -public enum AuthState { - ANONYMOUS, - AUTHENTICATED -} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java deleted file mode 100644 index be07743fe..000000000 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGateway.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; - -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.List; - - -/** - * Filters any incoming request with respect to the given ACL rules. - */ -public class ApiGateway { - - /** - * Checks if the user is authorized to receive the response of the request. - * - * @param request the HttpRequest - * @return true if authorized and ACL exists - */ - public boolean isAuthorized(HttpServletRequest request) { - return ((List) request.getAttribute(ACL.getName())).stream() - .anyMatch(rule -> FormulaEvaluator.evaluate(rule.getFormula(), new HashMap<>())); - } -} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/SharedAttributes.java similarity index 98% rename from endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java rename to endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/SharedAttributes.java index a67b1976e..4e06ed2b6 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/auth/SharedAttributes.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/SharedAttributes.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth; +package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; /** * Shared attributes during servlet request filtering. diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java index 534b11803..b5b1fdeab 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java @@ -14,7 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.post; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.SharedAttributes.ACL; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import jakarta.servlet.Filter; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java index 737fdcc40..81d1f1314 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java @@ -14,21 +14,25 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; - import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; import java.util.List; +import java.util.Objects; + +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.SharedAttributes.ACL; /** - * Contains common logic for extracting and storing the AAS ACL in a request. + * Contains common logic for extracting and storing the AAS ACL in a request. If the ACL is empty as a result of the AclFilter, the request will be denied. */ public abstract class AbstractAclFilter extends JwtAuthorizationFilter implements Filter { @@ -36,8 +40,16 @@ public abstract class AbstractAclFilter extends JwtAuthorizationFilter implement @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { List acl = ((List) request.getAttribute(ACL.getName())); - request.setAttribute(ACL.getName(), doFilter((HttpServletRequest) request, acl)); - chain.doFilter(request, response); + + List filtered = doFilter((HttpServletRequest) request, acl); + Objects.requireNonNull(filtered, "Filters need to return empty rule lists if none apply"); + + if (!acl.isEmpty()) { + request.setAttribute(ACL.getName(), filtered); + chain.doFilter(request, response); + } + + respondForbidden((HttpServletResponse) response); } @@ -49,4 +61,10 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha * @return Filtered ACL list */ protected abstract List doFilter(HttpServletRequest request, List acl); + + + private static void respondForbidden(HttpServletResponse httpResponse) throws IOException { + httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); + httpResponse.getWriter().write("forbidden"); + } } From de129c2bbf65bde052af90d8de106066b5363769 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:55:57 +0200 Subject: [PATCH 104/108] feat: add acl as request query --- .../service/persistence/Persistence.java | 16 +- ...tionShellIdsByAssetLinkRequestHandler.java | 3 +- ...hellsByAssetIdReferenceRequestHandler.java | 3 +- ...strationShellsByAssetIdRequestHandler.java | 3 +- ...hellsByIdShortReferenceRequestHandler.java | 3 +- ...strationShellsByIdShortRequestHandler.java | 3 +- ...strationShellsReferenceRequestHandler.java | 3 +- ...setAdministrationShellsRequestHandler.java | 3 +- ...setAdministrationShellsRequestHandler.java | 10 +- ...aSpecificationReferenceRequestHandler.java | 3 +- ...ptDescriptionsByIdShortRequestHandler.java | 3 +- ...tDescriptionsByIsCaseOfRequestHandler.java | 3 +- ...tAllConceptDescriptionsRequestHandler.java | 3 +- ...ueryConceptDescriptionsRequestHandler.java | 10 +- ...etAllSubmodelsByIdShortRequestHandler.java | 3 +- ...llSubmodelsBySemanticIdRequestHandler.java | 3 +- ...etAllSubmodelsReferenceRequestHandler.java | 3 +- .../GetAllSubmodelsRequestHandler.java | 3 +- .../QuerySubmodelsRequestHandler.java | 10 +- .../endpoint/http/RequestHandlerServlet.java | 146 +++--------------- .../endpoint/http/model/HttpRequest.java | 29 ++++ .../request/mapper/AbstractRequestMapper.java | 19 ++- .../filter/pre/AbstractAclFilter.java | 9 +- .../security/filter/ApiGatewayFilterTest.java | 89 ----------- .../ilt/faaast/service/model/api/Request.java | 33 +++- .../persistence/file/PersistenceFile.java | 16 +- .../memory/PersistenceInMemory.java | 35 ++--- .../persistence/mongo/PersistenceMongo.java | 10 +- 28 files changed, 192 insertions(+), 285 deletions(-) delete mode 100644 endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java index 923534103..f4ba6d1f4 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java @@ -24,7 +24,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceAlreadyExistsException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import java.util.Objects; @@ -265,12 +265,12 @@ public Page findAssetAdministrationShells(AssetAdminis * @param criteria the search criteria * @param modifier the modifier * @param paging paging information - * @param query the query to be executed + * @param formula the formula of the query to be executed * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s * @throws PersistenceException if there was an error with the storage. */ - public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, - Query query) + public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + LogicalExpression formula) throws PersistenceException; @@ -292,11 +292,11 @@ public Page findAssetAdministrationShellsWithQuery(Ass * @param criteria the search criteria * @param modifier the modifier * @param paging paging information - * @param query query to execute + * @param formula the formula of the query to be executed * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s * @throws PersistenceException if there was an error with the storage. */ - public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException; + public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException; /** @@ -331,11 +331,11 @@ public Page findSubmodelElements(SubmodelElementSearchCriteria * @param criteria the search criteria * @param modifier the modifier * @param paging paging information - * @param query query to execute + * @param formula the formula of the query to be executed * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s * @throws PersistenceException if there was an error with the storage. */ - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException; diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/GetAllAssetAdministrationShellIdsByAssetLinkRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/GetAllAssetAdministrationShellIdsByAssetLinkRequestHandler.java index dd348db7f..905e53c29 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/GetAllAssetAdministrationShellIdsByAssetLinkRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/GetAllAssetAdministrationShellIdsByAssetLinkRequestHandler.java @@ -46,7 +46,8 @@ public GetAllAssetAdministrationShellIdsByAssetLinkResponse process(GetAllAssetA .assetIds(parseSpecificAssetIds(request.getAssetIdentifierPairs())) .build(), QueryModifier.DEFAULT, - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); List result = aass.getContent() .stream() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdReferenceRequestHandler.java index d8c57efb8..6ed04e125 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdReferenceRequestHandler.java @@ -49,7 +49,8 @@ public GetAllAssetAdministrationShellsByAssetIdReferenceResponse process(GetAllA .assetIds(parseSpecificAssetIds(request.getAssetIds())) .build(), request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal()) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdRequestHandler.java index 95a5a738d..dbd53b8b2 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByAssetIdRequestHandler.java @@ -45,7 +45,8 @@ public GetAllAssetAdministrationShellsByAssetIdResponse process(GetAllAssetAdmin .assetIds(parseSpecificAssetIds(request.getAssetIds())) .build(), request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal()) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortReferenceRequestHandler.java index b5a6a57ee..e6f9f46f9 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortReferenceRequestHandler.java @@ -50,7 +50,8 @@ public GetAllAssetAdministrationShellsByIdShortReferenceResponse process(GetAllA .idShort(request.getIdShort()) .build(), request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortRequestHandler.java index ad9d7dbfa..b240524ed 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsByIdShortRequestHandler.java @@ -46,7 +46,8 @@ public GetAllAssetAdministrationShellsByIdShortResponse process(GetAllAssetAdmin .idShort(request.getIdShort()) .build(), request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsReferenceRequestHandler.java index 0204523fd..11ff42d87 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsReferenceRequestHandler.java @@ -48,7 +48,8 @@ public GetAllAssetAdministrationShellsReferenceResponse process(GetAllAssetAdmin Page page = context.getPersistence().findAssetAdministrationShells( AssetAdministrationShellSearchCriteria.NONE, request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsRequestHandler.java index a48260f70..5d383451f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/GetAllAssetAdministrationShellsRequestHandler.java @@ -43,7 +43,8 @@ public GetAllAssetAdministrationShellsResponse process(GetAllAssetAdministration Page page = context.getPersistence().findAssetAdministrationShells( AssetAdministrationShellSearchCriteria.NONE, request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAssetAdministrationShellsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAssetAdministrationShellsRequestHandler.java index a1ef57b49..4f134aee5 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAssetAdministrationShellsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/QueryAssetAdministrationShellsRequestHandler.java @@ -20,10 +20,13 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.QueryAssetAdministrationShellsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.ElementReadEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractRequestHandler; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; + +import java.util.List; import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -40,11 +43,14 @@ public class QueryAssetAdministrationShellsRequestHandler extends AbstractReques @Override public QueryAssetAdministrationShellsResponse process(QueryAssetAdministrationShellsRequest request, RequestExecutionContext context) throws MessageBusException, PersistenceException { - Page page = context.getPersistence().findAssetAdministrationShellsWithQuery( + LogicalExpression queryAndAccessControl = new LogicalExpression(); + queryAndAccessControl.set$and(List.of(request.getQuery().get$condition(), request.getFormula())); + + Page page = context.getPersistence().findAssetAdministrationShells( AssetAdministrationShellSearchCriteria.NONE, request.getOutputModifier(), request.getPagingInfo(), - request.getQuery()); + queryAndAccessControl); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByDataSpecificationReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByDataSpecificationReferenceRequestHandler.java index 0b194bbbc..690f92040 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByDataSpecificationReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByDataSpecificationReferenceRequestHandler.java @@ -47,7 +47,8 @@ public GetAllConceptDescriptionsByDataSpecificationReferenceResponse process(Get .dataSpecification(request.getDataSpecificationReference()) .build(), request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIdShortRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIdShortRequestHandler.java index eaf12bd69..09a3ed5e8 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIdShortRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIdShortRequestHandler.java @@ -46,7 +46,8 @@ public GetAllConceptDescriptionsByIdShortResponse process(GetAllConceptDescripti .idShort(request.getIdShort()) .build(), request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIsCaseOfRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIsCaseOfRequestHandler.java index cd8f99438..3a5d3c2d0 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIsCaseOfRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsByIsCaseOfRequestHandler.java @@ -46,7 +46,8 @@ public GetAllConceptDescriptionsByIsCaseOfResponse process(GetAllConceptDescript .isCaseOf(request.getIsCaseOf()) .build(), request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsRequestHandler.java index f06af1f72..f4cbba782 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetAllConceptDescriptionsRequestHandler.java @@ -42,7 +42,8 @@ public GetAllConceptDescriptionsResponse process(GetAllConceptDescriptionsReques Page page = context.getPersistence().findConceptDescriptions( ConceptDescriptionSearchCriteria.NONE, request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java index 9988deeda..e19ee87ef 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/QueryConceptDescriptionsRequestHandler.java @@ -20,10 +20,13 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.conceptdescription.QueryConceptDescriptionsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.ElementReadEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractRequestHandler; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; import de.fraunhofer.iosb.ilt.faaast.service.util.LambdaExceptionHelper; + +import java.util.List; import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; @@ -40,11 +43,14 @@ public class QueryConceptDescriptionsRequestHandler extends AbstractRequestHandl @Override public QueryConceptDescriptionsResponse process(QueryConceptDescriptionsRequest request, RequestExecutionContext context) throws MessageBusException, PersistenceException { - Page page = context.getPersistence().findConceptDescriptionsWithQuery( + LogicalExpression queryAndAccessControl = new LogicalExpression(); + queryAndAccessControl.set$and(List.of(request.getQuery().get$condition(), request.getFormula())); + + Page page = context.getPersistence().findConceptDescriptions( ConceptDescriptionSearchCriteria.NONE, request.getOutputModifier(), request.getPagingInfo(), - request.getQuery()); + queryAndAccessControl); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsByIdShortRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsByIdShortRequestHandler.java index f91a75ac3..cc00087d9 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsByIdShortRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsByIdShortRequestHandler.java @@ -47,7 +47,8 @@ public GetAllSubmodelsByIdShortResponse process(GetAllSubmodelsByIdShortRequest .idShort(request.getIdShort()) .build(), QueryModifier.DEFAULT, - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); context.getAssetConnectionManager().syncValueProvidersOnRead(null, page, !request.isInternal()); return GetAllSubmodelsByIdShortResponse.builder() .payload(page) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsBySemanticIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsBySemanticIdRequestHandler.java index e8d7d2adc..dbe134a1f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsBySemanticIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsBySemanticIdRequestHandler.java @@ -47,7 +47,8 @@ public GetAllSubmodelsBySemanticIdResponse process(GetAllSubmodelsBySemanticIdRe .semanticId(request.getSemanticId()) .build(), QueryModifier.DEFAULT, - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); context.getAssetConnectionManager().syncValueProvidersOnRead(null, page, !request.isInternal()); return GetAllSubmodelsBySemanticIdResponse.builder() .payload(page) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsReferenceRequestHandler.java index 946a4015f..61054f1dc 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsReferenceRequestHandler.java @@ -51,7 +51,8 @@ public GetAllSubmodelsReferenceResponse process(GetAllSubmodelsReferenceRequest Page page = context.getPersistence().findSubmodels( SubmodelSearchCriteria.NONE, request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { for (Submodel submodel: page.getContent()) { Reference reference = AasUtils.toReference(submodel); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsRequestHandler.java index e483cea35..6535e5d18 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/GetAllSubmodelsRequestHandler.java @@ -44,7 +44,8 @@ public GetAllSubmodelsResponse process(GetAllSubmodelsRequest request, RequestEx Page page = context.getPersistence().findSubmodels( SubmodelSearchCriteria.NONE, request.getOutputModifier(), - request.getPagingInfo()); + request.getPagingInfo(), + request.getFormula()); context.getAssetConnectionManager().syncValueProvidersOnRead(null, page, !request.isInternal()); return GetAllSubmodelsResponse.builder() .payload(page) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java index 368e71607..1d4445558 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/QuerySubmodelsRequestHandler.java @@ -24,9 +24,12 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueMappingException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.access.ElementReadEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.persistence.SubmodelSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractRequestHandler; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; + +import java.util.List; import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.AasUtils; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; @@ -45,11 +48,14 @@ public class QuerySubmodelsRequestHandler extends AbstractRequestHandler page = context.getPersistence().findSubmodelsWithQuery( + LogicalExpression queryAndAccessControl = new LogicalExpression(); + queryAndAccessControl.set$and(List.of(request.getQuery().get$condition(), request.getFormula())); + + Page page = context.getPersistence().findSubmodels( SubmodelSearchCriteria.NONE, request.getOutputModifier(), request.getPagingInfo(), - request.getQuery()); + queryAndAccessControl); if (Objects.nonNull(page.getContent())) { for (Submodel submodel: page.getContent()) { Reference reference = AasUtils.toReference(submodel); diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index bfc23700a..544d2936c 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -14,39 +14,23 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.SharedAttributes.ACL; import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.GET; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; -import de.fraunhofer.iosb.ilt.faaast.service.dataformat.SerializationException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.RequestMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.ApiGateway; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aas.GetAssetAdministrationShellRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.GetAllAssetAdministrationShellsByIdShortRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.GetAllAssetAdministrationShellsRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetAllConceptDescriptionsByIdShortRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetAllConceptDescriptionsRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.GetConceptDescriptionByIdRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.GetSubmodelRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.GetAllSubmodelsByIdShortRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.GetAllSubmodelsBySemanticIdRequest; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.GetAllSubmodelsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedModifierException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; -import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; @@ -58,10 +42,8 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import org.eclipse.digitaltwin.aas4j.v3.model.Key; import org.eclipse.digitaltwin.aas4j.v3.model.Message; import org.eclipse.digitaltwin.aas4j.v3.model.MessageTypeEnum; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.jetty.server.Response; @@ -78,7 +60,6 @@ public class RequestHandlerServlet extends HttpServlet { private final RequestMappingManager requestMappingManager; private final ResponseMappingManager responseMappingManager; private final HttpJsonApiSerializer serializer; - private final ApiGateway apiGateway; public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, ServiceContext serviceContext) { Ensure.requireNonNull(endpoint, "endpoint must be non-null"); @@ -90,7 +71,6 @@ public RequestHandlerServlet(HttpEndpoint endpoint, HttpEndpointConfig config, S this.requestMappingManager = new RequestMappingManager(serviceContext); this.responseMappingManager = new ResponseMappingManager(serviceContext); this.serializer = new HttpJsonApiSerializer(); - this.apiGateway = Objects.nonNull(config.getJwkProvider()) ? new ApiGateway() : null; } @@ -124,13 +104,11 @@ protected void service(HttpServletRequest request, HttpServletResponse response) .collect(Collectors.toMap( x -> x, request::getHeader))) + .accessPermissionRules((List) request.getAttribute(ACL.getName())) .build(); try { - if (Objects.nonNull(apiGateway) && GET == httpRequest.getMethod()) { - modifyRequest(request, httpRequest); - } - executeAndSend(request, response, requestMappingManager.map(httpRequest)); + executeAndSend(httpRequest, response, requestMappingManager.map(httpRequest)); } catch (Exception e) { doThrow(e); @@ -138,65 +116,6 @@ protected void service(HttpServletRequest request, HttpServletResponse response) } - private void modifyRequest(HttpServletRequest servletRequest, HttpRequest request) - throws SerializationException, InvalidRequestException { - var wouldHaveBeen = requestMappingManager.map(request); - List acl = (List) servletRequest.getAttribute(ACL.getName()); - if (acl.isEmpty()) { - throw new UnauthorizedException("unauthorized. TODO decide if 404 or 403"); - } - List formulas = acl.stream().map(AccessPermissionRule::getFormula).toList(); - LogicalExpression formulasOr = new LogicalExpression(); - formulasOr.set$or(formulas); - - if (wouldHaveBeen instanceof GetAllAssetAdministrationShellsRequest) { - request.setPath("query/shells"); - request.setBody(serializer.write(formulasOr)); - } - else if (wouldHaveBeen instanceof GetAllSubmodelsRequest) { - request.setPath("query/submodels"); - request.setBody(serializer.write(formulasOr)); - } - else if (wouldHaveBeen instanceof GetAllConceptDescriptionsRequest) { - request.setPath("query/concept-descriptions"); - request.setBody(serializer.write(formulasOr)); - } - - else if (wouldHaveBeen instanceof GetAssetAdministrationShellRequest getAssetAdministrationShellRequest) { - setQuery(request, "aas", "shells", "id", getAssetAdministrationShellRequest.getId(), formulasOr); - } - else if (wouldHaveBeen instanceof GetAllAssetAdministrationShellsByIdShortRequest getAllAssetAdministrationShellsByIdShortRequest) { - setQuery(request, "aas", "shells", "idShort", getAllAssetAdministrationShellsByIdShortRequest.getIdShort(), formulasOr); - } - - else if (wouldHaveBeen instanceof GetSubmodelRequest getSubmodelRequest) { - setQuery(request, "sm", "submodels", "id", getSubmodelRequest.getSubmodelId(), formulasOr); - } - else if (wouldHaveBeen instanceof GetAllSubmodelsByIdShortRequest getAllSubmodelsByIdShortRequest) { - setQuery(request, "sm", "submodels", "idShort", getAllSubmodelsByIdShortRequest.getIdShort(), formulasOr); - } - else if (wouldHaveBeen instanceof GetAllSubmodelsBySemanticIdRequest getAllSubmodelsBySemanticIdRequest) { - setQuery(request, "sm", "submodels", "semanticId", getSemanticIdString(getAllSubmodelsBySemanticIdRequest.getSemanticId()), formulasOr); - } - - else if (wouldHaveBeen instanceof GetConceptDescriptionByIdRequest getConceptDescriptionByIdRequest) { - setQuery(request, "cd", "concept-descriptions", "id", getConceptDescriptionByIdRequest.getId(), formulasOr); - } - else if (wouldHaveBeen instanceof GetAllConceptDescriptionsByIdShortRequest getAllConceptDescriptionsByIdShortRequest) { - setQuery(request, "cd", "concept-descriptions", "idShort", getAllConceptDescriptionsByIdShortRequest.getIdShort(), formulasOr); - } - } - - - private void setQuery(HttpRequest request, String res, String resource, String pattern, String id, LogicalExpression formula) - throws SerializationException, UnsupportedModifierException { - request.setPath(String.format("query/%s", resource)); - LogicalExpression parentFormula = new LogicalExpression(); - parentFormula.set$and(List.of(formEq(String.format("$%s#%s", res, pattern), id), formula)); - request.setBody(serializer.write(parentFormula)); - } - - private void checkRequestSupportedByProfiles(de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws InvalidRequestException { if (Objects.isNull(config.getProfiles()) || config.getProfiles().isEmpty()) { return; @@ -211,20 +130,17 @@ private void checkRequestSupportedByProfiles(de.fraunhofer.iosb.ilt.faaast.servi } - private void executeAndSend(HttpServletRequest request, HttpServletResponse response, + private void executeAndSend(HttpRequest request, HttpServletResponse response, de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws Exception { if (Objects.isNull(apiRequest)) { throw new InvalidRequestException("empty API request"); } checkRequestSupportedByProfiles(apiRequest); - de.fraunhofer.iosb.ilt.faaast.service.model.api.Response apiResponse = null; - if (Objects.nonNull(apiGateway)) { - apiResponse = handleResponseWithAcl(request, apiRequest); - } - else { - apiResponse = serviceContext.execute(endpoint, apiRequest); - } + checkAccess(request); + + de.fraunhofer.iosb.ilt.faaast.service.model.api.Response apiResponse = serviceContext.execute(endpoint, apiRequest); + if (Objects.isNull(apiResponse)) { throw new ServletException("empty API response"); } @@ -237,6 +153,21 @@ private void executeAndSend(HttpServletRequest request, HttpServletResponse resp } + private void checkAccess(HttpRequest request) + throws ServletException { + List rules = request.getAccessPermissionRules(); + + if (rules == null || request.getMethod() == GET) { + return; + } + + if (rules.stream().noneMatch(rule -> FormulaEvaluator.evaluate(rule.getFormula(), new HashMap<>()))) { + doThrow(new UnauthorizedException( + String.format("User not authorized '%s'", request.getPath()))); + } + } + + private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model.api.Response response) { return Objects.nonNull(response) && response.getStatusCode().isSuccess() @@ -248,35 +179,4 @@ private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model. .noneMatch(x -> Objects.equals(x, MessageTypeEnum.ERROR) || Objects.equals(x, MessageTypeEnum.EXCEPTION)); } - - private de.fraunhofer.iosb.ilt.faaast.service.model.api.Response handleResponseWithAcl(HttpServletRequest request, - de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) - throws ServletException { - List rules = ((List) request.getAttribute(ACL.getName())); - if (rules.stream().noneMatch(rule -> FormulaEvaluator.evaluate(rule.getFormula(), new HashMap<>()))) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getRequestURI()))); - } - - return serviceContext.execute(endpoint, apiRequest); - } - - - private String getSemanticIdString(Reference semanticId) { - return Optional.ofNullable(ReferenceHelper.getRoot(semanticId)) - .map(Key::getValue) - .orElse(null); - } - - - private LogicalExpression formEq(String left, String right) { - LogicalExpression eqFormula = new LogicalExpression(); - Value smId = new Value(); - smId.set$strVal(left); - Value identifier = new Value(); - identifier.set$strVal(right); - - eqFormula.set$eq(List.of(smId, identifier)); - return eqFormula; - } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java index 9b2824fd3..92f25566c 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java @@ -16,6 +16,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpConstants; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import java.util.ArrayList; import java.util.Arrays; @@ -35,6 +36,7 @@ public class HttpRequest extends HttpMessage { private String path; private Map queryParameters; private List pathElements; + private List accessPermissionRules; public static Builder builder() { return new Builder(); @@ -45,6 +47,7 @@ public HttpRequest() { method = HttpMethod.GET; queryParameters = new HashMap<>(); pathElements = new ArrayList<>(); + accessPermissionRules = new ArrayList<>(); } @@ -159,6 +162,26 @@ public void setQueryParameters(Map queryParameters) { } + /** + * Gets this request's applying Access Permission Rules according to AAS Security. + * + * @return List of access permission rules. + */ + public List getAccessPermissionRules() { + return accessPermissionRules; + } + + + /** + * Sets this request's applying Access Permission Rules according to AAS Security. + * + * @param accessPermissionRules the applying access permission rules. + */ + public void setAccessPermissionRules(List accessPermissionRules) { + this.accessPermissionRules = accessPermissionRules; + } + + private String[] splitKeyValue(String x, String regex) { String[] split = x.split(regex); if (split.length == 2) { @@ -213,6 +236,12 @@ public B query(String value) { getBuildingInstance().setQueryParametersFromQueryString(value); return getSelf(); } + + + public B accessPermissionRules(List value) { + getBuildingInstance().setAccessPermissionRules(value); + return getSelf(); + } } public static class Builder extends AbstractBuilder { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java index 923374b78..1960bb8ea 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java @@ -26,6 +26,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import de.fraunhofer.iosb.ilt.faaast.service.util.RegExHelper; @@ -134,7 +136,9 @@ public Request parse(HttpRequest httpRequest) throws InvalidRequestException { Ensure.requireNonNull(httpRequest, "httpRequest must be non-null"); Matcher matcher = Pattern.compile(urlPattern).matcher(httpRequest.getPath()); if (matcher.matches()) { - return doParse(httpRequest, RegExHelper.getGroupValues(urlPattern, httpRequest.getPath())); + Request request = doParse(httpRequest, RegExHelper.getGroupValues(urlPattern, httpRequest.getPath())); + request.setFormula(rulesToFormula(httpRequest.getAccessPermissionRules())); + return request; } throw new IllegalStateException(String.format("request was matched but no suitable parser found (HTTP method: %s, URL pattern: %s", method, urlPattern)); } @@ -295,6 +299,19 @@ protected List parseBodyAsList(HttpRequest httpRequest, Class type) th } + /** + * Transforms a list of resolved access permission rules to a LogicalExpression, using OR to combine them. + * + * @param rules The rules to OR-ify + * @return The LogicalExpression formula + */ + protected LogicalExpression rulesToFormula(List rules) { + LogicalExpression condition = new LogicalExpression(); + condition.set$or(rules.stream().map(AccessPermissionRule::getFormula).toList()); + return condition; + } + + /** * Parses a string to a JSON merge patch. * diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java index 81d1f1314..2f6c8fb84 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java @@ -14,8 +14,9 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; +import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.SharedAttributes.ACL; + import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -23,16 +24,14 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import java.io.IOException; import java.util.List; import java.util.Objects; -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.SharedAttributes.ACL; - /** - * Contains common logic for extracting and storing the AAS ACL in a request. If the ACL is empty as a result of the AclFilter, the request will be denied. + * Contains common logic for extracting and storing the AAS ACL in a request. If the ACL is empty as a result of the + * AclFilter, the request will be denied. */ public abstract class AbstractAclFilter extends JwtAuthorizationFilter implements Filter { diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java deleted file mode 100644 index 612c87eed..000000000 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/ApiGatewayFilterTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.auth.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem.Global.ANONYMOUS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Acl; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AllAccessPermissionRules; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.ObjectItem; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; -import jakarta.servlet.http.HttpServletRequest; -import java.util.List; -import org.junit.Test; - - -public class ApiGatewayFilterTest extends JwtAuthorizationFilterTest { - - private ApiGateway apiGateway; - - private static HttpServletRequest req(String method, String uri) { - HttpServletRequest r = mock(HttpServletRequest.class); - when(r.getMethod()).thenReturn(method); - when(r.getRequestURI()).thenReturn(uri); - return r; - } - - - @Test - public void anonymousAccessDependsOnAclFile() { - apiGateway = new ApiGateway(); - - HttpServletRequest request = req("GET", "/api/v3.0/submodels"); - when(request.getAttribute(ACL.getName())).thenReturn(List.of()); - - assertFalse(apiGateway.isAuthorized(request)); - - AllAccessPermissionRules env = mockEnvironment(); - - when(request.getAttribute(ACL.getName())).thenReturn(env.getRules()); - await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertTrue(apiGateway.isAuthorized(request))); - - when(request.getAttribute(ACL.getName())).thenReturn(List.of()); - await().atMost(5, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> assertFalse(apiGateway.isAuthorized(request))); - } - - - private AllAccessPermissionRules mockEnvironment() { - var env = new AllAccessPermissionRules(); - AccessPermissionRule rule = new AccessPermissionRule(); - Acl acl = new Acl(); - AttributeItem attributeItem = new AttributeItem(); - attributeItem.setGlobal(ANONYMOUS); - acl.setAttributes(List.of(attributeItem)); - acl.setRights(List.of(RightsEnum.READ)); - acl.setAccess(Acl.Access.ALLOW); - rule.setAcl(acl); - ObjectItem object = new ObjectItem(); - object.setRoute("*"); - rule.setObjects(List.of(object)); - LogicalExpression formula = new LogicalExpression(); - formula.set$boolean(true); - rule.setFormula(formula); - env.setRules(List.of(rule)); - return env; - } -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java index 5d785d519..8b9eabd5e 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.model.api; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import java.util.Objects; import org.eclipse.digitaltwin.aas4j.v3.model.builder.ExtendableBuilder; @@ -26,12 +27,33 @@ public abstract class Request { private boolean internal; + private LogicalExpression formula; protected Request() { this.internal = false; } + /** + * Get this requests access control formula. + * + * @return The formula. + */ + public LogicalExpression getFormula() { + return formula; + } + + + /** + * Set this requests access control formula. + * + * @param formula The formula. + */ + public void setFormula(LogicalExpression formula) { + this.formula = formula; + } + + public boolean isInternal() { return internal; } @@ -51,13 +73,14 @@ public boolean equals(Object o) { return false; } Request that = (Request) o; - return Objects.equals(internal, that.internal); + return Objects.equals(internal, that.internal) && + Objects.equals(formula, that.formula); } @Override public int hashCode() { - return Objects.hash(internal); + return Objects.hash(internal, formula); } public abstract static class AbstractBuilder> extends ExtendableBuilder { @@ -72,5 +95,11 @@ public B internal() { getBuildingInstance().setInternal(true); return getSelf(); } + + + public B formula(LogicalExpression value) { + getBuildingInstance().setFormula(value); + return getSelf(); + } } } diff --git a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java index dfdcd87c3..3fac87cac 100644 --- a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java +++ b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java @@ -37,7 +37,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; @@ -180,9 +180,9 @@ public Page findAssetAdministrationShells(AssetAdminis @Override - public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, - Query query) { - return persistence.findAssetAdministrationShellsWithQuery(criteria, modifier, paging, query); + public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + LogicalExpression formula) { + return persistence.findAssetAdministrationShells(criteria, modifier, paging, formula); } @@ -193,8 +193,8 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi @Override - public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { - return persistence.findSubmodelsWithQuery(criteria, modifier, paging, query); + public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { + return persistence.findSubmodels(criteria, modifier, paging, formula); } @@ -211,9 +211,9 @@ public Page findConceptDescriptions(ConceptDescriptionSearch @Override - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { - return persistence.findConceptDescriptionsWithQuery(criteria, modifier, paging, query); + return persistence.findConceptDescriptions(criteria, modifier, paging, formula); } diff --git a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java index c2b58ba85..84835d30c 100644 --- a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java +++ b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java @@ -31,7 +31,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.PersistenceException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.AssetAdministrationShellElementWalker; import de.fraunhofer.iosb.ilt.faaast.service.model.visitor.DefaultAssetAdministrationShellElementVisitor; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; @@ -220,24 +220,13 @@ public void deleteAll() throws PersistenceException { @Override public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { - Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); - Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); - Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); - - Stream result = environment.getAssetAdministrationShells().stream(); - if (criteria.isIdShortSet()) { - result = filterByIdShort(result, criteria.getIdShort()); - } - if (criteria.isAssetIdsSet()) { - result = filterByAssetIds(result, criteria.getAssetIds()); - } - return preparePagedResult(result, modifier, paging); + return findAssetAdministrationShells(criteria, modifier, paging, null); } @Override - public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, - Query query) { + public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + LogicalExpression formula) { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -250,8 +239,8 @@ public Page findAssetAdministrationShellsWithQuery(Ass result = filterByAssetIds(result, criteria.getAssetIds()); } QueryEvaluator evaluator = new QueryEvaluator(); - if (query != null) { - result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); + if (formula != null) { + result = result.filter(aas -> evaluator.matches(formula, aas)); } return preparePagedResult(result, modifier, paging); } @@ -277,7 +266,7 @@ public Page findConceptDescriptions(ConceptDescriptionSearch @Override - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); @@ -293,8 +282,8 @@ public Page findConceptDescriptionsWithQuery(ConceptDescript result = filterByDataSpecification(result, criteria.getDataSpecification()); } QueryEvaluator evaluator = new QueryEvaluator(); - if (query != null) { - result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); + if (formula != null) { + result = result.filter(aas -> evaluator.matches(formula, aas)); } return preparePagedResult(result, modifier, paging); } @@ -363,7 +352,7 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi @Override - public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -375,8 +364,8 @@ public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, Qu result = filterBySemanticId(result, criteria.getSemanticId()); } QueryEvaluator evaluator = new QueryEvaluator(); - if (query != null) { - result = result.filter(aas -> evaluator.matches(query.get$condition(), aas)); + if (formula != null) { + result = result.filter(aas -> evaluator.matches(formula, aas)); } return preparePagedResult(result, modifier, paging); } diff --git a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java index ed70bcdc4..7be984a3e 100644 --- a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java +++ b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java @@ -54,7 +54,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotAContainerElementException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.UnsupportedModifierException; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.persistence.AssetAdministrationShellSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.ConceptDescriptionSearchCriteria; import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; @@ -261,8 +261,8 @@ public Page findAssetAdministrationShells(AssetAdminis @Override - public Page findAssetAdministrationShellsWithQuery(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, - Query query) + public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + LogicalExpression formula) throws PersistenceException { throw new PersistenceException("Query not supported with mongoDB."); } @@ -285,7 +285,7 @@ public Page findConceptDescriptions(ConceptDescriptionSearch @Override - public Page findConceptDescriptionsWithQuery(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) + public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { throw new PersistenceException("Query not supported with mongoDB."); } @@ -306,7 +306,7 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi @Override - public Page findSubmodelsWithQuery(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, Query query) throws PersistenceException { + public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { throw new PersistenceException("Query not supported with mongoDB."); } From 41316f159d7226312dc59f7270daaec86841aca3 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:33:35 +0200 Subject: [PATCH 105/108] feat: add acl as request property --- .../iosb/ilt/faaast/service/Service.java | 4 +- .../service/persistence/Persistence.java | 227 ++++++++------- .../faaast/service/query/QueryEvaluator.java | 34 +-- .../registry/RegistrySynchronization.java | 23 +- ...stractSubmodelInterfaceRequestHandler.java | 3 +- ...DeleteSubmodelReferenceRequestHandler.java | 2 +- .../aas/DeleteThumbnailRequestHandler.java | 2 +- ...etAllSubmodelReferencesRequestHandler.java | 7 +- ...istrationShellReferenceRequestHandler.java | 2 +- ...ssetAdministrationShellRequestHandler.java | 2 +- .../GetAssetInformationRequestHandler.java | 2 +- .../aas/GetThumbnailRequestHandler.java | 2 +- .../PostSubmodelReferenceRequestHandler.java | 2 +- ...ssetAdministrationShellRequestHandler.java | 2 +- .../PutAssetInformationRequestHandler.java | 2 +- .../aas/PutThumbnailRequestHandler.java | 2 +- ...DeleteAllAssetLinksByIdRequestHandler.java | 2 +- .../GetAllAssetLinksByIdRequestHandler.java | 2 +- .../PostAllAssetLinksByIdRequestHandler.java | 2 +- ...AdministrationShellByIdRequestHandler.java | 2 +- ...erateSerializationByIdsRequestHandler.java | 13 +- ...eConceptDescriptionByIdRequestHandler.java | 2 +- ...tConceptDescriptionByIdRequestHandler.java | 2 +- ...tConceptDescriptionByIdRequestHandler.java | 2 +- ...OperationProviderByPathRequestHandler.java | 2 +- ...OperationProviderByPathRequestHandler.java | 2 +- .../proprietary/ResetRequestHandler.java | 9 +- ...AbstractInvokeOperationRequestHandler.java | 9 +- .../DeleteFileByPathRequestHandler.java | 2 +- ...teSubmodelElementByPathRequestHandler.java | 2 +- ...AllSubmodelElementsPathRequestHandler.java | 2 +- ...bmodelElementsReferenceRequestHandler.java | 2 +- .../GetAllSubmodelElementsRequestHandler.java | 2 +- ...llSubmodelElementsValueRequestHandler.java | 2 +- .../submodel/GetFileByPathRequestHandler.java | 2 +- ...GetOperationAsyncResultRequestHandler.java | 5 +- ...GetOperationAsyncStatusRequestHandler.java | 1 + ...lElementByPathReferenceRequestHandler.java | 7 +- ...etSubmodelElementByPathRequestHandler.java | 2 +- .../GetSubmodelReferenceRequestHandler.java | 2 +- .../submodel/GetSubmodelRequestHandler.java | 2 +- .../InvokeOperationAsyncRequestHandler.java | 5 +- .../InvokeOperationSyncRequestHandler.java | 2 +- ...chSubmodelElementByPathRequestHandler.java | 4 +- ...modelElementValueByPathRequestHandler.java | 3 +- .../submodel/PatchSubmodelRequestHandler.java | 2 +- ...stSubmodelElementByPathRequestHandler.java | 2 +- .../submodel/PutFileByPathRequestHandler.java | 2 +- ...utSubmodelElementByPathRequestHandler.java | 2 +- .../submodel/PutSubmodelRequestHandler.java | 6 +- .../DeleteSubmodelByIdRequestHandler.java | 2 +- .../SubmodelTemplateManager.java | 10 +- .../lambda/LambdaAssetConnectionTest.java | 6 +- .../persistence/AbstractPersistenceTest.java | 118 ++++---- .../registry/RegistrySynchronizationTest.java | 11 +- .../handler/RequestHandlerManagerTest.java | 93 +++--- .../SubmodelTemplateProcessorTest.java | 2 +- .../mixins/InvokeOperationRequestMixin.java | 3 + .../endpoint/http/RequestHandlerServlet.java | 48 ++-- .../endpoint/http/model/HttpRequest.java | 28 +- .../request/mapper/AbstractRequestMapper.java | 17 +- .../http/security/FormulaEvaluator.java | 231 --------------- .../security/filter/post/AclFilterFilter.java | 42 --- .../util/ExpressionInjectionHelper.java | 32 ++- .../http/AbstractHttpEndpointTest.java | 4 +- .../http/security/FormulaEvaluatorTest.java | 264 ------------------ .../ilt/faaast/service/model/api/Request.java | 2 + .../persistence/file/PersistenceFile.java | 46 +-- .../persistence/file/PersistenceFileTest.java | 4 +- .../memory/PersistenceInMemory.java | 95 +++---- .../persistence/mongo/PersistenceMongo.java | 58 ++-- .../mongo/PersistenceMongoTest.java | 9 +- .../service/test/AssetConnectionIT.java | 3 +- 73 files changed, 504 insertions(+), 1052 deletions(-) delete mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java delete mode 100644 endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java delete mode 100644 endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java index 96c8b440d..a11fe028e 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/Service.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnection; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionConfig; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; @@ -157,7 +159,7 @@ public Response execute(Endpoint source, Request request) { @Override public TypeInfo getTypeInfo(Reference reference) throws ResourceNotFoundException, PersistenceException { - return TypeExtractor.extractTypeInfo(persistence.getSubmodelElement(reference, QueryModifier.DEFAULT)); + return TypeExtractor.extractTypeInfo(persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity())); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java index f4ba6d1f4..e6c4ba3c1 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/Persistence.java @@ -41,12 +41,11 @@ /** * Interface used for managing AAS-related data, i.e. everything that is part of a - * {@code org.eclipse.digitaltwin.aas4j.v3.model.Environment}. Additionally manages to storing of oepration execution - * states and results. + * {@code org.eclipse.digitaltwin.aas4j.v3.model.Environment}. Additionally manages to storing of + * operation execution states and results. * *

Implement this interface if you wish to create a custom persistence, e.g. backed by a specific database or to - * connect - * to legacy systems. + * connect to legacy systems. * * @param type of the corresponding configuration class */ @@ -67,16 +66,18 @@ public interface Persistence extends Configurable extends Configurable getSubmodelRefs(String aasId, PagingInfo paging) throws ResourceNotFoundException, PersistenceException; + public Page getSubmodelRefs(String aasId, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException; /** @@ -99,13 +101,13 @@ public interface Persistence extends Configurable extends Configurable extends Configurable extends Configurable getSubmodelElements(SubmodelElementIdentifier identifier, QueryModifier modifier, PagingInfo paging) + public default Page getSubmodelElements(SubmodelElementIdentifier identifier, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException, ResourceNotAContainerElementException { return findSubmodelElements( SubmodelElementSearchCriteria.builder() .parent(identifier) .build(), modifier, - paging); + paging, + formula); } @@ -170,20 +176,21 @@ public default Page getSubmodelElements(SubmodelElementIdentifi * {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection}, or * {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList} or * {@code org.eclipse.digitaltwin.aas4j.v3.model.Entity} or - * {@code org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement} - * that are supported by valueOnly serialization. + * {@code org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement} that are supported by valueOnly + * serialization. * * @param identifier the identifier of the SubmodelElement * @param modifier the modifier * @param paging paging information + * @param formula a query or access rule or both * @return a list of all child {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement}s that are supported by * valueOnly serialization of the element identified by path * @throws ResourceNotFoundException if there is no element with the given path - * @throws ResourceNotAContainerElementException if the element identified by the path is not a container element, - * i.e. cannot have any child elements + * @throws ResourceNotAContainerElementException if the element identified by the path is not a container element, i.e. + * cannot have any child elements * @throws PersistenceException if there was an error with the storage. */ - public default Page getSubmodelElementsValueOnly(SubmodelElementIdentifier identifier, QueryModifier modifier, PagingInfo paging) + public default Page getSubmodelElementsValueOnly(SubmodelElementIdentifier identifier, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException, ResourceNotAContainerElementException { return findSubmodelElements( SubmodelElementSearchCriteria.builder() @@ -191,7 +198,8 @@ public default Page getSubmodelElementsValueOnly(SubmodelElemen .valueOnly() .build(), modifier, - paging); + paging, + formula); } @@ -201,22 +209,24 @@ public default Page getSubmodelElementsValueOnly(SubmodelElemen * {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection}, or * {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList} or * {@code org.eclipse.digitaltwin.aas4j.v3.model.Entity} or - * {@code org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement} - * that are supported by valueOnly serialization. + * {@code org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement} that are supported by valueOnly + * serialization. * * @param reference the reference to the parent/container element * @param modifier the modifier * @param paging paging information + * @param formula a query or access rule or both * @return a list of all child {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement}s that are supported by - * valueOnly serialization of the element identified by reference + * valueOnly serialization of the element identified by + * reference * @throws ResourceNotFoundException if there is no element with the given reference - * @throws ResourceNotAContainerElementException if the element identified by the reference is not a container - * element, i.e. cannot have any child elements + * @throws ResourceNotAContainerElementException if the element identified by the reference is not a container element, + * i.e. cannot have any child elements * @throws PersistenceException if there was an error with the storage. */ - public default Page getSubmodelElementsValueOnly(Reference reference, QueryModifier modifier, PagingInfo paging) + public default Page getSubmodelElementsValueOnly(Reference reference, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException, ResourceNotAContainerElementException { - return getSubmodelElementsValueOnly(SubmodelElementIdentifier.fromReference(reference), modifier, paging); + return getSubmodelElementsValueOnly(SubmodelElementIdentifier.fromReference(reference), modifier, paging, formula); } @@ -228,9 +238,9 @@ public default Page getSubmodelElementsValueOnly(Reference refe */ public default Environment getEnvironment() throws PersistenceException { return new DefaultEnvironment.Builder() - .assetAdministrationShells(getAllAssetAdministrationShells(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent()) - .submodels(getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent()) - .conceptDescriptions(getAllConceptDescriptions(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent()) + .assetAdministrationShells(getAllAssetAdministrationShells(QueryModifier.MAXIMAL, PagingInfo.ALL, identity()).getContent()) + .submodels(getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL, identity()).getContent()) + .conceptDescriptions(getAllConceptDescriptions(QueryModifier.MAXIMAL, PagingInfo.ALL, identity()).getContent()) .build(); } @@ -246,26 +256,13 @@ public default Environment getEnvironment() throws PersistenceException { public OperationResult getOperationResult(OperationHandle handle) throws ResourceNotFoundException, PersistenceException; - /** - * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s by search criteria. - * - * @param criteria the search criteria - * @param modifier the modifier - * @param paging paging information - * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s - * @throws PersistenceException if there was an error with the storage. - */ - public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) - throws PersistenceException; - - /** * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s by search criteria and query. * * @param criteria the search criteria * @param modifier the modifier * @param paging paging information - * @param formula the formula of the query to be executed + * @param formula a query or access rule or both * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s * @throws PersistenceException if there was an error with the storage. */ @@ -274,25 +271,13 @@ public Page findAssetAdministrationShells(AssetAdminis throws PersistenceException; - /** - * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s by search criteria. - * - * @param criteria the search criteria - * @param modifier the modifier - * @param paging paging information - * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s - * @throws PersistenceException if there was an error with the storage. - */ - public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException; - - /** * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s by search criteria and query. * * @param criteria the search criteria * @param modifier the modifier * @param paging paging information - * @param formula the formula of the query to be executed + * @param formula a query or access rule or both * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s * @throws PersistenceException if there was an error with the storage. */ @@ -305,33 +290,22 @@ public Page findAssetAdministrationShells(AssetAdminis * @param criteria the search criteria * @param modifier the modifier * @param paging paging information + * @param formula a query or access rule or both (only applies to parent submodels) * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement}s * @throws ResourceNotFoundException if the parent does not exist * @throws PersistenceException if there was an error with the storage. */ - public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) + public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException; - /** - * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s by search criteria. - * - * @param criteria the search criteria - * @param modifier the modifier - * @param paging paging information - * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s - * @throws PersistenceException if there was an error with the storage. - */ - public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException; - - /** * Finds {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s by search criteria and query. * * @param criteria the search criteria * @param modifier the modifier * @param paging paging information - * @param formula the formula of the query to be executed + * @param formula a query or access rule or both * @return the found {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s * @throws PersistenceException if there was an error with the storage. */ @@ -342,8 +316,7 @@ public Page findConceptDescriptions(ConceptDescriptionSearch /** * Save an {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}. * - * @param assetAdministrationShell the {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell} to - * insert + * @param assetAdministrationShell the {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell} to insert * @throws PersistenceException if there was an error with the storage. */ public void save(AssetAdministrationShell assetAdministrationShell) throws PersistenceException; @@ -454,8 +427,7 @@ public void insert(SubmodelElementIdentifier parentIdentifier, SubmodelElement s /** * Deletes an {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}. * - * @param assetAdministrationShell the {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell} to - * delete + * @param assetAdministrationShell the {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell} to delete * @throws ResourceNotFoundException if the resource does not exist * @throws PersistenceException if there was an error with the storage. */ @@ -540,18 +512,18 @@ public default void update(Reference reference, SubmodelElement submodelElement) * @param identifier the identifier of the SubmodelElement * @param modifier the modifier * @param type the concrete subtype of {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} + * @param formula a query or access rule or both * @return the {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} identified by the given path - * @throws ResourceNotFoundException if there is no - * {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} - * with the given path + * @throws ResourceNotFoundException if there is no {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} with + * the given path * @throws IllegalArgumentException if type is null * @throws ClassCastException if casting fails * @throws PersistenceException if there was an error with the storage. */ - public default T getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier, Class type) + public default T getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier, Class type, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException { Ensure.requireNonNull(type, "type must be non-null"); - return type.cast(getSubmodelElement(identifier, modifier)); + return type.cast(getSubmodelElement(identifier, modifier, formula)); } @@ -560,18 +532,19 @@ public default T getSubmodelElement(SubmodelElementI * * @param reference the reference * @param modifier the modifier + * @param formula a query or access rule or both * @return the {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} identified by the given path - * @throws ResourceNotFoundException if there is no - * {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} - * with the given path + * @throws ResourceNotFoundException if there is no {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} with + * the given path * @throws PersistenceException if there was an error with the storage. */ - public default SubmodelElement getSubmodelElement(Reference reference, QueryModifier modifier) throws ResourceNotFoundException, PersistenceException { + public default SubmodelElement getSubmodelElement(Reference reference, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException, + PersistenceException { String submodelId = ReferenceHelper.findFirstKeyType(reference, KeyTypes.SUBMODEL); if (Objects.isNull(submodelId)) { throw new ResourceNotFoundException(reference); } - return getSubmodelElement(SubmodelElementIdentifier.fromReference(reference), modifier); + return getSubmodelElement(SubmodelElementIdentifier.fromReference(reference), modifier, formula); } @@ -582,19 +555,19 @@ public default SubmodelElement getSubmodelElement(Reference reference, QueryModi * @param reference the reference * @param modifier the modifier * @param type the concrete subtype of {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} + * @param formula a query or access rule or both * @return the {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} identified by the given path - * @throws ResourceNotFoundException if there is no - * {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} - * with the given reference + * @throws ResourceNotFoundException if there is no {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement} with + * the given reference * @throws PersistenceException if there was an error with the storage. */ - public default T getSubmodelElement(Reference reference, QueryModifier modifier, Class type) + public default T getSubmodelElement(Reference reference, QueryModifier modifier, Class type, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException { String submodelId = ReferenceHelper.findFirstKeyType(reference, KeyTypes.SUBMODEL); if (Objects.isNull(submodelId)) { throw new ResourceNotFoundException(reference); } - return getSubmodelElement(SubmodelElementIdentifier.fromReference(reference), modifier, type); + return getSubmodelElement(SubmodelElementIdentifier.fromReference(reference), modifier, type, formula); } @@ -609,16 +582,17 @@ public default T getSubmodelElement(Reference refere * @param reference the reference to the parent/container element * @param modifier the modifier * @param paging paging information - * @return a list of all child {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement}s of the element - * identified by reference + * @param formula a query or access rule or both + * @return a list of all child {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement}s of the element identified + * by reference * @throws ResourceNotFoundException if there is no element with the given reference - * @throws ResourceNotAContainerElementException if the element identified by the reference is not a container - * element, i.e. cannot have any child elements + * @throws ResourceNotAContainerElementException if the element identified by the reference is not a container element, + * i.e. cannot have any child elements * @throws PersistenceException if there was an error with the storage. */ - public default Page getSubmodelElements(Reference reference, QueryModifier modifier, PagingInfo paging) + public default Page getSubmodelElements(Reference reference, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException, ResourceNotAContainerElementException { - return getSubmodelElements(SubmodelElementIdentifier.fromReference(reference), modifier, paging); + return getSubmodelElements(SubmodelElementIdentifier.fromReference(reference), modifier, paging, formula); } @@ -627,11 +601,13 @@ public default Page getSubmodelElements(Reference reference, Qu * * @param modifier the modifier * @param paging paging information + * @param formula a query or access rule or both * @return all {@code org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell}s * @throws PersistenceException if there was an error with the storage. */ - public default Page getAllAssetAdministrationShells(QueryModifier modifier, PagingInfo paging) throws PersistenceException { - return findAssetAdministrationShells(AssetAdministrationShellSearchCriteria.NONE, modifier, paging); + public default Page getAllAssetAdministrationShells(QueryModifier modifier, PagingInfo paging, LogicalExpression formula) + throws PersistenceException { + return findAssetAdministrationShells(AssetAdministrationShellSearchCriteria.NONE, modifier, paging, formula); } @@ -640,11 +616,12 @@ public default Page getAllAssetAdministrationShells(Qu * * @param modifier the modifier * @param paging paging information + * @param formula a query or access rule or both * @return all {@code org.eclipse.digitaltwin.aas4j.v3.model.Submodel}s * @throws PersistenceException if there was an error with the storage. */ - public default Page getAllSubmodels(QueryModifier modifier, PagingInfo paging) throws PersistenceException { - return findSubmodels(SubmodelSearchCriteria.NONE, modifier, paging); + public default Page getAllSubmodels(QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { + return findSubmodels(SubmodelSearchCriteria.NONE, modifier, paging, formula); } @@ -653,11 +630,12 @@ public default Page getAllSubmodels(QueryModifier modifier, PagingInfo * * @param modifier the modifier * @param paging paging information + * @param formula a query or access rule or both * @return all {@code org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription}s * @throws PersistenceException if there was an error with the storage. */ - public default Page getAllConceptDescriptions(QueryModifier modifier, PagingInfo paging) throws PersistenceException { - return findConceptDescriptions(ConceptDescriptionSearchCriteria.NONE, modifier, paging); + public default Page getAllConceptDescriptions(QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { + return findConceptDescriptions(ConceptDescriptionSearchCriteria.NONE, modifier, paging, formula); } @@ -666,12 +644,13 @@ public default Page getAllConceptDescriptions(QueryModifier * * @param modifier the modifier * @param paging paging information + * @param formula a query or access rule or both * @return all {@code org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement}s * @throws PersistenceException if there was an error with the storage. */ - public default Page getAllSubmodelElements(QueryModifier modifier, PagingInfo paging) throws PersistenceException { + public default Page getAllSubmodelElements(QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { try { - return findSubmodelElements(SubmodelElementSearchCriteria.NONE, modifier, paging); + return findSubmodelElements(SubmodelElementSearchCriteria.NONE, modifier, paging, formula); } catch (ResourceNotFoundException e) { throw new PersistenceException("unexpected persistence exception", e); @@ -687,7 +666,7 @@ public default Page getAllSubmodelElements(QueryModifier modifi */ public default boolean assetAdministrationShellExists(String id) { try { - return Objects.nonNull(getAssetAdministrationShell(id, QueryModifier.MINIMAL)); + return Objects.nonNull(getAssetAdministrationShell(id, QueryModifier.MINIMAL, identity())); } catch (ResourceNotFoundException | PersistenceException e) { return false; @@ -703,7 +682,7 @@ public default boolean assetAdministrationShellExists(String id) { */ public default boolean conceptDescriptionExists(String id) { try { - return Objects.nonNull(getConceptDescription(id, QueryModifier.DEFAULT)); + return Objects.nonNull(getConceptDescription(id, QueryModifier.DEFAULT, identity())); } catch (ResourceNotFoundException | PersistenceException e) { return false; @@ -719,7 +698,7 @@ public default boolean conceptDescriptionExists(String id) { */ public default boolean submodelExists(String id) { try { - return Objects.nonNull(getSubmodel(id, QueryModifier.DEFAULT)); + return Objects.nonNull(getSubmodel(id, QueryModifier.DEFAULT, identity())); } catch (ResourceNotFoundException | PersistenceException e) { return false; @@ -735,7 +714,7 @@ public default boolean submodelExists(String id) { */ public default boolean submodelElementExists(Reference reference) { try { - return Objects.nonNull(getSubmodelElement(reference, QueryModifier.DEFAULT)); + return Objects.nonNull(getSubmodelElement(reference, QueryModifier.DEFAULT, identity())); } catch (ResourceNotFoundException | PersistenceException e) { return false; @@ -751,10 +730,22 @@ public default boolean submodelElementExists(Reference reference) { */ public default boolean submodelElementExists(SubmodelElementIdentifier identifier) { try { - return Objects.nonNull(getSubmodelElement(identifier, QueryModifier.DEFAULT)); + return Objects.nonNull(getSubmodelElement(identifier, QueryModifier.DEFAULT, identity())); } catch (ResourceNotFoundException | PersistenceException e) { return false; } } + + + /** + * Get an identity ("True") formula. + * + * @return "True" as a formula + */ + public static LogicalExpression identity() { + LogicalExpression identity = new LogicalExpression(); + identity.set$boolean(true); + return identity; + } } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java index 008f8f618..74d6bc503 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/query/QueryEvaluator.java @@ -110,7 +110,7 @@ private enum StringValueKind { new StringValueKindCheck(StringValueKind.STR_CAST, v -> v.get$strCast() != null)); /** - * Used to decide whether to filter out the Identifiable. + * Used to decide whether to filter out an Identifiable. * * @param expr logical expression (tree) * @param identifiable AAS | Submodel | ConceptDescription @@ -122,14 +122,19 @@ public boolean matches(LogicalExpression expr, Identifiable identifiable) { return false; } - Boolean directBoolean = evaluateBooleanLiteral(expr); + Boolean directBoolean = expr.get$boolean(); if (directBoolean != null) { return directBoolean; } - Boolean logical = evaluateLogicalExpression(expr, identifiable); - if (logical != null) { - return logical; + if (expr.get$and() != null && !expr.get$and().isEmpty()) { + return expr.get$and().stream().allMatch(e -> matches(e, identifiable)); + } + if (expr.get$or() != null && !expr.get$or().isEmpty()) { + return expr.get$or().stream().anyMatch(e -> matches(e, identifiable)); + } + if (expr.get$not() != null) { + return !matches(expr.get$not(), identifiable); } if (hasMatchExpression(expr)) { @@ -144,25 +149,6 @@ public boolean matches(LogicalExpression expr, Identifiable identifiable) { } - private Boolean evaluateBooleanLiteral(LogicalExpression expr) { - return expr.get$boolean(); - } - - - private Boolean evaluateLogicalExpression(LogicalExpression expr, Identifiable identifiable) { - if (expr.get$and() != null && !expr.get$and().isEmpty()) { - return expr.get$and().stream().allMatch(e -> matches(e, identifiable)); - } - if (expr.get$or() != null && !expr.get$or().isEmpty()) { - return expr.get$or().stream().anyMatch(e -> matches(e, identifiable)); - } - if (expr.get$not() != null) { - return !matches(expr.get$not(), identifiable); - } - return null; - } - - private boolean hasMatchExpression(LogicalExpression expr) { return expr.get$match() != null && !expr.get$match().isEmpty(); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronization.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronization.java index 6d2cf7ec0..48f27e216 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronization.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronization.java @@ -18,6 +18,7 @@ import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.DELETE; import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.POST; import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.PUT; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; @@ -241,7 +242,7 @@ private void registerAllAass() throws PersistenceException { if (coreConfig.getAasRegistries().isEmpty()) { return; } - getPageSafe(persistence.getAllAssetAdministrationShells(QueryModifier.MINIMAL, PagingInfo.ALL)) + getPageSafe(persistence.getAllAssetAdministrationShells(QueryModifier.MINIMAL, PagingInfo.ALL, identity())) .getContent() .forEach(this::registerAas); } @@ -254,7 +255,7 @@ private void registerAas(AssetAdministrationShell aas) { private void registerAas(String id) { try { - registerAas(persistence.getAssetAdministrationShell(id, QueryModifier.MINIMAL)); + registerAas(persistence.getAssetAdministrationShell(id, QueryModifier.MINIMAL, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn(String.format( @@ -271,7 +272,7 @@ private void unregisterAllAass() throws PersistenceException { if (coreConfig.getAasRegistries().isEmpty()) { return; } - getPageSafe(persistence.getAllAssetAdministrationShells(QueryModifier.MINIMAL, PagingInfo.ALL)) + getPageSafe(persistence.getAllAssetAdministrationShells(QueryModifier.MINIMAL, PagingInfo.ALL, identity())) .getContent() .forEach(this::unregisterAas); } @@ -284,7 +285,7 @@ private void unregisterAas(AssetAdministrationShell aas) { private void unregisterAas(String id) { try { - unregisterAas(persistence.getAssetAdministrationShell(id, QueryModifier.MINIMAL)); + unregisterAas(persistence.getAssetAdministrationShell(id, QueryModifier.MINIMAL, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn(String.format( @@ -304,7 +305,7 @@ private void updateAas(AssetAdministrationShell aas) { private void updateAas(String id) { try { - updateAas(persistence.getAssetAdministrationShell(id, QueryModifier.MINIMAL)); + updateAas(persistence.getAssetAdministrationShell(id, QueryModifier.MINIMAL, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn(String.format( @@ -321,7 +322,7 @@ private void registerAllSubmodels() throws PersistenceException { if (coreConfig.getSubmodelRegistries().isEmpty()) { return; } - getPageSafe(persistence.getAllSubmodels(QueryModifier.MINIMAL, PagingInfo.ALL)) + getPageSafe(persistence.getAllSubmodels(QueryModifier.MINIMAL, PagingInfo.ALL, identity())) .getContent() .forEach(this::registerSubmodel); } @@ -334,7 +335,7 @@ private void registerSubmodel(Submodel submodel) { private void registerSubmodel(String id) { try { - registerSubmodel(persistence.getSubmodel(id, QueryModifier.MINIMAL)); + registerSubmodel(persistence.getSubmodel(id, QueryModifier.MINIMAL, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn(String.format( @@ -351,7 +352,7 @@ private void unregisterAllSubmodels() throws PersistenceException { if (coreConfig.getSubmodelRegistries().isEmpty()) { return; } - getPageSafe(persistence.getAllSubmodels(QueryModifier.MINIMAL, PagingInfo.ALL)) + getPageSafe(persistence.getAllSubmodels(QueryModifier.MINIMAL, PagingInfo.ALL, identity())) .getContent() .forEach(this::unregisterSubmodel); } @@ -364,7 +365,7 @@ private void unregisterSubmodel(Submodel submodel) { private void unregisterSubmodel(String id) { try { - unregisterSubmodel(persistence.getSubmodel(id, QueryModifier.MINIMAL)); + unregisterSubmodel(persistence.getSubmodel(id, QueryModifier.MINIMAL, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn(String.format( @@ -384,7 +385,7 @@ private void updateSubmodel(Submodel submodel) { private void updateSubmodel(String id) { try { - updateSubmodel(persistence.getSubmodel(id, QueryModifier.MINIMAL)); + updateSubmodel(persistence.getSubmodel(id, QueryModifier.MINIMAL, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn(String.format( @@ -439,7 +440,7 @@ private AssetAdministrationShellDescriptor asDescriptor(AssetAdministrationShell .submodelDescriptors(aas.getSubmodels().stream() .map(x -> ReferenceHelper.findFirstKeyType(x, KeyTypes.SUBMODEL)) .filter(persistence::submodelExists) - .map(LambdaExceptionHelper.wrapFunction(x -> persistence.getSubmodel(x, QueryModifier.MINIMAL))) + .map(LambdaExceptionHelper.wrapFunction(x -> persistence.getSubmodel(x, QueryModifier.MINIMAL, identity()))) .map(this::asDescriptor) .toList()) .endpoints(endpoints.stream() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractSubmodelInterfaceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractSubmodelInterfaceRequestHandler.java index a5353ddd7..b11b992ab 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractSubmodelInterfaceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/AbstractSubmodelInterfaceRequestHandler.java @@ -60,7 +60,8 @@ protected void validateSubmodelWithinAAS(T request, RequestExecutionContext cont request.getAasId(), new OutputModifier.Builder() .level(Level.CORE) - .build()); + .build(), + request.getFormula()); if (aas.getSubmodels().stream().noneMatch(x -> ReferenceHelper.equals(x, submodelRef))) { throw new ResourceNotFoundException(String.format( "AAS does not contain requested submodel (aasId: %s, submodelId: %s)", diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteSubmodelReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteSubmodelReferenceRequestHandler.java index e5a54a7c0..b8ca201a5 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteSubmodelReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteSubmodelReferenceRequestHandler.java @@ -41,7 +41,7 @@ public class DeleteSubmodelReferenceRequestHandler extends AbstractRequestHandle public DeleteSubmodelReferenceResponse process(DeleteSubmodelReferenceRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, PersistenceException { DeleteSubmodelReferenceResponse response = new DeleteSubmodelReferenceResponse(); - AssetAdministrationShell aas = context.getPersistence().getAssetAdministrationShell(request.getId(), QueryModifier.DEFAULT); + AssetAdministrationShell aas = context.getPersistence().getAssetAdministrationShell(request.getId(), QueryModifier.DEFAULT, request.getFormula()); Reference submodelRefToDelete = aas.getSubmodels().stream() .filter(x -> ReferenceHelper.equals(request.getSubmodelRef(), x, false)) .findFirst() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteThumbnailRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteThumbnailRequestHandler.java index 96deb9374..d0c0fb0da 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteThumbnailRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/DeleteThumbnailRequestHandler.java @@ -38,7 +38,7 @@ public class DeleteThumbnailRequestHandler extends AbstractRequestHandler { @Override public GetAllSubmodelReferencesResponse process(GetAllSubmodelReferencesRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, PersistenceException { - Page page = context.getPersistence().getSubmodelRefs(request.getId(), request.getPagingInfo()); + Page page = context.getPersistence().getSubmodelRefs(request.getId(), request.getPagingInfo(), request.getFormula()); if (!request.isInternal()) { - AssetAdministrationShell aas = context.getPersistence().getAssetAdministrationShell(request.getId(), OutputModifier.MINIMAL); + AssetAdministrationShell aas = context.getPersistence().getAssetAdministrationShell(request.getId(), OutputModifier.MINIMAL, request.getFormula()); context.getMessageBus().publish(ElementReadEventMessage.builder() .element(aas) .value(aas) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellReferenceRequestHandler.java index fca05f4d2..0d5caa0d3 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellReferenceRequestHandler.java @@ -40,7 +40,7 @@ public class GetAssetAdministrationShellReferenceRequestHandler @Override public GetAssetAdministrationShellReferenceResponse process(GetAssetAdministrationShellReferenceRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, PersistenceException { - AssetAdministrationShell shell = context.getPersistence().getAssetAdministrationShell(request.getId(), request.getOutputModifier()); + AssetAdministrationShell shell = context.getPersistence().getAssetAdministrationShell(request.getId(), request.getOutputModifier(), request.getFormula()); Reference reference = ReferenceBuilder.forAas(shell); if (!request.isInternal()) { context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellRequestHandler.java index adc7ed6b1..2d1956324 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetAdministrationShellRequestHandler.java @@ -37,7 +37,7 @@ public class GetAssetAdministrationShellRequestHandler extends AbstractRequestHa @Override public GetAssetAdministrationShellResponse process(GetAssetAdministrationShellRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, PersistenceException { - AssetAdministrationShell shell = context.getPersistence().getAssetAdministrationShell(request.getId(), request.getOutputModifier()); + AssetAdministrationShell shell = context.getPersistence().getAssetAdministrationShell(request.getId(), request.getOutputModifier(), request.getFormula()); if (!request.isInternal()) { context.getMessageBus().publish(ElementReadEventMessage.builder() .element(shell) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetInformationRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetInformationRequestHandler.java index e8636c50d..cfad48e43 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetInformationRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aas/GetAssetInformationRequestHandler.java @@ -37,7 +37,7 @@ public class GetAssetInformationRequestHandler extends AbstractRequestHandler result = new ArrayList<>(aas.getAssetInformation().getSpecificAssetIds()); if (Objects.nonNull(aas.getAssetInformation().getGlobalAssetId())) { result.add(new DefaultSpecificAssetId.Builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/PostAllAssetLinksByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/PostAllAssetLinksByIdRequestHandler.java index 990626f8f..30ac2409f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/PostAllAssetLinksByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasbasicdiscovery/PostAllAssetLinksByIdRequestHandler.java @@ -43,7 +43,7 @@ public class PostAllAssetLinksByIdRequestHandler extends AbstractRequestHandler< @Override public PostAllAssetLinksByIdResponse process(PostAllAssetLinksByIdRequest request, RequestExecutionContext context) throws ResourceNotFoundException, PersistenceException { - AssetAdministrationShell aas = context.getPersistence().getAssetAdministrationShell(request.getId(), QueryModifier.DEFAULT); + AssetAdministrationShell aas = context.getPersistence().getAssetAdministrationShell(request.getId(), QueryModifier.DEFAULT, request.getFormula()); List globalKeys = request.getAssetLinks().stream() .filter(x -> FaaastConstants.KEY_GLOBAL_ASSET_ID.equals(x.getName())) .collect(Collectors.toList()); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/DeleteAssetAdministrationShellByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/DeleteAssetAdministrationShellByIdRequestHandler.java index 950ca8292..1dfc141e1 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/DeleteAssetAdministrationShellByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasrepository/DeleteAssetAdministrationShellByIdRequestHandler.java @@ -41,7 +41,7 @@ public class DeleteAssetAdministrationShellByIdRequestHandler public DeleteAssetAdministrationShellByIdResponse process(DeleteAssetAdministrationShellByIdRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, PersistenceException { DeleteAssetAdministrationShellByIdResponse response = new DeleteAssetAdministrationShellByIdResponse(); - AssetAdministrationShell shell = context.getPersistence().getAssetAdministrationShell(request.getId(), QueryModifier.DEFAULT); + AssetAdministrationShell shell = context.getPersistence().getAssetAdministrationShell(request.getId(), QueryModifier.DEFAULT, request.getFormula()); context.getPersistence().deleteAssetAdministrationShell(request.getId()); response.setStatusCode(StatusCode.SUCCESS_NO_CONTENT); if (!request.isInternal()) { diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasserialization/GenerateSerializationByIdsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasserialization/GenerateSerializationByIdsRequestHandler.java index e5ec781d2..6ea557710 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasserialization/GenerateSerializationByIdsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/aasserialization/GenerateSerializationByIdsRequestHandler.java @@ -63,20 +63,20 @@ public GenerateSerializationByIdsResponse process(GenerateSerializationByIdsRequ DefaultEnvironment environment; if (request.getAasIds().isEmpty() && request.getSubmodelIds().isEmpty()) { environment = new DefaultEnvironment.Builder() - .assetAdministrationShells(context.getPersistence().getAllAssetAdministrationShells(OutputModifier.DEFAULT, PagingInfo.ALL).getContent()) - .submodels(context.getPersistence().getAllSubmodels(OutputModifier.DEFAULT, PagingInfo.ALL).getContent()) - .conceptDescriptions(context.getPersistence().getAllConceptDescriptions(OutputModifier.DEFAULT, PagingInfo.ALL).getContent()) + .assetAdministrationShells(context.getPersistence().getAllAssetAdministrationShells(OutputModifier.DEFAULT, PagingInfo.ALL, request.getFormula()).getContent()) + .submodels(context.getPersistence().getAllSubmodels(OutputModifier.DEFAULT, PagingInfo.ALL, request.getFormula()).getContent()) + .conceptDescriptions(context.getPersistence().getAllConceptDescriptions(OutputModifier.DEFAULT, PagingInfo.ALL, request.getFormula()).getContent()) .build(); } else { environment = new DefaultEnvironment.Builder() .assetAdministrationShells(request.getAasIds().stream() - .map(LambdaExceptionHelper.rethrowFunction(x -> context.getPersistence().getAssetAdministrationShell(x, OUTPUT_MODIFIER), + .map(LambdaExceptionHelper.rethrowFunction(x -> context.getPersistence().getAssetAdministrationShell(x, OUTPUT_MODIFIER, request.getFormula()), ResourceNotFoundException.class, PersistenceException.class)) .collect(Collectors.toList())) .submodels(request.getSubmodelIds().stream() - .map(LambdaExceptionHelper.rethrowFunction(x -> context.getPersistence().getSubmodel(x, OUTPUT_MODIFIER), + .map(LambdaExceptionHelper.rethrowFunction(x -> context.getPersistence().getSubmodel(x, OUTPUT_MODIFIER, request.getFormula()), ResourceNotFoundException.class, PersistenceException.class)) .collect(Collectors.toList())) @@ -84,7 +84,8 @@ public GenerateSerializationByIdsResponse process(GenerateSerializationByIdsRequ ? context.getPersistence().findConceptDescriptions( ConceptDescriptionSearchCriteria.NONE, OUTPUT_MODIFIER, - PagingInfo.ALL) + PagingInfo.ALL, + request.getFormula()) .getContent() : List.of()) .build(); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/DeleteConceptDescriptionByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/DeleteConceptDescriptionByIdRequestHandler.java index 4593571c9..efaebb2a6 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/DeleteConceptDescriptionByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/DeleteConceptDescriptionByIdRequestHandler.java @@ -40,7 +40,7 @@ public class DeleteConceptDescriptionByIdRequestHandler extends AbstractRequestH public DeleteConceptDescriptionByIdResponse process(DeleteConceptDescriptionByIdRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, PersistenceException { DeleteConceptDescriptionByIdResponse response = new DeleteConceptDescriptionByIdResponse(); - ConceptDescription conceptDescription = context.getPersistence().getConceptDescription(request.getId(), QueryModifier.DEFAULT); + ConceptDescription conceptDescription = context.getPersistence().getConceptDescription(request.getId(), QueryModifier.DEFAULT, request.getFormula()); context.getPersistence().deleteConceptDescription(request.getId()); response.setStatusCode(StatusCode.SUCCESS_NO_CONTENT); if (!request.isInternal()) { diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetConceptDescriptionByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetConceptDescriptionByIdRequestHandler.java index 1eaac34fe..16163a065 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetConceptDescriptionByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/GetConceptDescriptionByIdRequestHandler.java @@ -38,7 +38,7 @@ public class GetConceptDescriptionByIdRequestHandler extends AbstractRequestHand @Override public GetConceptDescriptionByIdResponse process(GetConceptDescriptionByIdRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, PersistenceException { - ConceptDescription conceptDescription = context.getPersistence().getConceptDescription(request.getId(), request.getOutputModifier()); + ConceptDescription conceptDescription = context.getPersistence().getConceptDescription(request.getId(), request.getOutputModifier(), request.getFormula()); if (!request.isInternal() && Objects.nonNull(conceptDescription)) { context.getMessageBus().publish(ElementReadEventMessage.builder() .element(conceptDescription) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/PutConceptDescriptionByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/PutConceptDescriptionByIdRequestHandler.java index 4711b9b90..dba506336 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/PutConceptDescriptionByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/conceptdescription/PutConceptDescriptionByIdRequestHandler.java @@ -41,7 +41,7 @@ public class PutConceptDescriptionByIdRequestHandler extends AbstractRequestHand public PutConceptDescriptionByIdResponse process(PutConceptDescriptionByIdRequest request, RequestExecutionContext context) throws ResourceNotFoundException, MessageBusException, ValidationException, PersistenceException { ModelValidator.validate(request.getConceptDescription(), context.getCoreConfig().getValidationOnUpdate()); - context.getPersistence().getConceptDescription(request.getConceptDescription().getId(), QueryModifier.DEFAULT); + context.getPersistence().getConceptDescription(request.getConceptDescription().getId(), QueryModifier.DEFAULT, request.getFormula()); context.getPersistence().save(request.getConceptDescription()); if (!request.isInternal()) { context.getMessageBus().publish(ElementUpdateEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/DeleteOperationProviderByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/DeleteOperationProviderByPathRequestHandler.java index 7d43d2943..5252627dc 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/DeleteOperationProviderByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/DeleteOperationProviderByPathRequestHandler.java @@ -52,7 +52,7 @@ protected DeleteOperationProviderByPathResponse doProcess(DeleteOperationProvide .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); + context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class, request.getFormula()); if (!context.getAssetConnectionManager().hasOperationProvider(reference)) { throw new ResourceNotFoundException(String.format( "no operation provider available for reference '%s'", diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/PostOperationProviderByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/PostOperationProviderByPathRequestHandler.java index ede0f021f..a8ba11907 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/PostOperationProviderByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/PostOperationProviderByPathRequestHandler.java @@ -49,7 +49,7 @@ protected PostOperationProviderByPathResponse doProcess(PostOperationProviderByP .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); + context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class, request.getFormula()); if (context.getAssetConnectionManager().hasOperationProvider(reference)) { throw new ResourceAlreadyExistsException(String.format( "operation provider already defined for reference '%s'", diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/ResetRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/ResetRequestHandler.java index a4a2e52b8..bff0c8b1e 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/ResetRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/proprietary/ResetRequestHandler.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.request.handler.proprietary; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode; import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.QueryModifier; @@ -42,10 +44,11 @@ public class ResetRequestHandler extends AbstractRequestHandler { try { context.getMessageBus().publish(ElementDeleteEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java index a41d1e424..8fd2c25d7 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/AbstractInvokeOperationRequestHandler.java @@ -25,6 +25,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueMappingException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.EventMessage; import de.fraunhofer.iosb.ilt.faaast.service.model.value.mapper.ElementValueMapper; +import de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractSubmodelInterfaceRequestHandler; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; import de.fraunhofer.iosb.ilt.faaast.service.util.DeepCopyHelper; @@ -70,7 +71,7 @@ public U doProcess(T request, RequestExecutionContext context) throws ResourceNo "error executing operation - no operation provider defined for reference '%s'", ReferenceHelper.toString(reference))); } - Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); + Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class, request.getFormula()); request.setInputArguments(validateAndPrepare( operation.getInputVariables(), request.getInputArguments(), @@ -199,7 +200,8 @@ private static Optional findValueProvidedViaReferenceQualifier(Submod private static T loadValueFromReference(T argument, Reference reference, RequestExecutionContext context) throws InvalidRequestException { try { - SubmodelElement referencedElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.MAXIMAL); + // TODO identity allowed here? + SubmodelElement referencedElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.MAXIMAL, Persistence.identity()); T result = DeepCopyHelper.deepCopy(argument); ElementValueMapper.setValue(result, ElementValueMapper.toValue(referencedElement)); return result; @@ -216,7 +218,8 @@ private static T loadValueFromReference(T argument, private static void writeValueToReference(SubmodelElement argument, Reference reference, RequestExecutionContext context) throws InvalidRequestException { try { - SubmodelElement referencedElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.MAXIMAL); + // TODO identity allowed here? + SubmodelElement referencedElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.MAXIMAL, Persistence.identity()); ElementValueMapper.setValue(referencedElement, ElementValueMapper.toValue(argument)); context.getPersistence().update(reference, referencedElement); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteFileByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteFileByPathRequestHandler.java index 91feeadce..c94a2b34c 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteFileByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteFileByPathRequestHandler.java @@ -48,7 +48,7 @@ public DeleteFileByPathResponse doProcess(DeleteFileByPathRequest request, Reque .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - File file = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier(), File.class); + File file = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier(), File.class, request.getFormula()); context.getFileStorage().delete(file.getValue()); File oldFile = file; file.setValue(""); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteSubmodelElementByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteSubmodelElementByPathRequestHandler.java index 1f0c534f7..1b4b43aac 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteSubmodelElementByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/DeleteSubmodelElementByPathRequestHandler.java @@ -48,7 +48,7 @@ protected DeleteSubmodelElementByPathResponse doProcess(DeleteSubmodelElementByP .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - SubmodelElement submodelElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT); + SubmodelElement submodelElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT, request.getFormula()); context.getPersistence().deleteSubmodelElement(SubmodelElementIdentifier.fromReference(reference)); response.setStatusCode(StatusCode.SUCCESS_NO_CONTENT); if (!request.isInternal()) { diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsPathRequestHandler.java index 997750f81..1229e9ffc 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsPathRequestHandler.java @@ -103,7 +103,7 @@ private static String nextCursor(PagingInfo paging, int resultCount) { public GetAllSubmodelElementsPathResponse doProcess(GetAllSubmodelElementsPathRequest request, RequestExecutionContext context) throws AssetConnectionException, ValueMappingException, ResourceNotFoundException, MessageBusException, ResourceNotAContainerElementException, PersistenceException { Reference reference = ReferenceBuilder.forSubmodel(request.getSubmodelId()); - Page submodelElements = context.getPersistence().getSubmodelElements(reference, request.getOutputModifier(), PagingInfo.ALL); + Page submodelElements = context.getPersistence().getSubmodelElements(reference, request.getOutputModifier(), PagingInfo.ALL, request.getFormula()); Page page; page = preparePagedResult(submodelElements.getContent().stream() .flatMap(x -> ReferenceCollector.collect(x).keySet().stream() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsReferenceRequestHandler.java index ee0b0b4db..53c2aec38 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsReferenceRequestHandler.java @@ -49,7 +49,7 @@ public class GetAllSubmodelElementsReferenceRequestHandler public GetAllSubmodelElementsReferenceResponse doProcess(GetAllSubmodelElementsReferenceRequest request, RequestExecutionContext context) throws AssetConnectionException, ValueMappingException, ResourceNotFoundException, MessageBusException, ResourceNotAContainerElementException, PersistenceException { Reference reference = ReferenceBuilder.forSubmodel(request.getSubmodelId()); - Page page = context.getPersistence().getSubmodelElements(reference, request.getOutputModifier(), request.getPagingInfo()); + Page page = context.getPersistence().getSubmodelElements(reference, request.getOutputModifier(), request.getPagingInfo(), request.getFormula()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( x -> context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsRequestHandler.java index fef30ad7d..a62eb232d 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsRequestHandler.java @@ -47,7 +47,7 @@ public class GetAllSubmodelElementsRequestHandler extends AbstractSubmodelInterf public GetAllSubmodelElementsResponse doProcess(GetAllSubmodelElementsRequest request, RequestExecutionContext context) throws AssetConnectionException, ValueMappingException, ResourceNotFoundException, MessageBusException, ResourceNotAContainerElementException, PersistenceException { Reference reference = ReferenceBuilder.forSubmodel(request.getSubmodelId()); - Page page = context.getPersistence().getSubmodelElements(reference, request.getOutputModifier(), request.getPagingInfo()); + Page page = context.getPersistence().getSubmodelElements(reference, request.getOutputModifier(), request.getPagingInfo(), request.getFormula()); context.getAssetConnectionManager().syncValueProvidersOnRead(reference, page, !request.isInternal()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsValueRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsValueRequestHandler.java index 9c871416d..68edd8608 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsValueRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetAllSubmodelElementsValueRequestHandler.java @@ -47,7 +47,7 @@ public class GetAllSubmodelElementsValueRequestHandler extends AbstractSubmodelI public GetAllSubmodelElementsValueResponse doProcess(GetAllSubmodelElementsValueRequest request, RequestExecutionContext context) throws AssetConnectionException, ValueMappingException, ResourceNotFoundException, MessageBusException, ResourceNotAContainerElementException, PersistenceException { Reference reference = ReferenceBuilder.forSubmodel(request.getSubmodelId()); - Page page = context.getPersistence().getSubmodelElementsValueOnly(reference, request.getOutputModifier(), request.getPagingInfo()); + Page page = context.getPersistence().getSubmodelElementsValueOnly(reference, request.getOutputModifier(), request.getPagingInfo(), request.getFormula()); context.getAssetConnectionManager().syncValueProvidersOnRead(reference, page, !request.isInternal()); if (!request.isInternal() && Objects.nonNull(page.getContent())) { page.getContent().forEach(LambdaExceptionHelper.rethrowConsumer( diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetFileByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetFileByPathRequestHandler.java index c272c2e37..9354eef5a 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetFileByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetFileByPathRequestHandler.java @@ -44,7 +44,7 @@ public GetFileByPathResponse doProcess(GetFileByPathRequest request, RequestExec .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - File file = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier(), File.class); + File file = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier(), File.class, request.getFormula()); if (!request.isInternal()) { context.getMessageBus().publish(ValueReadEventMessage.builder() .element(reference) diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncResultRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncResultRequestHandler.java index 303a3a46b..9f4451739 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncResultRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncResultRequestHandler.java @@ -20,7 +20,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.AbstractRequestHandler; import de.fraunhofer.iosb.ilt.faaast.service.request.handler.RequestExecutionContext; -import org.eclipse.digitaltwin.aas4j.v3.model.OperationResult; /** @@ -34,9 +33,9 @@ public class GetOperationAsyncResultRequestHandler extends AbstractRequestHandle @Override public GetOperationAsyncResultResponse process(GetOperationAsyncResultRequest request, RequestExecutionContext context) throws ResourceNotFoundException, PersistenceException { - OperationResult operationResult = context.getPersistence().getOperationResult(request.getHandle()); + // TODO need to be able to enforce access control here. The submodel of the operation could be checked with formula. return GetOperationAsyncResultResponse.builder() - .payload(operationResult) + .payload(context.getPersistence().getOperationResult(request.getHandle())) .success() .build(); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncStatusRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncStatusRequestHandler.java index b784b9e02..8f20bab71 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncStatusRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetOperationAsyncStatusRequestHandler.java @@ -30,6 +30,7 @@ public class GetOperationAsyncStatusRequestHandler extends AbstractRequestHandle @Override public GetOperationAsyncStatusResponse process(GetOperationAsyncStatusRequest request, RequestExecutionContext context) throws ResourceNotFoundException, PersistenceException { + // TODO need to be able to enforce access control here. The submodel of the operation could be checked with formula. return GetOperationAsyncStatusResponse.builder() .payload(context.getPersistence().getOperationResult(request.getHandle())) .success() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathReferenceRequestHandler.java index 88687d1ac..ac1c5e148 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathReferenceRequestHandler.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.request.handler.submodel; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.model.IdShortPath; @@ -47,7 +49,7 @@ public class GetSubmodelElementByPathReferenceRequestHandler public GetSubmodelElementByPathReferenceResponse doProcess(GetSubmodelElementByPathReferenceRequest request, RequestExecutionContext context) throws ResourceNotFoundException, ValueMappingException, AssetConnectionException, MessageBusException, ResourceNotAContainerElementException, PersistenceException { Reference reference = resolveReferenceWithTypes(request.getSubmodelId(), request.getPath(), context); - SubmodelElement submodelElement = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier()); + SubmodelElement submodelElement = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier(), request.getFormula()); if (!request.isInternal()) { context.getMessageBus().publish(ElementReadEventMessage.builder() .element(reference) @@ -72,7 +74,8 @@ private Reference resolveReferenceWithTypes(String submodelId, String idShortPat .submodelId(submodelId) .idShortPath(subPath) .build(), - QueryModifier.MINIMAL); + QueryModifier.MINIMAL, + identity()); if (pathElement.startsWith("[") && pathElement.endsWith("]")) { builder.element(pathElement.substring(1, pathElement.length() - 1), submodelElement.getClass()); } diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathRequestHandler.java index e18ff7a39..7ea82aae6 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelElementByPathRequestHandler.java @@ -47,7 +47,7 @@ public GetSubmodelElementByPathResponse doProcess(GetSubmodelElementByPathReques .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - SubmodelElement submodelElement = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier()); + SubmodelElement submodelElement = context.getPersistence().getSubmodelElement(reference, request.getOutputModifier(), request.getFormula()); context.getAssetConnectionManager().syncValueProvidersOnRead(reference, submodelElement, !request.isInternal()); if (!request.isInternal()) { context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelReferenceRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelReferenceRequestHandler.java index bec06cdd5..62e09b6f4 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelReferenceRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelReferenceRequestHandler.java @@ -42,7 +42,7 @@ public class GetSubmodelReferenceRequestHandler extends AbstractSubmodelInterfac @Override public GetSubmodelReferenceResponse doProcess(GetSubmodelReferenceRequest request, RequestExecutionContext context) throws ResourceNotFoundException, AssetConnectionException, ValueMappingException, MessageBusException, ResourceNotAContainerElementException, PersistenceException { - Submodel submodel = context.getPersistence().getSubmodel(request.getSubmodelId(), request.getOutputModifier()); + Submodel submodel = context.getPersistence().getSubmodel(request.getSubmodelId(), request.getOutputModifier(), request.getFormula()); Reference reference = ReferenceBuilder.forSubmodel(submodel); if (!request.isInternal()) { context.getMessageBus().publish(ElementReadEventMessage.builder() diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelRequestHandler.java index 2ce6542b7..15e96f409 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/GetSubmodelRequestHandler.java @@ -41,7 +41,7 @@ public class GetSubmodelRequestHandler extends AbstractSubmodelInterfaceRequestH @Override public GetSubmodelResponse doProcess(GetSubmodelRequest request, RequestExecutionContext context) throws ResourceNotFoundException, AssetConnectionException, ValueMappingException, MessageBusException, ResourceNotAContainerElementException, PersistenceException { - Submodel submodel = context.getPersistence().getSubmodel(request.getSubmodelId(), request.getOutputModifier()); + Submodel submodel = context.getPersistence().getSubmodel(request.getSubmodelId(), request.getOutputModifier(), request.getFormula()); Reference reference = AasUtils.toReference(submodel); context.getAssetConnectionManager().syncValueProvidersOnRead(null, submodel, !request.isInternal()); if (!request.isInternal()) { diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java index 8946b2465..bcea4c345 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationAsyncRequestHandler.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.request.handler.submodel; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Message; import de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode; @@ -137,7 +139,8 @@ private void handleOperationResult(Reference reference, OperationResult operationResult, RequestExecutionContext context) { try { - Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); + // TODO identity() allowed here? + Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class, identity()); if (operationResult.getSuccess()) { operationResult.setOutputArguments( validateAndPrepare( diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java index 6a2027df1..1906357f9 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/InvokeOperationSyncRequestHandler.java @@ -64,7 +64,7 @@ public InvokeOperationSyncResponse doProcess(InvokeOperationSyncRequest request, .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class); + Operation operation = context.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Operation.class, request.getFormula()); if (result.getPayload().getSuccess()) { result.getPayload().setOutputArguments( validateAndPrepare( diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java index 1abd2b23c..ed932216f 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementByPathRequestHandler.java @@ -48,14 +48,14 @@ public class PatchSubmodelElementByPathRequestHandler extends AbstractSubmodelIn public PatchSubmodelElementByPathResponse doProcess(PatchSubmodelElementByPathRequest request, RequestExecutionContext context) throws ResourceNotFoundException, ValueMappingException, AssetConnectionException, MessageBusException, ValidationException, ResourceNotAContainerElementException, InvalidRequestException, PersistenceException { - Submodel current = context.getPersistence().getSubmodel(request.getSubmodelId(), QueryModifier.DEFAULT); + Submodel current = context.getPersistence().getSubmodel(request.getSubmodelId(), QueryModifier.DEFAULT, request.getFormula()); Submodel updated = applyMergePatch(request.getChanges(), current, Submodel.class); context.getPersistence().save(updated); Reference reference = new ReferenceBuilder() .submodel(request.getSubmodelId()) .idShortPath(request.getPath()) .build(); - SubmodelElement oldSubmodelElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT); + SubmodelElement oldSubmodelElement = context.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT, request.getFormula()); SubmodelElement newSubmodelElement = applyMergePatch(request.getChanges(), oldSubmodelElement, SubmodelElement.class); ModelValidator.validate(newSubmodelElement, context.getCoreConfig().getValidationOnUpdate()); context.getPersistence().update(reference, newSubmodelElement); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementValueByPathRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementValueByPathRequestHandler.java index a5eebf184..8333576ba 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementValueByPathRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelElementValueByPathRequestHandler.java @@ -55,7 +55,8 @@ public PatchSubmodelElementValueByPathResponse doProcess(PatchSubmodelElementVal reference, new OutputModifier.Builder() .extend(Extent.WITH_BLOB_VALUE) - .build()); + .build(), + request.getFormula()); ElementValue oldValue = ElementValueMapper.toValue(submodelElement); ElementValue newValue = request.getValueParser().parse(request.getRawValue(), oldValue.getClass()); SubmodelElement newSubmodelElement = ElementValueMapper.setValue(DeepCopyHelper.deepCopy(submodelElement), newValue); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelRequestHandler.java index 11382fb7f..ece22398b 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodel/PatchSubmodelRequestHandler.java @@ -44,7 +44,7 @@ public class PatchSubmodelRequestHandler extends AbstractRequestHandler aas.getSubmodels().stream().anyMatch(submodelRef -> ReferenceHelper.equals(submodelRef, submodelRefOld))) .forEach(LambdaExceptionHelper.rethrowConsumer(aas -> { aas.getSubmodels().removeIf(submodelRef -> ReferenceHelper.equals(submodelRef, submodelRefOld)); diff --git a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java index b049a054d..601656523 100644 --- a/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java +++ b/core/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/submodelrepository/DeleteSubmodelByIdRequestHandler.java @@ -42,7 +42,7 @@ public class DeleteSubmodelByIdRequestHandler extends AbstractRequestHandler submodels = serviceContext.getPersistence().getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL).getContent(); + List submodels = serviceContext.getPersistence().getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL, identity()).getContent(); for (var submodel: submodels) { addSubmodel(submodel); } @@ -92,7 +94,7 @@ else if (event.getValue() instanceof SubmodelElement) { // if a SubmodelElement changed, we use updateSubodel SubmodelElementIdentifier submodelElementIdentifier = SubmodelElementIdentifier.fromReference(event.getElement()); try { - updateSubmodel(serviceContext.getPersistence().getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT)); + updateSubmodel(serviceContext.getPersistence().getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn("Failed to read submodel (submodelId: {})", submodelElementIdentifier.getSubmodelId(), e); @@ -114,7 +116,7 @@ else if (event.getValue() instanceof SubmodelElement) { // if a SubmodelElement changed, we use updateSubodel SubmodelElementIdentifier submodelElementIdentifier = SubmodelElementIdentifier.fromReference(event.getElement()); try { - updateSubmodel(serviceContext.getPersistence().getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT)); + updateSubmodel(serviceContext.getPersistence().getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn("Failed to read submodel (submodelId: {})", submodelElementIdentifier.getSubmodelId(), e); @@ -136,7 +138,7 @@ else if (event.getValue() instanceof SubmodelElement) { // if a SubmodelElement changed, we use updateSubodel SubmodelElementIdentifier submodelElementIdentifier = SubmodelElementIdentifier.fromReference(event.getElement()); try { - updateSubmodel(serviceContext.getPersistence().getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT)); + updateSubmodel(serviceContext.getPersistence().getSubmodel(submodelElementIdentifier.getSubmodelId(), QueryModifier.DEFAULT, identity())); } catch (ResourceNotFoundException | PersistenceException e) { LOGGER.warn("Failed to read submodel (submodelId: {})", submodelElementIdentifier.getSubmodelId(), e); diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java index e47ad0671..77675efd9 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/lambda/LambdaAssetConnectionTest.java @@ -103,7 +103,7 @@ public void testValueProvider() throws MessageBusException, EndpointException, R .element(propertyId) .build(); - when(persistence.getSubmodelElement(eq(propertyRef), any())).thenReturn(property); + when(persistence.getSubmodelElement(eq(propertyRef), any(), any())).thenReturn(property); AtomicInteger value = new AtomicInteger(initialValueAsset); service.getAssetConnectionManager().registerLambdaValueProvider( @@ -169,7 +169,7 @@ public void testSubscriptionProvider() .element(propertyId) .build(); - when(persistence.getSubmodelElement(eq(propertyRef), any())).thenReturn(property); + when(persistence.getSubmodelElement(eq(propertyRef), any(), any())).thenReturn(property); List values = List.of(1, 2, 3, 4); Semaphore canUpdate = new Semaphore(1); @@ -255,7 +255,7 @@ public void testOperationProvider() .element(operationId) .build(); - when(persistence.getSubmodelElement(eq(reference), any(), eq(Operation.class))).thenReturn(operation); + when(persistence.getSubmodelElement(eq(reference), any(), eq(Operation.class), any())).thenReturn(operation); service.getAssetConnectionManager().registerLambdaOperationProvider( reference, LambdaOperationProvider.builder() diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/AbstractPersistenceTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/AbstractPersistenceTest.java index 795e20974..5e71fcb0d 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/AbstractPersistenceTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/AbstractPersistenceTest.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.persistence; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; import de.fraunhofer.iosb.ilt.faaast.service.exception.ConfigurationException; @@ -129,7 +131,7 @@ public void withInitialModelFile() throws ResourceNotFoundException, Configurati AssetAdministrationShell expected = environment.getAssetAdministrationShells().stream() .filter(x -> x.getId().equals(aasId)) .findFirst().get(); - AssetAdministrationShell actual = persistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT); + AssetAdministrationShell actual = persistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); persistence.stop(); } @@ -144,7 +146,7 @@ public void withInitialModelAndModelFile() throws ResourceNotFoundException, Con AssetAdministrationShell expected = environment.getAssetAdministrationShells().stream() .filter(x -> x.getId().equals(aasId)) .findFirst().get(); - AssetAdministrationShell actual = tempPersistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT); + AssetAdministrationShell actual = tempPersistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); tempPersistence.stop(); } @@ -168,7 +170,7 @@ public void getSubmodelElement() throws ResourceNotFoundException, PersistenceEx .submodelId(submodelId) .idShortPath(path) .build(), - QueryModifier.DEFAULT); + QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -202,7 +204,7 @@ public void getSubmodelElementWithSimilarIDShort() throws ResourceNotFoundExcept .submodelId(submodelId) .idShortPath(path) .build(), - QueryModifier.DEFAULT); + QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -231,7 +233,7 @@ public void getSubmodelElementWithBlob() throws ResourceNotFoundException, Persi .submodelId(submodelId) .idShortPath(path) .build(), - queryModifier); + queryModifier, identity()); Assert.assertEquals(expected, actual); } @@ -253,7 +255,7 @@ public void getSubmodelElementWithOutBlob() throws ResourceNotFoundException, Pe .submodelId(submodelId) .idShortPath(IdShortPath.fromReference(reference)) .build(), - QueryModifier.DEFAULT); + QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -264,7 +266,7 @@ public void getIdentifiableAAS() throws ResourceNotFoundException, PersistenceEx AssetAdministrationShell expected = environment.getAssetAdministrationShells().stream() .filter(x -> x.getId().equals(id)) .findFirst().get(); - AssetAdministrationShell actual = persistence.getAssetAdministrationShell(id, QueryModifier.DEFAULT); + AssetAdministrationShell actual = persistence.getAssetAdministrationShell(id, QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -276,7 +278,7 @@ public void getIdentifiableSubmodel() throws ResourceNotFoundException, Persiste Submodel expected = environment.getSubmodels().stream() .filter(x -> x.getId().equals(id)) .findFirst().get(); - Submodel actual = persistence.getSubmodel(id, QueryModifier.DEFAULT); + Submodel actual = persistence.getSubmodel(id, QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -287,7 +289,7 @@ public void getIdentifiableConceptDescription() throws ResourceNotFoundException ConceptDescription expected = environment.getConceptDescriptions().stream() .filter(x -> x.getId().equals(id)) .findFirst().get(); - ConceptDescription actual = persistence.getConceptDescription(id, QueryModifier.DEFAULT); + ConceptDescription actual = persistence.getConceptDescription(id, QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -303,7 +305,8 @@ public void getShellsNull() throws PersistenceException { .assetIds(List.of(GlobalAssetIdentification.builder().build())) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, + identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -313,7 +316,7 @@ public void getShellsNull() throws PersistenceException { public void getShellsAll() throws PersistenceException { List expected = environment.getAssetAdministrationShells(); List actual = persistence - .getAllAssetAdministrationShells(QueryModifier.DEFAULT, PagingInfo.ALL) + .getAllAssetAdministrationShells(QueryModifier.DEFAULT, PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -330,7 +333,8 @@ public void getShellsWithIdShort() throws PersistenceException { .idShort(aasIdShort) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, + identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -348,7 +352,8 @@ public void getShellsWithGlobalAssetIdentification() throws PersistenceException .assetIds(List.of(globalAssetIdentification)) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, + identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -359,7 +364,7 @@ public void getSubmodelsAll() throws PersistenceException { List expected = environment.getSubmodels(); ExtendHelper.withoutBlobValue(expected); List actual = persistence - .getAllSubmodels(QueryModifier.DEFAULT, PagingInfo.ALL) + .getAllSubmodels(QueryModifier.DEFAULT, PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -376,7 +381,8 @@ public void getSubmodelsWithIdShort() throws PersistenceException { .idShort(submodelIdShort) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, + identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -400,7 +406,8 @@ public void getSubmodelsWithsemanticId() throws PersistenceException { .semanticId(semanticId) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, + identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -421,7 +428,8 @@ public void getSubmodelElements() throws ResourceNotFoundException, PersistenceE .idShortPath(path) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, + identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -445,7 +453,7 @@ public void getSubmodelElementsWithsemanticId() throws ResourceNotFoundException .semanticId(semanticId) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -458,7 +466,7 @@ public void getSubmodelElementsFromSubmodelElementCollection() throws ResourceNo Reference reference = ReferenceBuilder.forSubmodel(submodelId, submodelElementId); Collection expected = EnvironmentHelper.resolve(reference, environment, SubmodelElementCollection.class).getValue(); List actual = persistence - .getSubmodelElements(reference, QueryModifier.DEFAULT, PagingInfo.ALL) + .getSubmodelElements(reference, QueryModifier.DEFAULT, PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -471,7 +479,7 @@ public void getSubmodelElementsFromSubmodelElementList() throws ResourceNotFound Reference reference = ReferenceBuilder.forSubmodel(submodelId, submodelElementId); List expected = EnvironmentHelper.resolve(reference, environment, SubmodelElementList.class).getValue(); List actual = persistence - .getSubmodelElements(reference, QueryModifier.DEFAULT, PagingInfo.ALL) + .getSubmodelElements(reference, QueryModifier.DEFAULT, PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -481,7 +489,7 @@ public void getSubmodelElementsFromSubmodelElementList() throws ResourceNotFound public void getConceptDescriptionsAll() throws PersistenceException { List expected = environment.getConceptDescriptions(); List actual = persistence - .getAllConceptDescriptions(QueryModifier.DEFAULT, PagingInfo.ALL) + .getAllConceptDescriptions(QueryModifier.DEFAULT, PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -495,7 +503,7 @@ public void getConceptDescriptionsWithIdShort() throws PersistenceException { .idShort(idShort) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, identity()) .getContent(); List expected = environment.getConceptDescriptions().stream() .filter(x -> x.getIdShort().equalsIgnoreCase(idShort)) @@ -522,7 +530,7 @@ public void getConceptDescriptionsWithIsCaseOf() throws PersistenceException { .isCaseOf(isCaseOf) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -546,7 +554,7 @@ public void getConceptDescriptionsWithDataSpecification() throws PersistenceExce .dataSpecification(dataSpecification) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -571,7 +579,7 @@ public void getConceptDescriptionsWithCombination() throws PersistenceException .isCaseOf(isCaseOf) .build(), QueryModifier.DEFAULT, - PagingInfo.ALL) + PagingInfo.ALL, identity()) .getContent(); Assert.assertEquals(expected, actual); } @@ -592,7 +600,7 @@ public void putSubmodelElementNewInSubmodel() throws ResourceNotFoundException, .idShort(idShort) .build()) .build(), - QueryModifier.DEFAULT); + QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -612,8 +620,8 @@ public void putSubmodelElementChangeInSubmodel() throws ResourceNotFoundExceptio .element(submodelElement) .build(); persistence.update(reference, expected); - SubmodelElement actualSubmodelElement = persistence.getSubmodelElement(reference, QueryModifier.DEFAULT); - Submodel actualSubmodel = persistence.getSubmodel(submodel.getId(), QueryModifier.DEFAULT); + SubmodelElement actualSubmodelElement = persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity()); + Submodel actualSubmodel = persistence.getSubmodel(submodel.getId(), QueryModifier.DEFAULT, identity()); int idxActual = actualSubmodel.getSubmodelElements().indexOf(expected); Assert.assertEquals(expected, actualSubmodelElement); Assert.assertEquals(idxExpected, idxActual); @@ -646,7 +654,8 @@ public void putSubmodelElementNewInSubmodelElementCollection() .with(parent) .element(idShort) .build(), - QueryModifier.DEFAULT); + QueryModifier.DEFAULT, + identity()); Assert.assertEquals(expected, actual); } @@ -667,7 +676,8 @@ public void putSubmodelElementNewInSubmodelElementList() reference, new QueryModifier.Builder() .extend(Extent.WITH_BLOB_VALUE) - .build()); + .build(), + identity()); Assert.assertEquals(expected, actual); } @@ -694,7 +704,7 @@ public void putSubmodelElementChangeInSubmodelElementCollection() throws Resourc .element(submodelElement) .build(); persistence.update(reference, expected); - SubmodelElement actual = persistence.getSubmodelElement(reference, new QueryModifier.Builder().extend(Extent.WITH_BLOB_VALUE).build()); + SubmodelElement actual = persistence.getSubmodelElement(reference, new QueryModifier.Builder().extend(Extent.WITH_BLOB_VALUE).build(), identity()); Assert.assertEquals(expected, actual); } @@ -707,7 +717,7 @@ public void putSubmodelElementChangeInSubmodelElementList() throws ResourceNotFo String category = "NewCategory"; expected.setCategory(category); persistence.update(reference, expected); - SubmodelElement actual = persistence.getSubmodelElement(reference, new QueryModifier.Builder().extend(Extent.WITH_BLOB_VALUE).build()); + SubmodelElement actual = persistence.getSubmodelElement(reference, new QueryModifier.Builder().extend(Extent.WITH_BLOB_VALUE).build(), identity()); Assert.assertEquals(expected, actual); } @@ -715,27 +725,27 @@ public void putSubmodelElementChangeInSubmodelElementList() throws ResourceNotFo @Test public void removeSubmodel() throws ResourceNotFoundException, PersistenceException { String submodelId = "https://acplt.org/Test_Submodel_Mandatory"; - Assert.assertNotNull(persistence.getSubmodel(submodelId, QueryModifier.DEFAULT)); + Assert.assertNotNull(persistence.getSubmodel(submodelId, QueryModifier.DEFAULT, identity())); persistence.deleteSubmodel(submodelId); - Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodel(submodelId, QueryModifier.DEFAULT)); + Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodel(submodelId, QueryModifier.DEFAULT, identity())); } @Test public void removeAAS() throws ResourceNotFoundException, PersistenceException { String aasId = "https://acplt.org/Test_AssetAdministrationShell_Mandatory"; - Assert.assertNotNull(persistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT)); + Assert.assertNotNull(persistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT, identity())); persistence.deleteAssetAdministrationShell(aasId); - Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT)); + Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getAssetAdministrationShell(aasId, QueryModifier.DEFAULT, identity())); } @Test public void removeAll() throws PersistenceException { persistence.deleteAll(); - Assert.assertTrue(persistence.getAllAssetAdministrationShells(QueryModifier.MINIMAL, PagingInfo.ALL).getContent().isEmpty()); - Assert.assertTrue(persistence.getAllSubmodels(QueryModifier.MINIMAL, PagingInfo.ALL).getContent().isEmpty()); - Assert.assertTrue(persistence.getAllConceptDescriptions(QueryModifier.MINIMAL, PagingInfo.ALL).getContent().isEmpty()); + Assert.assertTrue(persistence.getAllAssetAdministrationShells(QueryModifier.MINIMAL, PagingInfo.ALL, identity()).getContent().isEmpty()); + Assert.assertTrue(persistence.getAllSubmodels(QueryModifier.MINIMAL, PagingInfo.ALL, identity()).getContent().isEmpty()); + Assert.assertTrue(persistence.getAllConceptDescriptions(QueryModifier.MINIMAL, PagingInfo.ALL, identity()).getContent().isEmpty()); } @@ -747,9 +757,9 @@ public void removeByReference() throws ResourceNotFoundException, PersistenceExc .submodel(submodelId) .element(submodelElementCollectionId) .build(); - Assert.assertNotNull(persistence.getSubmodelElement(reference, QueryModifier.DEFAULT)); + Assert.assertNotNull(persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity())); persistence.deleteSubmodelElement(reference); - Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodelElement(reference, QueryModifier.DEFAULT)); + Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity())); } @@ -763,9 +773,9 @@ public void removeByReferencePropertyInSubmodelElementCollection() throws Resour .element(submodelElementCollectionId) .element(submodelElementId) .build(); - Assert.assertNotNull(persistence.getSubmodelElement(reference, new OutputModifier())); + Assert.assertNotNull(persistence.getSubmodelElement(reference, new OutputModifier(), identity())); persistence.deleteSubmodelElement(reference); - Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodelElement(reference, QueryModifier.DEFAULT)); + Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity())); } @@ -778,9 +788,9 @@ public void removeByReferencePropertyInSubmodelElementList() throws ResourceNotF .element(submodelElementCollectionId) .index(0) .build(); - SubmodelElement original = persistence.getSubmodelElement(reference, QueryModifier.DEFAULT); + SubmodelElement original = persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity()); persistence.deleteSubmodelElement(reference); - SubmodelElement actual = persistence.getSubmodelElement(reference, QueryModifier.DEFAULT); + SubmodelElement actual = persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity()); Assert.assertNotEquals(original, actual); } @@ -793,9 +803,9 @@ public void removeByReferenceProperty() throws ResourceNotFoundException, Persis .submodel(submodelId) .element(submodelElementId) .build(); - Assert.assertNotNull(persistence.getSubmodelElement(reference, new OutputModifier())); + Assert.assertNotNull(persistence.getSubmodelElement(reference, new OutputModifier(), identity())); persistence.deleteSubmodelElement(reference); - Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodelElement(reference, QueryModifier.DEFAULT)); + Assert.assertThrows(ResourceNotFoundException.class, () -> persistence.getSubmodelElement(reference, QueryModifier.DEFAULT, identity())); } @@ -807,7 +817,7 @@ public void putIdentifiableNew() throws ResourceNotFoundException, PersistenceEx expected.setIdShort(idShort); expected.setId("http://newIdentifier.org"); persistence.save(expected); - Submodel actual = persistence.getSubmodel(expected.getId(), QueryModifier.DEFAULT); + Submodel actual = persistence.getSubmodel(expected.getId(), QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); } @@ -821,9 +831,9 @@ public void putIdentifiableChange() throws ResourceNotFoundException, Persistenc String category = "NewCategory"; expected.setCategory(category); persistence.save(expected); - ConceptDescription actual = persistence.getConceptDescription(expected.getId(), QueryModifier.DEFAULT); + ConceptDescription actual = persistence.getConceptDescription(expected.getId(), QueryModifier.DEFAULT, identity()); int actualIndex = persistence - .getAllConceptDescriptions(QueryModifier.DEFAULT, PagingInfo.ALL) + .getAllConceptDescriptions(QueryModifier.DEFAULT, PagingInfo.ALL, identity()) .getContent() .indexOf(actual); Assert.assertEquals(expected, actual); @@ -851,13 +861,13 @@ public void testQueryModifierExtend() throws ResourceNotFoundException, Persiste .getValue().stream() .filter(z -> z.getIdShort().equalsIgnoreCase(submodelElementId)) .findFirst().get(); - SubmodelElement actual = persistence.getSubmodelElement(reference, queryModifier); + SubmodelElement actual = persistence.getSubmodelElement(reference, queryModifier, identity()); Assert.assertEquals(expected, actual); queryModifier = new QueryModifier.Builder().extend(Extent.WITHOUT_BLOB_VALUE).build(); expected = DeepCopyHelper.deepCopy(expected, SubmodelElement.class); ((Blob) expected).setValue(null); - actual = persistence.getSubmodelElement(reference, queryModifier); + actual = persistence.getSubmodelElement(reference, queryModifier, identity()); Assert.assertEquals(expected, actual); } @@ -868,11 +878,11 @@ public void testQueryModifierLevel() throws ResourceNotFoundException, Persisten String submodelId = "https://acplt.org/Test_Submodel_Mandatory"; Submodel expected = environment.getSubmodels().stream() .filter(x -> x.getId().equals(submodelId)).findFirst().get(); - Submodel actual = persistence.getSubmodel(submodelId, queryModifier); + Submodel actual = persistence.getSubmodel(submodelId, queryModifier, identity()); Assert.assertEquals(expected, actual); queryModifier = new QueryModifier.Builder().level(Level.CORE).build(); - actual = persistence.getSubmodel(submodelId, queryModifier); + actual = persistence.getSubmodel(submodelId, queryModifier, identity()); List submodelElementCollections = actual.getSubmodelElements().stream() .filter(x -> SubmodelElementCollection.class.isAssignableFrom(x.getClass())) .collect(Collectors.toList()); diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronizationTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronizationTest.java index aa82842d6..e672b996d 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronizationTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/registry/RegistrySynchronizationTest.java @@ -23,6 +23,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -342,13 +343,13 @@ private void mockMessageBus() throws MessageBusException { private void mockPersistence() throws ResourceNotFoundException, PersistenceException { persistence = Mockito.mock(Persistence.class); environment = AASFull.createEnvironment(); - when(persistence.getAllAssetAdministrationShells(any(), any())) + when(persistence.getAllAssetAdministrationShells(any(), any(), any())) .thenReturn(Page. builder().result(environment.getAssetAdministrationShells()).build()); - when(persistence.getAllSubmodels(any(), any())) + when(persistence.getAllSubmodels(any(), any(), any())) .thenReturn(Page. builder().result(environment.getSubmodels()).build()); - when(persistence.getSubmodel(any(String.class), any())) + when(persistence.getSubmodel(any(String.class), any(), any())) .thenAnswer((Answer) invocation -> { String id = invocation.getArgument(0); return environment.getSubmodels().stream() @@ -356,7 +357,7 @@ private void mockPersistence() throws ResourceNotFoundException, PersistenceExce .findFirst() .orElseThrow(() -> new ResourceNotFoundException(id, Submodel.class)); }); - when(persistence.getAssetAdministrationShell(any(String.class), any())) + when(persistence.getAssetAdministrationShell(any(String.class), any(), any())) .thenAnswer((Answer) invocation -> { String id = invocation.getArgument(0); return environment.getAssetAdministrationShells().stream() @@ -410,7 +411,7 @@ private List getSubmodelDescriptorsFromAas(AssetAdministrati return aas.getSubmodels().stream() .map(x -> ReferenceHelper.findFirstKeyType(x, KeyTypes.SUBMODEL)) .filter(persistence::submodelExists) - .map(LambdaExceptionHelper.wrapFunction(x -> persistence.getSubmodel(x, QueryModifier.MINIMAL))) + .map(LambdaExceptionHelper.wrapFunction(x -> persistence.getSubmodel(x, QueryModifier.MINIMAL, identity()))) .map(x -> new DefaultSubmodelDescriptor.Builder() .administration(x.getAdministration()) .id(x.getId()) diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java index 8a263ab9a..725c1dae8 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/request/handler/RequestHandlerManagerTest.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.request.handler; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; import static org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd.STRING; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; @@ -288,7 +289,7 @@ public void createRequestHandlerManager() throws ConfigurationException, AssetCo public void testGetAllAssetAdministrationShellRequest() throws Exception { doReturn(Page.of(environment.getAssetAdministrationShells())) .when(persistence) - .findAssetAdministrationShells(any(), any(), any()); + .findAssetAdministrationShells(any(), any(), any(), any()); GetAllAssetAdministrationShellsRequest request = new GetAllAssetAdministrationShellsRequest(); GetAllAssetAdministrationShellsResponse actual = manager.execute(request, context); GetAllAssetAdministrationShellsResponse expected = new GetAllAssetAdministrationShellsResponse.Builder() @@ -318,7 +319,7 @@ public void testGetAllAssetAdministrationShellsByAssetIdRequest() throws Excepti .assetIds(List.of(globalAssetIdentification, specificAssetIdentification)) .build()), any(), - any()); + any(), any()); List assetIds = List.of( new DefaultSpecificAssetId.Builder() @@ -351,6 +352,7 @@ public void testGetAllAssetAdministrationShellsByIdShortRequest() throws Excepti .idShort("Test") .build()), any(), + any(), any()); GetAllAssetAdministrationShellsByIdShortRequest request = new GetAllAssetAdministrationShellsByIdShortRequest.Builder() @@ -399,6 +401,7 @@ public void testDeleteAssetAdministrationShellByIdRequest() throws Exception { .when(persistence) .getAssetAdministrationShell( eq(environment.getAssetAdministrationShells().get(0).getId()), + any(), any()); DeleteAssetAdministrationShellByIdRequest request = DeleteAssetAdministrationShellByIdRequest.builder() @@ -419,6 +422,7 @@ public void testGetAssetAdministrationShellRequest() throws Exception { .when(persistence) .getAssetAdministrationShell( eq(environment.getAssetAdministrationShells().get(0).getId()), + any(), any()); GetAssetAdministrationShellRequest request = new GetAssetAdministrationShellRequest.Builder() @@ -466,6 +470,7 @@ public void testGetAssetInformationRequest() throws Exception { .when(persistence) .getAssetAdministrationShell( eq(environment.getAssetAdministrationShells().get(0).getId()), + any(), any()); GetAssetInformationRequest request = new GetAssetInformationRequest.Builder() @@ -496,7 +501,7 @@ public void testGetThumbnailRequest() throws Exception { .build()) .build()) .when(persistence) - .getAssetAdministrationShell(eq(aasId), any()); + .getAssetAdministrationShell(eq(aasId), any(), any()); doReturn(file.getContent()) .when(fileStorage) .get(file.getPath()); @@ -529,7 +534,7 @@ public void testDeleteThumbnailRequest() throws Exception { .build()) .build()) .when(persistence) - .getAssetAdministrationShell(eq(aasId), any()); + .getAssetAdministrationShell(eq(aasId), any(), any()); doReturn(file.getContent()) .when(fileStorage) .get(file.getPath()); @@ -576,7 +581,7 @@ public void testPutFileRequest() throws Exception { .build(); doReturn(file) .when(persistence) - .getSubmodelElement(any(SubmodelElementIdentifier.class), any()); + .getSubmodelElement(any(SubmodelElementIdentifier.class), any(), any()); PutFileByPathRequest putFileByPathRequest = new PutFileByPathRequest.Builder() .submodelId(environment.getSubmodels().get(0).getId()) .path(file.getIdShort()) @@ -608,7 +613,7 @@ public void testPutFileRequest() throws Exception { public void testPutAssetInformationRequest() throws Exception { doReturn(environment.getAssetAdministrationShells().get(0)) .when(persistence) - .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any()); + .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any(), any()); PutAssetInformationRequest request = new PutAssetInformationRequest.Builder() .id(environment.getAssetAdministrationShells().get(0).getId()) @@ -627,10 +632,10 @@ public void testPutAssetInformationRequest() throws Exception { public void testGetAllSubmodelReferencesRequest() throws Exception { doReturn(Page.of(environment.getAssetAdministrationShells().get(0).getSubmodels())) .when(persistence) - .getSubmodelRefs(eq(environment.getAssetAdministrationShells().get(0).getId()), any()); + .getSubmodelRefs(eq(environment.getAssetAdministrationShells().get(0).getId()), any(), any()); doReturn(environment.getAssetAdministrationShells().get(0)) .when(persistence) - .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any()); + .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any(), any()); GetAllSubmodelReferencesRequest request = new GetAllSubmodelReferencesRequest.Builder() .id(environment.getAssetAdministrationShells().get(0).getId()) @@ -648,7 +653,7 @@ public void testGetAllSubmodelReferencesRequest() throws Exception { public void testPostSubmodelReferenceRequest() throws Exception { doReturn(environment.getAssetAdministrationShells().get(0)) .when(persistence) - .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any()); + .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any(), any()); PostSubmodelReferenceRequest request = new PostSubmodelReferenceRequest.Builder() .id(environment.getAssetAdministrationShells().get(0).getId()) @@ -668,7 +673,7 @@ public void testPostSubmodelReferenceRequest() throws Exception { public void testDeleteSubmodelReferenceRequest() throws Exception { doReturn(environment.getAssetAdministrationShells().get(0)) .when(persistence) - .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any()); + .getAssetAdministrationShell(eq(environment.getAssetAdministrationShells().get(0).getId()), any(), any()); DeleteSubmodelReferenceRequest request = new DeleteSubmodelReferenceRequest.Builder() .id(environment.getAssetAdministrationShells().get(0).getId()) @@ -686,7 +691,7 @@ public void testDeleteSubmodelReferenceRequest() throws Exception { public void testGetAllSubmodelsRequest() throws Exception { doReturn(Page.of(environment.getSubmodels())) .when(persistence) - .findSubmodels(eq(SubmodelSearchCriteria.NONE), any(), any()); + .findSubmodels(eq(SubmodelSearchCriteria.NONE), any(), any(), any()); GetAllSubmodelsRequest request = new GetAllSubmodelsRequest.Builder() .outputModifier(OutputModifier.DEFAULT) @@ -705,7 +710,7 @@ public void testGetAllSubmodelsRequestWithAssetConnection() throws Exception { Page data = Page. of(environment.getSubmodels()); doReturn(data) .when(persistence) - .findSubmodels(eq(SubmodelSearchCriteria.NONE), any(), any()); + .findSubmodels(eq(SubmodelSearchCriteria.NONE), any(), any(), any()); assertReadWithAssetConnection( data, @@ -727,6 +732,7 @@ public void testGetAllSubmodelsBySemanticIdRequest() throws Exception { .semanticId(SUBMODEL_ELEMENT_REF) .build()), any(), + any(), any()); GetAllSubmodelsBySemanticIdRequest request = new GetAllSubmodelsBySemanticIdRequest.Builder() @@ -752,6 +758,7 @@ public void testGetAllSubmodelsBySemanticIdRequestWithAssetConnection() throws E .semanticId(SUBMODEL_ELEMENT_REF) .build()), any(), + any(), any()); assertReadWithAssetConnection( @@ -775,6 +782,7 @@ public void testGetAllSubmodelsByIdShortRequest() throws Exception { .idShort("Test") .build()), any(), + any(), any()); GetAllSubmodelsByIdShortRequest request = new GetAllSubmodelsByIdShortRequest.Builder() @@ -800,6 +808,7 @@ public void testGetAllSubmodelsByIdShortRequestWithAssetConnection() throws Exce .idShort("Test") .build()), any(), + any(), any()); assertReadWithAssetConnection( @@ -884,7 +893,7 @@ public void testPutSubmodelRequest() throws Exception { Submodel submodel = environment.getSubmodels().get(0); doReturn(submodel) .when(persistence) - .getSubmodel(eq(submodel.getId()), any()); + .getSubmodel(eq(submodel.getId()), any(), any()); PutSubmodelRequest request = new PutSubmodelRequest.Builder() .submodelId(submodel.getId()) .submodel(submodel) @@ -903,7 +912,7 @@ public void testPutSubmodelRequestWithAssetConnection() throws Exception { Submodel submodel = environment.getSubmodels().get(0); doReturn(submodel) .when(persistence) - .getSubmodel(eq(submodel.getId()), any()); + .getSubmodel(eq(submodel.getId()), any(), any()); assertWriteWithAssetConnection( submodel, null, @@ -922,7 +931,7 @@ public void testPutSubmodelRequestWithAssetConnection() throws Exception { public void testDeleteSubmodelByIdRequest() throws Exception { doReturn(environment.getSubmodels().get(0)) .when(persistence) - .getSubmodel(eq(environment.getSubmodels().get(0).getId()), any()); + .getSubmodel(eq(environment.getSubmodels().get(0).getId()), any(), any()); DeleteSubmodelByIdRequest request = new DeleteSubmodelByIdRequest.Builder() .submodelId(environment.getSubmodels().get(0).getId()) @@ -940,7 +949,7 @@ public void testDeleteSubmodelByIdRequest() throws Exception { public void testGetSubmodelRequest() throws Exception { doReturn(environment.getSubmodels().get(0)) .when(persistence) - .getSubmodel(eq(environment.getSubmodels().get(0).getId()), any()); + .getSubmodel(eq(environment.getSubmodels().get(0).getId()), any(), any()); GetSubmodelRequest request = new GetSubmodelRequest.Builder() .submodelId(environment.getSubmodels().get(0).getId()) @@ -960,7 +969,7 @@ public void testGetSubmodelRequestWithAssetConnection() throws Exception { Submodel submodel = environment.getSubmodels().get(0); doReturn(submodel) .when(persistence) - .getSubmodel(eq(submodel.getId()), any()); + .getSubmodel(eq(submodel.getId()), any(), any()); assertReadWithAssetConnection( submodel, @@ -979,7 +988,7 @@ public void testGetAllSubmodelElementsRequest() throws Exception { Reference reference = ReferenceBuilder.forSubmodel(environment.getSubmodels().get(0)); doReturn(Page.of(environment.getSubmodels().get(0).getSubmodelElements())) .when(persistence) - .getSubmodelElements(eq(SubmodelElementIdentifier.fromReference(reference)), any(), any()); + .getSubmodelElements(eq(SubmodelElementIdentifier.fromReference(reference)), any(), any(), any()); GetAllSubmodelElementsRequest request = new GetAllSubmodelElementsRequest.Builder() .submodelId(environment.getSubmodels().get(0).getId()) @@ -1005,6 +1014,7 @@ public void testGetAllSubmodelElementsRequestWithAssetConnection() throws Except .submodelId(submodel.getId()) .build()), any(), + any(), any()); assertReadWithAssetConnection( @@ -1091,7 +1101,7 @@ public void testGetSubmodelElementByPathRequest() throws ResourceNotFoundExcepti PropertyValue propertyValue = new PropertyValue.Builder().value(new StringValue("test")).build(); doReturn(cur_submodelElement) .when(persistence) - .getSubmodelElement((SubmodelElementIdentifier) any(), eq(OutputModifier.DEFAULT)); + .getSubmodelElement((SubmodelElementIdentifier) any(), eq(OutputModifier.DEFAULT), any()); doReturn(true) .when(assetConnectionManager) .hasValueProvider(any()); @@ -1126,7 +1136,7 @@ public void testGetSubmodelElementByPathRequestWithAssetConnection() throws Exce SubmodelElement submodelElement = submodel.getSubmodelElements().stream().filter(Property.class::isInstance).findFirst().get(); doReturn(submodelElement) .when(persistence) - .getSubmodelElement((SubmodelElementIdentifier) any(), eq(OutputModifier.DEFAULT)); + .getSubmodelElement((SubmodelElementIdentifier) any(), eq(OutputModifier.DEFAULT), any()); assertReadWithAssetConnection( submodelElement, @@ -1164,7 +1174,7 @@ public void testPostSubmodelElementByPathRequest() throws Exception { .build(); doReturn(list) .when(persistence) - .getSubmodelElement(eq(listIdentifier), any()); + .getSubmodelElement(eq(listIdentifier), any(), any()); Reference refNewElement = new ReferenceBuilder() .submodel(submodel) .element(list) @@ -1215,7 +1225,7 @@ public void testPostSubmodelElementByPathRequestWithAssetConnection() throws Exc .build(); doReturn(list) .when(persistence) - .getSubmodelElement(eq(listIdentifier), any()); + .getSubmodelElement(eq(listIdentifier), any(), any()); Reference refNewElement = new ReferenceBuilder() .submodel(submodel) .element(list) @@ -1271,7 +1281,7 @@ public void testPostSubmodelElementByPathRequestAlreadyExists() throws Exception .build(); doReturn(list) .when(persistence) - .getSubmodelElement((SubmodelElementIdentifier) any(), eq(QueryModifier.DEFAULT)); + .getSubmodelElement((SubmodelElementIdentifier) any(), eq(QueryModifier.DEFAULT), any()); Property newProperty = new DefaultProperty.Builder() .valueType(DataTypeDefXsd.STRING) .value("new") @@ -1330,7 +1340,7 @@ public void testPutSubmodelElementByPathRequest() throws ResourceNotFoundExcepti doReturn(originalProperty) .when(persistence) - .getSubmodelElement(eq(propertyIdentifier), any()); + .getSubmodelElement(eq(propertyIdentifier), any(), any()); doReturn(true) .when(assetConnectionManager) .hasValueProvider(any()); @@ -1362,7 +1372,7 @@ public void testPutSubmodelElementByPathRequest() throws ResourceNotFoundExcepti public void testPatchSubmodelElementValueByPathRequest() throws ResourceNotFoundException, AssetConnectionException, Exception { doReturn(environment.getSubmodels().get(0).getSubmodelElements().get(0)) .when(persistence) - .getSubmodelElement((SubmodelElementIdentifier) any(), any()); + .getSubmodelElement((SubmodelElementIdentifier) any(), any(), any()); doReturn(true) .when(assetConnectionManager) .hasValueProvider(any()); @@ -1399,7 +1409,7 @@ public void testDeleteSubmodelElementByPathRequest() throws Exception { .build(); doReturn(environment.getSubmodels().get(0).getSubmodelElements().get(0)) .when(persistence) - .getSubmodelElement(reference, QueryModifier.DEFAULT); + .getSubmodelElement(reference, QueryModifier.DEFAULT, identity()); DeleteSubmodelElementByPathRequest request = new DeleteSubmodelElementByPathRequest.Builder() .submodelId(submodel.getId()) @@ -1427,7 +1437,8 @@ public void testInvokeOperationAsyncRequest() throws Exception { .getSubmodelElement( ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, - Operation.class); + Operation.class, + identity()); doNothing().when(assetConnectionManager) .invokeAsync(any(), any(), any(), any(), any()); @@ -1455,7 +1466,8 @@ public void testInvokeOperationSyncRequest() throws Exception { .getSubmodelElement( ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, - Operation.class); + Operation.class, + identity()); InvokeOperationSyncRequest invokeOperationSyncRequest = new InvokeOperationSyncRequest.Builder() .inoutputArguments(operation.getInoutputVariables()) @@ -1495,7 +1507,8 @@ public void testInvokeOperationSyncRequestMissingInputArgument() throws Exceptio .getSubmodelElement( ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, - Operation.class); + Operation.class, + identity()); InvokeOperationSyncRequest invokeOperationSyncRequest = new InvokeOperationSyncRequest.Builder() .inoutputArguments(operation.getInoutputVariables()) @@ -1517,7 +1530,8 @@ public void testInvokeOperationSyncRequestWithDefaultInputArgument() throws Exce .getSubmodelElement( ReferenceBuilder.forSubmodel(submodelId, operation.getIdShort()), QueryModifier.MINIMAL, - Operation.class); + Operation.class, + identity()); InvokeOperationSyncRequest invokeOperationSyncRequest = new InvokeOperationSyncRequest.Builder() .inoutputArguments(operation.getInoutputVariables()) @@ -1548,7 +1562,7 @@ public void testInvokeOperationSyncRequestWithDefaultInputArgument() throws Exce public void testGetAllConceptDescriptionsRequest() throws Exception { doReturn(Page.of(environment.getConceptDescriptions())) .when(persistence) - .findConceptDescriptions(eq(ConceptDescriptionSearchCriteria.NONE), any(), any()); + .findConceptDescriptions(eq(ConceptDescriptionSearchCriteria.NONE), any(), any(), any()); GetAllConceptDescriptionsRequest request = new GetAllConceptDescriptionsRequest.Builder() .outputModifier(OutputModifier.DEFAULT) @@ -1571,7 +1585,7 @@ public void testGetAllConceptDescriptionsByIdShortRequest() throws Exception { .idShort(environment.getConceptDescriptions().get(0).getIdShort()) .build()), any(), - any()); + any(), any()); GetAllConceptDescriptionsByIdShortRequest request = new GetAllConceptDescriptionsByIdShortRequest.Builder() .outputModifier(OutputModifier.DEFAULT) @@ -1596,7 +1610,7 @@ public void testGetAllConceptDescriptionsByIsCaseOfRequest() throws Exception { .isCaseOf(reference) .build()), any(), - any()); + any(), any()); GetAllConceptDescriptionsByIsCaseOfRequest request = new GetAllConceptDescriptionsByIsCaseOfRequest.Builder() .outputModifier(OutputModifier.DEFAULT) @@ -1621,6 +1635,7 @@ public void testGetAllConceptDescriptionsByDataSpecificationReferenceRequest() t .dataSpecification(reference) .build()), any(), + any(), any()); GetAllConceptDescriptionsByDataSpecificationReferenceRequest request = new GetAllConceptDescriptionsByDataSpecificationReferenceRequest.Builder() @@ -1683,7 +1698,7 @@ public void testPostConceptDescriptionRequestEmptyConceptDescription() throws Ex public void testGetConceptDescriptionByIdRequest() throws Exception { doReturn(environment.getConceptDescriptions().get(0)) .when(persistence) - .getConceptDescription(eq(environment.getConceptDescriptions().get(0).getId()), any()); + .getConceptDescription(eq(environment.getConceptDescriptions().get(0).getId()), any(), any()); GetConceptDescriptionByIdRequest request = new GetConceptDescriptionByIdRequest.Builder() .outputModifier(OutputModifier.DEFAULT) @@ -1716,7 +1731,7 @@ public void testPutConceptDescriptionByIdRequest() throws Exception { public void testDeleteConceptDescriptionByIdRequest() throws Exception { doReturn(environment.getConceptDescriptions().get(0)) .when(persistence) - .getConceptDescription(eq(environment.getConceptDescriptions().get(0).getId()), any()); + .getConceptDescription(eq(environment.getConceptDescriptions().get(0).getId()), any(), any()); DeleteConceptDescriptionByIdRequest request = new DeleteConceptDescriptionByIdRequest.Builder() .id(environment.getConceptDescriptions().get(0).getId()) @@ -1734,7 +1749,7 @@ public void testDeleteConceptDescriptionByIdRequest() throws Exception { public void testGetIdentifiableWithInvalidIdRequest() throws Exception { doThrow(new ResourceNotFoundException("Resource not found with id")) .when(persistence) - .getSubmodel(any(), any()); + .getSubmodel(any(), any(), any()); GetSubmodelRequest request = new GetSubmodelRequest.Builder() .submodelId("foo") @@ -1757,7 +1772,7 @@ public void testGetIdentifiableWithInvalidIdRequest() throws Exception { public void testGetReferableWithInvalidIdRequest() throws Exception { doThrow(new ResourceNotFoundException("Resource not found with id")) .when(persistence) - .getSubmodelElement(any(SubmodelElementIdentifier.class), any()); + .getSubmodelElement(any(SubmodelElementIdentifier.class), any(), any()); GetSubmodelElementByPathRequest request = getExampleGetSubmodelElementByPathRequest(); GetSubmodelElementByPathResponse actual = manager.execute(request, context); @@ -1778,7 +1793,7 @@ public void testGetReferableWithInvalidIdRequest() throws Exception { public void testGetReferableWithMessageBusExceptionRequest() throws ResourceNotFoundException, MessageBusException, Exception { doReturn(new DefaultProperty()) .when(persistence) - .getSubmodelElement(any(SubmodelElementIdentifier.class), any()); + .getSubmodelElement(any(SubmodelElementIdentifier.class), any(), any()); doThrow(new MessageBusException("Invalid Messagbus Call")) .when(messageBus) .publish(any()); @@ -1793,7 +1808,7 @@ public void testGetReferableWithMessageBusExceptionRequest() throws ResourceNotF public void testGetAllAssetAdministrationShellRequestAsync() throws InterruptedException, PersistenceException { doReturn(Page.of(environment.getAssetAdministrationShells())) .when(persistence) - .findAssetAdministrationShells(eq(AssetAdministrationShellSearchCriteria.NONE), any(), any()); + .findAssetAdministrationShells(eq(AssetAdministrationShellSearchCriteria.NONE), any(), any(), any()); GetAllAssetAdministrationShellsRequest request = new GetAllAssetAdministrationShellsRequest(); final AtomicReference response = new AtomicReference<>(); diff --git a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java index fe40691b7..1ab73ba28 100644 --- a/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java +++ b/core/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/submodeltemplate/SubmodelTemplateProcessorTest.java @@ -137,7 +137,7 @@ public void testCreate() throws Exception { private void createService(List submodels) throws Exception { Persistence persistence = mock(Persistence.class); FileStorage fileStorage = mock(FileStorage.class); - when(persistence.getAllSubmodels(any(), any())).thenReturn(Page.of(submodels)); + when(persistence.getAllSubmodels(any(), any(), any())).thenReturn(Page.of(submodels)); service = new Service(CoreConfig.DEFAULT, persistence, fileStorage, messageBus, List.of(), List.of(), List.of(processor)); service.start(); } diff --git a/dataformat/json/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/json/mixins/InvokeOperationRequestMixin.java b/dataformat/json/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/json/mixins/InvokeOperationRequestMixin.java index 477a4dc6f..7a31ac802 100644 --- a/dataformat/json/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/json/mixins/InvokeOperationRequestMixin.java +++ b/dataformat/json/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/dataformat/json/mixins/InvokeOperationRequestMixin.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.Content; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodel.InvokeOperationRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import java.util.List; import javax.xml.datatype.Duration; import org.eclipse.digitaltwin.aas4j.v3.model.Key; @@ -32,6 +33,8 @@ public abstract class InvokeOperationRequestMixin { @JsonIgnore protected boolean internal; @JsonIgnore + protected LogicalExpression formula; + @JsonIgnore protected String id; @JsonIgnore protected Content content; diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java index 544d2936c..da776b777 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/RequestHandlerServlet.java @@ -15,21 +15,20 @@ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.SharedAttributes.ACL; -import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.GET; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.MethodNotAllowedException; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.exception.UnauthorizedException; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.RequestMappingManager; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.response.ResponseMappingManager; -import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.FormulaEvaluator; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.serialization.HttpJsonApiSerializer; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ResourceNotFoundException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; @@ -37,7 +36,6 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -104,11 +102,10 @@ protected void service(HttpServletRequest request, HttpServletResponse response) .collect(Collectors.toMap( x -> x, request::getHeader))) - .accessPermissionRules((List) request.getAttribute(ACL.getName())) + .formula(rulesToFormula((List) request.getAttribute(ACL.getName()))) .build(); - try { - executeAndSend(httpRequest, response, requestMappingManager.map(httpRequest)); + executeAndSend(response, requestMappingManager.map(httpRequest)); } catch (Exception e) { doThrow(e); @@ -130,14 +127,11 @@ private void checkRequestSupportedByProfiles(de.fraunhofer.iosb.ilt.faaast.servi } - private void executeAndSend(HttpRequest request, HttpServletResponse response, - de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) - throws Exception { + private void executeAndSend(HttpServletResponse response, de.fraunhofer.iosb.ilt.faaast.service.model.api.Request apiRequest) throws Exception { if (Objects.isNull(apiRequest)) { throw new InvalidRequestException("empty API request"); } checkRequestSupportedByProfiles(apiRequest); - checkAccess(request); de.fraunhofer.iosb.ilt.faaast.service.model.api.Response apiResponse = serviceContext.execute(endpoint, apiRequest); @@ -153,21 +147,6 @@ private void executeAndSend(HttpRequest request, HttpServletResponse response, } - private void checkAccess(HttpRequest request) - throws ServletException { - List rules = request.getAccessPermissionRules(); - - if (rules == null || request.getMethod() == GET) { - return; - } - - if (rules.stream().noneMatch(rule -> FormulaEvaluator.evaluate(rule.getFormula(), new HashMap<>()))) { - doThrow(new UnauthorizedException( - String.format("User not authorized '%s'", request.getPath()))); - } - } - - private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model.api.Response response) { return Objects.nonNull(response) && response.getStatusCode().isSuccess() @@ -179,4 +158,21 @@ private static boolean isSuccessful(de.fraunhofer.iosb.ilt.faaast.service.model. .noneMatch(x -> Objects.equals(x, MessageTypeEnum.ERROR) || Objects.equals(x, MessageTypeEnum.EXCEPTION)); } + + /** + * Transforms a list of resolved access permission rules to a LogicalExpression, using OR to combine them. + * + * @param rules The rules to OR-ify + * @return The LogicalExpression formula + */ + protected LogicalExpression rulesToFormula(List rules) { + // Security turned off + if (rules == null) { + return identity(); + } + LogicalExpression condition = new LogicalExpression(); + condition.set$or(rules.stream().map(AccessPermissionRule::getFormula).toList()); + return condition; + } + } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java index 92f25566c..2ce7a6efd 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/model/HttpRequest.java @@ -14,9 +14,11 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpConstants; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +38,7 @@ public class HttpRequest extends HttpMessage { private String path; private Map queryParameters; private List pathElements; - private List accessPermissionRules; + private LogicalExpression formula; public static Builder builder() { return new Builder(); @@ -47,7 +49,7 @@ public HttpRequest() { method = HttpMethod.GET; queryParameters = new HashMap<>(); pathElements = new ArrayList<>(); - accessPermissionRules = new ArrayList<>(); + formula = identity(); } @@ -163,22 +165,22 @@ public void setQueryParameters(Map queryParameters) { /** - * Gets this request's applying Access Permission Rules according to AAS Security. + * Gets this request's applying formula according to AAS Security. * - * @return List of access permission rules. + * @return List of formula. */ - public List getAccessPermissionRules() { - return accessPermissionRules; + public LogicalExpression getFormula() { + return formula; } /** - * Sets this request's applying Access Permission Rules according to AAS Security. + * Sets this request's applying formula according to AAS Security. * - * @param accessPermissionRules the applying access permission rules. + * @param formula the applying formula. */ - public void setAccessPermissionRules(List accessPermissionRules) { - this.accessPermissionRules = accessPermissionRules; + public void setFormula(LogicalExpression formula) { + this.formula = formula; } @@ -238,8 +240,8 @@ public B query(String value) { } - public B accessPermissionRules(List value) { - getBuildingInstance().setAccessPermissionRules(value); + public B formula(LogicalExpression value) { + getBuildingInstance().setFormula(value); return getSelf(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java index 1960bb8ea..1fb70538c 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/AbstractRequestMapper.java @@ -26,8 +26,6 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import de.fraunhofer.iosb.ilt.faaast.service.util.RegExHelper; @@ -137,7 +135,7 @@ public Request parse(HttpRequest httpRequest) throws InvalidRequestException { Matcher matcher = Pattern.compile(urlPattern).matcher(httpRequest.getPath()); if (matcher.matches()) { Request request = doParse(httpRequest, RegExHelper.getGroupValues(urlPattern, httpRequest.getPath())); - request.setFormula(rulesToFormula(httpRequest.getAccessPermissionRules())); + request.setFormula(httpRequest.getFormula()); return request; } throw new IllegalStateException(String.format("request was matched but no suitable parser found (HTTP method: %s, URL pattern: %s", method, urlPattern)); @@ -299,19 +297,6 @@ protected List parseBodyAsList(HttpRequest httpRequest, Class type) th } - /** - * Transforms a list of resolved access permission rules to a LogicalExpression, using OR to combine them. - * - * @param rules The rules to OR-ify - * @return The LogicalExpression formula - */ - protected LogicalExpression rulesToFormula(List rules) { - LogicalExpression condition = new LogicalExpression(); - condition.set$or(rules.stream().map(AccessPermissionRule::getFormula).toList()); - return condition; - } - - /** * Parses a string to a JSON merge patch. * diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java deleted file mode 100644 index c0db81e52..000000000 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluator.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security; - -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AttributeItem; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.StringValue; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; -import java.time.LocalTime; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.IntPredicate; -import java.util.function.Supplier; -import java.util.regex.Pattern; - - -/** - * Class to evaluate formulas. - */ -public final class FormulaEvaluator { - - @FunctionalInterface - private interface ValueComparator { - boolean compare(Object left, Object right); - } - - @FunctionalInterface - private interface StringComparator { - boolean compare(String left, String right); - } - - private record ValueOperationSpec(String name, Supplier> operands, ValueComparator comparator) {} - - private record StringOperationSpec(String name, Supplier> operands, StringComparator comparator) {} - - /** - * Evaluates the given formula. - * - * @param formula The formula. - * @param runtimeValues The runtime values. - * @return True if the evaluation is successful, false otherwise. - */ - public static boolean evaluate(LogicalExpression formula, - Map runtimeValues) { - return eval(formula, runtimeValues); - } - - - private static boolean eval(LogicalExpression node, - Map ctx) { - Boolean logical = evaluateLogical(node, ctx); - if (logical != null) { - return logical; - } - - Boolean valueComparison = evaluateValueComparison(node, ctx); - if (valueComparison != null) { - return valueComparison; - } - - Boolean stringComparison = evaluateStringComparison(node, ctx); - if (stringComparison != null) { - return stringComparison; - } - - if (node.get$boolean() != null) { - return node.get$boolean(); - } - if (!node.get$match().isEmpty()) { - throw new UnsupportedOperationException("Operator $match not supported"); - } - throw new IllegalArgumentException("No supported operator found in node"); - } - - - private static Boolean evaluateLogical(LogicalExpression node, - Map ctx) { - if (!node.get$and().isEmpty()) { - for (LogicalExpression child: node.get$and()) { - if (!eval(child, ctx)) { - return false; - } - } - return true; - } - if (!node.get$or().isEmpty()) { - for (LogicalExpression child: node.get$or()) { - if (eval(child, ctx)) { - return true; - } - } - return false; - } - if (node.get$not() != null) { - return !eval(node.get$not(), ctx); - } - return null; - } - - - private static Boolean evaluateValueComparison(LogicalExpression node, - Map ctx) { - List operations = List.of( - new ValueOperationSpec("$eq", node::get$eq, Objects::equals), - new ValueOperationSpec("$ne", node::get$ne, (left, right) -> !Objects.equals(left, right)), - new ValueOperationSpec("$gt", node::get$gt, (left, right) -> compareComparable(left, right, v -> v > 0)), - new ValueOperationSpec("$ge", node::get$ge, (left, right) -> compareComparable(left, right, v -> v >= 0)), - new ValueOperationSpec("$lt", node::get$lt, (left, right) -> compareComparable(left, right, v -> v < 0)), - new ValueOperationSpec("$le", node::get$le, (left, right) -> compareComparable(left, right, v -> v <= 0))); - - for (ValueOperationSpec spec: operations) { - List ops = spec.operands.get(); - if (!ops.isEmpty()) { - validateOperandCount(ops.size(), spec.name); - Object left = resolve(ops.get(0), ctx); - Object right = resolve(ops.get(1), ctx); - return spec.comparator.compare(left, right); - } - } - return null; - } - - - private static Boolean evaluateStringComparison(LogicalExpression node, - Map ctx) { - List operations = List.of( - new StringOperationSpec("$contains", node::get$contains, (left, right) -> left != null && left.contains(right)), - new StringOperationSpec("$starts-with", node::get$startsWith, (left, right) -> left != null && left.startsWith(right)), - new StringOperationSpec("$ends-with", node::get$endsWith, (left, right) -> left != null && left.endsWith(right)), - new StringOperationSpec("$regex", node::get$regex, (left, right) -> left != null && Pattern.matches(right, left))); - - for (StringOperationSpec spec: operations) { - List ops = spec.operands.get(); - if (!ops.isEmpty()) { - validateOperandCount(ops.size(), spec.name); - String left = resolveString(ops.get(0), ctx); - String right = resolveString(ops.get(1), ctx); - return spec.comparator.compare(left, right); - } - } - return null; - } - - - private static void validateOperandCount(int count, - String operator) { - if (count != 2) { - throw new IllegalArgumentException(operator + " requires exactly 2 operands"); - } - } - - - private static boolean compareComparable(Object left, - Object right, - IntPredicate predicate) { - if (!(left instanceof Comparable) || !(right instanceof Comparable)) { - throw new IllegalArgumentException("Operands are not comparable: " - + left + ", " + right); - } - int cmp = ((Comparable) left).compareTo(right); - return predicate.test(cmp); - } - - - private static Object resolve(Value operand, - Map ctx) { - if (operand.get$strVal() != null) { - return operand.get$strVal(); - } - if (operand.get$timeVal() != null) { - return LocalTime.parse(operand.get$timeVal()); - } - if (operand.get$field() != null) { - return ctx.get(operand.get$field()); - } - if (operand.get$attribute() != null) { - AttributeItem path = operand.get$attribute(); - if (!Objects.isNull(path.getClaim())) { - return ctx.get("CLAIM:" + path.getClaim()); - } - if (!Objects.isNull(path.getReference())) { - return ctx.get("REF:" + path.getReference()); - } - return ctx.get("UTCNOW"); - } - throw new IllegalArgumentException("Unresolvable operand " + operand); - } - - - private static String resolveString(StringValue operand, - Map ctx) { - if (operand.get$strVal() != null) { - return operand.get$strVal(); - } - if (operand.get$field() != null) { - Object val = ctx.get(operand.get$field()); - return val != null ? val.toString() : null; - } - if (operand.get$attribute() != null) { - AttributeItem path = operand.get$attribute(); - String key = null; - if (!Objects.isNull(path.getClaim())) { - key = "CLAIM:" + path.getClaim(); - } - else if (!Objects.isNull(path.getReference())) { - key = "REF:" + path.getReference(); - } - else { - key = "UTCNOW"; - } - if (key != null) { - Object val = ctx.get(key); - return val != null ? val.toString() : null; - } - } - throw new IllegalArgumentException("Unresolvable operand " + operand); - } -} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java deleted file mode 100644 index b5b1fdeab..000000000 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/post/AclFilterFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.post; - -import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.SharedAttributes.ACL; - -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import java.io.IOException; -import java.util.List; - - -/** - * Applies post-persistence filtering of responses according to AAS Security Filter. - */ -public class AclFilterFilter implements Filter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - doFilter(request, response, chain); - List acl = ((List) request.getAttribute(ACL.getName())); - - // TODO apply filters post-query - } - -} diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java index 474b56cae..f72745280 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/util/ExpressionInjectionHelper.java @@ -21,9 +21,13 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.StringValue; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Value; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.time.Clock; import java.time.LocalTime; +import java.util.Date; import java.util.Map; +import java.util.TimeZone; /** @@ -154,20 +158,34 @@ private static void injectValue(Value value, Map claims) { else if (value.get$attribute().getGlobal() != null) { AttributeItem.Global global = value.get$attribute().getGlobal(); if (global == AttributeItem.Global.UTCNOW) { - // TODO might be a bug. Have to consider how ACL creator defined UTC w.r.t. format value.set$timeVal(LocalTime.now(Clock.systemUTC()).toString()); } else if (global == AttributeItem.Global.LOCALNOW) { - // TODO see UTCNOW - value.set$timeVal(LocalTime.now().toString()); + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat df = new SimpleDateFormat("HH:mm:ss"); + df.setTimeZone(tz); + String nowAsISO = df.format(new Date()); + value.set$timeVal(nowAsISO); } else if (global == AttributeItem.Global.CLIENTNOW) { - value.set$timeVal(null); - // TODO how to get client time? - value.set$boolean(false); + if (claims.containsKey("iat")) { + TimeZone tz = TimeZone.getDefault(); + DateFormat df = new SimpleDateFormat("HH:mm:ss"); + df.setTimeZone(tz); + String nowAsISO = df.format(claims.get("iat").asDate()); + value.set$timeVal(nowAsISO); + } + else { + value.set$boolean(false); + value.set$attribute(null); + } + + } + else if (global == AttributeItem.Global.ANONYMOUS) { + value.set$boolean(true); } else { - return; + throw new IllegalArgumentException(String.format("Unknown attribute: %s", global)); } } else if (value.get$strCast() != null) { diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/AbstractHttpEndpointTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/AbstractHttpEndpointTest.java index 319e050e0..20b8dc4a2 100644 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/AbstractHttpEndpointTest.java +++ b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/AbstractHttpEndpointTest.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -645,7 +646,8 @@ public void testGetAllSubmodelElementsInAasContext() throws Exception { aas.getId(), new OutputModifier.Builder() .level(Level.CORE) - .build())) + .build(), + identity())) .thenReturn(aas); ContentResponse response = execute( HttpMethod.GET, diff --git a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java b/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java deleted file mode 100644 index c6d0edb36..000000000 --- a/endpoint/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/FormulaEvaluatorTest.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.LogicalExpression; -import java.time.LocalTime; -import java.util.HashMap; -import java.util.Map; -import org.junit.Test; - - -/** - * Unit tests for {@link FormulaEvaluator}. - */ -public class FormulaEvaluatorTest { - - private static final ObjectMapper MAPPER = new ObjectMapper(); - - /* ------------------------------------------------------------------ */ - @Test - public void complexFormulaWithMatchingClaims() throws Exception { - String json = """ - { - "$and": [ - { - "$or": [ - { - "$eq": [ - { "$field": "$sm#semanticId" }, - { "$strVal": "SemanticID-Nameplate" } - ] - }, - { - "$eq": [ - { "$field": "$sm#semanticId" }, - { "$strVal": "SemanticID-TechnicalData" } - ] - } - ] - }, - { - "$or": [ - { - "$eq": [ - { "$attribute": { "CLAIM": "email" } }, - { "$strVal": "user1@company1.com" } - ] - }, - { - "$eq": [ - { "$attribute": { "CLAIM": "email" } }, - { "$strVal": "user2@company2.com" } - ] - } - ] - } - ] - } - """; - - LogicalExpression formula = MAPPER.readValue( - json, new TypeReference<>() {}); - - Map ctx = new HashMap<>(); - ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); // matches 2nd $eq - ctx.put("CLAIM:email", "user2@company2.com"); // matches 2nd $eq - boolean result = FormulaEvaluator.evaluate(formula, ctx); - assertTrue(result); - } - - - /* ------------------------------------------------------------------ */ - @Test - public void regexFormulaWithNonMatchingEmail() throws Exception { - String json = """ - { - "$and": [ - { - "$or": [ - { - "$eq": [ - { "$field": "$sm#semanticId" }, - { "$strVal": "SemanticID-Nameplate" } - ] - }, - { - "$eq": [ - { "$field": "$sm#semanticId" }, - { "$strVal": "SemanticID-TechnicalData" } - ] - } - ] - }, - { - "$regex": [ - { "$attribute": { "CLAIM": "email" } }, - { "$strVal": "[\\\\w\\\\.]+'@company\\\\.com" } - ] - } - ] - } - """; - - LogicalExpression formula = MAPPER.readValue( - json, new TypeReference<>() {}); - Map ctx = new HashMap<>(); - ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); - ctx.put("CLAIM:email", "other.user@other-company.org"); // does NOT match - assertFalse(FormulaEvaluator.evaluate(formula, ctx)); - } - - - /* ------------------------------------------------------------------ */ - @Test - public void fullFormulaAllConditionsMet() throws Exception { - String json = """ - { - "$and": [ - { - "$or": [ - { - "$eq": [ - { "$field": "$sm#semanticId" }, - { "$strVal": "SemanticID-Nameplate" } - ] - }, - { - "$eq": [ - { "$field": "$sm#semanticId" }, - { "$strVal": "SemanticID-TechnicalData" } - ] - } - ] - }, - { - "$eq": [ - { "$attribute": { "CLAIM": "companyName" } }, - { "$strVal": "company1-name" } - ] - }, - { - "$regex": [ - { "$attribute": { "REFERENCE": "(Submodel)*#Id" } }, - { "$strVal": "^https://company1.com/.*$" } - ] - }, - { - "$ge": [ - { "$attribute": { "GLOBAL": "UTCNOW" } }, - { "$timeVal": "09:00" } - ] - }, - { - "$le": [ - { "$attribute": { "GLOBAL": "UTCNOW" } }, - { "$timeVal": "17:00" } - ] - } - ] - } - """; - - LogicalExpression formula = MAPPER.readValue( - json, new TypeReference<>() {}); - Map ctx = new HashMap<>(); - ctx.put("$sm#semanticId", "SemanticID-TechnicalData"); - ctx.put("CLAIM:companyName", "company1-name"); - ctx.put("REF:(Submodel)*#Id", "https://company1.com/id-0815"); - ctx.put("UTCNOW", LocalTime.of(10, 30)); // between 09:00 and 17:00 - assertTrue(FormulaEvaluator.evaluate(formula, ctx)); - } - - - @Test - public void testFormulaConditionsNotMet() throws JsonProcessingException { - String json = """ - { - "$and": [ - { - "$or": [ - { - "$eq": [ - { - "$attribute": { - "CLAIM": "organization" - } - }, - { - "$strVal": "[MyCompany]" - } - ] - }, - { - "$eq": [ - { - "$attribute": { - "CLAIM": "organization" - } - }, - { - "$strVal": "Company2" - } - ] - } - ] - }, - { - "$or": [ - { - "$eq": [ - { - "$attribute": { - "CLAIM": "email" - } - }, - { - "$strVal": "bob@example.com" - } - ] - }, - { - "$eq": [ - { - "$attribute": { - "CLAIM": "email" - } - }, - { - "$strVal": "user2@company2.com" - } - ] - } - ] - } - ] - } - """; - LogicalExpression formula = MAPPER.readValue( - json, new TypeReference<>() {}); - Map ctx = new HashMap<>(); - ctx.put("CLAIM:organization", "Company2"); - //ctx.put("CLAIM:email", "user2@company2.com"); - assertFalse(FormulaEvaluator.evaluate(formula, ctx)); - } - -} diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java index 8b9eabd5e..664328cb4 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/Request.java @@ -31,6 +31,8 @@ public abstract class Request { protected Request() { this.internal = false; + this.formula = new LogicalExpression(); + this.formula.set$boolean(true); } diff --git a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java index 3fac87cac..13c3d3b62 100644 --- a/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java +++ b/persistence/file/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFile.java @@ -138,32 +138,33 @@ public void stop() { @Override - public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModifier modifier) throws ResourceNotFoundException { - return persistence.getAssetAdministrationShell(id, modifier); + public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException { + return persistence.getAssetAdministrationShell(id, modifier, formula); } @Override - public Submodel getSubmodel(String id, QueryModifier modifier) throws ResourceNotFoundException { - return persistence.getSubmodel(id, modifier); + public Submodel getSubmodel(String id, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException { + return persistence.getSubmodel(id, modifier, formula); } @Override - public ConceptDescription getConceptDescription(String id, QueryModifier modifier) throws ResourceNotFoundException { - return persistence.getConceptDescription(id, modifier); + public ConceptDescription getConceptDescription(String id, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException { + return persistence.getConceptDescription(id, modifier, formula); } @Override - public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier) throws ResourceNotFoundException { - return persistence.getSubmodelElement(identifier, modifier); + public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier, LogicalExpression formula) + throws ResourceNotFoundException, PersistenceException { + return persistence.getSubmodelElement(identifier, modifier, formula); } @Override - public Page getSubmodelRefs(String aasId, PagingInfo paging) throws ResourceNotFoundException { - return persistence.getSubmodelRefs(aasId, paging); + public Page getSubmodelRefs(String aasId, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException { + return persistence.getSubmodelRefs(aasId, paging, formula); } @@ -173,12 +174,6 @@ public OperationResult getOperationResult(OperationHandle handle) throws Resourc } - @Override - public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { - return persistence.findAssetAdministrationShells(criteria, modifier, paging); - } - - @Override public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) { @@ -186,12 +181,6 @@ public Page findAssetAdministrationShells(AssetAdminis } - @Override - public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { - return persistence.findSubmodels(criteria, modifier, paging); - } - - @Override public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { return persistence.findSubmodels(criteria, modifier, paging, formula); @@ -199,14 +188,9 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi @Override - public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws ResourceNotFoundException { - return persistence.findSubmodelElements(criteria, modifier, paging); - } - - - @Override - public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { - return persistence.findConceptDescriptions(criteria, modifier, paging); + public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) + throws ResourceNotFoundException, PersistenceException { + return persistence.findSubmodelElements(criteria, modifier, paging, formula); } @@ -246,7 +230,7 @@ public void insert(SubmodelElementIdentifier parentIdentifier, SubmodelElement s @Override - public void update(SubmodelElementIdentifier identifier, SubmodelElement submodelElement) throws ResourceNotFoundException { + public void update(SubmodelElementIdentifier identifier, SubmodelElement submodelElement) throws ResourceNotFoundException, PersistenceException { persistence.update(identifier, submodelElement); saveEnvironment(); } diff --git a/persistence/file/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFileTest.java b/persistence/file/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFileTest.java index 87e9813f6..1dd0c13ab 100644 --- a/persistence/file/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFileTest.java +++ b/persistence/file/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/file/PersistenceFileTest.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.persistence.file; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.config.CoreConfig; @@ -155,7 +157,7 @@ public void testOverrideInitial() throws ResourceNotFoundException, Configuratio .map(Path::toString) .filter(x -> x.endsWith(".json")) .count()); - Assert.assertThrows(ResourceNotFoundException.class, () -> newPersistence.getAssetAdministrationShell(identifier, QueryModifier.DEFAULT)); + Assert.assertThrows(ResourceNotFoundException.class, () -> newPersistence.getAssetAdministrationShell(identifier, QueryModifier.DEFAULT, identity())); } diff --git a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java index 84835d30c..8f8b533fd 100644 --- a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java +++ b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java @@ -218,12 +218,6 @@ public void deleteAll() throws PersistenceException { } - @Override - public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { - return findAssetAdministrationShells(criteria, modifier, paging, null); - } - - @Override public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) { @@ -247,27 +241,7 @@ public Page findAssetAdministrationShells(AssetAdminis @Override - public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { - Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); - Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); - Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); - Stream result = environment.getConceptDescriptions().stream(); - if (criteria.isIdShortSet()) { - result = filterByIdShort(result, criteria.getIdShort()); - } - if (criteria.isIsCaseOfSet()) { - result = filterByIsCaseOf(result, criteria.getIsCaseOf()); - } - if (criteria.isDataSpecificationSet()) { - result = filterByDataSpecification(result, criteria.getDataSpecification()); - } - return preparePagedResult(result, modifier, paging); - } - - - @Override - public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) - throws PersistenceException { + public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -290,13 +264,16 @@ public Page findConceptDescriptions(ConceptDescriptionSearch @Override - public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws ResourceNotFoundException { + public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) + throws ResourceNotFoundException, PersistenceException { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); + Environment filteredEnvironment = getFilteredEnvironment(formula); + final Collection elements = new ArrayList<>(); if (criteria.isParentSet()) { - Referable parent = EnvironmentHelper.resolve(criteria.getParent().toReference(), environment); + Referable parent = EnvironmentHelper.resolve(criteria.getParent().toReference(), filteredEnvironment); if (Submodel.class.isAssignableFrom(parent.getClass())) { elements.addAll(((Submodel) parent).getSubmodelElements()); } @@ -322,7 +299,7 @@ public void visit(SubmodelElement submodelElement) { } }) .build() - .walk(environment); + .walk(filteredEnvironment); } Stream result = elements.stream(); if (criteria.isSemanticIdSet()) { @@ -331,28 +308,13 @@ public void visit(SubmodelElement submodelElement) { if (criteria.getValueOnly()) { result = filterByHasValueOnlySerialization(result); } - return preparePagedResult(result, modifier, paging); - } - - @Override - public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) { - Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); - Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); - Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); - Stream result = environment.getSubmodels().stream(); - if (criteria.isIdShortSet()) { - result = filterByIdShort(result, criteria.getIdShort()); - } - if (criteria.isSemanticIdSet()) { - result = filterBySemanticId(result, criteria.getSemanticId()); - } return preparePagedResult(result, modifier, paging); } @Override - public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { + public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) { Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -372,9 +334,9 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi @Override - public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModifier modifier) throws ResourceNotFoundException { + public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException { return prepareResult( - filterById(environment.getAssetAdministrationShells().stream(), id) + filterById(findAssetAdministrationShells(AssetAdministrationShellSearchCriteria.NONE, modifier, PagingInfo.ALL, formula).getContent().stream(), id) .findFirst() .orElseThrow(() -> new ResourceNotFoundException(String.format(MSG_RESOURCE_NOT_FOUND_BY_ID, id))), modifier); @@ -382,9 +344,9 @@ public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModi @Override - public ConceptDescription getConceptDescription(String id, QueryModifier modifier) throws ResourceNotFoundException { + public ConceptDescription getConceptDescription(String id, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException { return prepareResult( - filterById(environment.getConceptDescriptions().stream(), id) + filterById(findConceptDescriptions(ConceptDescriptionSearchCriteria.NONE, modifier, PagingInfo.ALL, formula).getContent().stream(), id) .findFirst() .orElseThrow(() -> new ResourceNotFoundException(String.format(MSG_RESOURCE_NOT_FOUND_BY_ID, id))), modifier); @@ -401,9 +363,9 @@ public OperationResult getOperationResult(OperationHandle handle) throws Resourc @Override - public Submodel getSubmodel(String id, QueryModifier modifier) throws ResourceNotFoundException { + public Submodel getSubmodel(String id, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException { return prepareResult( - filterById(environment.getSubmodels().stream(), id) + filterById(findSubmodels(SubmodelSearchCriteria.NONE, modifier, PagingInfo.ALL, formula).getContent().stream(), id) .findFirst() .orElseThrow(() -> new ResourceNotFoundException(String.format(MSG_RESOURCE_NOT_FOUND_BY_ID, id))), modifier); @@ -411,17 +373,18 @@ public Submodel getSubmodel(String id, QueryModifier modifier) throws ResourceNo @Override - public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier) throws ResourceNotFoundException { + public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier, LogicalExpression formula) + throws ResourceNotFoundException, PersistenceException { return prepareResult( - EnvironmentHelper.resolve(identifier.toReference(), environment, SubmodelElement.class), + EnvironmentHelper.resolve(identifier.toReference(), getFilteredEnvironment(formula), SubmodelElement.class), modifier); } @Override - public Page getSubmodelRefs(String aasId, PagingInfo paging) throws ResourceNotFoundException { + public Page getSubmodelRefs(String aasId, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException { return preparePagedResult( - getAssetAdministrationShell(aasId, QueryModifier.MINIMAL).getSubmodels().stream(), + getAssetAdministrationShell(aasId, QueryModifier.MINIMAL, formula).getSubmodels().stream(), paging); } @@ -511,10 +474,10 @@ else if (AnnotatedRelationshipElement.class.isAssignableFrom(parent.getClass())) @Override - public void update(SubmodelElementIdentifier identifier, SubmodelElement submodelElement) throws ResourceNotFoundException { + public void update(SubmodelElementIdentifier identifier, SubmodelElement submodelElement) throws ResourceNotFoundException, PersistenceException { Ensure.requireNonNull(identifier, "identifier must be non-null"); Ensure.requireNonNull(submodelElement, "submodelElement must be non-null"); - SubmodelElement oldElement = getSubmodelElement(identifier, QueryModifier.DEFAULT); + SubmodelElement oldElement = getSubmodelElement(identifier, QueryModifier.DEFAULT, Persistence.identity()); Referable parent = EnvironmentHelper.resolve(ReferenceHelper.getParent(identifier.toReference()), environment); if (SubmodelElementList.class.isAssignableFrom(parent.getClass())) { @@ -628,8 +591,8 @@ private static Stream filterByAssetIds(Stream globalAssetIdentificators = assetIds.stream() .filter(x -> GlobalAssetIdentification.class.isAssignableFrom(x.getClass())) .map(GlobalAssetIdentification.class::cast) - .map(x -> x.getValue()) - .collect(Collectors.toList()); + .map(AssetIdentification::getValue) + .toList(); List specificAssetIdentificators = assetIds.stream() .filter(x -> SpecificAssetIdentification.class.isAssignableFrom(x.getClass())) .map(x -> new DefaultSpecificAssetId.Builder() @@ -710,7 +673,7 @@ private static Page preparePagedResult(Stream input, PagingInfo paging if (paging.hasLimit()) { result = result.limit(paging.getLimit() + 1); } - List temp = result.collect(Collectors.toList()); + List temp = result.toList(); return Page. builder() .result(temp.stream() .limit(paging.hasLimit() ? paging.getLimit() : temp.size()) @@ -741,4 +704,14 @@ private static void saveOrUpdateById(Collection cont .orElse(null), element); } + + + private Environment getFilteredEnvironment(LogicalExpression formula) { + return new DefaultEnvironment.Builder() + .assetAdministrationShells(findAssetAdministrationShells(AssetAdministrationShellSearchCriteria.NONE, QueryModifier.DEFAULT, PagingInfo.ALL, formula).getContent()) + .submodels(findSubmodels(SubmodelSearchCriteria.NONE, QueryModifier.DEFAULT, PagingInfo.ALL, formula).getContent()) + .conceptDescriptions(findConceptDescriptions(ConceptDescriptionSearchCriteria.NONE, QueryModifier.DEFAULT, PagingInfo.ALL, formula).getContent()) + .build(); + } + } diff --git a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java index 7be984a3e..f1e5a8625 100644 --- a/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java +++ b/persistence/mongo/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongo.java @@ -111,6 +111,7 @@ public class PersistenceMongo implements Persistence { private static final int RANDOM_VALUE_LENGTH = 100; private static final String SERIALIZATION_ERROR = "Serialization of document with id %s failed!"; + private static final String MONGODB_UNSUPPORTED = "Security and query are not supported with mongoDB. This FA³ST Service is not secure and queries won't work!"; private static final String HANDLE = "handle"; private static final String MSG_RESOURCE_NOT_FOUND_BY_ID = "resource not found (id %s)"; @@ -246,8 +247,10 @@ private MongoCollection resetCollection(String name) throws Persistenc @Override - public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) + public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, + LogicalExpression formula) throws PersistenceException { + LOGGER.warn(MONGODB_UNSUPPORTED); Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -261,15 +264,9 @@ public Page findAssetAdministrationShells(AssetAdminis @Override - public Page findAssetAdministrationShells(AssetAdministrationShellSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, - LogicalExpression formula) + public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { - throw new PersistenceException("Query not supported with mongoDB."); - } - - - @Override - public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException { + LOGGER.warn(MONGODB_UNSUPPORTED); Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -285,14 +282,8 @@ public Page findConceptDescriptions(ConceptDescriptionSearch @Override - public Page findConceptDescriptions(ConceptDescriptionSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) - throws PersistenceException { - throw new PersistenceException("Query not supported with mongoDB."); - } - - - @Override - public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) throws PersistenceException { + public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { + LOGGER.warn(MONGODB_UNSUPPORTED); Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -306,14 +297,9 @@ public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifi @Override - public Page findSubmodels(SubmodelSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) throws PersistenceException { - throw new PersistenceException("Query not supported with mongoDB."); - } - - - @Override - public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging) - throws ResourceNotFoundException, PersistenceException { + public Page findSubmodelElements(SubmodelElementSearchCriteria criteria, QueryModifier modifier, PagingInfo paging, LogicalExpression formula) + throws PersistenceException, ResourceNotFoundException { + LOGGER.warn(MONGODB_UNSUPPORTED); Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); @@ -331,7 +317,9 @@ public Page findSubmodelElements(SubmodelElementSearchCriteria @Override - public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModifier modifier) throws ResourceNotFoundException, PersistenceException { + public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModifier modifier, LogicalExpression formula) + throws ResourceNotFoundException, PersistenceException { + LOGGER.warn(MONGODB_UNSUPPORTED); return prepareResult( fetch(aasCollection, id, AssetAdministrationShell.class), modifier); @@ -339,7 +327,8 @@ public AssetAdministrationShell getAssetAdministrationShell(String id, QueryModi @Override - public ConceptDescription getConceptDescription(String id, QueryModifier modifier) throws ResourceNotFoundException, PersistenceException { + public ConceptDescription getConceptDescription(String id, QueryModifier modifier, LogicalExpression formula) throws PersistenceException, ResourceNotFoundException { + LOGGER.warn(MONGODB_UNSUPPORTED); return prepareResult( fetch(cdCollection, id, ConceptDescription.class), modifier); @@ -347,7 +336,8 @@ public ConceptDescription getConceptDescription(String id, QueryModifier modifie @Override - public Submodel getSubmodel(String id, QueryModifier modifier) throws ResourceNotFoundException, PersistenceException { + public Submodel getSubmodel(String id, QueryModifier modifier, LogicalExpression formula) throws PersistenceException, ResourceNotFoundException { + LOGGER.warn(MONGODB_UNSUPPORTED); return prepareResult( fetch(submodelCollection, id, Submodel.class), modifier); @@ -355,7 +345,8 @@ public Submodel getSubmodel(String id, QueryModifier modifier) throws ResourceNo @Override - public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier) throws ResourceNotFoundException, PersistenceException { + public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier, LogicalExpression formula) + throws PersistenceException, ResourceNotFoundException { return prepareResult( fetch(identifier, SubmodelElement.class), modifier); @@ -363,9 +354,10 @@ public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, @Override - public Page getSubmodelRefs(String aasId, PagingInfo paging) throws ResourceNotFoundException, PersistenceException { + public Page getSubmodelRefs(String aasId, PagingInfo paging, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException { + LOGGER.warn(MONGODB_UNSUPPORTED); return preparePagedResult( - getAssetAdministrationShell(aasId, QueryModifier.MINIMAL) + getAssetAdministrationShell(aasId, QueryModifier.MINIMAL, formula) .getSubmodels() .stream(), paging); @@ -401,10 +393,10 @@ public void insert(SubmodelElementIdentifier parentIdentifier, SubmodelElement s Ensure.requireNonNull(submodelElement, "submodelElement must be non-null"); Referable parent; if (parentIdentifier.getIdShortPath().isEmpty()) { - parent = getSubmodel(parentIdentifier.getSubmodelId(), QueryModifier.MINIMAL); + parent = getSubmodel(parentIdentifier.getSubmodelId(), QueryModifier.MINIMAL, Persistence.identity()); } else { - parent = getSubmodelElement(parentIdentifier, QueryModifier.MINIMAL); + parent = getSubmodelElement(parentIdentifier, QueryModifier.MINIMAL, Persistence.identity()); } if (!SubmodelElementCollection.class.isAssignableFrom(parent.getClass()) && !SubmodelElementList.class.isAssignableFrom(parent.getClass()) diff --git a/persistence/mongo/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongoTest.java b/persistence/mongo/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongoTest.java index bf001f4fd..785fe59e5 100644 --- a/persistence/mongo/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongoTest.java +++ b/persistence/mongo/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/mongo/PersistenceMongoTest.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.persistence.mongo; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.flapdoodle.embed.mongo.distribution.Version; import de.flapdoodle.embed.mongo.transitions.Mongod; import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess; @@ -103,13 +105,13 @@ public void testEnvironmentOverride() throws ConfigurationException, ResourceNot Persistence noOverridePersistence = getPersistenceConfig(null, environment, false).newInstance(CoreConfig.DEFAULT, SERVICE_CONTEXT); noOverridePersistence.start(); Assert.assertThrows(ResourceNotFoundException.class, () -> { - noOverridePersistence.getAssetAdministrationShell(AASSimple.AAS_IDENTIFIER, QueryModifier.DEFAULT); + noOverridePersistence.getAssetAdministrationShell(AASSimple.AAS_IDENTIFIER, QueryModifier.DEFAULT, identity()); }); noOverridePersistence.stop(); Persistence overridePersistence = getPersistenceConfig(null, environment, true).newInstance(CoreConfig.DEFAULT, SERVICE_CONTEXT); overridePersistence.start(); AssetAdministrationShell expected = environment.getAssetAdministrationShells().get(0); - AssetAdministrationShell actual = overridePersistence.getAssetAdministrationShell(AASSimple.AAS_IDENTIFIER, QueryModifier.DEFAULT); + AssetAdministrationShell actual = overridePersistence.getAssetAdministrationShell(AASSimple.AAS_IDENTIFIER, QueryModifier.DEFAULT, identity()); Assert.assertEquals(expected, actual); overridePersistence.stop(); } @@ -135,7 +137,8 @@ public void putSubmodelElementNewInDeepSubmodelElementList() reference, new QueryModifier.Builder() .extend(Extent.WITH_BLOB_VALUE) - .build()); + .build(), + identity()); Assert.assertEquals(expected, actual); persistence.stop(); } diff --git a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java index a521194da..d2c1b6e94 100644 --- a/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java +++ b/test/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/test/AssetConnectionIT.java @@ -15,6 +15,7 @@ package de.fraunhofer.iosb.ilt.faaast.service.test; import static de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.util.HttpHelper.toHttpStatusCode; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; import static de.fraunhofer.iosb.ilt.faaast.service.test.model.AssetConnectionModelSimple.ENVIRONMENT; import static de.fraunhofer.iosb.ilt.faaast.service.test.model.AssetConnectionModelSimple.INITIAL_VALUE; import static de.fraunhofer.iosb.ilt.faaast.service.test.model.AssetConnectionModelSimple.NODE_ID_SOURCE_1; @@ -429,7 +430,7 @@ private void setValue(Reference reference, String value) throws ResourceNotFound private void setValue(Service service, Reference reference, String value) throws ResourceNotFoundException, PersistenceException, ValueFormatException { - Property property = (Property) service.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Property.class); + Property property = (Property) service.getPersistence().getSubmodelElement(reference, QueryModifier.MINIMAL, Property.class, identity()); property.setValue(value); PatchSubmodelElementValueByPathResponse response = service.execute(PatchSubmodelElementValueByPathRequest.builder() .submodelId(SubmodelElementIdentifier.fromReference(reference).getSubmodelId()) From bec11093015c099216df80b8dddeb144808d73d3 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:16:45 +0200 Subject: [PATCH 106/108] fix: tests for new structure --- .../http/provider/HttpOperationProvider.java | 4 +++- .../http/HttpAssetConnectionTest.java | 2 +- .../opcua/provider/OpcUaOperationProvider.java | 3 ++- .../opcua/OpcUaAssetConnectionTest.java | 2 +- .../persistence/memory/PersistenceInMemory.java | 12 ++++++------ 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpOperationProvider.java b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpOperationProvider.java index 7ea92bbd8..e8a578b5a 100644 --- a/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpOperationProvider.java +++ b/assetconnection/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/provider/HttpOperationProvider.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.http.provider; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.common.provider.MultiFormatOperationProvider; @@ -80,7 +82,7 @@ protected OperationVariable[] getOutputParameters() { throw new IllegalArgumentException("reference must be non-null"); } try { - SubmodelElement element = serviceContext.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT); + SubmodelElement element = serviceContext.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT, identity()); if (element == null) { throw new ResourceNotFoundException(String.format("reference could not be resolved (reference: %s)", ReferenceHelper.toString(reference))); } diff --git a/assetconnection/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionTest.java b/assetconnection/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionTest.java index 3fb6f2928..7eb8bc049 100644 --- a/assetconnection/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionTest.java +++ b/assetconnection/http/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/http/HttpAssetConnectionTest.java @@ -695,7 +695,7 @@ private void assertOperationProviderPropertyJson(RequestMethod method, .outputVariables(Arrays.asList(output)) .build()) .when(persistence) - .getSubmodelElement(eq(REFERENCE), any()); + .getSubmodelElement(eq(REFERENCE), any(), any()); if (output != null) { Stream.of(output).forEach(x -> { try { diff --git a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaOperationProvider.java b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaOperationProvider.java index 80273a089..c167e85ff 100644 --- a/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaOperationProvider.java +++ b/assetconnection/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/provider/OpcUaOperationProvider.java @@ -14,6 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.assetconnection.opcua.provider; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; import static java.util.Objects.requireNonNull; import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; @@ -216,7 +217,7 @@ private OperationVariable[] getOperationOutputVariables(Reference reference) thr if (reference == null) { throw new IllegalArgumentException("reference must be non-null"); } - SubmodelElement element = serviceContext.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT); + SubmodelElement element = serviceContext.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT, identity()); if (element == null) { throw new ResourceNotFoundException(String.format("reference could not be resolved (reference: %s)", ReferenceHelper.toString(reference))); } diff --git a/assetconnection/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionTest.java b/assetconnection/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionTest.java index da2bda709..66ef9f04b 100644 --- a/assetconnection/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionTest.java +++ b/assetconnection/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/assetconnection/opcua/OpcUaAssetConnectionTest.java @@ -760,7 +760,7 @@ private void assertInvokeOperation( .outputVariables(Arrays.asList(expectedOut)) .build()) .when(persistence) - .getSubmodelElement(eq(reference), any()); + .getSubmodelElement(eq(reference), any(), any()); OpcUaAssetConnection connection = config.newInstance(CoreConfig.DEFAULT, serviceContext); awaitConnection(connection); OperationVariable[] actual; diff --git a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java index 8f8b533fd..4a8c49ea3 100644 --- a/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java +++ b/persistence/memory/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/persistence/memory/PersistenceInMemory.java @@ -269,7 +269,7 @@ public Page findSubmodelElements(SubmodelElementSearchCriteria Ensure.requireNonNull(criteria, MSG_CRITERIA_NOT_NULL); Ensure.requireNonNull(modifier, MSG_MODIFIER_NOT_NULL); Ensure.requireNonNull(paging, MSG_PAGING_NOT_NULL); - Environment filteredEnvironment = getFilteredEnvironment(formula); + Environment filteredEnvironment = getFilteredEnvironment(formula, modifier); final Collection elements = new ArrayList<>(); if (criteria.isParentSet()) { @@ -376,7 +376,7 @@ public Submodel getSubmodel(String id, QueryModifier modifier, LogicalExpression public SubmodelElement getSubmodelElement(SubmodelElementIdentifier identifier, QueryModifier modifier, LogicalExpression formula) throws ResourceNotFoundException, PersistenceException { return prepareResult( - EnvironmentHelper.resolve(identifier.toReference(), getFilteredEnvironment(formula), SubmodelElement.class), + EnvironmentHelper.resolve(identifier.toReference(), getFilteredEnvironment(formula, modifier), SubmodelElement.class), modifier); } @@ -706,11 +706,11 @@ private static void saveOrUpdateById(Collection cont } - private Environment getFilteredEnvironment(LogicalExpression formula) { + private Environment getFilteredEnvironment(LogicalExpression formula, QueryModifier queryModifier) { return new DefaultEnvironment.Builder() - .assetAdministrationShells(findAssetAdministrationShells(AssetAdministrationShellSearchCriteria.NONE, QueryModifier.DEFAULT, PagingInfo.ALL, formula).getContent()) - .submodels(findSubmodels(SubmodelSearchCriteria.NONE, QueryModifier.DEFAULT, PagingInfo.ALL, formula).getContent()) - .conceptDescriptions(findConceptDescriptions(ConceptDescriptionSearchCriteria.NONE, QueryModifier.DEFAULT, PagingInfo.ALL, formula).getContent()) + .assetAdministrationShells(findAssetAdministrationShells(AssetAdministrationShellSearchCriteria.NONE, queryModifier, PagingInfo.ALL, formula).getContent()) + .submodels(findSubmodels(SubmodelSearchCriteria.NONE, queryModifier, PagingInfo.ALL, formula).getContent()) + .conceptDescriptions(findConceptDescriptions(ConceptDescriptionSearchCriteria.NONE, queryModifier, PagingInfo.ALL, formula).getContent()) .build(); } From 2519343c62f0b76e7c6c74a452afe82c35b9b8c3 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:40:53 +0200 Subject: [PATCH 107/108] feat: output modifier if query.select --- ...yAssetAdministrationShellsRequestMapper.java | 13 ++++++++++--- .../QueryConceptDescriptionsRequestMapper.java | 13 ++++++++++--- .../QuerySubmodelsRequestMapper.java | 17 ++++++++++++----- .../provider/CustomOperationProvider.java | 4 +++- .../assetconnection/custom/util/AasHelper.java | 4 +++- .../QueryAssetAdministrationShellsRequest.java | 5 +++-- .../QueryConceptDescriptionsRequest.java | 5 +++-- .../QuerySubmodelsRequest.java | 4 ++-- 8 files changed, 46 insertions(+), 19 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java index c6aa3225f..d63f5b57e 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/aasrepository/QueryAssetAdministrationShellsRequestMapper.java @@ -18,9 +18,12 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.Content; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.OutputModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository.QueryAssetAdministrationShellsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; @@ -39,8 +42,12 @@ public QueryAssetAdministrationShellsRequestMapper(ServiceContext serviceContext @Override public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { - return QueryAssetAdministrationShellsRequest.builder() - .query(parseBody(httpRequest, Schema.class).getQuery()) - .build(); + Query query = parseBody(httpRequest, Schema.class).getQuery(); + QueryAssetAdministrationShellsRequest.Builder request = QueryAssetAdministrationShellsRequest.builder().query(query); + if (query.get$select() != null) { + // TODO need an 'id' output modifier? + request.outputModifier(new OutputModifier.Builder().content(Content.PATH).build()); + } + return request.build(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java index 6d11f0a74..9a45221e4 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/conceptdescription/QueryConceptDescriptionsRequestMapper.java @@ -18,9 +18,12 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.Content; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.OutputModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription.QueryConceptDescriptionsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; import java.util.Map; @@ -39,8 +42,12 @@ public QueryConceptDescriptionsRequestMapper(ServiceContext serviceContext) { @Override public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { - return QueryConceptDescriptionsRequest.builder() - .query(parseBody(httpRequest, Schema.class).getQuery()) - .build(); + Query query = parseBody(httpRequest, Schema.class).getQuery(); + QueryConceptDescriptionsRequest.Builder request = QueryConceptDescriptionsRequest.builder().query(query); + if (query.get$select() != null) { + // TODO need an 'id' output modifier? + request.outputModifier(new OutputModifier.Builder().content(Content.PATH).build()); + } + return request.build(); } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java index 75d2b7da8..3f0bfd5b8 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/request/mapper/submodelrepository/QuerySubmodelsRequestMapper.java @@ -17,11 +17,14 @@ import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.model.HttpRequest; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.request.mapper.AbstractRequestMapper; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.Content; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.OutputModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository.QuerySubmodelsRequest; import de.fraunhofer.iosb.ilt.faaast.service.model.exception.InvalidRequestException; import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; +import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Schema; + import java.util.Map; @@ -38,9 +41,13 @@ public QuerySubmodelsRequestMapper(ServiceContext serviceContext) { @Override - public Request doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { - return QuerySubmodelsRequest.builder() - .query(parseBody(httpRequest, Schema.class).getQuery()) - .build(); + public QuerySubmodelsRequest doParse(HttpRequest httpRequest, Map urlParameters) throws InvalidRequestException { + Query query = parseBody(httpRequest, Schema.class).getQuery(); + QuerySubmodelsRequest.Builder request = QuerySubmodelsRequest.builder().query(query); + if (query.get$select() != null) { + // TODO need an 'id' output modifier? + request.outputModifier(new OutputModifier.Builder().content(Content.PATH).build()); + } + return request.build(); } } diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java index 0e6f0ccd4..ecd57ae77 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/provider/CustomOperationProvider.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.provider; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetOperationProvider; @@ -107,7 +109,7 @@ private OperationVariable[] getOperationOutputVariables(Reference reference) thr if (reference == null) { throw new IllegalArgumentException("reference must be non-null"); } - SubmodelElement element = serviceContext.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT); + SubmodelElement element = serviceContext.getPersistence().getSubmodelElement(reference, QueryModifier.DEFAULT, identity()); if (element == null) { throw new ResourceNotFoundException(String.format("reference could not be resolved (reference: %s)", ReferenceHelper.toString(reference))); } diff --git a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/util/AasHelper.java b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/util/AasHelper.java index 118661570..e282e0017 100644 --- a/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/util/AasHelper.java +++ b/examples/assetconnection-custom/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/example/assetconnection/custom/util/AasHelper.java @@ -14,6 +14,8 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.example.assetconnection.custom.util; +import static de.fraunhofer.iosb.ilt.faaast.service.persistence.Persistence.identity; + import de.fraunhofer.iosb.ilt.faaast.service.ServiceContext; import de.fraunhofer.iosb.ilt.faaast.service.model.api.modifier.QueryModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.PagingInfo; @@ -45,7 +47,7 @@ public static Datatype getDatatype(Reference reference, ServiceContext serviceCo public static void ensureType(Reference reference, Class type, ServiceContext serviceContext) throws ResourceNotFoundException, PersistenceException { - Referable element = EnvironmentHelper.resolve(reference, serviceContext.getPersistence().getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL)); + Referable element = EnvironmentHelper.resolve(reference, serviceContext.getPersistence().getAllSubmodels(QueryModifier.MAXIMAL, PagingInfo.ALL, identity())); if (element == null) { throw new IllegalArgumentException(String.format("element could not be resolved (reference: %s)", ReferenceHelper.toString(reference))); } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java index f4b742776..3527832b7 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/aasrepository/QueryAssetAdministrationShellsRequest.java @@ -14,7 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.model.api.request.aasrepository; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifierAndPaging; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.aasrepository.QueryAssetAdministrationShellsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; @@ -62,7 +62,8 @@ public static Builder builder() { return new Builder(); } - public abstract static class AbstractBuilder> extends Request.AbstractBuilder { + public abstract static class AbstractBuilder> + extends AbstractRequestWithModifier.AbstractBuilder { public B query(Query value) { getBuildingInstance().setQuery(value); diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java index 93253d426..0f114972a 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/conceptdescription/QueryConceptDescriptionsRequest.java @@ -14,7 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.model.api.request.conceptdescription; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifierAndPaging; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.conceptdescription.QueryConceptDescriptionsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; @@ -62,7 +62,8 @@ public static Builder builder() { return new Builder(); } - public abstract static class AbstractBuilder> extends Request.AbstractBuilder { + public abstract static class AbstractBuilder> + extends AbstractRequestWithModifier.AbstractBuilder { public B query(Query value) { getBuildingInstance().setQuery(value); diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java index b1737ab8e..72dfb0859 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/api/request/submodelrepository/QuerySubmodelsRequest.java @@ -14,7 +14,7 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.model.api.request.submodelrepository; -import de.fraunhofer.iosb.ilt.faaast.service.model.api.Request; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifier; import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.AbstractRequestWithModifierAndPaging; import de.fraunhofer.iosb.ilt.faaast.service.model.api.response.submodelrepository.QuerySubmodelsResponse; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.Query; @@ -62,7 +62,7 @@ public static Builder builder() { return new Builder(); } - public abstract static class AbstractBuilder> extends Request.AbstractBuilder { + public abstract static class AbstractBuilder> extends AbstractRequestWithModifier.AbstractBuilder { public B query(Query value) { getBuildingInstance().setQuery(value); From 786e6bc1acd7428f3fb54559f8495eabc7b11f2c Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:30:20 +0200 Subject: [PATCH 108/108] feat: improve acl objects filter --- .../filter/pre/AbstractAclFilter.java | 23 ++- .../filter/pre/AclAttributeFilter.java | 6 +- .../pre/AclAttributeInjectionInterceptor.java | 6 +- .../filter/pre/AclDisabledFilter.java | 6 +- .../security/filter/pre/AclObjectsFilter.java | 147 ++++++++++++------ .../security/filter/pre/AclRightsFilter.java | 57 ++++--- .../filter/pre/AclRulesInceptionFilter.java | 2 +- 7 files changed, 157 insertions(+), 90 deletions(-) diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java index 2f6c8fb84..7dd30ba25 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AbstractAclFilter.java @@ -38,17 +38,24 @@ public abstract class AbstractAclFilter extends JwtAuthorizationFilter implement @SuppressWarnings("unchecked") @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - List acl = ((List) request.getAttribute(ACL.getName())); + Object maybeAcl = request.getAttribute(ACL.getName()); + List filtered; + if (maybeAcl != null) { + filtered = doFilter((HttpServletRequest) request, (List) maybeAcl); + } + else { + filtered = doFilter((HttpServletRequest) request, null); + } - List filtered = doFilter((HttpServletRequest) request, acl); - Objects.requireNonNull(filtered, "Filters need to return empty rule lists if none apply"); + Objects.requireNonNull(filtered, "AclFilters may not return null"); - if (!acl.isEmpty()) { + if (!filtered.isEmpty()) { request.setAttribute(ACL.getName(), filtered); chain.doFilter(request, response); } - - respondForbidden((HttpServletResponse) response); + else { + respondForbidden((HttpServletResponse) response); + } } @@ -56,10 +63,10 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha * Perform filtering of the ACL dependent on the HTTP request at hand. * * @param request The request to filter with - * @param acl The ACL to filter + * @param rules The ACL to filter * @return Filtered ACL list */ - protected abstract List doFilter(HttpServletRequest request, List acl); + protected abstract List doFilter(HttpServletRequest request, List rules); private static void respondForbidden(HttpServletResponse httpResponse) throws IOException { diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java index b07af7652..16f42792d 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeFilter.java @@ -30,10 +30,10 @@ public class AclAttributeFilter extends AbstractAclFilter { @Override - protected List doFilter(HttpServletRequest request, List acl) { + protected List doFilter(HttpServletRequest request, List rules) { Map claims = Optional.ofNullable(extractAndDecodeJwt(request).getClaims()).orElse(Map.of()); - acl.removeIf(rule -> { + rules.removeIf(rule -> { for (AttributeItem item: rule.getAcl().getAttributes()) { if (AttributeItem.Global.ANONYMOUS == item.getGlobal()) { return false; @@ -48,6 +48,6 @@ else if (item.getClaim() != null && claims.containsKey(item.getClaim())) { } return false; }); - return acl; + return rules; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeInjectionInterceptor.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeInjectionInterceptor.java index 8c8c448bc..41117200a 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeInjectionInterceptor.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclAttributeInjectionInterceptor.java @@ -27,8 +27,8 @@ */ public class AclAttributeInjectionInterceptor extends AbstractAclFilter { @Override - protected List doFilter(HttpServletRequest request, List acl) { - acl.forEach(rule -> injectLogicalExpression(rule.getFormula(), extractClaims(request))); - return acl; + protected List doFilter(HttpServletRequest request, List rules) { + rules.forEach(rule -> injectLogicalExpression(rule.getFormula(), extractClaims(request))); + return rules; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java index f8098461e..8445041a0 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclDisabledFilter.java @@ -26,8 +26,8 @@ */ public class AclDisabledFilter extends AbstractAclFilter { @Override - protected List doFilter(HttpServletRequest request, List acl) { - acl.removeIf(rule -> Acl.Access.DISABLED == rule.getAcl().getAccess()); - return acl; + protected List doFilter(HttpServletRequest request, List rules) { + rules.removeIf(rule -> Acl.Access.DISABLED == rule.getAcl().getAccess()); + return rules; } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java index 72b805b58..fd7b45ea2 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclObjectsFilter.java @@ -14,13 +14,17 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.GET; + +import de.fraunhofer.iosb.ilt.faaast.service.model.IdShortPath; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; import jakarta.servlet.http.HttpServletRequest; - import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.regex.Pattern; /** @@ -28,30 +32,41 @@ */ public class AclObjectsFilter extends AbstractAclFilter { + private static final String identifiableObjectSplitRegex = "^(?:IDENTIFIABLE\\s+)?(\\$(?:aas|sm|cd))\\s*\\(\\s*\"([^\"]*)\"\\s*\\)$"; + private static final String referableObjectSplitRegex = "^(?:REFERABLE\\s+)?(\\$sme)\\s*\\(\\s*\"([^\"]*)\"\\s*\\)\\.(.+)$"; + + private final Map identifiableToPathMapping = Map.of("$aas", "shells", + "$sm", "submodels", + "$cd", "concept-descriptions", + "$sme", "submodels"); + @Override - protected List doFilter(HttpServletRequest request, List acl) { - String path = (request).getRequestURI(); + protected List doFilter(HttpServletRequest request, List rules) { + String path = request.getRequestURI(); + String method = request.getMethod(); List filteredRules = new ArrayList<>(); - for (AccessPermissionRule rule: acl) { + for (AccessPermissionRule rule: rules) { boolean anyMatch = rule.getObjects().stream().anyMatch(objectItem -> { if (objectItem.getRoute() != null) { - String route = objectItem.getRoute(); - // Warning: potentially does not allow trailing slash at requests. - return route.contains("*") && path.startsWith(route.substring(0, route.indexOf("*"))) || route.equals(path); + return checkRoute(objectItem.getRoute(), path); } else if (objectItem.getIdentifiable() != null) { - return checkIdentifiable(path, objectItem.getIdentifiable()); + return checkIdentifiable(path, objectItem.getIdentifiable(), method); } else if (objectItem.getReferable() != null) { - return false; // TODO + return checkReferable(path, objectItem.getReferable(), method); } else if (objectItem.getFragment() != null) { - return false; // TODO + // TODO Objects to be protected are either API Routes, Identifiables (e.g. AAS or Submodel), Referables (e.g. SubmodelElements), Descriptors or + // Fragments of all those (e.g. AssetId, SemanticId, SpecificAssetId). + // -> What to do + return false; } else if (objectItem.getDescriptor() != null) { - return checkDescriptor(path, objectItem.getDescriptor()); + // don't have descriptors + return false; } return false; }); @@ -65,55 +80,91 @@ else if (objectItem.getDescriptor() != null) { } - private boolean checkIdentifiable(String path, String identifiable) { - if (!(path.startsWith("/submodels") || path.startsWith("/shells"))) { + private boolean checkRoute(String route, String requestPath) { + if (route == null) { return false; } + // Escape all regex metacharacters, then turn '*' into '.*' + String regex = "^" + Pattern.quote(route).replace("\\*", ".*") + "$"; + return Pattern.compile(regex).matcher(requestPath).matches(); + } - if ("(Submodel)*".equals(identifiable)) { - return true; + + private boolean checkIdentifiable(String identifiable, String requestPath, String method) { + // ["$aas/sm/cd", identifier] + String[] identifiableObjectSegments = identifiable.split(identifiableObjectSplitRegex); + if (identifiableObjectSegments.length != 2) { + return false; } - else if (identifiable.startsWith("(Submodel)")) { - String id = identifiable.substring(10); - return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); + + // requestPath does not lead or end with / + String[] requestPathSegments = requestPath.split("/"); + String identifiableMarker = identifiableObjectSegments[0]; + short actualResourceSegment = 0; + + if (requestPathSegments[0].equals("lookup") && requestPathSegments.length > 1) { + // /lookup/shells/{aasIdentifier} + actualResourceSegment = 1; } - if ("(AssetAdministrationShell)*".equals(identifiable)) { - return true; + + if (identifiableObjectSegments[0].equals("$sm") && requestPathSegments[0].equals("shells") && requestPathSegments.length > 2) { + // /shells/{aasIdentifier}/submodels/{submodelIdentifier} + actualResourceSegment = 2; } - else if (identifiable.startsWith("(AssetAdministrationShell)")) { - String id = identifiable.substring(26); - return path.contains(Objects.requireNonNull(EncodingHelper.base64Encode(id))); + + if (!requestPathSegments[actualResourceSegment].equals(identifiableToPathMapping.get(identifiableMarker))) { + return false; } - return false; + + return checkIdentifierInstanceOrAll(identifiableObjectSegments[1], requestPathSegments, method); } - private static boolean checkDescriptor(String path, String descriptor) { - if (descriptor.startsWith("(aasDesc)")) { - if (!path.startsWith("/shell-descriptors")) { - return false; - } - if ("(aasDesc)*".equals(descriptor)) { - return true; - } - else if (descriptor.startsWith("(aasDesc)")) { - String id = descriptor.substring(9); - return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); - } + private boolean checkReferable(String referable, String requestPath, String method) { + // ["$sme", identifier, idShortPath] + String[] referableObjectSegments = referable.split(referableObjectSplitRegex); + if (referableObjectSegments.length != 3) { + return false; } - else if (descriptor.startsWith("(smDesc)")) { - if (!path.startsWith("/submodel-descriptors")) { - return false; - } - if ("(smDesc)*".equals(descriptor)) { - return true; - } - else if (descriptor.startsWith("(smDesc)")) { - String id = descriptor.substring(8); - return path.contains(Objects.requireNonNull(EncodingHelper.base64UrlEncode(id))); - } + + // requestPath does not lead or end with / + String[] requestPathSegments = requestPath.split("/"); + String identifiableMarker = referableObjectSegments[0]; + short actualResourceSegment = 0; + + if (requestPathSegments[0].equals("shells") && requestPathSegments.length > 2) { + // /shells/{aasIdentifier}/submodels/{submodelIdentifier} + actualResourceSegment = 2; + } + + if (!requestPathSegments[actualResourceSegment].equals(identifiableToPathMapping.get(identifiableMarker))) { + return false; + } + + if (checkIdentifierInstanceOrAll(referableObjectSegments[1], requestPathSegments, method)) { + return true; } - return false; + + if (requestPathSegments.length < 4) { + // all submodel elements or more requested + return method.equalsIgnoreCase(GET.name()); + } + + // IdShortPath matches + return IdShortPath.parse(requestPathSegments[3]).equals(IdShortPath.parse(referableObjectSegments[2])); } + + private boolean checkIdentifierInstanceOrAll(String identifier, String[] requestPathSegments, String httpMethod) { + // grammar states that wildcard is surrounded by escaped quotes, identifiers are not + if (identifier.equals("\"*\"")) { + return true; + } + if (requestPathSegments.length < 2) { + // Specific identifiable permitted, all requested -> Will be filtered at persistence + // If requests tries to manipulate state, wildcard or the id to manipulate needs to be present + return httpMethod.equalsIgnoreCase(GET.name()); + } + return requestPathSegments[1].equals(Objects.requireNonNull(EncodingHelper.base64Encode(identifier))); + } } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java index 20c78bad1..ff837215b 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRightsFilter.java @@ -14,12 +14,22 @@ */ package de.fraunhofer.iosb.ilt.faaast.service.endpoint.http.security.filter.pre; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.CONNECT; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.DELETE; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.GET; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.HEAD; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.OPTIONS; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.PATCH; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.POST; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.PUT; +import static de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod.TRACE; + import de.fraunhofer.iosb.ilt.faaast.service.model.http.HttpMethod; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.AccessPermissionRule; import de.fraunhofer.iosb.ilt.faaast.service.model.query.json.RightsEnum; import jakarta.servlet.http.HttpServletRequest; - import java.util.List; +import java.util.Map; /** @@ -27,43 +37,42 @@ */ public class AclRightsFilter extends AbstractAclFilter { + private static final Map> RIGHT_TO_HTTP_METHOD_MAPPING = Map.of( + RightsEnum.CREATE, List.of(POST, PUT), + RightsEnum.READ, List.of(GET), + RightsEnum.UPDATE, List.of(PATCH, PUT), + RightsEnum.DELETE, List.of(DELETE), + RightsEnum.EXECUTE, List.of(POST), + RightsEnum.VIEW, List.of(GET), + RightsEnum.ALL, List.of(GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD, TRACE, CONNECT)); + @Override - protected List doFilter(HttpServletRequest request, List acl) { + protected List doFilter(HttpServletRequest request, List rules) { String method = request.getMethod(); - String requiredRight = isOperationRequest(method, request.getContextPath()) ? "EXECUTE" : getRequiredRight(method); - - acl.removeIf( - rule -> rule.getAcl().getRights().contains(RightsEnum.ALL) || - rule.getAcl().getRights().contains(RightsEnum.valueOf(requiredRight))); + boolean isOperation = isOperationRequest(method, request.getContextPath()); - return acl; + rules.removeIf( + rule -> rule.getAcl().getRights().stream() + .noneMatch(right -> isOperation && right == RightsEnum.EXECUTE || + RIGHT_TO_HTTP_METHOD_MAPPING.get(right).stream() + .map(Enum::name) + .anyMatch(m -> m.equalsIgnoreCase(method)))); + return rules; } private static boolean isOperationRequest(String method, String path) { // Requirements: POST and URL suffix: invoke, invoke-async, invoke/$value, invoke-async/$value - String cleanPath; + String maybeInvokeKeyword; String[] pathParts = path.split("/"); if (pathParts.length > 1 && "$value".equals(pathParts[pathParts.length - 1])) { - cleanPath = pathParts[pathParts.length - 2]; + maybeInvokeKeyword = pathParts[pathParts.length - 2]; } else { - cleanPath = pathParts[pathParts.length - 1]; + maybeInvokeKeyword = pathParts[pathParts.length - 1]; } - return HttpMethod.POST.name().equals(method) && ("invoke".equals(cleanPath) || "invoke-async".equals(cleanPath)); + return POST.name().equalsIgnoreCase(method) && ("invoke".equals(maybeInvokeKeyword) || "invoke-async".equals(maybeInvokeKeyword)); } - - - private static String getRequiredRight(String method) { - return switch (method) { - case "GET" -> "READ"; - case "POST" -> "CREATE"; - case "PUT" -> "UPDATE"; - case "DELETE" -> "DELETE"; - default -> throw new IllegalArgumentException("Unsupported method: " + method); - }; - } - } diff --git a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java index 6738201f1..af7ff3fc0 100644 --- a/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java +++ b/endpoint/http/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/http/security/filter/pre/AclRulesInceptionFilter.java @@ -39,7 +39,7 @@ public AclRulesInceptionFilter(AclRepository aclRepository) { @Override - protected List doFilter(HttpServletRequest request, List acl) { + protected List doFilter(HttpServletRequest request, List rules) { return aclRepository.getAccessPermissionRules(); } }

Status
 License defined in the pom of the dependency
GroupId:ArtifactId:Versionde.fraunhofer.iosb.ilt.faaast.service:starter:1.2.0
de.fraunhofer.iosb.ilt.faaast.service:starter:1.3.0
Scope compile