From a972761d7a7530874c306a88a3fcbee39ca118b0 Mon Sep 17 00:00:00 2001 From: fried Date: Wed, 4 Jun 2025 14:54:00 +0200 Subject: [PATCH 01/52] Adds ElasticSearch and first draft of AASQL to ES QueryDSL Converter --- .../pom.xml | 33 + .../feature/search/SearchAasRepository.java | 177 ++++ .../SearchAasRepositoryConfiguration.java | 36 + ...SearchAasRepositoryConfigurationGuard.java | 68 ++ .../search/SearchAasRepositoryFactory.java | 48 + .../search/SearchAasRepositoryFeature.java | 74 ++ .../basyx.aasrepository-http/pom.xml | 5 + .../http/AasRepositoryApiHTTPController.java | 15 + .../http/AasRepositoryHTTPApi.java | 50 ++ .../basyx.aasrepository.component/pom.xml | 4 + .../src/main/resources/application.properties | 30 +- basyx.aasrepository/pom.xml | 3 +- basyx.common/basyx.core/pom.xml | 5 + .../core/query/AccessPermissionRule.java | 208 +++++ .../digitaltwin/basyx/core/query/Acl.java | 191 ++++ .../core/query/AllAccessPermissionRules.java | 151 ++++ .../basyx/core/query/AttributeItem.java | 143 +++ .../digitaltwin/basyx/core/query/Defacl.java | 110 +++ .../basyx/core/query/Defattribute.java | 112 +++ .../basyx/core/query/Defformula.java | 110 +++ .../basyx/core/query/Defobject.java | 115 +++ .../basyx/core/query/LogicalExpression.java | 316 +++++++ .../basyx/core/query/MatchExpression.java | 262 ++++++ .../basyx/core/query/ObjectItem.java | 134 +++ .../basyx/core/query/QlSchema.java | 87 ++ .../digitaltwin/basyx/core/query/Query.java | 95 ++ .../basyx/core/query/RightsEnum.java | 52 ++ .../basyx/core/query/StringValue.java | 116 +++ .../digitaltwin/basyx/core/query/Value.java | 369 ++++++++ .../AasElasticsearchQueryConverter.java | 89 ++ .../ComparisonOperatorConverter.java | 290 ++++++ .../LogicalOperatorConverter.java | 72 ++ .../MatchExpressionConverter.java | 84 ++ .../StringOperatorConverter.java | 203 +++++ .../basyx/core/query/ql.schema.json | 849 ++++++++++++++++++ examples/BaSyxQueryLanguage/.env | 13 + examples/BaSyxQueryLanguage/README.md | 0 .../basyx/aas-discovery.properties | 11 + .../basyx/aas-env.properties | 15 + .../BaSyxQueryLanguage/basyx/aas-registry.yml | 8 + .../BaSyxQueryLanguage/basyx/sm-registry.yml | 8 + .../docker-compose-elastic.yml | 85 ++ .../BaSyxQueryLanguage/docker-compose.yml | 90 ++ pom.xml | 11 + ql.schema.json | 849 ++++++++++++++++++ 45 files changed, 5783 insertions(+), 13 deletions(-) create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AccessPermissionRule.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Acl.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AllAccessPermissionRules.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AttributeItem.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defacl.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defattribute.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defformula.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defobject.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpression.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpression.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ObjectItem.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Query.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/RightsEnum.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/StringValue.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Value.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ql.schema.json create mode 100644 examples/BaSyxQueryLanguage/.env create mode 100644 examples/BaSyxQueryLanguage/README.md create mode 100644 examples/BaSyxQueryLanguage/basyx/aas-discovery.properties create mode 100644 examples/BaSyxQueryLanguage/basyx/aas-env.properties create mode 100644 examples/BaSyxQueryLanguage/basyx/aas-registry.yml create mode 100644 examples/BaSyxQueryLanguage/basyx/sm-registry.yml create mode 100644 examples/BaSyxQueryLanguage/docker-compose-elastic.yml create mode 100644 examples/BaSyxQueryLanguage/docker-compose.yml create mode 100644 ql.schema.json diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml new file mode 100644 index 000000000..704bb17a6 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.aasrepository + ${revision} + + + basyx.aasrepository-feature-search + BaSyx AAS Repository Feature Search + Feature Search for the BaSyx AAS Repository + + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-core + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + + + + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java new file mode 100644 index 000000000..91dafc936 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class SearchAasRepository implements AasRepository { + private static final Logger logger = LoggerFactory.getLogger(SearchAasRepository.class); + private static final String ES_INDEX = "aas-index"; + private final ElasticsearchClient esclient; + + private AasRepository decorated; + + public SearchAasRepository(AasRepository decorated, ElasticsearchClient esclient) { + this.decorated = decorated; + this.esclient = esclient; + } + + @Override + public CursorResult> getAllAas(List assetIds, String idShort, PaginationInfo pInfo) { + return decorated.getAllAas(assetIds, idShort, pInfo); + } + + @Override + public AssetAdministrationShell getAas(String aasId) throws ElementDoesNotExistException { + return decorated.getAas(aasId); + } + + @Override + public void createAas(AssetAdministrationShell aas) throws CollidingIdentifierException { + decorated.createAas(aas); + indexAAS(aas); + } + + @Override + public void updateAas(String aasId, AssetAdministrationShell aas) { + decorated.updateAas(aasId, aas); + updateAASIndex(aas); + } + + @Override + public void deleteAas(String aasId) { + decorated.deleteAas(aasId); + deindexAAS(aasId); + } + + @Override + public String getName() { + return decorated.getName(); + } + + @Override + public CursorResult> getSubmodelReferences(String aasId, PaginationInfo pInfo) { + return decorated.getSubmodelReferences(aasId, pInfo); + } + + @Override + public void addSubmodelReference(String aasId, Reference submodelReference) { + decorated.addSubmodelReference(aasId, submodelReference); + reindexAAS(aasId); + } + + @Override + public void removeSubmodelReference(String aasId, String submodelId) { + decorated.removeSubmodelReference(aasId, submodelId); + reindexAAS(aasId); + } + + @Override + public void setAssetInformation(String aasId, AssetInformation aasInfo) throws ElementDoesNotExistException { + decorated.setAssetInformation(aasId, aasInfo); + reindexAAS(aasId); + } + + @Override + public AssetInformation getAssetInformation(String aasId) throws ElementDoesNotExistException { + return decorated.getAssetInformation(aasId); + } + + @Override + public File getThumbnail(String aasId) { + return decorated.getThumbnail(aasId); + } + + @Override + public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { + decorated.setThumbnail(aasId, fileName, contentType, inputStream); + reindexAAS(aasId); + } + + @Override + public void deleteThumbnail(String aasId) { + decorated.deleteThumbnail(aasId); + reindexAAS(aasId); + } + + private void indexAAS(AssetAdministrationShell aas) { + try { + esclient.create( + c -> c.index(ES_INDEX) + .id(aas.getId()) + .document(aas) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void updateAASIndex(AssetAdministrationShell aas) { + try { + esclient.update( + u -> u.index(ES_INDEX) + .id(aas.getId()) + .doc(aas), + AssetAdministrationShell.class + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void deindexAAS(String aasId) { + try { + esclient.delete( + d -> d.index(ES_INDEX) + .id(aasId) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void reindexAAS(String aasId) { + AssetAdministrationShell shell = getAas(aasId); + deindexAAS(aasId); + indexAAS(shell); + } + +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java new file mode 100644 index 000000000..b5f83dc74 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Configuration; + +@ConditionalOnExpression("#{${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Configuration +public class SearchAasRepositoryConfiguration { + +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java new file mode 100644 index 000000000..c29a04b20 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java @@ -0,0 +1,68 @@ +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Class that prints error and warning messages to inform the user about possible misconfiguration + * + * @author fried, aaronzi + */ +@Component +public class SearchAasRepositoryConfigurationGuard implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(SearchAasRepositoryConfigurationGuard.class); + + @Value("${spring.elasticsearch.uris:#{null}}") + private String elasticsearchUrl; + + @Value("${spring.elasticsearch.username:#{null}}") + private String elasticsearchUsername; + + @Value("${spring.elasticsearch.password:#{null}}") + private String elasticsearchPassword; + + @Value("${basyx.backend:#{null}}") + private String basyxBackend; + + @Override + public void afterPropertiesSet() throws Exception { + boolean error = false; + logger.info(":::::::::::::::: BaSyx Feature Search Configuration ::::::::::::::::"); + + if (elasticsearchUrl == null || elasticsearchUrl.isEmpty()) { + logger.error("Elasticsearch URL is not configured. Please set the property 'spring.elasticsearch.uris'."); + error = true; + } else { + logger.info("Elasticsearch URL: " + elasticsearchUrl); + } + + if (elasticsearchUsername == null || elasticsearchUsername.isEmpty()) { + logger.error("Elasticsearch username is not configured. Please set the property 'spring.elasticsearch.username'."); + error = true; + } else { + logger.info("Elasticsearch Username: " + elasticsearchUsername); + } + + if (elasticsearchPassword == null || elasticsearchPassword.isEmpty()) { + logger.error("Elasticsearch password is not configured. Please set the property 'spring.elasticsearch.password'."); + error = true; + } else { + logger.info("Elasticsearch Password: " + "***"); + } + + if(basyxBackend.equals("InMemory")){ + logger.error("BaSyx Backend is set to InMemory. Search feature requires a persistent backend."); + error = true; + } else { + logger.info("BaSyx Backend: " + basyxBackend); + } + + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + if(error){ + System.exit(1); + } + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java new file mode 100644 index 000000000..9df809c3b --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; + +public class SearchAasRepositoryFactory implements AasRepositoryFactory { + + private final ElasticsearchClient esclient; + private AasRepositoryFactory decorated; + + public SearchAasRepositoryFactory(AasRepositoryFactory decorated, ElasticsearchClient client) { + this.decorated = decorated; + this.esclient = client; + } + + @Override + public AasRepository create() { + return new SearchAasRepository(decorated.create(), esclient); + } + +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java new file mode 100644 index 000000000..f6e465fb4 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; +import org.eclipse.digitaltwin.basyx.aasrepository.feature.AasRepositoryFeature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +@ConditionalOnExpression("#{${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Component +public class SearchAasRepositoryFeature implements AasRepositoryFeature { + public final static String FEATURENAME = "basyx.aasrepository.feature.search"; + private final ElasticsearchClient esclient; + + @Value("#{${" + FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") + private boolean enabled; + + @Autowired + public SearchAasRepositoryFeature(ElasticsearchClient client) { + this.esclient = client; + } + + @Override + public AasRepositoryFactory decorate(AasRepositoryFactory aasServiceFactory) { + return new SearchAasRepositoryFactory(aasServiceFactory, esclient); + } + + @Override + public void initialize() { + } + + @Override + public void cleanUp() { + + } + + @Override + public String getName() { + return "AasRepository Search"; + } + + @Override + public boolean isEnabled() { + return enabled; + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-http/pom.xml b/basyx.aasrepository/basyx.aasrepository-http/pom.xml index 7f09d7aba..0e96fc1bd 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-http/pom.xml @@ -69,5 +69,10 @@ commons-io test + + org.elasticsearch + elasticsearch + 9.0.1 + diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java index 4f05139fb..ca24e3f35 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java @@ -45,10 +45,14 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingSubmodelReferenceException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.query.Query; +import org.eclipse.digitaltwin.basyx.core.query.elasticsearch.AasElasticsearchQueryConverter; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResultPagingMetadata; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.QueryBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.InputStreamResource; @@ -221,6 +225,17 @@ public ResponseEntity putThumbnailAasRepository(Base64UrlEncodedIdentifier } } + @Override + public ResponseEntity queryAssetAdministrationShells(Query query, Integer limit, Base64UrlEncodedCursor cursor) { + QueryBuilder esQuery = AasElasticsearchQueryConverter.convert(query); + + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); +// SearchResponse response = client.prepareSearch("aas-index") +// .setQuery(esQuery) +// .execute() +// .actionGet(); + } + private void closeInputStream(InputStream fileInputstream) { if (fileInputstream == null) return; diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java index 8e19779b8..2639efb6c 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java @@ -34,6 +34,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; import org.eclipse.digitaltwin.basyx.aasrepository.http.pagination.GetAssetAdministrationShellsResult; import org.eclipse.digitaltwin.basyx.aasrepository.http.pagination.GetReferencesResult; +import org.eclipse.digitaltwin.basyx.core.query.Query; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; @@ -316,4 +317,53 @@ ResponseEntity putThumbnailAasRepository( @Parameter(in = ParameterIn.DEFAULT, description = "", required = true, schema = @Schema()) @RequestParam(value = "fileName", required = true) String fileName, @Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file); + + @Operation( + summary = "Returns all Asset Administration Shells that conform to the input query", + tags = { "Asset Administration Shell Repository API" }, + operationId = "queryAssetAdministrationShells" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested Asset Administration Shells", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "403", description = "Forbidden", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) + }) + @RequestMapping( + value = "/query/shells", + produces = { "application/json" }, + consumes = { "application/json" }, + method = RequestMethod.POST + ) + ResponseEntity queryAssetAdministrationShells( + @Parameter( + description = "Query object", + required = true, + schema = @Schema(implementation = String.class) + ) + @Valid @RequestBody Query query, + + @Parameter( + in = ParameterIn.QUERY, + description = "Maximum number of results to be returned" + ) + @RequestParam(value = "limit", required = false) Integer limit, + + @Parameter( + in = ParameterIn.QUERY, + description = "Cursor for pagination" + ) + @RequestParam(value = "cursor", required = false) Base64UrlEncodedCursor cursor + ); + + } diff --git a/basyx.aasrepository/basyx.aasrepository.component/pom.xml b/basyx.aasrepository/basyx.aasrepository.component/pom.xml index 822c5b7f6..36985541a 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository.component/pom.xml @@ -63,6 +63,10 @@ org.eclipse.digitaltwin.basyx basyx.aasrepository-feature-kafka + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-search + org.eclipse.digitaltwin.basyx basyx.aasrepository-feature-kafka diff --git a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties index 202c05021..1a9922365 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties +++ b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties @@ -4,17 +4,15 @@ server.error.path=/error spring.application.name=AAS Repository basyx.aasrepo.name=aas-repo -basyx.backend = InMemory +#basyx.backend = InMemory - - -#basyx.backend = MongoDB -#spring.data.mongodb.host=127.0.0.1 -#spring.data.mongodb.port=27017 -#spring.data.mongodb.database=aas -#spring.data.mongodb.authentication-database=admin -#spring.data.mongodb.username=mongoAdmin -#spring.data.mongodb.password=mongoPassword +basyx.backend = MongoDB +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword # basyx.aasrepository.feature.mqtt.enabled = true @@ -22,8 +20,8 @@ basyx.backend = InMemory # mqtt.hostname = localhost # mqtt.port = 1883 -# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 -# basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 +basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD #################################################################################### # Authorization @@ -62,6 +60,14 @@ basyx.backend = InMemory #basyx.aasrepository.feature.discoveryintegration.authorization.client-id=workstation-1 #basyx.aasrepository.feature.discoveryintegration.authorization.client-secret=nY0mjyECF60DGzNmQUjL81XurSl8etom +#################################################################################### +# Feature: Search +#################################################################################### +basyx.aasrepository.feature.search.enabled=true +spring.elasticsearch.uris=http://localhost:9200 +spring.elasticsearch.username=elastic +spring.elasticsearch.password=vtzJFt1b + #################################################################################### # Disable the Swagger UI #################################################################################### diff --git a/basyx.aasrepository/pom.xml b/basyx.aasrepository/pom.xml index 1b20fd9bb..da29be01f 100644 --- a/basyx.aasrepository/pom.xml +++ b/basyx.aasrepository/pom.xml @@ -27,5 +27,6 @@ basyx.aasrepository-tck basyx.aasrepository.component basyx.aasrepository-client - + basyx.aasrepository-feature-search + \ No newline at end of file diff --git a/basyx.common/basyx.core/pom.xml b/basyx.common/basyx.core/pom.xml index e0c89f549..8bcdb2f60 100644 --- a/basyx.common/basyx.core/pom.xml +++ b/basyx.common/basyx.core/pom.xml @@ -35,6 +35,11 @@ junit test + + org.elasticsearch + elasticsearch + 9.0.1 + \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AccessPermissionRule.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AccessPermissionRule.java new file mode 100644 index 000000000..930c6032c --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AccessPermissionRule.java @@ -0,0 +1,208 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "ACL", + "USEACL", + "OBJECTS", + "USEOBJECTS", + "FORMULA", + "USEFORMULA", + "FRAGMENT", + "FILTER", + "USEFILTER" +}) +public class AccessPermissionRule { + + @JsonProperty("ACL") + private Acl acl; + @JsonProperty("USEACL") + private String useacl; + @JsonProperty("OBJECTS") + private List objects = new ArrayList(); + @JsonProperty("USEOBJECTS") + private List useobjects = new ArrayList(); + @JsonProperty("FORMULA") + private LogicalExpression formula; + @JsonProperty("USEFORMULA") + private String useformula; + @JsonProperty("FRAGMENT") + private String fragment; + @JsonProperty("FILTER") + private LogicalExpression filter; + @JsonProperty("USEFILTER") + private String usefilter; + + @JsonProperty("ACL") + public Acl getAcl() { + return acl; + } + + @JsonProperty("ACL") + public void setAcl(Acl acl) { + this.acl = acl; + } + + @JsonProperty("USEACL") + public String getUseacl() { + return useacl; + } + + @JsonProperty("USEACL") + public void setUseacl(String useacl) { + this.useacl = useacl; + } + + @JsonProperty("OBJECTS") + public List getObjects() { + return objects; + } + + @JsonProperty("OBJECTS") + public void setObjects(List objects) { + this.objects = objects; + } + + @JsonProperty("USEOBJECTS") + public List getUseobjects() { + return useobjects; + } + + @JsonProperty("USEOBJECTS") + public void setUseobjects(List useobjects) { + this.useobjects = useobjects; + } + + @JsonProperty("FORMULA") + public LogicalExpression getFormula() { + return formula; + } + + @JsonProperty("FORMULA") + public void setFormula(LogicalExpression formula) { + this.formula = formula; + } + + @JsonProperty("USEFORMULA") + public String getUseformula() { + return useformula; + } + + @JsonProperty("USEFORMULA") + public void setUseformula(String useformula) { + this.useformula = useformula; + } + + @JsonProperty("FRAGMENT") + public String getFragment() { + return fragment; + } + + @JsonProperty("FRAGMENT") + public void setFragment(String fragment) { + this.fragment = fragment; + } + + @JsonProperty("FILTER") + public LogicalExpression getFilter() { + return filter; + } + + @JsonProperty("FILTER") + public void setFilter(LogicalExpression filter) { + this.filter = filter; + } + + @JsonProperty("USEFILTER") + public String getUsefilter() { + return usefilter; + } + + @JsonProperty("USEFILTER") + public void setUsefilter(String usefilter) { + this.usefilter = usefilter; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(AccessPermissionRule.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("acl"); + sb.append('='); + sb.append(((this.acl == null)?"":this.acl)); + sb.append(','); + sb.append("useacl"); + sb.append('='); + sb.append(((this.useacl == null)?"":this.useacl)); + sb.append(','); + sb.append("objects"); + sb.append('='); + sb.append(((this.objects == null)?"":this.objects)); + sb.append(','); + sb.append("useobjects"); + sb.append('='); + sb.append(((this.useobjects == null)?"":this.useobjects)); + sb.append(','); + sb.append("formula"); + sb.append('='); + sb.append(((this.formula == null)?"":this.formula)); + sb.append(','); + sb.append("useformula"); + sb.append('='); + sb.append(((this.useformula == null)?"":this.useformula)); + sb.append(','); + sb.append("fragment"); + sb.append('='); + sb.append(((this.fragment == null)?"":this.fragment)); + sb.append(','); + sb.append("filter"); + sb.append('='); + sb.append(((this.filter == null)?"":this.filter)); + sb.append(','); + sb.append("usefilter"); + sb.append('='); + sb.append(((this.usefilter == null)?"":this.usefilter)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.filter == null)? 0 :this.filter.hashCode())); + result = ((result* 31)+((this.fragment == null)? 0 :this.fragment.hashCode())); + result = ((result* 31)+((this.usefilter == null)? 0 :this.usefilter.hashCode())); + result = ((result* 31)+((this.useobjects == null)? 0 :this.useobjects.hashCode())); + result = ((result* 31)+((this.objects == null)? 0 :this.objects.hashCode())); + result = ((result* 31)+((this.useacl == null)? 0 :this.useacl.hashCode())); + result = ((result* 31)+((this.formula == null)? 0 :this.formula.hashCode())); + result = ((result* 31)+((this.acl == null)? 0 :this.acl.hashCode())); + result = ((result* 31)+((this.useformula == null)? 0 :this.useformula.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof AccessPermissionRule) == false) { + return false; + } + AccessPermissionRule rhs = ((AccessPermissionRule) other); + return ((((((((((this.filter == rhs.filter)||((this.filter!= null)&&this.filter.equals(rhs.filter)))&&((this.fragment == rhs.fragment)||((this.fragment!= null)&&this.fragment.equals(rhs.fragment))))&&((this.usefilter == rhs.usefilter)||((this.usefilter!= null)&&this.usefilter.equals(rhs.usefilter))))&&((this.useobjects == rhs.useobjects)||((this.useobjects!= null)&&this.useobjects.equals(rhs.useobjects))))&&((this.objects == rhs.objects)||((this.objects!= null)&&this.objects.equals(rhs.objects))))&&((this.useacl == rhs.useacl)||((this.useacl!= null)&&this.useacl.equals(rhs.useacl))))&&((this.formula == rhs.formula)||((this.formula!= null)&&this.formula.equals(rhs.formula))))&&((this.acl == rhs.acl)||((this.acl!= null)&&this.acl.equals(rhs.acl))))&&((this.useformula == rhs.useformula)||((this.useformula!= null)&&this.useformula.equals(rhs.useformula)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Acl.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Acl.java new file mode 100644 index 000000000..d119ab11b --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Acl.java @@ -0,0 +1,191 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "ATTRIBUTES", + "USEATTRIBUTES", + "RIGHTS", + "ACCESS" +}) +public class Acl { + + @JsonProperty("ATTRIBUTES") + private List attributes = new ArrayList(); + @JsonProperty("USEATTRIBUTES") + private String useattributes; + /** + * + * (Required) + * + */ + @JsonProperty("RIGHTS") + private List rights = new ArrayList(); + /** + * + * (Required) + * + */ + @JsonProperty("ACCESS") + private Acl.Access access; + + @JsonProperty("ATTRIBUTES") + public List getAttributes() { + return attributes; + } + + @JsonProperty("ATTRIBUTES") + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + @JsonProperty("USEATTRIBUTES") + public String getUseattributes() { + return useattributes; + } + + @JsonProperty("USEATTRIBUTES") + public void setUseattributes(String useattributes) { + this.useattributes = useattributes; + } + + /** + * + * (Required) + * + */ + @JsonProperty("RIGHTS") + public List getRights() { + return rights; + } + + /** + * + * (Required) + * + */ + @JsonProperty("RIGHTS") + public void setRights(List rights) { + this.rights = rights; + } + + /** + * + * (Required) + * + */ + @JsonProperty("ACCESS") + public Acl.Access getAccess() { + return access; + } + + /** + * + * (Required) + * + */ + @JsonProperty("ACCESS") + public void setAccess(Acl.Access access) { + this.access = access; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Acl.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("attributes"); + sb.append('='); + sb.append(((this.attributes == null)?"":this.attributes)); + sb.append(','); + sb.append("useattributes"); + sb.append('='); + sb.append(((this.useattributes == null)?"":this.useattributes)); + sb.append(','); + sb.append("rights"); + sb.append('='); + sb.append(((this.rights == null)?"":this.rights)); + sb.append(','); + sb.append("access"); + sb.append('='); + sb.append(((this.access == null)?"":this.access)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.useattributes == null)? 0 :this.useattributes.hashCode())); + result = ((result* 31)+((this.attributes == null)? 0 :this.attributes.hashCode())); + result = ((result* 31)+((this.access == null)? 0 :this.access.hashCode())); + result = ((result* 31)+((this.rights == null)? 0 :this.rights.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Acl) == false) { + return false; + } + Acl rhs = ((Acl) other); + return (((((this.useattributes == rhs.useattributes)||((this.useattributes!= null)&&this.useattributes.equals(rhs.useattributes)))&&((this.attributes == rhs.attributes)||((this.attributes!= null)&&this.attributes.equals(rhs.attributes))))&&((this.access == rhs.access)||((this.access!= null)&&this.access.equals(rhs.access))))&&((this.rights == rhs.rights)||((this.rights!= null)&&this.rights.equals(rhs.rights)))); + } + + public enum Access { + + ALLOW("ALLOW"), + DISABLED("DISABLED"); + private final String value; + private final static Map CONSTANTS = new HashMap(); + + static { + for (Acl.Access c: values()) { + CONSTANTS.put(c.value, c); + } + } + + Access(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Acl.Access fromValue(String value) { + Acl.Access constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AllAccessPermissionRules.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AllAccessPermissionRules.java new file mode 100644 index 000000000..0051d3e4d --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AllAccessPermissionRules.java @@ -0,0 +1,151 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "DEFATTRIBUTES", + "DEFACLS", + "DEFOBJECTS", + "DEFFORMULAS", + "rules" +}) +public class AllAccessPermissionRules { + + @JsonProperty("DEFATTRIBUTES") + private List defattributes = new ArrayList(); + @JsonProperty("DEFACLS") + private List defacls = new ArrayList(); + @JsonProperty("DEFOBJECTS") + private List defobjects = new ArrayList(); + @JsonProperty("DEFFORMULAS") + private List defformulas = new ArrayList(); + /** + * + * (Required) + * + */ + @JsonProperty("rules") + private List rules = new ArrayList(); + + @JsonProperty("DEFATTRIBUTES") + public List getDefattributes() { + return defattributes; + } + + @JsonProperty("DEFATTRIBUTES") + public void setDefattributes(List defattributes) { + this.defattributes = defattributes; + } + + @JsonProperty("DEFACLS") + public List getDefacls() { + return defacls; + } + + @JsonProperty("DEFACLS") + public void setDefacls(List defacls) { + this.defacls = defacls; + } + + @JsonProperty("DEFOBJECTS") + public List getDefobjects() { + return defobjects; + } + + @JsonProperty("DEFOBJECTS") + public void setDefobjects(List defobjects) { + this.defobjects = defobjects; + } + + @JsonProperty("DEFFORMULAS") + public List getDefformulas() { + return defformulas; + } + + @JsonProperty("DEFFORMULAS") + public void setDefformulas(List defformulas) { + this.defformulas = defformulas; + } + + /** + * + * (Required) + * + */ + @JsonProperty("rules") + public List getRules() { + return rules; + } + + /** + * + * (Required) + * + */ + @JsonProperty("rules") + public void setRules(List rules) { + this.rules = rules; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(AllAccessPermissionRules.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("defattributes"); + sb.append('='); + sb.append(((this.defattributes == null)?"":this.defattributes)); + sb.append(','); + sb.append("defacls"); + sb.append('='); + sb.append(((this.defacls == null)?"":this.defacls)); + sb.append(','); + sb.append("defobjects"); + sb.append('='); + sb.append(((this.defobjects == null)?"":this.defobjects)); + sb.append(','); + sb.append("defformulas"); + sb.append('='); + sb.append(((this.defformulas == null)?"":this.defformulas)); + sb.append(','); + sb.append("rules"); + sb.append('='); + sb.append(((this.rules == null)?"":this.rules)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.rules == null)? 0 :this.rules.hashCode())); + result = ((result* 31)+((this.defobjects == null)? 0 :this.defobjects.hashCode())); + result = ((result* 31)+((this.defformulas == null)? 0 :this.defformulas.hashCode())); + result = ((result* 31)+((this.defattributes == null)? 0 :this.defattributes.hashCode())); + result = ((result* 31)+((this.defacls == null)? 0 :this.defacls.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof AllAccessPermissionRules) == false) { + return false; + } + AllAccessPermissionRules rhs = ((AllAccessPermissionRules) other); + return ((((((this.rules == rhs.rules)||((this.rules!= null)&&this.rules.equals(rhs.rules)))&&((this.defobjects == rhs.defobjects)||((this.defobjects!= null)&&this.defobjects.equals(rhs.defobjects))))&&((this.defformulas == rhs.defformulas)||((this.defformulas!= null)&&this.defformulas.equals(rhs.defformulas))))&&((this.defattributes == rhs.defattributes)||((this.defattributes!= null)&&this.defattributes.equals(rhs.defattributes))))&&((this.defacls == rhs.defacls)||((this.defacls!= null)&&this.defacls.equals(rhs.defacls)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AttributeItem.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AttributeItem.java new file mode 100644 index 000000000..ad05a8fb2 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AttributeItem.java @@ -0,0 +1,143 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "CLAIM", + "GLOBAL", + "REFERENCE" +}) +public class AttributeItem { + + @JsonProperty("CLAIM") + private String claim; + @JsonProperty("GLOBAL") + private AttributeItem.Global global; + @JsonProperty("REFERENCE") + private String reference; + + @JsonProperty("CLAIM") + public String getClaim() { + return claim; + } + + @JsonProperty("CLAIM") + public void setClaim(String claim) { + this.claim = claim; + } + + @JsonProperty("GLOBAL") + public AttributeItem.Global getGlobal() { + return global; + } + + @JsonProperty("GLOBAL") + public void setGlobal(AttributeItem.Global global) { + this.global = global; + } + + @JsonProperty("REFERENCE") + public String getReference() { + return reference; + } + + @JsonProperty("REFERENCE") + public void setReference(String reference) { + this.reference = reference; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(AttributeItem.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("claim"); + sb.append('='); + sb.append(((this.claim == null)?"":this.claim)); + sb.append(','); + sb.append("global"); + sb.append('='); + sb.append(((this.global == null)?"":this.global)); + sb.append(','); + sb.append("reference"); + sb.append('='); + sb.append(((this.reference == null)?"":this.reference)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.reference == null)? 0 :this.reference.hashCode())); + result = ((result* 31)+((this.claim == null)? 0 :this.claim.hashCode())); + result = ((result* 31)+((this.global == null)? 0 :this.global.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof AttributeItem) == false) { + return false; + } + AttributeItem rhs = ((AttributeItem) other); + return ((((this.reference == rhs.reference)||((this.reference!= null)&&this.reference.equals(rhs.reference)))&&((this.claim == rhs.claim)||((this.claim!= null)&&this.claim.equals(rhs.claim))))&&((this.global == rhs.global)||((this.global!= null)&&this.global.equals(rhs.global)))); + } + + public enum Global { + + LOCALNOW("LOCALNOW"), + UTCNOW("UTCNOW"), + CLIENTNOW("CLIENTNOW"), + ANONYMOUS("ANONYMOUS"); + private final String value; + private final static Map CONSTANTS = new HashMap(); + + static { + for (AttributeItem.Global c: values()) { + CONSTANTS.put(c.value, c); + } + } + + Global(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static AttributeItem.Global fromValue(String value) { + AttributeItem.Global constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defacl.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defacl.java new file mode 100644 index 000000000..e46088d7a --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defacl.java @@ -0,0 +1,110 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "acl" +}) +public class Defacl { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + /** + * + * (Required) + * + */ + @JsonProperty("acl") + private Acl acl; + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("acl") + public Acl getAcl() { + return acl; + } + + /** + * + * (Required) + * + */ + @JsonProperty("acl") + public void setAcl(Acl acl) { + this.acl = acl; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defacl.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("acl"); + sb.append('='); + sb.append(((this.acl == null)?"":this.acl)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.acl == null)? 0 :this.acl.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defacl) == false) { + return false; + } + Defacl rhs = ((Defacl) other); + return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.acl == rhs.acl)||((this.acl!= null)&&this.acl.equals(rhs.acl)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defattribute.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defattribute.java new file mode 100644 index 000000000..3ea37d5ed --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defattribute.java @@ -0,0 +1,112 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "attributes" +}) +public class Defattribute { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + /** + * + * (Required) + * + */ + @JsonProperty("attributes") + private List attributes = new ArrayList(); + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("attributes") + public List getAttributes() { + return attributes; + } + + /** + * + * (Required) + * + */ + @JsonProperty("attributes") + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defattribute.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("attributes"); + sb.append('='); + sb.append(((this.attributes == null)?"":this.attributes)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.attributes == null)? 0 :this.attributes.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defattribute) == false) { + return false; + } + Defattribute rhs = ((Defattribute) other); + return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.attributes == rhs.attributes)||((this.attributes!= null)&&this.attributes.equals(rhs.attributes)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defformula.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defformula.java new file mode 100644 index 000000000..622fccadf --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defformula.java @@ -0,0 +1,110 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "formula" +}) +public class Defformula { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + /** + * + * (Required) + * + */ + @JsonProperty("formula") + private LogicalExpression formula; + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("formula") + public LogicalExpression getFormula() { + return formula; + } + + /** + * + * (Required) + * + */ + @JsonProperty("formula") + public void setFormula(LogicalExpression formula) { + this.formula = formula; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defformula.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("formula"); + sb.append('='); + sb.append(((this.formula == null)?"":this.formula)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.formula == null)? 0 :this.formula.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defformula) == false) { + return false; + } + Defformula rhs = ((Defformula) other); + return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.formula == rhs.formula)||((this.formula!= null)&&this.formula.equals(rhs.formula)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defobject.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defobject.java new file mode 100644 index 000000000..0decc0f7b --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defobject.java @@ -0,0 +1,115 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "objects", + "USEOBJECTS" +}) +public class Defobject { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + @JsonProperty("objects") + private List objects = new ArrayList(); + @JsonProperty("USEOBJECTS") + private List useobjects = new ArrayList(); + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + @JsonProperty("objects") + public List getObjects() { + return objects; + } + + @JsonProperty("objects") + public void setObjects(List objects) { + this.objects = objects; + } + + @JsonProperty("USEOBJECTS") + public List getUseobjects() { + return useobjects; + } + + @JsonProperty("USEOBJECTS") + public void setUseobjects(List useobjects) { + this.useobjects = useobjects; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defobject.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("objects"); + sb.append('='); + sb.append(((this.objects == null)?"":this.objects)); + sb.append(','); + sb.append("useobjects"); + sb.append('='); + sb.append(((this.useobjects == null)?"":this.useobjects)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.useobjects == null)? 0 :this.useobjects.hashCode())); + result = ((result* 31)+((this.objects == null)? 0 :this.objects.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defobject) == false) { + return false; + } + Defobject rhs = ((Defobject) other); + return ((((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.useobjects == rhs.useobjects)||((this.useobjects!= null)&&this.useobjects.equals(rhs.useobjects))))&&((this.objects == rhs.objects)||((this.objects!= null)&&this.objects.equals(rhs.objects)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpression.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpression.java new file mode 100644 index 000000000..fd05570bd --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpression.java @@ -0,0 +1,316 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$and", + "$match", + "$or", + "$not", + "$eq", + "$ne", + "$gt", + "$ge", + "$lt", + "$le", + "$contains", + "$starts-with", + "$ends-with", + "$regex", + "$boolean" +}) +public class LogicalExpression { + + @JsonProperty("$and") + private List $and = new ArrayList(); + @JsonProperty("$match") + private List $match = new ArrayList(); + @JsonProperty("$or") + private List $or = new ArrayList(); + @JsonProperty("$not") + private LogicalExpression $not; + @JsonProperty("$eq") + private List $eq = new ArrayList(); + @JsonProperty("$ne") + private List $ne = new ArrayList(); + @JsonProperty("$gt") + private List $gt = new ArrayList(); + @JsonProperty("$ge") + private List $ge = new ArrayList(); + @JsonProperty("$lt") + private List $lt = new ArrayList(); + @JsonProperty("$le") + private List $le = new ArrayList(); + @JsonProperty("$contains") + private List $contains = new ArrayList(); + @JsonProperty("$starts-with") + private List $startsWith = new ArrayList(); + @JsonProperty("$ends-with") + private List $endsWith = new ArrayList(); + @JsonProperty("$regex") + private List $regex = new ArrayList(); + @JsonProperty("$boolean") + private Boolean $boolean; + + @JsonProperty("$and") + public List get$and() { + return $and; + } + + @JsonProperty("$and") + public void set$and(List $and) { + this.$and = $and; + } + + @JsonProperty("$match") + public List get$match() { + return $match; + } + + @JsonProperty("$match") + public void set$match(List $match) { + this.$match = $match; + } + + @JsonProperty("$or") + public List get$or() { + return $or; + } + + @JsonProperty("$or") + public void set$or(List $or) { + this.$or = $or; + } + + @JsonProperty("$not") + public LogicalExpression get$not() { + return $not; + } + + @JsonProperty("$not") + public void set$not(LogicalExpression $not) { + this.$not = $not; + } + + @JsonProperty("$eq") + public List get$eq() { + return $eq; + } + + @JsonProperty("$eq") + public void set$eq(List $eq) { + this.$eq = $eq; + } + + @JsonProperty("$ne") + public List get$ne() { + return $ne; + } + + @JsonProperty("$ne") + public void set$ne(List $ne) { + this.$ne = $ne; + } + + @JsonProperty("$gt") + public List get$gt() { + return $gt; + } + + @JsonProperty("$gt") + public void set$gt(List $gt) { + this.$gt = $gt; + } + + @JsonProperty("$ge") + public List get$ge() { + return $ge; + } + + @JsonProperty("$ge") + public void set$ge(List $ge) { + this.$ge = $ge; + } + + @JsonProperty("$lt") + public List get$lt() { + return $lt; + } + + @JsonProperty("$lt") + public void set$lt(List $lt) { + this.$lt = $lt; + } + + @JsonProperty("$le") + public List get$le() { + return $le; + } + + @JsonProperty("$le") + public void set$le(List $le) { + this.$le = $le; + } + + @JsonProperty("$contains") + public List get$contains() { + return $contains; + } + + @JsonProperty("$contains") + public void set$contains(List $contains) { + this.$contains = $contains; + } + + @JsonProperty("$starts-with") + public List get$startsWith() { + return $startsWith; + } + + @JsonProperty("$starts-with") + public void set$startsWith(List $startsWith) { + this.$startsWith = $startsWith; + } + + @JsonProperty("$ends-with") + public List get$endsWith() { + return $endsWith; + } + + @JsonProperty("$ends-with") + public void set$endsWith(List $endsWith) { + this.$endsWith = $endsWith; + } + + @JsonProperty("$regex") + public List get$regex() { + return $regex; + } + + @JsonProperty("$regex") + public void set$regex(List $regex) { + this.$regex = $regex; + } + + @JsonProperty("$boolean") + public Boolean get$boolean() { + return $boolean; + } + + @JsonProperty("$boolean") + public void set$boolean(Boolean $boolean) { + this.$boolean = $boolean; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(LogicalExpression.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$and"); + sb.append('='); + sb.append(((this.$and == null)?"":this.$and)); + sb.append(','); + sb.append("$match"); + sb.append('='); + sb.append(((this.$match == null)?"":this.$match)); + sb.append(','); + sb.append("$or"); + sb.append('='); + sb.append(((this.$or == null)?"":this.$or)); + sb.append(','); + sb.append("$not"); + sb.append('='); + sb.append(((this.$not == null)?"":this.$not)); + sb.append(','); + sb.append("$eq"); + sb.append('='); + sb.append(((this.$eq == null)?"":this.$eq)); + sb.append(','); + sb.append("$ne"); + sb.append('='); + sb.append(((this.$ne == null)?"":this.$ne)); + sb.append(','); + sb.append("$gt"); + sb.append('='); + sb.append(((this.$gt == null)?"":this.$gt)); + sb.append(','); + sb.append("$ge"); + sb.append('='); + sb.append(((this.$ge == null)?"":this.$ge)); + sb.append(','); + sb.append("$lt"); + sb.append('='); + sb.append(((this.$lt == null)?"":this.$lt)); + sb.append(','); + sb.append("$le"); + sb.append('='); + sb.append(((this.$le == null)?"":this.$le)); + sb.append(','); + sb.append("$contains"); + sb.append('='); + sb.append(((this.$contains == null)?"":this.$contains)); + sb.append(','); + sb.append("$startsWith"); + sb.append('='); + sb.append(((this.$startsWith == null)?"":this.$startsWith)); + sb.append(','); + sb.append("$endsWith"); + sb.append('='); + sb.append(((this.$endsWith == null)?"":this.$endsWith)); + sb.append(','); + sb.append("$regex"); + sb.append('='); + sb.append(((this.$regex == null)?"":this.$regex)); + sb.append(','); + sb.append("$boolean"); + sb.append('='); + sb.append(((this.$boolean == null)?"":this.$boolean)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); + result = ((result* 31)+((this.$and == null)? 0 :this.$and.hashCode())); + result = ((result* 31)+((this.$ge == null)? 0 :this.$ge.hashCode())); + result = ((result* 31)+((this.$endsWith == null)? 0 :this.$endsWith.hashCode())); + result = ((result* 31)+((this.$or == null)? 0 :this.$or.hashCode())); + result = ((result* 31)+((this.$regex == null)? 0 :this.$regex.hashCode())); + result = ((result* 31)+((this.$startsWith == null)? 0 :this.$startsWith.hashCode())); + result = ((result* 31)+((this.$lt == null)? 0 :this.$lt.hashCode())); + result = ((result* 31)+((this.$contains == null)? 0 :this.$contains.hashCode())); + result = ((result* 31)+((this.$eq == null)? 0 :this.$eq.hashCode())); + result = ((result* 31)+((this.$gt == null)? 0 :this.$gt.hashCode())); + result = ((result* 31)+((this.$ne == null)? 0 :this.$ne.hashCode())); + result = ((result* 31)+((this.$match == null)? 0 :this.$match.hashCode())); + result = ((result* 31)+((this.$not == null)? 0 :this.$not.hashCode())); + result = ((result* 31)+((this.$le == null)? 0 :this.$le.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof LogicalExpression) == false) { + return false; + } + LogicalExpression rhs = ((LogicalExpression) other); + return ((((((((((((((((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean)))&&((this.$and == rhs.$and)||((this.$and!= null)&&this.$and.equals(rhs.$and))))&&((this.$ge == rhs.$ge)||((this.$ge!= null)&&this.$ge.equals(rhs.$ge))))&&((this.$endsWith == rhs.$endsWith)||((this.$endsWith!= null)&&this.$endsWith.equals(rhs.$endsWith))))&&((this.$or == rhs.$or)||((this.$or!= null)&&this.$or.equals(rhs.$or))))&&((this.$regex == rhs.$regex)||((this.$regex!= null)&&this.$regex.equals(rhs.$regex))))&&((this.$startsWith == rhs.$startsWith)||((this.$startsWith!= null)&&this.$startsWith.equals(rhs.$startsWith))))&&((this.$lt == rhs.$lt)||((this.$lt!= null)&&this.$lt.equals(rhs.$lt))))&&((this.$contains == rhs.$contains)||((this.$contains!= null)&&this.$contains.equals(rhs.$contains))))&&((this.$eq == rhs.$eq)||((this.$eq!= null)&&this.$eq.equals(rhs.$eq))))&&((this.$gt == rhs.$gt)||((this.$gt!= null)&&this.$gt.equals(rhs.$gt))))&&((this.$ne == rhs.$ne)||((this.$ne!= null)&&this.$ne.equals(rhs.$ne))))&&((this.$match == rhs.$match)||((this.$match!= null)&&this.$match.equals(rhs.$match))))&&((this.$not == rhs.$not)||((this.$not!= null)&&this.$not.equals(rhs.$not))))&&((this.$le == rhs.$le)||((this.$le!= null)&&this.$le.equals(rhs.$le)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpression.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpression.java new file mode 100644 index 000000000..8233d4a36 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpression.java @@ -0,0 +1,262 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$match", + "$eq", + "$ne", + "$gt", + "$ge", + "$lt", + "$le", + "$contains", + "$starts-with", + "$ends-with", + "$regex", + "$boolean" +}) +public class MatchExpression { + + @JsonProperty("$match") + private List $match = new ArrayList(); + @JsonProperty("$eq") + private List $eq = new ArrayList(); + @JsonProperty("$ne") + private List $ne = new ArrayList(); + @JsonProperty("$gt") + private List $gt = new ArrayList(); + @JsonProperty("$ge") + private List $ge = new ArrayList(); + @JsonProperty("$lt") + private List $lt = new ArrayList(); + @JsonProperty("$le") + private List $le = new ArrayList(); + @JsonProperty("$contains") + private List $contains = new ArrayList(); + @JsonProperty("$starts-with") + private List $startsWith = new ArrayList(); + @JsonProperty("$ends-with") + private List $endsWith = new ArrayList(); + @JsonProperty("$regex") + private List $regex = new ArrayList(); + @JsonProperty("$boolean") + private Boolean $boolean; + + @JsonProperty("$match") + public List get$match() { + return $match; + } + + @JsonProperty("$match") + public void set$match(List $match) { + this.$match = $match; + } + + @JsonProperty("$eq") + public List get$eq() { + return $eq; + } + + @JsonProperty("$eq") + public void set$eq(List $eq) { + this.$eq = $eq; + } + + @JsonProperty("$ne") + public List get$ne() { + return $ne; + } + + @JsonProperty("$ne") + public void set$ne(List $ne) { + this.$ne = $ne; + } + + @JsonProperty("$gt") + public List get$gt() { + return $gt; + } + + @JsonProperty("$gt") + public void set$gt(List $gt) { + this.$gt = $gt; + } + + @JsonProperty("$ge") + public List get$ge() { + return $ge; + } + + @JsonProperty("$ge") + public void set$ge(List $ge) { + this.$ge = $ge; + } + + @JsonProperty("$lt") + public List get$lt() { + return $lt; + } + + @JsonProperty("$lt") + public void set$lt(List $lt) { + this.$lt = $lt; + } + + @JsonProperty("$le") + public List get$le() { + return $le; + } + + @JsonProperty("$le") + public void set$le(List $le) { + this.$le = $le; + } + + @JsonProperty("$contains") + public List get$contains() { + return $contains; + } + + @JsonProperty("$contains") + public void set$contains(List $contains) { + this.$contains = $contains; + } + + @JsonProperty("$starts-with") + public List get$startsWith() { + return $startsWith; + } + + @JsonProperty("$starts-with") + public void set$startsWith(List $startsWith) { + this.$startsWith = $startsWith; + } + + @JsonProperty("$ends-with") + public List get$endsWith() { + return $endsWith; + } + + @JsonProperty("$ends-with") + public void set$endsWith(List $endsWith) { + this.$endsWith = $endsWith; + } + + @JsonProperty("$regex") + public List get$regex() { + return $regex; + } + + @JsonProperty("$regex") + public void set$regex(List $regex) { + this.$regex = $regex; + } + + @JsonProperty("$boolean") + public Boolean get$boolean() { + return $boolean; + } + + @JsonProperty("$boolean") + public void set$boolean(Boolean $boolean) { + this.$boolean = $boolean; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(MatchExpression.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$match"); + sb.append('='); + sb.append(((this.$match == null)?"":this.$match)); + sb.append(','); + sb.append("$eq"); + sb.append('='); + sb.append(((this.$eq == null)?"":this.$eq)); + sb.append(','); + sb.append("$ne"); + sb.append('='); + sb.append(((this.$ne == null)?"":this.$ne)); + sb.append(','); + sb.append("$gt"); + sb.append('='); + sb.append(((this.$gt == null)?"":this.$gt)); + sb.append(','); + sb.append("$ge"); + sb.append('='); + sb.append(((this.$ge == null)?"":this.$ge)); + sb.append(','); + sb.append("$lt"); + sb.append('='); + sb.append(((this.$lt == null)?"":this.$lt)); + sb.append(','); + sb.append("$le"); + sb.append('='); + sb.append(((this.$le == null)?"":this.$le)); + sb.append(','); + sb.append("$contains"); + sb.append('='); + sb.append(((this.$contains == null)?"":this.$contains)); + sb.append(','); + sb.append("$startsWith"); + sb.append('='); + sb.append(((this.$startsWith == null)?"":this.$startsWith)); + sb.append(','); + sb.append("$endsWith"); + sb.append('='); + sb.append(((this.$endsWith == null)?"":this.$endsWith)); + sb.append(','); + sb.append("$regex"); + sb.append('='); + sb.append(((this.$regex == null)?"":this.$regex)); + sb.append(','); + sb.append("$boolean"); + sb.append('='); + sb.append(((this.$boolean == null)?"":this.$boolean)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); + result = ((result* 31)+((this.$ge == null)? 0 :this.$ge.hashCode())); + result = ((result* 31)+((this.$endsWith == null)? 0 :this.$endsWith.hashCode())); + result = ((result* 31)+((this.$regex == null)? 0 :this.$regex.hashCode())); + result = ((result* 31)+((this.$startsWith == null)? 0 :this.$startsWith.hashCode())); + result = ((result* 31)+((this.$lt == null)? 0 :this.$lt.hashCode())); + result = ((result* 31)+((this.$contains == null)? 0 :this.$contains.hashCode())); + result = ((result* 31)+((this.$eq == null)? 0 :this.$eq.hashCode())); + result = ((result* 31)+((this.$gt == null)? 0 :this.$gt.hashCode())); + result = ((result* 31)+((this.$ne == null)? 0 :this.$ne.hashCode())); + result = ((result* 31)+((this.$match == null)? 0 :this.$match.hashCode())); + result = ((result* 31)+((this.$le == null)? 0 :this.$le.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof MatchExpression) == false) { + return false; + } + MatchExpression rhs = ((MatchExpression) other); + return (((((((((((((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean)))&&((this.$ge == rhs.$ge)||((this.$ge!= null)&&this.$ge.equals(rhs.$ge))))&&((this.$endsWith == rhs.$endsWith)||((this.$endsWith!= null)&&this.$endsWith.equals(rhs.$endsWith))))&&((this.$regex == rhs.$regex)||((this.$regex!= null)&&this.$regex.equals(rhs.$regex))))&&((this.$startsWith == rhs.$startsWith)||((this.$startsWith!= null)&&this.$startsWith.equals(rhs.$startsWith))))&&((this.$lt == rhs.$lt)||((this.$lt!= null)&&this.$lt.equals(rhs.$lt))))&&((this.$contains == rhs.$contains)||((this.$contains!= null)&&this.$contains.equals(rhs.$contains))))&&((this.$eq == rhs.$eq)||((this.$eq!= null)&&this.$eq.equals(rhs.$eq))))&&((this.$gt == rhs.$gt)||((this.$gt!= null)&&this.$gt.equals(rhs.$gt))))&&((this.$ne == rhs.$ne)||((this.$ne!= null)&&this.$ne.equals(rhs.$ne))))&&((this.$match == rhs.$match)||((this.$match!= null)&&this.$match.equals(rhs.$match))))&&((this.$le == rhs.$le)||((this.$le!= null)&&this.$le.equals(rhs.$le)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ObjectItem.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ObjectItem.java new file mode 100644 index 000000000..95b101905 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ObjectItem.java @@ -0,0 +1,134 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "ROUTE", + "IDENTIFIABLE", + "REFERABLE", + "FRAGMENT", + "DESCRIPTOR" +}) +public class ObjectItem { + + @JsonProperty("ROUTE") + private String route; + @JsonProperty("IDENTIFIABLE") + private String identifiable; + @JsonProperty("REFERABLE") + private String referable; + @JsonProperty("FRAGMENT") + private String fragment; + @JsonProperty("DESCRIPTOR") + private String descriptor; + + @JsonProperty("ROUTE") + public String getRoute() { + return route; + } + + @JsonProperty("ROUTE") + public void setRoute(String route) { + this.route = route; + } + + @JsonProperty("IDENTIFIABLE") + public String getIdentifiable() { + return identifiable; + } + + @JsonProperty("IDENTIFIABLE") + public void setIdentifiable(String identifiable) { + this.identifiable = identifiable; + } + + @JsonProperty("REFERABLE") + public String getReferable() { + return referable; + } + + @JsonProperty("REFERABLE") + public void setReferable(String referable) { + this.referable = referable; + } + + @JsonProperty("FRAGMENT") + public String getFragment() { + return fragment; + } + + @JsonProperty("FRAGMENT") + public void setFragment(String fragment) { + this.fragment = fragment; + } + + @JsonProperty("DESCRIPTOR") + public String getDescriptor() { + return descriptor; + } + + @JsonProperty("DESCRIPTOR") + public void setDescriptor(String descriptor) { + this.descriptor = descriptor; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(ObjectItem.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("route"); + sb.append('='); + sb.append(((this.route == null)?"":this.route)); + sb.append(','); + sb.append("identifiable"); + sb.append('='); + sb.append(((this.identifiable == null)?"":this.identifiable)); + sb.append(','); + sb.append("referable"); + sb.append('='); + sb.append(((this.referable == null)?"":this.referable)); + sb.append(','); + sb.append("fragment"); + sb.append('='); + sb.append(((this.fragment == null)?"":this.fragment)); + sb.append(','); + sb.append("descriptor"); + sb.append('='); + sb.append(((this.descriptor == null)?"":this.descriptor)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.identifiable == null)? 0 :this.identifiable.hashCode())); + result = ((result* 31)+((this.fragment == null)? 0 :this.fragment.hashCode())); + result = ((result* 31)+((this.route == null)? 0 :this.route.hashCode())); + result = ((result* 31)+((this.referable == null)? 0 :this.referable.hashCode())); + result = ((result* 31)+((this.descriptor == null)? 0 :this.descriptor.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof ObjectItem) == false) { + return false; + } + ObjectItem rhs = ((ObjectItem) other); + return ((((((this.identifiable == rhs.identifiable)||((this.identifiable!= null)&&this.identifiable.equals(rhs.identifiable)))&&((this.fragment == rhs.fragment)||((this.fragment!= null)&&this.fragment.equals(rhs.fragment))))&&((this.route == rhs.route)||((this.route!= null)&&this.route.equals(rhs.route))))&&((this.referable == rhs.referable)||((this.referable!= null)&&this.referable.equals(rhs.referable))))&&((this.descriptor == rhs.descriptor)||((this.descriptor!= null)&&this.descriptor.equals(rhs.descriptor)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java new file mode 100644 index 000000000..76f6de9ff --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java @@ -0,0 +1,87 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * Common JSON Schema for AAS Queries and Access Rules + *

+ * This schema contains all classes that are shared between the AAS Query Language and the AAS Access Rule Language. + * + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "Query", + "AllAccessPermissionRules" +}) +public class QlSchema { + + @JsonProperty("Query") + private Query query; + @JsonProperty("AllAccessPermissionRules") + private AllAccessPermissionRules allAccessPermissionRules; + + @JsonProperty("Query") + public Query getQuery() { + return query; + } + + @JsonProperty("Query") + public void setQuery(Query query) { + this.query = query; + } + + @JsonProperty("AllAccessPermissionRules") + public AllAccessPermissionRules getAllAccessPermissionRules() { + return allAccessPermissionRules; + } + + @JsonProperty("AllAccessPermissionRules") + public void setAllAccessPermissionRules(AllAccessPermissionRules allAccessPermissionRules) { + this.allAccessPermissionRules = allAccessPermissionRules; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(QlSchema.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("query"); + sb.append('='); + sb.append(((this.query == null)?"":this.query)); + sb.append(','); + sb.append("allAccessPermissionRules"); + sb.append('='); + sb.append(((this.allAccessPermissionRules == null)?"":this.allAccessPermissionRules)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.allAccessPermissionRules == null)? 0 :this.allAccessPermissionRules.hashCode())); + result = ((result* 31)+((this.query == null)? 0 :this.query.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof QlSchema) == false) { + return false; + } + QlSchema rhs = ((QlSchema) other); + return (((this.allAccessPermissionRules == rhs.allAccessPermissionRules)||((this.allAccessPermissionRules!= null)&&this.allAccessPermissionRules.equals(rhs.allAccessPermissionRules)))&&((this.query == rhs.query)||((this.query!= null)&&this.query.equals(rhs.query)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Query.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Query.java new file mode 100644 index 000000000..a3fa4be0f --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Query.java @@ -0,0 +1,95 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$select", + "$condition" +}) +public class Query { + + @JsonProperty("$select") + private String $select; + /** + * + * (Required) + * + */ + @JsonProperty("$condition") + private LogicalExpression $condition; + + @JsonProperty("$select") + public String get$select() { + return $select; + } + + @JsonProperty("$select") + public void set$select(String $select) { + this.$select = $select; + } + + /** + * + * (Required) + * + */ + @JsonProperty("$condition") + public LogicalExpression get$condition() { + return $condition; + } + + /** + * + * (Required) + * + */ + @JsonProperty("$condition") + public void set$condition(LogicalExpression $condition) { + this.$condition = $condition; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Query.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$select"); + sb.append('='); + sb.append(((this.$select == null)?"":this.$select)); + sb.append(','); + sb.append("$condition"); + sb.append('='); + sb.append(((this.$condition == null)?"":this.$condition)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$condition == null)? 0 :this.$condition.hashCode())); + result = ((result* 31)+((this.$select == null)? 0 :this.$select.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Query) == false) { + return false; + } + Query rhs = ((Query) other); + return (((this.$condition == rhs.$condition)||((this.$condition!= null)&&this.$condition.equals(rhs.$condition)))&&((this.$select == rhs.$select)||((this.$select!= null)&&this.$select.equals(rhs.$select)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/RightsEnum.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/RightsEnum.java new file mode 100644 index 000000000..ed73c9932 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/RightsEnum.java @@ -0,0 +1,52 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum RightsEnum { + + CREATE("CREATE"), + READ("READ"), + UPDATE("UPDATE"), + DELETE("DELETE"), + EXECUTE("EXECUTE"), + VIEW("VIEW"), + ALL("ALL"), + TREE("TREE"); + private final String value; + private final static Map CONSTANTS = new HashMap(); + + static { + for (RightsEnum c: values()) { + CONSTANTS.put(c.value, c); + } + } + + RightsEnum(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static RightsEnum fromValue(String value) { + RightsEnum constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/StringValue.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/StringValue.java new file mode 100644 index 000000000..f238f4588 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/StringValue.java @@ -0,0 +1,116 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$field", + "$strVal", + "$strCast", + "$attribute" +}) +public class StringValue { + + @JsonProperty("$field") + private String $field; + @JsonProperty("$strVal") + private String $strVal; + @JsonProperty("$strCast") + private Value $strCast; + @JsonProperty("$attribute") + private AttributeItem $attribute; + + @JsonProperty("$field") + public String get$field() { + return $field; + } + + @JsonProperty("$field") + public void set$field(String $field) { + this.$field = $field; + } + + @JsonProperty("$strVal") + public String get$strVal() { + return $strVal; + } + + @JsonProperty("$strVal") + public void set$strVal(String $strVal) { + this.$strVal = $strVal; + } + + @JsonProperty("$strCast") + public Value get$strCast() { + return $strCast; + } + + @JsonProperty("$strCast") + public void set$strCast(Value $strCast) { + this.$strCast = $strCast; + } + + @JsonProperty("$attribute") + public AttributeItem get$attribute() { + return $attribute; + } + + @JsonProperty("$attribute") + public void set$attribute(AttributeItem $attribute) { + this.$attribute = $attribute; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(StringValue.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$field"); + sb.append('='); + sb.append(((this.$field == null)?"":this.$field)); + sb.append(','); + sb.append("$strVal"); + sb.append('='); + sb.append(((this.$strVal == null)?"":this.$strVal)); + sb.append(','); + sb.append("$strCast"); + sb.append('='); + sb.append(((this.$strCast == null)?"":this.$strCast)); + sb.append(','); + sb.append("$attribute"); + sb.append('='); + sb.append(((this.$attribute == null)?"":this.$attribute)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$strVal == null)? 0 :this.$strVal.hashCode())); + result = ((result* 31)+((this.$field == null)? 0 :this.$field.hashCode())); + result = ((result* 31)+((this.$strCast == null)? 0 :this.$strCast.hashCode())); + result = ((result* 31)+((this.$attribute == null)? 0 :this.$attribute.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof StringValue) == false) { + return false; + } + StringValue rhs = ((StringValue) other); + return (((((this.$strVal == rhs.$strVal)||((this.$strVal!= null)&&this.$strVal.equals(rhs.$strVal)))&&((this.$field == rhs.$field)||((this.$field!= null)&&this.$field.equals(rhs.$field))))&&((this.$strCast == rhs.$strCast)||((this.$strCast!= null)&&this.$strCast.equals(rhs.$strCast))))&&((this.$attribute == rhs.$attribute)||((this.$attribute!= null)&&this.$attribute.equals(rhs.$attribute)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Value.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Value.java new file mode 100644 index 000000000..7afe9df2d --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Value.java @@ -0,0 +1,369 @@ + +package org.eclipse.digitaltwin.basyx.core.query; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$field", + "$strVal", + "$attribute", + "$numVal", + "$hexVal", + "$dateTimeVal", + "$timeVal", + "$boolean", + "$strCast", + "$numCast", + "$hexCast", + "$boolCast", + "$dateTimeCast", + "$timeCast", + "$dayOfWeek", + "$dayOfMonth", + "$month", + "$year" +}) +public class Value { + + @JsonProperty("$field") + private String $field; + @JsonProperty("$strVal") + private String $strVal; + @JsonProperty("$attribute") + private AttributeItem $attribute; + @JsonProperty("$numVal") + private Double $numVal; + @JsonProperty("$hexVal") + private String $hexVal; + @JsonProperty("$dateTimeVal") + private Date $dateTimeVal; + @JsonProperty("$timeVal") + private String $timeVal; + @JsonProperty("$boolean") + private Boolean $boolean; + @JsonProperty("$strCast") + private Value $strCast; + @JsonProperty("$numCast") + private Value $numCast; + @JsonProperty("$hexCast") + private Value $hexCast; + @JsonProperty("$boolCast") + private Value $boolCast; + @JsonProperty("$dateTimeCast") + private Value $dateTimeCast; + @JsonProperty("$timeCast") + private Value $timeCast; + @JsonProperty("$dayOfWeek") + private Date $dayOfWeek; + @JsonProperty("$dayOfMonth") + private Date $dayOfMonth; + @JsonProperty("$month") + private Date $month; + @JsonProperty("$year") + private Date $year; + + @JsonProperty("$field") + public String get$field() { + return $field; + } + + @JsonProperty("$field") + public void set$field(String $field) { + this.$field = $field; + } + + @JsonProperty("$strVal") + public String get$strVal() { + return $strVal; + } + + @JsonProperty("$strVal") + public void set$strVal(String $strVal) { + this.$strVal = $strVal; + } + + @JsonProperty("$attribute") + public AttributeItem get$attribute() { + return $attribute; + } + + @JsonProperty("$attribute") + public void set$attribute(AttributeItem $attribute) { + this.$attribute = $attribute; + } + + @JsonProperty("$numVal") + public Double get$numVal() { + return $numVal; + } + + @JsonProperty("$numVal") + public void set$numVal(Double $numVal) { + this.$numVal = $numVal; + } + + @JsonProperty("$hexVal") + public String get$hexVal() { + return $hexVal; + } + + @JsonProperty("$hexVal") + public void set$hexVal(String $hexVal) { + this.$hexVal = $hexVal; + } + + @JsonProperty("$dateTimeVal") + public Date get$dateTimeVal() { + return $dateTimeVal; + } + + @JsonProperty("$dateTimeVal") + public void set$dateTimeVal(Date $dateTimeVal) { + this.$dateTimeVal = $dateTimeVal; + } + + @JsonProperty("$timeVal") + public String get$timeVal() { + return $timeVal; + } + + @JsonProperty("$timeVal") + public void set$timeVal(String $timeVal) { + this.$timeVal = $timeVal; + } + + @JsonProperty("$boolean") + public Boolean get$boolean() { + return $boolean; + } + + @JsonProperty("$boolean") + public void set$boolean(Boolean $boolean) { + this.$boolean = $boolean; + } + + @JsonProperty("$strCast") + public Value get$strCast() { + return $strCast; + } + + @JsonProperty("$strCast") + public void set$strCast(Value $strCast) { + this.$strCast = $strCast; + } + + @JsonProperty("$numCast") + public Value get$numCast() { + return $numCast; + } + + @JsonProperty("$numCast") + public void set$numCast(Value $numCast) { + this.$numCast = $numCast; + } + + @JsonProperty("$hexCast") + public Value get$hexCast() { + return $hexCast; + } + + @JsonProperty("$hexCast") + public void set$hexCast(Value $hexCast) { + this.$hexCast = $hexCast; + } + + @JsonProperty("$boolCast") + public Value get$boolCast() { + return $boolCast; + } + + @JsonProperty("$boolCast") + public void set$boolCast(Value $boolCast) { + this.$boolCast = $boolCast; + } + + @JsonProperty("$dateTimeCast") + public Value get$dateTimeCast() { + return $dateTimeCast; + } + + @JsonProperty("$dateTimeCast") + public void set$dateTimeCast(Value $dateTimeCast) { + this.$dateTimeCast = $dateTimeCast; + } + + @JsonProperty("$timeCast") + public Value get$timeCast() { + return $timeCast; + } + + @JsonProperty("$timeCast") + public void set$timeCast(Value $timeCast) { + this.$timeCast = $timeCast; + } + + @JsonProperty("$dayOfWeek") + public Date get$dayOfWeek() { + return $dayOfWeek; + } + + @JsonProperty("$dayOfWeek") + public void set$dayOfWeek(Date $dayOfWeek) { + this.$dayOfWeek = $dayOfWeek; + } + + @JsonProperty("$dayOfMonth") + public Date get$dayOfMonth() { + return $dayOfMonth; + } + + @JsonProperty("$dayOfMonth") + public void set$dayOfMonth(Date $dayOfMonth) { + this.$dayOfMonth = $dayOfMonth; + } + + @JsonProperty("$month") + public Date get$month() { + return $month; + } + + @JsonProperty("$month") + public void set$month(Date $month) { + this.$month = $month; + } + + @JsonProperty("$year") + public Date get$year() { + return $year; + } + + @JsonProperty("$year") + public void set$year(Date $year) { + this.$year = $year; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Value.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$field"); + sb.append('='); + sb.append(((this.$field == null)?"":this.$field)); + sb.append(','); + sb.append("$strVal"); + sb.append('='); + sb.append(((this.$strVal == null)?"":this.$strVal)); + sb.append(','); + sb.append("$attribute"); + sb.append('='); + sb.append(((this.$attribute == null)?"":this.$attribute)); + sb.append(','); + sb.append("$numVal"); + sb.append('='); + sb.append(((this.$numVal == null)?"":this.$numVal)); + sb.append(','); + sb.append("$hexVal"); + sb.append('='); + sb.append(((this.$hexVal == null)?"":this.$hexVal)); + sb.append(','); + sb.append("$dateTimeVal"); + sb.append('='); + sb.append(((this.$dateTimeVal == null)?"":this.$dateTimeVal)); + sb.append(','); + sb.append("$timeVal"); + sb.append('='); + sb.append(((this.$timeVal == null)?"":this.$timeVal)); + sb.append(','); + sb.append("$boolean"); + sb.append('='); + sb.append(((this.$boolean == null)?"":this.$boolean)); + sb.append(','); + sb.append("$strCast"); + sb.append('='); + sb.append(((this.$strCast == null)?"":this.$strCast)); + sb.append(','); + sb.append("$numCast"); + sb.append('='); + sb.append(((this.$numCast == null)?"":this.$numCast)); + sb.append(','); + sb.append("$hexCast"); + sb.append('='); + sb.append(((this.$hexCast == null)?"":this.$hexCast)); + sb.append(','); + sb.append("$boolCast"); + sb.append('='); + sb.append(((this.$boolCast == null)?"":this.$boolCast)); + sb.append(','); + sb.append("$dateTimeCast"); + sb.append('='); + sb.append(((this.$dateTimeCast == null)?"":this.$dateTimeCast)); + sb.append(','); + sb.append("$timeCast"); + sb.append('='); + sb.append(((this.$timeCast == null)?"":this.$timeCast)); + sb.append(','); + sb.append("$dayOfWeek"); + sb.append('='); + sb.append(((this.$dayOfWeek == null)?"":this.$dayOfWeek)); + sb.append(','); + sb.append("$dayOfMonth"); + sb.append('='); + sb.append(((this.$dayOfMonth == null)?"":this.$dayOfMonth)); + sb.append(','); + sb.append("$month"); + sb.append('='); + sb.append(((this.$month == null)?"":this.$month)); + sb.append(','); + sb.append("$year"); + sb.append('='); + sb.append(((this.$year == null)?"":this.$year)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$strVal == null)? 0 :this.$strVal.hashCode())); + result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); + result = ((result* 31)+((this.$attribute == null)? 0 :this.$attribute.hashCode())); + result = ((result* 31)+((this.$strCast == null)? 0 :this.$strCast.hashCode())); + result = ((result* 31)+((this.$numVal == null)? 0 :this.$numVal.hashCode())); + result = ((result* 31)+((this.$dateTimeVal == null)? 0 :this.$dateTimeVal.hashCode())); + result = ((result* 31)+((this.$timeVal == null)? 0 :this.$timeVal.hashCode())); + result = ((result* 31)+((this.$field == null)? 0 :this.$field.hashCode())); + result = ((result* 31)+((this.$dayOfWeek == null)? 0 :this.$dayOfWeek.hashCode())); + result = ((result* 31)+((this.$boolCast == null)? 0 :this.$boolCast.hashCode())); + result = ((result* 31)+((this.$hexCast == null)? 0 :this.$hexCast.hashCode())); + result = ((result* 31)+((this.$dayOfMonth == null)? 0 :this.$dayOfMonth.hashCode())); + result = ((result* 31)+((this.$year == null)? 0 :this.$year.hashCode())); + result = ((result* 31)+((this.$hexVal == null)? 0 :this.$hexVal.hashCode())); + result = ((result* 31)+((this.$timeCast == null)? 0 :this.$timeCast.hashCode())); + result = ((result* 31)+((this.$dateTimeCast == null)? 0 :this.$dateTimeCast.hashCode())); + result = ((result* 31)+((this.$month == null)? 0 :this.$month.hashCode())); + result = ((result* 31)+((this.$numCast == null)? 0 :this.$numCast.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Value) == false) { + return false; + } + Value rhs = ((Value) other); + return (((((((((((((((((((this.$strVal == rhs.$strVal)||((this.$strVal!= null)&&this.$strVal.equals(rhs.$strVal)))&&((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean))))&&((this.$attribute == rhs.$attribute)||((this.$attribute!= null)&&this.$attribute.equals(rhs.$attribute))))&&((this.$strCast == rhs.$strCast)||((this.$strCast!= null)&&this.$strCast.equals(rhs.$strCast))))&&((this.$numVal == rhs.$numVal)||((this.$numVal!= null)&&this.$numVal.equals(rhs.$numVal))))&&((this.$dateTimeVal == rhs.$dateTimeVal)||((this.$dateTimeVal!= null)&&this.$dateTimeVal.equals(rhs.$dateTimeVal))))&&((this.$timeVal == rhs.$timeVal)||((this.$timeVal!= null)&&this.$timeVal.equals(rhs.$timeVal))))&&((this.$field == rhs.$field)||((this.$field!= null)&&this.$field.equals(rhs.$field))))&&((this.$dayOfWeek == rhs.$dayOfWeek)||((this.$dayOfWeek!= null)&&this.$dayOfWeek.equals(rhs.$dayOfWeek))))&&((this.$boolCast == rhs.$boolCast)||((this.$boolCast!= null)&&this.$boolCast.equals(rhs.$boolCast))))&&((this.$hexCast == rhs.$hexCast)||((this.$hexCast!= null)&&this.$hexCast.equals(rhs.$hexCast))))&&((this.$dayOfMonth == rhs.$dayOfMonth)||((this.$dayOfMonth!= null)&&this.$dayOfMonth.equals(rhs.$dayOfMonth))))&&((this.$year == rhs.$year)||((this.$year!= null)&&this.$year.equals(rhs.$year))))&&((this.$hexVal == rhs.$hexVal)||((this.$hexVal!= null)&&this.$hexVal.equals(rhs.$hexVal))))&&((this.$timeCast == rhs.$timeCast)||((this.$timeCast!= null)&&this.$timeCast.equals(rhs.$timeCast))))&&((this.$dateTimeCast == rhs.$dateTimeCast)||((this.$dateTimeCast!= null)&&this.$dateTimeCast.equals(rhs.$dateTimeCast))))&&((this.$month == rhs.$month)||((this.$month!= null)&&this.$month.equals(rhs.$month))))&&((this.$numCast == rhs.$numCast)||((this.$numCast!= null)&&this.$numCast.equals(rhs.$numCast)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java new file mode 100644 index 000000000..c54923d50 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java @@ -0,0 +1,89 @@ +package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; + +import org.eclipse.digitaltwin.basyx.core.query.*; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; + +/** + * Converter to transform AAS query structures to Elasticsearch QueryBuilder objects + * + * This class provides functionality to convert Query objects from the AAS query language + * to Elasticsearch's QueryBuilder format for executing searches in Elasticsearch. + */ +public class AasElasticsearchQueryConverter { + + /** + * Converts an AAS Query object to an Elasticsearch QueryBuilder + * + * @param query The AAS Query to convert + * @return Elasticsearch QueryBuilder object + */ + public static QueryBuilder convert(Query query) { + if (query == null) { + return null; + } + + // The main query condition is mandatory according to the schema + return convertLogicalExpression(query.get$condition()); + } + + /** + * Converts a LogicalExpression to an Elasticsearch QueryBuilder + * + * @param expression The logical expression to convert + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertLogicalExpression(LogicalExpression expression) { + if (expression == null) { + return null; + } + + // Handle logical operators + if (!expression.get$and().isEmpty()) { + return LogicalOperatorConverter.convertAndExpression(expression.get$and()); + } else if (!expression.get$or().isEmpty()) { + return LogicalOperatorConverter.convertOrExpression(expression.get$or()); + } else if (expression.get$not() != null) { + return LogicalOperatorConverter.convertNotExpression(expression.get$not()); + } + + // Handle match expressions embedded directly in logical expression + else if (!expression.get$match().isEmpty()) { + return MatchExpressionConverter.convertMatchExpressions(expression.get$match()); + } + + // Handle comparison operators + else if (!expression.get$eq().isEmpty()) { + return ComparisonOperatorConverter.convertEqualsExpression(expression.get$eq()); + } else if (!expression.get$ne().isEmpty()) { + return ComparisonOperatorConverter.convertNotEqualsExpression(expression.get$ne()); + } else if (!expression.get$gt().isEmpty()) { + return ComparisonOperatorConverter.convertGreaterThanExpression(expression.get$gt()); + } else if (!expression.get$ge().isEmpty()) { + return ComparisonOperatorConverter.convertGreaterThanOrEqualsExpression(expression.get$ge()); + } else if (!expression.get$lt().isEmpty()) { + return ComparisonOperatorConverter.convertLessThanExpression(expression.get$lt()); + } else if (!expression.get$le().isEmpty()) { + return ComparisonOperatorConverter.convertLessThanOrEqualsExpression(expression.get$le()); + } + + // Handle string operators + else if (!expression.get$contains().isEmpty()) { + return StringOperatorConverter.convertContainsExpression(expression.get$contains()); + } else if (!expression.get$startsWith().isEmpty()) { + return StringOperatorConverter.convertStartsWithExpression(expression.get$startsWith()); + } else if (!expression.get$endsWith().isEmpty()) { + return StringOperatorConverter.convertEndsWithExpression(expression.get$endsWith()); + } else if (!expression.get$regex().isEmpty()) { + return StringOperatorConverter.convertRegexExpression(expression.get$regex()); + } + + // Handle boolean + else if (expression.get$boolean() != null) { + return QueryBuilders.matchQuery("_exists_", expression.get$boolean()); + } + + // Default case: return a match all query + return QueryBuilders.matchAllQuery(); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java new file mode 100644 index 000000000..6bd926e80 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java @@ -0,0 +1,290 @@ +package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; + +import java.util.Date; +import java.util.List; +import org.eclipse.digitaltwin.basyx.core.query.Value; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.RangeQueryBuilder; + +/** + * Converter for comparison operators (=, !=, >, >=, <, <=) from AAS query structure to Elasticsearch queries + */ +public class ComparisonOperatorConverter { + + /** + * Converts an equals expression to an Elasticsearch term query or script query for field-to-field comparison + * + * @param values List of values to compare + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertEqualsExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + Value value1 = values.get(0); + Value value2 = values.get(1); + + // Check if both values are field references (field-to-field comparison) + if (value1.get$field() != null && value2.get$field() != null) { + String field1 = normalizeFieldName(value1.get$field()); + String field2 = normalizeFieldName(value2.get$field()); + + // Create a script query to compare the two fields + String scriptSource = "doc['" + field1 + ".keyword'].value == doc['" + field2 + ".keyword'].value"; + return QueryBuilders.scriptQuery( + new org.elasticsearch.script.Script(scriptSource) + ); + } + + // Handle regular field-to-value comparison + String fieldName = extractFieldName(value1, value2); + Object fieldValue = extractFieldValue(value1, value2); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Use term query for exact matches + return QueryBuilders.termQuery(fieldName, fieldValue); + } + + /** + * Converts a not equals expression to an Elasticsearch query + * + * @param values List of values to compare + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertNotEqualsExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + QueryBuilder equalsQuery = convertEqualsExpression(values); + + BoolQueryBuilder notQuery = QueryBuilders.boolQuery(); + notQuery.mustNot(equalsQuery); + + return notQuery; + } + + /** + * Converts a greater than expression to an Elasticsearch range query + * + * @param values List of values to compare + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertGreaterThanExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractFieldName(values.get(0), values.get(1)); + Object fieldValue = extractFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Create range query with gt + RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); + rangeQuery.gt(fieldValue); + + return rangeQuery; + } + + /** + * Converts a greater than or equals expression to an Elasticsearch range query + * + * @param values List of values to compare + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertGreaterThanOrEqualsExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractFieldName(values.get(0), values.get(1)); + Object fieldValue = extractFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Create range query with gte + RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); + rangeQuery.gte(fieldValue); + + return rangeQuery; + } + + /** + * Converts a less than expression to an Elasticsearch range query + * + * @param values List of values to compare + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertLessThanExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractFieldName(values.get(0), values.get(1)); + Object fieldValue = extractFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Create range query with lt + RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); + rangeQuery.lt(fieldValue); + + return rangeQuery; + } + + /** + * Converts a less than or equals expression to an Elasticsearch range query + * + * @param values List of values to compare + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertLessThanOrEqualsExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractFieldName(values.get(0), values.get(1)); + Object fieldValue = extractFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Create range query with lte + RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); + rangeQuery.lte(fieldValue); + + return rangeQuery; + } + + /** + * Helper method to extract field name from a pair of values + * + * @param value1 First value + * @param value2 Second value + * @return The field name or null if not found + */ + public static String extractFieldName(Value value1, Value value2) { + // Check if the first value has a field name + if (value1.get$field() != null) { + return normalizeFieldName(value1.get$field()); + } + + // Check if the second value has a field name + if (value2.get$field() != null) { + return normalizeFieldName(value2.get$field()); + } + + return null; + } + + /** + * Helper method to extract field value from a pair of values + * + * @param value1 First value + * @param value2 Second value + * @return The field value or null if not found + */ + public static Object extractFieldValue(Value value1, Value value2) { + // If first value is a field reference, then second value should contain the value + if (value1.get$field() != null) { + return extractValueFromValue(value2); + } + + // If second value is a field reference, then first value should contain the value + if (value2.get$field() != null) { + return extractValueFromValue(value1); + } + + return null; + } + + /** + * Extract actual value from Value object + * + * @param value The Value object + * @return The extracted value as an Object + */ + private static Object extractValueFromValue(Value value) { + if (value.get$strVal() != null) { + return value.get$strVal(); + } else if (value.get$numVal() != null) { + return value.get$numVal(); + } else if (value.get$dateTimeVal() != null) { + return value.get$dateTimeVal(); + } else if (value.get$timeVal() != null) { + return value.get$timeVal(); + } else if (value.get$hexVal() != null) { + return value.get$hexVal(); + } else if (value.get$boolean() != null) { + return value.get$boolean(); + } + + // Handle casting if needed + if (value.get$strCast() != null) { + Object innerValue = extractValueFromValue(value.get$strCast()); + if (innerValue != null) { + return String.valueOf(innerValue); + } + } else if (value.get$numCast() != null) { + Object innerValue = extractValueFromValue(value.get$numCast()); + if (innerValue != null && innerValue instanceof String) { + try { + return Double.parseDouble((String) innerValue); + } catch (NumberFormatException e) { + return null; + } + } + } else if (value.get$dateTimeCast() != null) { + // Date handling would be more complex, simplified here + return null; + } + + return null; + } + + /** + * Normalize field name for Elasticsearch + * Handles the special AAS field patterns by removing type prefixes + * + * @param fieldName The AAS field name + * @return Normalized field name for Elasticsearch + */ + private static String normalizeFieldName(String fieldName) { + // Handle AAS specific field patterns by removing the type prefixes completely + if (fieldName.startsWith("$aas#")) { + return fieldName.substring(5); // Remove the "$aas#" prefix + } else if (fieldName.startsWith("$sm#")) { + return fieldName.substring(4); // Remove the "$sm#" prefix + } else if (fieldName.startsWith("$sme")) { + // For $sme, extract everything after the '#' if present + int hashIndex = fieldName.indexOf('#'); + if (hashIndex != -1) { + return fieldName.substring(hashIndex + 1); + } + return fieldName.substring(4); // Remove the "$sme" prefix if no # found + } else if (fieldName.startsWith("$cd#")) { + return fieldName.substring(4); // Remove the "$cd#" prefix + } else if (fieldName.startsWith("$aasdesc#")) { + return fieldName.substring(9); // Remove the "$aasdesc#" prefix + } else if (fieldName.startsWith("$smdesc#")) { + return fieldName.substring(8); // Remove the "$smdesc#" prefix + } + + // Replace dots with underscores for Elasticsearch compatibility + return fieldName.replace(".", "_"); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java new file mode 100644 index 000000000..a532fbd8b --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java @@ -0,0 +1,72 @@ +package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; + +import java.util.List; +import org.eclipse.digitaltwin.basyx.core.query.LogicalExpression; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; + +/** + * Converter for logical operators (AND, OR, NOT) from AAS query structure to Elasticsearch queries + */ +public class LogicalOperatorConverter { + + /** + * Converts a list of logical expressions connected with AND to an Elasticsearch query + * + * @param expressions List of expressions to be combined with AND + * @return Elasticsearch QueryBuilder representing the AND operation + */ + public static QueryBuilder convertAndExpression(List expressions) { + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + + for (LogicalExpression expr : expressions) { + QueryBuilder convertedExpr = AasElasticsearchQueryConverter.convertLogicalExpression(expr); + if (convertedExpr != null) { + boolQuery.must(convertedExpr); + } + } + + return boolQuery; + } + + /** + * Converts a list of logical expressions connected with OR to an Elasticsearch query + * + * @param expressions List of expressions to be combined with OR + * @return Elasticsearch QueryBuilder representing the OR operation + */ + public static QueryBuilder convertOrExpression(List expressions) { + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + + for (LogicalExpression expr : expressions) { + QueryBuilder convertedExpr = AasElasticsearchQueryConverter.convertLogicalExpression(expr); + if (convertedExpr != null) { + boolQuery.should(convertedExpr); + } + } + + // Ensure at least one should clause matches + boolQuery.minimumShouldMatch(1); + + return boolQuery; + } + + /** + * Converts a NOT expression to an Elasticsearch query + * + * @param expression The expression to negate + * @return Elasticsearch QueryBuilder representing the NOT operation + */ + public static QueryBuilder convertNotExpression(LogicalExpression expression) { + QueryBuilder convertedExpr = AasElasticsearchQueryConverter.convertLogicalExpression(expression); + + if (convertedExpr != null) { + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + boolQuery.mustNot(convertedExpr); + return boolQuery; + } + + return QueryBuilders.matchAllQuery(); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java new file mode 100644 index 000000000..cea79a5b2 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java @@ -0,0 +1,84 @@ +package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; + +import java.util.List; +import org.eclipse.digitaltwin.basyx.core.query.MatchExpression; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; + +/** + * Converter for Match expressions from AAS query structure to Elasticsearch queries + */ +public class MatchExpressionConverter { + + /** + * Converts a list of match expressions to an Elasticsearch query. + * Match expressions in a list are implicitly AND'ed together. + * + * @param expressions List of match expressions + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertMatchExpressions(List expressions) { + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + + for (MatchExpression expr : expressions) { + QueryBuilder convertedExpr = convertMatchExpression(expr); + if (convertedExpr != null) { + boolQuery.must(convertedExpr); + } + } + + return boolQuery; + } + + /** + * Converts a single match expression to an Elasticsearch query + * + * @param expression The match expression to convert + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertMatchExpression(MatchExpression expression) { + if (expression == null) { + return null; + } + + // Handle nested match expressions + if (!expression.get$match().isEmpty()) { + return convertMatchExpressions(expression.get$match()); + } + + // Handle comparison operators + if (!expression.get$eq().isEmpty()) { + return ComparisonOperatorConverter.convertEqualsExpression(expression.get$eq()); + } else if (!expression.get$ne().isEmpty()) { + return ComparisonOperatorConverter.convertNotEqualsExpression(expression.get$ne()); + } else if (!expression.get$gt().isEmpty()) { + return ComparisonOperatorConverter.convertGreaterThanExpression(expression.get$gt()); + } else if (!expression.get$ge().isEmpty()) { + return ComparisonOperatorConverter.convertGreaterThanOrEqualsExpression(expression.get$ge()); + } else if (!expression.get$lt().isEmpty()) { + return ComparisonOperatorConverter.convertLessThanExpression(expression.get$lt()); + } else if (!expression.get$le().isEmpty()) { + return ComparisonOperatorConverter.convertLessThanOrEqualsExpression(expression.get$le()); + } + + // Handle string operators + else if (!expression.get$contains().isEmpty()) { + return StringOperatorConverter.convertContainsExpression(expression.get$contains()); + } else if (!expression.get$startsWith().isEmpty()) { + return StringOperatorConverter.convertStartsWithExpression(expression.get$startsWith()); + } else if (!expression.get$endsWith().isEmpty()) { + return StringOperatorConverter.convertEndsWithExpression(expression.get$endsWith()); + } else if (!expression.get$regex().isEmpty()) { + return StringOperatorConverter.convertRegexExpression(expression.get$regex()); + } + + // Handle boolean + else if (expression.get$boolean() != null) { + return QueryBuilders.termQuery("_exists_", expression.get$boolean()); + } + + // Default case + return QueryBuilders.matchAllQuery(); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java new file mode 100644 index 000000000..2239d0932 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java @@ -0,0 +1,203 @@ +package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; + +import java.util.List; +import org.eclipse.digitaltwin.basyx.core.query.StringValue; +import org.eclipse.digitaltwin.basyx.core.query.Value; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.WildcardQueryBuilder; +import org.elasticsearch.index.query.RegexpQueryBuilder; + +/** + * Converter for string operations (contains, starts-with, ends-with, regex) from AAS query structure to Elasticsearch queries + */ +public class StringOperatorConverter { + + /** + * Converts a contains expression to an Elasticsearch wildcard query + * + * @param values List of string values to check + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertContainsExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractStringFieldName(values.get(0), values.get(1)); + String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Use wildcard query with * before and after to represent "contains" + return QueryBuilders.wildcardQuery(fieldName, "*" + fieldValue + "*"); + } + + /** + * Converts a starts-with expression to an Elasticsearch prefix query + * + * @param values List of string values to check + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertStartsWithExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractStringFieldName(values.get(0), values.get(1)); + String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Use prefix query for "starts with" + return QueryBuilders.prefixQuery(fieldName, fieldValue); + } + + /** + * Converts an ends-with expression to an Elasticsearch wildcard query + * + * @param values List of string values to check + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertEndsWithExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractStringFieldName(values.get(0), values.get(1)); + String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Use wildcard query with * at beginning to represent "ends with" + return QueryBuilders.wildcardQuery(fieldName, "*" + fieldValue); + } + + /** + * Converts a regex expression to an Elasticsearch regexp query + * + * @param values List of string values to check + * @return Elasticsearch QueryBuilder + */ + public static QueryBuilder convertRegexExpression(List values) { + if (values.size() != 2) { + return QueryBuilders.matchAllQuery(); + } + + String fieldName = extractStringFieldName(values.get(0), values.get(1)); + String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); + + if (fieldName == null || fieldValue == null) { + return QueryBuilders.matchAllQuery(); + } + + // Use regexp query for regex matching + return QueryBuilders.regexpQuery(fieldName, fieldValue); + } + + /** + * Helper method to extract field name from a pair of string values + * + * @param value1 First string value + * @param value2 Second string value + * @return The field name or null if not found + */ + private static String extractStringFieldName(StringValue value1, StringValue value2) { + // Check if the first value has a field name + if (value1.get$field() != null) { + return normalizeFieldName(value1.get$field()); + } + + // Check if the second value has a field name + if (value2.get$field() != null) { + return normalizeFieldName(value2.get$field()); + } + + // Handle strCast field if present + if (value1.get$strCast() != null && value1.get$strCast().get$field() != null) { + return normalizeFieldName(value1.get$strCast().get$field()); + } + + if (value2.get$strCast() != null && value2.get$strCast().get$field() != null) { + return normalizeFieldName(value2.get$strCast().get$field()); + } + + return null; + } + + /** + * Helper method to extract field value from a pair of string values + * + * @param value1 First string value + * @param value2 Second string value + * @return The field value or null if not found + */ + private static String extractStringFieldValue(StringValue value1, StringValue value2) { + // If first value is a field reference, then second value should contain the value + if (value1.get$field() != null || (value1.get$strCast() != null && value1.get$strCast().get$field() != null)) { + return extractValueFromStringValue(value2); + } + + // If second value is a field reference, then first value should contain the value + if (value2.get$field() != null || (value2.get$strCast() != null && value2.get$strCast().get$field() != null)) { + return extractValueFromStringValue(value1); + } + + return null; + } + + /** + * Extract actual string value from StringValue object + * + * @param value The StringValue object + * @return The extracted string value + */ + private static String extractValueFromStringValue(StringValue value) { + if (value.get$strVal() != null) { + return value.get$strVal(); + } + + // Handle strCast if present + if (value.get$strCast() != null) { + Value castedValue = value.get$strCast(); + if (castedValue.get$strVal() != null) { + return castedValue.get$strVal(); + } else if (castedValue.get$numVal() != null) { + return String.valueOf(castedValue.get$numVal()); + } else if (castedValue.get$boolean() != null) { + return String.valueOf(castedValue.get$boolean()); + } + } + + // Handle attribute reference if present + if (value.get$attribute() != null) { + if (value.get$attribute().getClaim() != null) { + return value.get$attribute().getClaim(); + } else if (value.get$attribute().getReference() != null) { + return value.get$attribute().getReference(); + } + } + + return null; + } + + /** + * Normalize field name for Elasticsearch + * Handles the special AAS field patterns + * + * @param fieldName The AAS field name + * @return Normalized field name for Elasticsearch + */ + private static String normalizeFieldName(String fieldName) { + // Reuse the normalization logic from ComparisonOperatorConverter + return ComparisonOperatorConverter.extractFieldName( + new Value() {{ set$field(fieldName); }}, + new Value()); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ql.schema.json b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ql.schema.json new file mode 100644 index 000000000..ce5fc1753 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ql.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/examples/BaSyxQueryLanguage/.env b/examples/BaSyxQueryLanguage/.env new file mode 100644 index 000000000..1ed0c159d --- /dev/null +++ b/examples/BaSyxQueryLanguage/.env @@ -0,0 +1,13 @@ +START_LOCAL_VERSION=0.9.0 +ES_LOCAL_VERSION=9.0.1 +ES_LOCAL_CONTAINER_NAME=es-local-dev +ES_LOCAL_PASSWORD=vtzJFt1b +ES_LOCAL_PORT=9200 +ES_LOCAL_URL=http://localhost:${ES_LOCAL_PORT} +ES_LOCAL_HEAP_INIT=128m +ES_LOCAL_HEAP_MAX=2g +ES_LOCAL_DISK_SPACE_REQUIRED=1gb +KIBANA_LOCAL_CONTAINER_NAME=kibana-local-dev +KIBANA_LOCAL_PORT=5601 +KIBANA_LOCAL_PASSWORD=088WlnyQ +KIBANA_ENCRYPTION_KEY=HX1DOnbuaJmSQYOspJMnnMtVl3hRITx2 diff --git a/examples/BaSyxQueryLanguage/README.md b/examples/BaSyxQueryLanguage/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/examples/BaSyxQueryLanguage/basyx/aas-discovery.properties b/examples/BaSyxQueryLanguage/basyx/aas-discovery.properties new file mode 100644 index 000000000..5c9e45343 --- /dev/null +++ b/examples/BaSyxQueryLanguage/basyx/aas-discovery.properties @@ -0,0 +1,11 @@ +server.port=8081 +spring.application.name=AAS Discovery Service +basyx.aasdiscoveryservice.name=aas-discovery-service +basyx.backend=MongoDB +basyx.cors.allowed-origins=* +basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +spring.data.mongodb.host=mongo +spring.data.mongodb.database=aas-discovery +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword diff --git a/examples/BaSyxQueryLanguage/basyx/aas-env.properties b/examples/BaSyxQueryLanguage/basyx/aas-env.properties new file mode 100644 index 000000000..2ed0983e2 --- /dev/null +++ b/examples/BaSyxQueryLanguage/basyx/aas-env.properties @@ -0,0 +1,15 @@ +server.port=8081 +basyx.backend=MongoDB +basyx.environment=file:aas +basyx.cors.allowed-origins=* +basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +basyx.aasrepository.feature.registryintegration=http://aas-registry:8080 +basyx.submodelrepository.feature.registryintegration=http://sm-registry:8080 +basyx.externalurl=http://localhost:8081 +spring.servlet.multipart.max-file-size=500MB +spring.servlet.multipart.max-request-size=500MB +spring.data.mongodb.host=mongo +spring.data.mongodb.database=aas-env +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword diff --git a/examples/BaSyxQueryLanguage/basyx/aas-registry.yml b/examples/BaSyxQueryLanguage/basyx/aas-registry.yml new file mode 100644 index 000000000..fb071597c --- /dev/null +++ b/examples/BaSyxQueryLanguage/basyx/aas-registry.yml @@ -0,0 +1,8 @@ +basyx: + cors: + allowed-origins: '*' + allowed-methods: GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +spring: + data: + mongodb: + uri: mongodb://mongoAdmin:mongoPassword@mongo:27017 diff --git a/examples/BaSyxQueryLanguage/basyx/sm-registry.yml b/examples/BaSyxQueryLanguage/basyx/sm-registry.yml new file mode 100644 index 000000000..fb071597c --- /dev/null +++ b/examples/BaSyxQueryLanguage/basyx/sm-registry.yml @@ -0,0 +1,8 @@ +basyx: + cors: + allowed-origins: '*' + allowed-methods: GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +spring: + data: + mongodb: + uri: mongodb://mongoAdmin:mongoPassword@mongo:27017 diff --git a/examples/BaSyxQueryLanguage/docker-compose-elastic.yml b/examples/BaSyxQueryLanguage/docker-compose-elastic.yml new file mode 100644 index 000000000..719f53cb7 --- /dev/null +++ b/examples/BaSyxQueryLanguage/docker-compose-elastic.yml @@ -0,0 +1,85 @@ +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:${ES_LOCAL_VERSION} + container_name: ${ES_LOCAL_CONTAINER_NAME} + volumes: + - dev-elasticsearch:/usr/share/elasticsearch/data + ports: + - 127.0.0.1:${ES_LOCAL_PORT}:9200 + environment: + - discovery.type=single-node + - ELASTIC_PASSWORD=${ES_LOCAL_PASSWORD} + - xpack.security.enabled=true + - xpack.security.http.ssl.enabled=false + - xpack.license.self_generated.type=trial + - xpack.ml.use_auto_machine_memory_percent=true + - ES_JAVA_OPTS=-Xms${ES_LOCAL_HEAP_INIT} -Xmx${ES_LOCAL_HEAP_MAX} + - cluster.routing.allocation.disk.watermark.low=${ES_LOCAL_DISK_SPACE_REQUIRED} + - cluster.routing.allocation.disk.watermark.high=${ES_LOCAL_DISK_SPACE_REQUIRED} + - cluster.routing.allocation.disk.watermark.flood_stage=${ES_LOCAL_DISK_SPACE_REQUIRED} + ulimits: + memlock: + soft: -1 + hard: -1 + healthcheck: + test: + [ + "CMD-SHELL", + "curl --output /dev/null --silent --head --fail -u elastic:${ES_LOCAL_PASSWORD} http://elasticsearch:9200", + ] + interval: 10s + timeout: 10s + retries: 30 + + kibana_settings: + depends_on: + elasticsearch: + condition: service_healthy + image: docker.elastic.co/elasticsearch/elasticsearch:${ES_LOCAL_VERSION} + container_name: kibana_settings + restart: 'no' + command: > + bash -c ' + echo "Setup the kibana_system password"; + start_time=$$(date +%s); + timeout=60; + until curl -s -u "elastic:${ES_LOCAL_PASSWORD}" -X POST http://elasticsearch:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_LOCAL_PASSWORD}\"}" -H "Content-Type: application/json" | grep -q "^{}"; do + if [ $$(($$(date +%s) - $$start_time)) -ge $$timeout ]; then + echo "Error: Elasticsearch timeout"; + exit 1; + fi; + sleep 2; + done; + ' + + kibana: + depends_on: + kibana_settings: + condition: service_completed_successfully + image: docker.elastic.co/kibana/kibana:${ES_LOCAL_VERSION} + container_name: ${KIBANA_LOCAL_CONTAINER_NAME} + volumes: + - dev-kibana:/usr/share/kibana/data + - ./config/telemetry.yml:/usr/share/kibana/config/telemetry.yml + ports: + - 127.0.0.1:${KIBANA_LOCAL_PORT}:5601 + environment: + - SERVER_NAME=kibana + - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 + - ELASTICSEARCH_USERNAME=kibana_system + - ELASTICSEARCH_PASSWORD=${KIBANA_LOCAL_PASSWORD} + - XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY} + - ELASTICSEARCH_PUBLICBASEURL=http://localhost:${ES_LOCAL_PORT} + healthcheck: + test: + [ + "CMD-SHELL", + "curl -s -I http://kibana:5601 | grep -q 'HTTP/1.1 302 Found'", + ] + interval: 10s + timeout: 10s + retries: 30 + +volumes: + dev-elasticsearch: + dev-kibana: \ No newline at end of file diff --git a/examples/BaSyxQueryLanguage/docker-compose.yml b/examples/BaSyxQueryLanguage/docker-compose.yml new file mode 100644 index 000000000..d325ba3b7 --- /dev/null +++ b/examples/BaSyxQueryLanguage/docker-compose.yml @@ -0,0 +1,90 @@ +include: + - docker-compose-elastic.yml +services: + aas-env: + image: eclipsebasyx/aas-environment:2.0.0-SNAPSHOT + container_name: aas-env + environment: + - SERVER_PORT=8081 + volumes: + - ./aas:/application/aas + - ./basyx/aas-env.properties:/application/application.properties + ports: + - '8081:8081' + restart: always + depends_on: + aas-registry: + condition: service_healthy + sm-registry: + condition: service_healthy + mongo: + condition: service_healthy + aas-registry: + image: eclipsebasyx/aas-registry-log-mongodb:2.0.0-SNAPSHOT + container_name: aas-registry + ports: + - '8082:8080' + environment: + - SERVER_PORT=8080 + volumes: + - ./basyx/aas-registry.yml:/workspace/config/application.yml + restart: always + depends_on: + mongo: + condition: service_healthy + sm-registry: + image: eclipsebasyx/submodel-registry-log-mongodb:2.0.0-SNAPSHOT + container_name: sm-registry + ports: + - '8083:8080' + environment: + - SERVER_PORT=8080 + volumes: + - ./basyx/sm-registry.yml:/workspace/config/application.yml + restart: always + depends_on: + mongo: + condition: service_healthy + aas-discovery: + image: eclipsebasyx/aas-discovery:2.0.0-SNAPSHOT + container_name: aas-discovery + environment: + - SERVER_PORT=8081 + volumes: + - ./basyx/aas-discovery.properties:/application/application.properties + ports: + - '8084:8081' + restart: always + depends_on: + mongo: + condition: service_healthy + mongo: + image: mongo:5.0.10 + container_name: mongo + ports: + - '27017:27017' + environment: + MONGO_INITDB_ROOT_USERNAME: mongoAdmin + MONGO_INITDB_ROOT_PASSWORD: mongoPassword + restart: always + healthcheck: + test: mongo + interval: 10s + timeout: 5s + retries: 5 + aas-web-ui: + image: eclipsebasyx/aas-gui:SNAPSHOT + container_name: aas-ui + ports: + - '3000:3000' + environment: + AAS_REGISTRY_PATH: http://localhost:8082/shell-descriptors + SUBMODEL_REGISTRY_PATH: http://localhost:8083/submodel-descriptors + AAS_REPO_PATH: http://localhost:8081/shells + SUBMODEL_REPO_PATH: http://localhost:8081/submodels + CD_REPO_PATH: http://localhost:8081/concept-descriptions + AAS_DISCOVERY_PATH: http://localhost:8084/lookup/shells + restart: always + depends_on: + aas-env: + condition: service_healthy diff --git a/pom.xml b/pom.xml index 913e63e8d..dfbc5a66f 100644 --- a/pom.xml +++ b/pom.xml @@ -674,6 +674,11 @@ basyx.aasrepository-feature-kafka ${revision} + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-search + ${revision} + org.eclipse.digitaltwin.basyx basyx.aasrepository-feature-kafka @@ -1100,6 +1105,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-search + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.aasrepository-feature-registry-integration diff --git a/ql.schema.json b/ql.schema.json new file mode 100644 index 000000000..ce5fc1753 --- /dev/null +++ b/ql.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 From b5440393517b13e1c9f64ae3d91434e23666754f Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 5 Jun 2025 10:28:23 +0200 Subject: [PATCH 02/52] New Converter --- .../README.md | 53 +++ .../pom.xml | 16 + .../SearchAasRepositoryApiHTTPController.java | 67 ++++ .../search/SearchAasRepositoryHTTPApi.java | 94 +++++ .../basyx.aasrepository-http/pom.xml | 4 +- .../http/AasRepositoryApiHTTPController.java | 18 - .../http/AasRepositoryHTTPApi.java | 50 --- basyx.common/basyx.core/pom.xml | 4 +- .../core/query/{Query.java => AASQuery.java} | 190 +++++----- .../query/ElasticSearchRequestBuilder.java | 135 +++++++ .../query/LogicalExpressionConverter.java | 215 +++++++++++ .../core/query/MatchExpressionConverter.java | 164 +++++++++ .../basyx/core/query/QlSchema.java | 6 +- .../core/query/QueryConverterExample.java | 210 +++++++++++ .../query/QueryToElasticSearchConverter.java | 46 +++ .../basyx/core/query/ValueConverter.java | 344 ++++++++++++++++++ .../AasElasticsearchQueryConverter.java | 89 ----- .../ComparisonOperatorConverter.java | 290 --------------- .../LogicalOperatorConverter.java | 72 ---- .../MatchExpressionConverter.java | 84 ----- .../StringOperatorConverter.java | 203 ----------- 21 files changed, 1446 insertions(+), 908 deletions(-) create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/README.md create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java rename basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/{Query.java => AASQuery.java} (87%) create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ElasticSearchRequestBuilder.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpressionConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpressionConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryConverterExample.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryToElasticSearchConverter.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java delete mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java delete mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java delete mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java delete mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java delete mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md new file mode 100644 index 000000000..f65e7b89e --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -0,0 +1,53 @@ +# Implementation of the AASQL and Atribute Based Access Control in BaSyx Java + +## Summary + +* The standardized components of the AAS need to be secured as defined in the [AAS specification Part 4](https://industrialdigitaltwin.io/aas-specifications/IDTA-01004/v3.0/index.html) +* The choice of the security model is based on the AASQL and Attribute Based Access Control (ABAC) +* The currently implemented Role Based Access Control (RBAC) will be handled as a subset of the ABAC +* The authentication flow is not defined by the specificatiion and can be chosen by the implementer (in our case the OAuth2.0 protocol is used) +* The exchange of access rules should be possible (the [JSON schema serialization](https://industrialdigitaltwin.io/aas-specifications/IDTA-01004/v3.0/access-rule-model.html#json-serialization) of rules is reccommended by the specification) +* An identity provider is used to manage the users and their attributes (in our case Keycloak is used) +* The acces rule model applies to all types of repositories and registries in the context of the AAS (AAS Discovery Service is not mentioned here!) +* The AAS Query Language (AASQL) and the AAS Access Rules share the same BNF (Backus-Naur-Form) grammar for formula expressions +* Examples for Access Rules in JSON serialization can be found [here](https://industrialdigitaltwin.io/aas-specifications/IDTA-01004/v3.0/annex/json-access-rule-examples.html) +* The `/query` endpoint of the respective component is used to query the Access Rule(s) by using the AASQL +* AAS queries are sent via a POST request to the `/query` endpoint including the AASQL query in the body of the request +* For the AAS Repository, the endpoint looks like this: `.../query/shells` +* The query language is specified as part of the [AAS specification Part 2](https://industrialdigitaltwin.io/aas-specifications/IDTA-01002/v3.2/query-language.html) +* The Rest API for querying is defined in the [HTTP/REST API](https://industrialdigitaltwin.io/aas-specifications/IDTA-01002/v3.2/http-rest-api/http-rest-api.html#_querying) +* From a technical point of view, ElasticSearch (ES) will be used to index AAS/Submodels/ConceptDescriptions and Descriptors +* ES will constantly be synchronized with the respective BaSyx components +* The JSON serialized query (send via the `/query` endpoint) is translated into an ES query +* The ES query is executed and the results are returned to the client + +## Implementation of the AASQL + +* [ ] Integrate ES component into Docker Compose +* [ ] Define test cases first +* [ ] Spring configuration has to be adapted -> AASQL and ES have to be active at the same component (see gateway for reference) +* [ ] Implement mechanism to synchronize AAS/Submodels/ConceptDescriptions and Descriptors with ES +* [ ] Implement AASQL query parser +* [ ] Implement `/query` endpoint for AASQL queries in the respective components + +## Implementation of ABAC + +## Challenges + +* ES and the respective backend have to be kept in sync at all times (maybe cron job each day to correct if faults happen) +* Synchronization will happen in java (decorator pattern) +* Testing is key! + +## Assumptions + +* All BaSyx components need ABAC and AASQL (even Discovery) +* ABAC needs active AASQL and ES +* THe AAS Repo will be the first component to implement AASQL and ABAC (as a proof of concept) + +## Risks + +* Errors can be catched in the decorated functions (transactional) + + +## TODOS +* [ ] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml index 704bb17a6..e1aabd33d 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml @@ -28,6 +28,22 @@ jackson-databind 2.19.0 + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter-web + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + org.eclipse.digitaltwin.basyx + basyx.http + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java new file mode 100644 index 000000000..79a0744cc --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.core.query.ElasticSearchRequestBuilder; +import org.eclipse.digitaltwin.basyx.core.query.AASQuery; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") +@RestController +@ConditionalOnExpression("#{${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +public class SearchAasRepositoryApiHTTPController implements SearchAasRepositoryHTTPApi { + + private final ElasticsearchClient client; + + @Autowired + public SearchAasRepositoryApiHTTPController(ElasticsearchClient client) { + this.client = client; + } + + @Override + public ResponseEntity queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { + ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); + SearchRequest searchRequest = builder.buildSearchRequest(query, "aas-index"); + try { + SearchResponse response = client.search(searchRequest, AssetAdministrationShell.class); + System.out.println(response); + } catch (IOException e) { + throw new RuntimeException(e); + } + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java new file mode 100644 index 000000000..71bd3b69e --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.eclipse.digitaltwin.aas4j.v3.model.Result; +import org.eclipse.digitaltwin.basyx.core.query.AASQuery; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") +@Validated +public interface SearchAasRepositoryHTTPApi { + @Operation( + summary = "Returns all Asset Administration Shells that conform to the input query", + tags = { "Asset Administration Shell Repository API" }, + operationId = "queryAssetAdministrationShells" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested Asset Administration Shells", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "403", description = "Forbidden", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) + }) + @RequestMapping( + value = "/query/shells", + produces = { "application/json" }, + consumes = { "application/json" }, + method = RequestMethod.POST + ) + ResponseEntity queryAssetAdministrationShells( + @Parameter( + description = "Query object", + required = true, + schema = @Schema(implementation = String.class) + ) + @Valid @RequestBody AASQuery query, + + @Parameter( + in = ParameterIn.QUERY, + description = "Maximum number of results to be returned" + ) + @RequestParam(value = "limit", required = false) Integer limit, + + @Parameter( + in = ParameterIn.QUERY, + description = "Cursor for pagination" + ) + @RequestParam(value = "cursor", required = false) Base64UrlEncodedCursor cursor + ); + + +} diff --git a/basyx.aasrepository/basyx.aasrepository-http/pom.xml b/basyx.aasrepository/basyx.aasrepository-http/pom.xml index 0e96fc1bd..d051e0fac 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-http/pom.xml @@ -70,8 +70,8 @@ test - org.elasticsearch - elasticsearch + co.elastic.clients + elasticsearch-java 9.0.1 diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java index ca24e3f35..9402b4a54 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java @@ -25,8 +25,6 @@ package org.eclipse.digitaltwin.basyx.aasrepository.http; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -45,17 +43,12 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingSubmodelReferenceException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; -import org.eclipse.digitaltwin.basyx.core.query.Query; -import org.eclipse.digitaltwin.basyx.core.query.elasticsearch.AasElasticsearchQueryConverter; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResultPagingMetadata; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.index.query.QueryBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -225,17 +218,6 @@ public ResponseEntity putThumbnailAasRepository(Base64UrlEncodedIdentifier } } - @Override - public ResponseEntity queryAssetAdministrationShells(Query query, Integer limit, Base64UrlEncodedCursor cursor) { - QueryBuilder esQuery = AasElasticsearchQueryConverter.convert(query); - - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); -// SearchResponse response = client.prepareSearch("aas-index") -// .setQuery(esQuery) -// .execute() -// .actionGet(); - } - private void closeInputStream(InputStream fileInputstream) { if (fileInputstream == null) return; diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java index 2639efb6c..fbe8c057f 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java @@ -31,10 +31,8 @@ import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.digitaltwin.aas4j.v3.model.Result; -import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; import org.eclipse.digitaltwin.basyx.aasrepository.http.pagination.GetAssetAdministrationShellsResult; import org.eclipse.digitaltwin.basyx.aasrepository.http.pagination.GetReferencesResult; -import org.eclipse.digitaltwin.basyx.core.query.Query; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; @@ -318,52 +316,4 @@ ResponseEntity putThumbnailAasRepository( @Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file); - @Operation( - summary = "Returns all Asset Administration Shells that conform to the input query", - tags = { "Asset Administration Shell Repository API" }, - operationId = "queryAssetAdministrationShells" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Requested Asset Administration Shells", - content = @Content(mediaType = "application/json", - schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "400", description = "Bad Request", - content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - @ApiResponse(responseCode = "401", description = "Unauthorized", - content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - @ApiResponse(responseCode = "403", description = "Forbidden", - content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - @ApiResponse(responseCode = "500", description = "Internal Server Error", - content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", - content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) - }) - @RequestMapping( - value = "/query/shells", - produces = { "application/json" }, - consumes = { "application/json" }, - method = RequestMethod.POST - ) - ResponseEntity queryAssetAdministrationShells( - @Parameter( - description = "Query object", - required = true, - schema = @Schema(implementation = String.class) - ) - @Valid @RequestBody Query query, - - @Parameter( - in = ParameterIn.QUERY, - description = "Maximum number of results to be returned" - ) - @RequestParam(value = "limit", required = false) Integer limit, - - @Parameter( - in = ParameterIn.QUERY, - description = "Cursor for pagination" - ) - @RequestParam(value = "cursor", required = false) Base64UrlEncodedCursor cursor - ); - - } diff --git a/basyx.common/basyx.core/pom.xml b/basyx.common/basyx.core/pom.xml index 8bcdb2f60..17e443bfb 100644 --- a/basyx.common/basyx.core/pom.xml +++ b/basyx.common/basyx.core/pom.xml @@ -36,8 +36,8 @@ test - org.elasticsearch - elasticsearch + co.elastic.clients + elasticsearch-java 9.0.1 diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Query.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AASQuery.java similarity index 87% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Query.java rename to basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AASQuery.java index a3fa4be0f..fb7d4fb1c 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Query.java +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AASQuery.java @@ -1,95 +1,95 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "$select", - "$condition" -}) -public class Query { - - @JsonProperty("$select") - private String $select; - /** - * - * (Required) - * - */ - @JsonProperty("$condition") - private LogicalExpression $condition; - - @JsonProperty("$select") - public String get$select() { - return $select; - } - - @JsonProperty("$select") - public void set$select(String $select) { - this.$select = $select; - } - - /** - * - * (Required) - * - */ - @JsonProperty("$condition") - public LogicalExpression get$condition() { - return $condition; - } - - /** - * - * (Required) - * - */ - @JsonProperty("$condition") - public void set$condition(LogicalExpression $condition) { - this.$condition = $condition; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Query.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("$select"); - sb.append('='); - sb.append(((this.$select == null)?"":this.$select)); - sb.append(','); - sb.append("$condition"); - sb.append('='); - sb.append(((this.$condition == null)?"":this.$condition)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.$condition == null)? 0 :this.$condition.hashCode())); - result = ((result* 31)+((this.$select == null)? 0 :this.$select.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof Query) == false) { - return false; - } - Query rhs = ((Query) other); - return (((this.$condition == rhs.$condition)||((this.$condition!= null)&&this.$condition.equals(rhs.$condition)))&&((this.$select == rhs.$select)||((this.$select!= null)&&this.$select.equals(rhs.$select)))); - } - -} + +package org.eclipse.digitaltwin.basyx.core.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$select", + "$condition" +}) +public class AASQuery { + + @JsonProperty("$select") + private String $select; + /** + * + * (Required) + * + */ + @JsonProperty("$condition") + private LogicalExpression $condition; + + @JsonProperty("$select") + public String get$select() { + return $select; + } + + @JsonProperty("$select") + public void set$select(String $select) { + this.$select = $select; + } + + /** + * + * (Required) + * + */ + @JsonProperty("$condition") + public LogicalExpression get$condition() { + return $condition; + } + + /** + * + * (Required) + * + */ + @JsonProperty("$condition") + public void set$condition(LogicalExpression $condition) { + this.$condition = $condition; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(AASQuery.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$select"); + sb.append('='); + sb.append(((this.$select == null)?"":this.$select)); + sb.append(','); + sb.append("$condition"); + sb.append('='); + sb.append(((this.$condition == null)?"":this.$condition)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$condition == null)? 0 :this.$condition.hashCode())); + result = ((result* 31)+((this.$select == null)? 0 :this.$select.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof AASQuery) == false) { + return false; + } + AASQuery rhs = ((AASQuery) other); + return (((this.$condition == rhs.$condition)||((this.$condition!= null)&&this.$condition.equals(rhs.$condition)))&&((this.$select == rhs.$select)||((this.$select!= null)&&this.$select.equals(rhs.$select)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ElasticSearchRequestBuilder.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ElasticSearchRequestBuilder.java new file mode 100644 index 000000000..55365d381 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ElasticSearchRequestBuilder.java @@ -0,0 +1,135 @@ +package org.eclipse.digitaltwin.basyx.core.query; + +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.search.SourceConfig; +import co.elastic.clients.elasticsearch.core.search.SourceFilter; + +import java.util.List; +import java.util.Arrays; + +/** + * Utility class for building ElasticSearch SearchRequest objects from custom Query objects + */ +public class ElasticSearchRequestBuilder { + + private final QueryToElasticSearchConverter queryConverter; + + public ElasticSearchRequestBuilder() { + this.queryConverter = new QueryToElasticSearchConverter(); + } + + /** + * Builds a complete ElasticSearch SearchRequest from a custom Query + * + * @param customQuery The custom query to convert + * @param indexName The ElasticSearch index name to search + * @return ElasticSearch SearchRequest + */ + public SearchRequest buildSearchRequest(AASQuery customQuery, String indexName) { + return buildSearchRequest(customQuery, indexName, null, null); + } + + /** + * Builds a complete ElasticSearch SearchRequest from a custom Query with pagination + * + * @param customQuery The custom query to convert + * @param indexName The ElasticSearch index name to search + * @param from The starting point for pagination (offset) + * @param size The number of results to return + * @return ElasticSearch SearchRequest + */ + public SearchRequest buildSearchRequest(AASQuery customQuery, String indexName, Integer from, Integer size) { + co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = queryConverter.convert(customQuery); + + SearchRequest.Builder searchBuilder = new SearchRequest.Builder() + .index(indexName) + .query(esQuery); + + // Handle source filtering based on $select field + if (customQuery != null && customQuery.get$select() != null) { + String selectField = customQuery.get$select(); + if ("id".equals(selectField)) { + // Only return the id field + searchBuilder.source(SourceConfig.of(s -> s + .filter(SourceFilter.of(f -> f + .includes(Arrays.asList("id", "_id")) + )) + )); + } + } + + // Handle pagination + if (from != null) { + searchBuilder.from(from); + } + if (size != null) { + searchBuilder.size(size); + } + + return searchBuilder.build(); + } + + /** + * Builds ElasticSearch SearchRequest with custom source includes + * + * @param customQuery The custom query to convert + * @param indexName The ElasticSearch index name to search + * @param sourceIncludes List of fields to include in the response + * @return ElasticSearch SearchRequest + */ + public SearchRequest buildSearchRequestWithSources(AASQuery customQuery, String indexName, List sourceIncludes) { + co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = queryConverter.convert(customQuery); + + SearchRequest.Builder searchBuilder = new SearchRequest.Builder() + .index(indexName) + .query(esQuery); + + if (sourceIncludes != null && !sourceIncludes.isEmpty()) { + searchBuilder.source(SourceConfig.of(s -> s + .filter(SourceFilter.of(f -> f + .includes(sourceIncludes) + )) + )); + } + + return searchBuilder.build(); + } + + /** + * Builds ElasticSearch SearchRequest with custom source includes and excludes + * + * @param customQuery The custom query to convert + * @param indexName The ElasticSearch index name to search + * @param sourceIncludes List of fields to include in the response + * @param sourceExcludes List of fields to exclude from the response + * @return ElasticSearch SearchRequest + */ + public SearchRequest buildSearchRequestWithSourceFilters(AASQuery customQuery, String indexName, + List sourceIncludes, List sourceExcludes) { + co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = queryConverter.convert(customQuery); + + SearchRequest.Builder searchBuilder = new SearchRequest.Builder() + .index(indexName) + .query(esQuery); + + if ((sourceIncludes != null && !sourceIncludes.isEmpty()) || + (sourceExcludes != null && !sourceExcludes.isEmpty())) { + + SourceFilter.Builder filterBuilder = new SourceFilter.Builder(); + + if (sourceIncludes != null && !sourceIncludes.isEmpty()) { + filterBuilder.includes(sourceIncludes); + } + + if (sourceExcludes != null && !sourceExcludes.isEmpty()) { + filterBuilder.excludes(sourceExcludes); + } + + searchBuilder.source(SourceConfig.of(s -> s + .filter(filterBuilder.build()) + )); + } + + return searchBuilder.build(); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpressionConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpressionConverter.java new file mode 100644 index 000000000..4ad22f7c4 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpressionConverter.java @@ -0,0 +1,215 @@ +package org.eclipse.digitaltwin.basyx.core.query; + +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import java.util.List; +import java.util.ArrayList; + +/** + * Converts LogicalExpression objects to ElasticSearch QueryDSL Query objects + */ +public class LogicalExpressionConverter { + + private final MatchExpressionConverter matchExpressionConverter; + private final ValueConverter valueConverter; + + public LogicalExpressionConverter() { + this.matchExpressionConverter = new MatchExpressionConverter(); + this.valueConverter = new ValueConverter(); + } + + /** + * Converts a LogicalExpression to ElasticSearch Query + * + * @param logicalExpression The logical expression to convert + * @return ElasticSearch Query + */ + public Query convert(LogicalExpression logicalExpression) { + if (logicalExpression == null) { + return QueryBuilders.matchAll().build()._toQuery(); + } + + // Handle $and operations + if (logicalExpression.get$and() != null && !logicalExpression.get$and().isEmpty()) { + return convertAndExpression(logicalExpression.get$and()); + } + + // Handle $or operations + if (logicalExpression.get$or() != null && !logicalExpression.get$or().isEmpty()) { + return convertOrExpression(logicalExpression.get$or()); + } + + // Handle $not operations + if (logicalExpression.get$not() != null) { + return convertNotExpression(logicalExpression.get$not()); + } + + // Handle $match operations + if (logicalExpression.get$match() != null && !logicalExpression.get$match().isEmpty()) { + return convertMatchExpressions(logicalExpression.get$match()); + } + + // Handle boolean literal + if (logicalExpression.get$boolean() != null) { + return convertBooleanLiteral(logicalExpression.get$boolean()); + } + + // Handle comparison operations + return convertComparisonOperation(logicalExpression); + } + + private Query convertAndExpression(List andExpressions) { + BoolQuery.Builder boolBuilder = QueryBuilders.bool(); + + for (LogicalExpression expr : andExpressions) { + Query convertedQuery = convert(expr); + boolBuilder.must(convertedQuery); + } + + return boolBuilder.build()._toQuery(); + } + + private Query convertOrExpression(List orExpressions) { + BoolQuery.Builder boolBuilder = QueryBuilders.bool(); + + for (LogicalExpression expr : orExpressions) { + Query convertedQuery = convert(expr); + boolBuilder.should(convertedQuery); + } + + // Set minimum should match to 1 for OR behavior + boolBuilder.minimumShouldMatch("1"); + + return boolBuilder.build()._toQuery(); + } + + private Query convertNotExpression(LogicalExpression notExpression) { + Query innerQuery = convert(notExpression); + + return QueryBuilders.bool() + .mustNot(innerQuery) + .build()._toQuery(); + } + + private Query convertMatchExpressions(List matchExpressions) { + if (matchExpressions.size() == 1) { + return matchExpressionConverter.convert(matchExpressions.get(0)); + } + + // Multiple match expressions are combined with AND + BoolQuery.Builder boolBuilder = QueryBuilders.bool(); + for (MatchExpression matchExpr : matchExpressions) { + Query convertedQuery = matchExpressionConverter.convert(matchExpr); + boolBuilder.must(convertedQuery); + } + + return boolBuilder.build()._toQuery(); + } + + private Query convertBooleanLiteral(Boolean boolValue) { + if (boolValue) { + return QueryBuilders.matchAll().build()._toQuery(); + } else { + return QueryBuilders.bool().mustNot(QueryBuilders.matchAll().build()).build()._toQuery(); + } + } + + private Query convertComparisonOperation(LogicalExpression logicalExpression) { + // Handle $eq + if (logicalExpression.get$eq() != null && logicalExpression.get$eq().size() == 2) { + return valueConverter.convertEqualityComparison( + logicalExpression.get$eq().get(0), + logicalExpression.get$eq().get(1) + ); + } + + // Handle $ne + if (logicalExpression.get$ne() != null && logicalExpression.get$ne().size() == 2) { + return valueConverter.convertInequalityComparison( + logicalExpression.get$ne().get(0), + logicalExpression.get$ne().get(1) + ); + } + + // Handle $gt + if (logicalExpression.get$gt() != null && logicalExpression.get$gt().size() == 2) { + return valueConverter.convertRangeComparison( + logicalExpression.get$gt().get(0), + logicalExpression.get$gt().get(1), + "gt" + ); + } + + // Handle $ge + if (logicalExpression.get$ge() != null && logicalExpression.get$ge().size() == 2) { + return valueConverter.convertRangeComparison( + logicalExpression.get$ge().get(0), + logicalExpression.get$ge().get(1), + "gte" + ); + } + + // Handle $lt + if (logicalExpression.get$lt() != null && logicalExpression.get$lt().size() == 2) { + return valueConverter.convertRangeComparison( + logicalExpression.get$lt().get(0), + logicalExpression.get$lt().get(1), + "lt" + ); + } + + // Handle $le + if (logicalExpression.get$le() != null && logicalExpression.get$le().size() == 2) { + return valueConverter.convertRangeComparison( + logicalExpression.get$le().get(0), + logicalExpression.get$le().get(1), + "lte" + ); + } + + // Handle string operations + return convertStringOperations(logicalExpression); + } + + private Query convertStringOperations(LogicalExpression logicalExpression) { + // Handle $contains + if (logicalExpression.get$contains() != null && logicalExpression.get$contains().size() == 2) { + return valueConverter.convertStringComparison( + logicalExpression.get$contains().get(0), + logicalExpression.get$contains().get(1), + "contains" + ); + } + + // Handle $starts-with + if (logicalExpression.get$startsWith() != null && logicalExpression.get$startsWith().size() == 2) { + return valueConverter.convertStringComparison( + logicalExpression.get$startsWith().get(0), + logicalExpression.get$startsWith().get(1), + "starts-with" + ); + } + + // Handle $ends-with + if (logicalExpression.get$endsWith() != null && logicalExpression.get$endsWith().size() == 2) { + return valueConverter.convertStringComparison( + logicalExpression.get$endsWith().get(0), + logicalExpression.get$endsWith().get(1), + "ends-with" + ); + } + + // Handle $regex + if (logicalExpression.get$regex() != null && logicalExpression.get$regex().size() == 2) { + return valueConverter.convertStringComparison( + logicalExpression.get$regex().get(0), + logicalExpression.get$regex().get(1), + "regex" + ); + } + + // Default fallback + return QueryBuilders.matchAll().build()._toQuery(); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpressionConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpressionConverter.java new file mode 100644 index 000000000..04f2521c4 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpressionConverter.java @@ -0,0 +1,164 @@ +package org.eclipse.digitaltwin.basyx.core.query; + +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import java.util.List; + +/** + * Converts MatchExpression objects to ElasticSearch QueryDSL Query objects + */ +public class MatchExpressionConverter { + + private final ValueConverter valueConverter; + + public MatchExpressionConverter() { + this.valueConverter = new ValueConverter(); + } + + /** + * Converts a MatchExpression to ElasticSearch Query + * + * @param matchExpression The match expression to convert + * @return ElasticSearch Query + */ + public Query convert(MatchExpression matchExpression) { + if (matchExpression == null) { + return QueryBuilders.matchAll().build()._toQuery(); + } + + // Handle nested $match operations + if (matchExpression.get$match() != null && !matchExpression.get$match().isEmpty()) { + return convertNestedMatch(matchExpression.get$match()); + } + + // Handle boolean literal + if (matchExpression.get$boolean() != null) { + return convertBooleanLiteral(matchExpression.get$boolean()); + } + + // Handle comparison operations + return convertComparisonOperation(matchExpression); + } + + private Query convertNestedMatch(List matchExpressions) { + if (matchExpressions.size() == 1) { + return convert(matchExpressions.get(0)); + } + + // Multiple nested match expressions are combined with AND + BoolQuery.Builder boolBuilder = QueryBuilders.bool(); + for (MatchExpression matchExpr : matchExpressions) { + Query convertedQuery = convert(matchExpr); + boolBuilder.must(convertedQuery); + } + + return boolBuilder.build()._toQuery(); + } + + private Query convertBooleanLiteral(Boolean boolValue) { + if (boolValue) { + return QueryBuilders.matchAll().build()._toQuery(); + } else { + return QueryBuilders.bool().mustNot(QueryBuilders.matchAll().build()).build()._toQuery(); + } + } + + private Query convertComparisonOperation(MatchExpression matchExpression) { + // Handle $eq + if (matchExpression.get$eq() != null && matchExpression.get$eq().size() == 2) { + return valueConverter.convertEqualityComparison( + matchExpression.get$eq().get(0), + matchExpression.get$eq().get(1) + ); + } + + // Handle $ne + if (matchExpression.get$ne() != null && matchExpression.get$ne().size() == 2) { + return valueConverter.convertInequalityComparison( + matchExpression.get$ne().get(0), + matchExpression.get$ne().get(1) + ); + } + + // Handle $gt + if (matchExpression.get$gt() != null && matchExpression.get$gt().size() == 2) { + return valueConverter.convertRangeComparison( + matchExpression.get$gt().get(0), + matchExpression.get$gt().get(1), + "gt" + ); + } + + // Handle $ge + if (matchExpression.get$ge() != null && matchExpression.get$ge().size() == 2) { + return valueConverter.convertRangeComparison( + matchExpression.get$ge().get(0), + matchExpression.get$ge().get(1), + "gte" + ); + } + + // Handle $lt + if (matchExpression.get$lt() != null && matchExpression.get$lt().size() == 2) { + return valueConverter.convertRangeComparison( + matchExpression.get$lt().get(0), + matchExpression.get$lt().get(1), + "lt" + ); + } + + // Handle $le + if (matchExpression.get$le() != null && matchExpression.get$le().size() == 2) { + return valueConverter.convertRangeComparison( + matchExpression.get$le().get(0), + matchExpression.get$le().get(1), + "lte" + ); + } + + // Handle string operations + return convertStringOperations(matchExpression); + } + + private Query convertStringOperations(MatchExpression matchExpression) { + // Handle $contains + if (matchExpression.get$contains() != null && matchExpression.get$contains().size() == 2) { + return valueConverter.convertStringComparison( + matchExpression.get$contains().get(0), + matchExpression.get$contains().get(1), + "contains" + ); + } + + // Handle $starts-with + if (matchExpression.get$startsWith() != null && matchExpression.get$startsWith().size() == 2) { + return valueConverter.convertStringComparison( + matchExpression.get$startsWith().get(0), + matchExpression.get$startsWith().get(1), + "starts-with" + ); + } + + // Handle $ends-with + if (matchExpression.get$endsWith() != null && matchExpression.get$endsWith().size() == 2) { + return valueConverter.convertStringComparison( + matchExpression.get$endsWith().get(0), + matchExpression.get$endsWith().get(1), + "ends-with" + ); + } + + // Handle $regex + if (matchExpression.get$regex() != null && matchExpression.get$regex().size() == 2) { + return valueConverter.convertStringComparison( + matchExpression.get$regex().get(0), + matchExpression.get$regex().get(1), + "regex" + ); + } + + // Default fallback + return QueryBuilders.matchAll().build()._toQuery(); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java index 76f6de9ff..0dcc88e33 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java @@ -20,17 +20,17 @@ public class QlSchema { @JsonProperty("Query") - private Query query; + private AASQuery query; @JsonProperty("AllAccessPermissionRules") private AllAccessPermissionRules allAccessPermissionRules; @JsonProperty("Query") - public Query getQuery() { + public AASQuery getQuery() { return query; } @JsonProperty("Query") - public void setQuery(Query query) { + public void setQuery(AASQuery query) { this.query = query; } diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryConverterExample.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryConverterExample.java new file mode 100644 index 000000000..938dfe916 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryConverterExample.java @@ -0,0 +1,210 @@ +package org.eclipse.digitaltwin.basyx.core.query; + +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import java.util.Arrays; +import java.util.List; + +/** + * Example usage and factory class for the Query to ElasticSearch converters + */ +public class QueryConverterExample { + + private final QueryToElasticSearchConverter queryConverter; + private final ElasticSearchRequestBuilder requestBuilder; + + public QueryConverterExample() { + this.queryConverter = new QueryToElasticSearchConverter(); + this.requestBuilder = new ElasticSearchRequestBuilder(); + } + + /** + * Example: Convert a simple equality query + * Custom Query: { "$condition": { "$eq": [{"$field": "$aas#idShort"}, {"$strVal": "MyAAS"}] } } + */ + public Query createSimpleEqualityQuery() { + // Create value objects + Value fieldValue = new Value(); + fieldValue.set$field("$aas#idShort"); + + Value stringValue = new Value(); + stringValue.set$strVal("MyAAS"); + + // Create logical expression with equality + LogicalExpression condition = new LogicalExpression(); + condition.set$eq(Arrays.asList(fieldValue, stringValue)); + + // Create query + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: Convert a range query + * Custom Query: { "$condition": { "$gt": [{"$field": "$sm#value"}, {"$numVal": 100}] } } + */ + public Query createRangeQuery() { + Value fieldValue = new Value(); + fieldValue.set$field("$sm#value"); + + Value numValue = new Value(); + numValue.set$numVal(100.0); + + LogicalExpression condition = new LogicalExpression(); + condition.set$gt(Arrays.asList(fieldValue, numValue)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: Convert a complex AND query + * Custom Query: { "$condition": { "$and": [...] } } + */ + public Query createComplexAndQuery() { + // First condition: name equals "Test" + Value fieldValue1 = new Value(); + fieldValue1.set$field("$aas#idShort"); + Value stringValue1 = new Value(); + stringValue1.set$strVal("Test"); + + LogicalExpression condition1 = new LogicalExpression(); + condition1.set$eq(Arrays.asList(fieldValue1, stringValue1)); + + // Second condition: value > 50 + Value fieldValue2 = new Value(); + fieldValue2.set$field("$sme#value"); + Value numValue2 = new Value(); + numValue2.set$numVal(50.0); + + LogicalExpression condition2 = new LogicalExpression(); + condition2.set$gt(Arrays.asList(fieldValue2, numValue2)); + + // Combine with AND + LogicalExpression andCondition = new LogicalExpression(); + andCondition.set$and(Arrays.asList(condition1, condition2)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(andCondition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: Convert a string contains query + * Custom Query: { "$condition": { "$contains": [{"$field": "$aas#description"}, {"$strVal": "sensor"}] } } + */ + public Query createStringContainsQuery() { + StringValue fieldValue = new StringValue(); + fieldValue.set$field("$aas#description"); + + StringValue stringValue = new StringValue(); + stringValue.set$strVal("sensor"); + + LogicalExpression condition = new LogicalExpression(); + condition.set$contains(Arrays.asList(fieldValue, stringValue)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: Create a complete SearchRequest for ElasticSearch + */ + public SearchRequest createSearchRequest() { + // Create a query that searches for AAS with idShort="TestAAS" + Value fieldValue = new Value(); + fieldValue.set$field("$aas#idShort"); + + Value stringValue = new Value(); + stringValue.set$strVal("TestAAS"); + + LogicalExpression condition = new LogicalExpression(); + condition.set$eq(Arrays.asList(fieldValue, stringValue)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$select("id"); // Only return ID fields + customQuery.set$condition(condition); + + return requestBuilder.buildSearchRequest(customQuery, "aas-index"); + } + + /** + * Example: Create SearchRequest with custom field selection + */ + public SearchRequest createSearchRequestWithCustomFields() { + AASQuery customQuery = createSimpleQuery(); + + List sourceFields = Arrays.asList("aas.idShort", "aas.id", "aas.assetInformation"); + + return requestBuilder.buildSearchRequestWithSources(customQuery, "aas-index", sourceFields); + } + + /** + * Example: Create SearchRequest with pagination + */ + public SearchRequest createPaginatedSearchRequest(int page, int pageSize) { + AASQuery customQuery = createSimpleQuery(); + + int from = page * pageSize; + return requestBuilder.buildSearchRequest(customQuery, "aas-index", from, pageSize); + } + + private AASQuery createSimpleQuery() { + Value fieldValue = new Value(); + fieldValue.set$field("$aas#idShort"); + + Value stringValue = new Value(); + stringValue.set$strVal("*"); + + LogicalExpression condition = new LogicalExpression(); + condition.set$eq(Arrays.asList(fieldValue, stringValue)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return customQuery; + } + + /** + * Example: Boolean literal query + */ + public Query createBooleanQuery(boolean value) { + LogicalExpression condition = new LogicalExpression(); + condition.set$boolean(value); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: NOT query + */ + public Query createNotQuery() { + // Create inner condition: name = "Test" + Value fieldValue = new Value(); + fieldValue.set$field("$aas#idShort"); + Value stringValue = new Value(); + stringValue.set$strVal("Test"); + + LogicalExpression innerCondition = new LogicalExpression(); + innerCondition.set$eq(Arrays.asList(fieldValue, stringValue)); + + // Wrap in NOT + LogicalExpression notCondition = new LogicalExpression(); + notCondition.set$not(innerCondition); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(notCondition); + + return queryConverter.convert(customQuery); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryToElasticSearchConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryToElasticSearchConverter.java new file mode 100644 index 000000000..502f171fa --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryToElasticSearchConverter.java @@ -0,0 +1,46 @@ +package org.eclipse.digitaltwin.basyx.core.query; + +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; + +/** + * Converts custom Query objects to ElasticSearch QueryDSL Query objects for ElasticSearch 9.0.1 + */ +public class QueryToElasticSearchConverter { + + private final LogicalExpressionConverter logicalExpressionConverter; + + public QueryToElasticSearchConverter() { + this.logicalExpressionConverter = new LogicalExpressionConverter(); + } + + /** + * Converts a custom Query object to ElasticSearch QueryDSL Query + * + * @param customQuery The custom query to convert + * @return ElasticSearch QueryDSL Query + */ + public co.elastic.clients.elasticsearch._types.query_dsl.Query convert(AASQuery customQuery) { + if (customQuery == null) { + return QueryBuilders.matchAll().build()._toQuery(); + } + + LogicalExpression condition = customQuery.get$condition(); + if (condition == null) { + return QueryBuilders.matchAll().build()._toQuery(); + } + + return logicalExpressionConverter.convert(condition); + } + + /** + * Converts a custom Query object to ElasticSearch QueryDSL Query with source filtering + * + * @param customQuery The custom query to convert + * @return ElasticSearch QueryDSL Query + */ + public co.elastic.clients.elasticsearch._types.query_dsl.Query convertWithSelect(AASQuery customQuery) { + // For now, we focus on the query conversion + // Source filtering would be handled at the search request level, not in the query itself + return convert(customQuery); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java new file mode 100644 index 000000000..aea6e90dd --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java @@ -0,0 +1,344 @@ +package org.eclipse.digitaltwin.basyx.core.query; + +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery; +import co.elastic.clients.elasticsearch._types.FieldValue; +import co.elastic.clients.json.JsonData; +import java.util.Date; +import java.text.SimpleDateFormat; +import java.util.regex.Pattern; + +/** + * Converts Value and StringValue objects to ElasticSearch QueryDSL operations + */ +public class ValueConverter { + + private static final SimpleDateFormat ISO_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + + /** + * Converts equality comparison between two values + */ + public Query convertEqualityComparison(Value leftValue, Value rightValue) { + String fieldName = extractFieldName(leftValue); + Object value = extractValue(rightValue); + + if (fieldName != null && value != null) { + return QueryBuilders.term() + .field(fieldName) + .value(convertToFieldValue(value)) + .build()._toQuery(); + } + + return QueryBuilders.matchAll().build()._toQuery(); + } + + /** + * Converts inequality comparison between two values + */ + public Query convertInequalityComparison(Value leftValue, Value rightValue) { + String fieldName = extractFieldName(leftValue); + Object value = extractValue(rightValue); + + if (fieldName != null && value != null) { + return QueryBuilders.bool() + .mustNot(QueryBuilders.term() + .field(fieldName) + .value(convertToFieldValue(value)) + .build()) + .build()._toQuery(); + } + + return QueryBuilders.matchAll().build()._toQuery(); + } + +/** + * Converts range comparison between two values + */ + + public Query convertRangeComparison(Value leftValue, + Value rightValue, + String operator) { + + String fieldName = extractFieldName(leftValue); + Object rawValue = extractValue(rightValue); + + if (fieldName != null && rawValue != null) { + JsonData value = JsonData.of(rawValue); // works for all scalar types + + return QueryBuilders.range(r -> r + .untyped(u -> { // <— pick *untyped* variant + u.field(fieldName); + switch (operator) { // map your operators + case "gt" -> u.gt(value); + case "gte" -> u.gte(value); + case "lt" -> u.lt(value); + case "lte" -> u.lte(value); + default -> throw new IllegalArgumentException( + "Unsupported operator: " + operator); + } + return u; // must return the builder + }) + ); + } + + // fall-back: match-all + return QueryBuilders.matchAll(m -> m); + } + /** + * Converts string comparison operations + */ + public Query convertStringComparison(StringValue leftValue, StringValue rightValue, String operation) { + String fieldName = extractStringFieldName(leftValue); + String value = extractStringValue(rightValue); + + if (fieldName != null && value != null) { + switch (operation) { + case "contains": + return QueryBuilders.wildcard() + .field(fieldName) + .value("*" + escapeWildcard(value) + "*") + .build()._toQuery(); + + case "starts-with": + return QueryBuilders.wildcard() + .field(fieldName) + .value(escapeWildcard(value) + "*") + .build()._toQuery(); + + case "ends-with": + return QueryBuilders.wildcard() + .field(fieldName) + .value("*" + escapeWildcard(value)) + .build()._toQuery(); + + case "regex": + return QueryBuilders.regexp() + .field(fieldName) + .value(value) + .build()._toQuery(); + + default: + return QueryBuilders.term() + .field(fieldName) + .value(value) + .build()._toQuery(); + } + } + + return QueryBuilders.matchAll().build()._toQuery(); + } + + /** + * Extracts field name from a Value object + */ + private String extractFieldName(Value value) { + if (value == null) return null; + + if (value.get$field() != null) { + return convertModelFieldToElasticField(value.get$field()); + } + + // Handle cast operations - extract field from the casted value + if (value.get$strCast() != null) { + return extractFieldName(value.get$strCast()); + } + if (value.get$numCast() != null) { + return extractFieldName(value.get$numCast()); + } + if (value.get$boolCast() != null) { + return extractFieldName(value.get$boolCast()); + } + if (value.get$hexCast() != null) { + return extractFieldName(value.get$hexCast()); + } + if (value.get$dateTimeCast() != null) { + return extractFieldName(value.get$dateTimeCast()); + } + if (value.get$timeCast() != null) { + return extractFieldName(value.get$timeCast()); + } + + return null; + } + + /** + * Extracts field name from a StringValue object + */ + private String extractStringFieldName(StringValue value) { + if (value == null) return null; + + if (value.get$field() != null) { + return convertModelFieldToElasticField(value.get$field()); + } + + if (value.get$strCast() != null) { + return extractFieldName(value.get$strCast()); + } + + return null; + } + + /** + * Extracts the actual value from a Value object + */ + private Object extractValue(Value value) { + if (value == null) return null; + + if (value.get$strVal() != null) return value.get$strVal(); + if (value.get$numVal() != null) return value.get$numVal(); + if (value.get$boolean() != null) return value.get$boolean(); + if (value.get$hexVal() != null) return parseHexValue(value.get$hexVal()); + if (value.get$dateTimeVal() != null) return formatDate(value.get$dateTimeVal()); + if (value.get$timeVal() != null) return value.get$timeVal(); + + // Handle date extraction functions + if (value.get$dayOfWeek() != null) return extractDayOfWeek(value.get$dayOfWeek()); + if (value.get$dayOfMonth() != null) return extractDayOfMonth(value.get$dayOfMonth()); + if (value.get$month() != null) return extractMonth(value.get$month()); + if (value.get$year() != null) return extractYear(value.get$year()); + + // Handle cast operations - extract value from casted expression + if (value.get$strCast() != null) return convertToString(extractValue(value.get$strCast())); + if (value.get$numCast() != null) return convertToNumber(extractValue(value.get$numCast())); + if (value.get$boolCast() != null) return convertToBoolean(extractValue(value.get$boolCast())); + if (value.get$hexCast() != null) return convertToHex(extractValue(value.get$hexCast())); + if (value.get$dateTimeCast() != null) return convertToDateTime(extractValue(value.get$dateTimeCast())); + if (value.get$timeCast() != null) return convertToTime(extractValue(value.get$timeCast())); + + return null; + } + + /** + * Extracts string value from a StringValue object + */ + private String extractStringValue(StringValue value) { + if (value == null) return null; + + if (value.get$strVal() != null) return value.get$strVal(); + if (value.get$strCast() != null) { + Object castValue = extractValue(value.get$strCast()); + return castValue != null ? castValue.toString() : null; + } + + return null; + } + + /** + * Converts model field patterns to ElasticSearch field names + */ + private String convertModelFieldToElasticField(String modelField) { + // Convert the model pattern to ElasticSearch field name + // This is a simplified conversion - you may need to customize based on your mapping + if (modelField.startsWith("$aas#")) { + return modelField.replace("$aas#", "aas."); + } else if (modelField.startsWith("$sm#")) { + return modelField.replace("$sm#", "submodel."); + } else if (modelField.startsWith("$sme")) { + return modelField.replaceFirst("\\$sme(?:\\.[^#]*)?#", "submodelElement."); + } else if (modelField.startsWith("$cd#")) { + return modelField.replace("$cd#", "conceptDescription."); + } else if (modelField.startsWith("$aasdesc#")) { + return modelField.replace("$aasdesc#", "aasDescriptor."); + } else if (modelField.startsWith("$smdesc#")) { + return modelField.replace("$smdesc#", "submodelDescriptor."); + } + + return modelField; + } + + /** + * Converts Java object to ElasticSearch FieldValue + */ + private FieldValue convertToFieldValue(Object value) { + if (value instanceof String) { + return FieldValue.of((String) value); + } else if (value instanceof Number) { + return FieldValue.of(((Number) value).doubleValue()); + } else if (value instanceof Boolean) { + return FieldValue.of((Boolean) value); + } else { + return FieldValue.of(value.toString()); + } + } + + // Utility methods for value conversion and extraction + + private String parseHexValue(String hexVal) { + if (hexVal.startsWith("16#")) { + return hexVal.substring(3); + } + return hexVal; + } + + private String formatDate(Date date) { + return ISO_DATE_FORMAT.format(date); + } + + private Integer extractDayOfWeek(Date date) { + return date.getDay() + 1; // Java getDay() returns 0-6, convert to 1-7 + } + + private Integer extractDayOfMonth(Date date) { + return date.getDate(); + } + + private Integer extractMonth(Date date) { + return date.getMonth() + 1; // Java getMonth() returns 0-11, convert to 1-12 + } + + private Integer extractYear(Date date) { + return date.getYear() + 1900; // Java getYear() returns years since 1900 + } + + private String convertToString(Object value) { + return value != null ? value.toString() : null; + } + + private Double convertToNumber(Object value) { + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } else if (value instanceof String) { + try { + return Double.parseDouble((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + private Boolean convertToBoolean(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof String) { + return Boolean.parseBoolean((String) value); + } + return null; + } + + private String convertToHex(Object value) { + if (value instanceof Number) { + return "16#" + Integer.toHexString(((Number) value).intValue()).toUpperCase(); + } + return value != null ? value.toString() : null; + } + + private String convertToDateTime(Object value) { + if (value instanceof Date) { + return formatDate((Date) value); + } + return value != null ? value.toString() : null; + } + + private String convertToTime(Object value) { + return value != null ? value.toString() : null; + } + + private String escapeWildcard(String value) { + // Escape ElasticSearch wildcard characters + return value.replace("\\", "\\\\") + .replace("*", "\\*") + .replace("?", "\\?"); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java deleted file mode 100644 index c54923d50..000000000 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/AasElasticsearchQueryConverter.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; - -import org.eclipse.digitaltwin.basyx.core.query.*; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; - -/** - * Converter to transform AAS query structures to Elasticsearch QueryBuilder objects - * - * This class provides functionality to convert Query objects from the AAS query language - * to Elasticsearch's QueryBuilder format for executing searches in Elasticsearch. - */ -public class AasElasticsearchQueryConverter { - - /** - * Converts an AAS Query object to an Elasticsearch QueryBuilder - * - * @param query The AAS Query to convert - * @return Elasticsearch QueryBuilder object - */ - public static QueryBuilder convert(Query query) { - if (query == null) { - return null; - } - - // The main query condition is mandatory according to the schema - return convertLogicalExpression(query.get$condition()); - } - - /** - * Converts a LogicalExpression to an Elasticsearch QueryBuilder - * - * @param expression The logical expression to convert - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertLogicalExpression(LogicalExpression expression) { - if (expression == null) { - return null; - } - - // Handle logical operators - if (!expression.get$and().isEmpty()) { - return LogicalOperatorConverter.convertAndExpression(expression.get$and()); - } else if (!expression.get$or().isEmpty()) { - return LogicalOperatorConverter.convertOrExpression(expression.get$or()); - } else if (expression.get$not() != null) { - return LogicalOperatorConverter.convertNotExpression(expression.get$not()); - } - - // Handle match expressions embedded directly in logical expression - else if (!expression.get$match().isEmpty()) { - return MatchExpressionConverter.convertMatchExpressions(expression.get$match()); - } - - // Handle comparison operators - else if (!expression.get$eq().isEmpty()) { - return ComparisonOperatorConverter.convertEqualsExpression(expression.get$eq()); - } else if (!expression.get$ne().isEmpty()) { - return ComparisonOperatorConverter.convertNotEqualsExpression(expression.get$ne()); - } else if (!expression.get$gt().isEmpty()) { - return ComparisonOperatorConverter.convertGreaterThanExpression(expression.get$gt()); - } else if (!expression.get$ge().isEmpty()) { - return ComparisonOperatorConverter.convertGreaterThanOrEqualsExpression(expression.get$ge()); - } else if (!expression.get$lt().isEmpty()) { - return ComparisonOperatorConverter.convertLessThanExpression(expression.get$lt()); - } else if (!expression.get$le().isEmpty()) { - return ComparisonOperatorConverter.convertLessThanOrEqualsExpression(expression.get$le()); - } - - // Handle string operators - else if (!expression.get$contains().isEmpty()) { - return StringOperatorConverter.convertContainsExpression(expression.get$contains()); - } else if (!expression.get$startsWith().isEmpty()) { - return StringOperatorConverter.convertStartsWithExpression(expression.get$startsWith()); - } else if (!expression.get$endsWith().isEmpty()) { - return StringOperatorConverter.convertEndsWithExpression(expression.get$endsWith()); - } else if (!expression.get$regex().isEmpty()) { - return StringOperatorConverter.convertRegexExpression(expression.get$regex()); - } - - // Handle boolean - else if (expression.get$boolean() != null) { - return QueryBuilders.matchQuery("_exists_", expression.get$boolean()); - } - - // Default case: return a match all query - return QueryBuilders.matchAllQuery(); - } -} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java deleted file mode 100644 index 6bd926e80..000000000 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/ComparisonOperatorConverter.java +++ /dev/null @@ -1,290 +0,0 @@ -package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; - -import java.util.Date; -import java.util.List; -import org.eclipse.digitaltwin.basyx.core.query.Value; -import org.elasticsearch.index.query.BoolQueryBuilder; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.index.query.RangeQueryBuilder; - -/** - * Converter for comparison operators (=, !=, >, >=, <, <=) from AAS query structure to Elasticsearch queries - */ -public class ComparisonOperatorConverter { - - /** - * Converts an equals expression to an Elasticsearch term query or script query for field-to-field comparison - * - * @param values List of values to compare - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertEqualsExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - Value value1 = values.get(0); - Value value2 = values.get(1); - - // Check if both values are field references (field-to-field comparison) - if (value1.get$field() != null && value2.get$field() != null) { - String field1 = normalizeFieldName(value1.get$field()); - String field2 = normalizeFieldName(value2.get$field()); - - // Create a script query to compare the two fields - String scriptSource = "doc['" + field1 + ".keyword'].value == doc['" + field2 + ".keyword'].value"; - return QueryBuilders.scriptQuery( - new org.elasticsearch.script.Script(scriptSource) - ); - } - - // Handle regular field-to-value comparison - String fieldName = extractFieldName(value1, value2); - Object fieldValue = extractFieldValue(value1, value2); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Use term query for exact matches - return QueryBuilders.termQuery(fieldName, fieldValue); - } - - /** - * Converts a not equals expression to an Elasticsearch query - * - * @param values List of values to compare - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertNotEqualsExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - QueryBuilder equalsQuery = convertEqualsExpression(values); - - BoolQueryBuilder notQuery = QueryBuilders.boolQuery(); - notQuery.mustNot(equalsQuery); - - return notQuery; - } - - /** - * Converts a greater than expression to an Elasticsearch range query - * - * @param values List of values to compare - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertGreaterThanExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractFieldName(values.get(0), values.get(1)); - Object fieldValue = extractFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Create range query with gt - RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); - rangeQuery.gt(fieldValue); - - return rangeQuery; - } - - /** - * Converts a greater than or equals expression to an Elasticsearch range query - * - * @param values List of values to compare - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertGreaterThanOrEqualsExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractFieldName(values.get(0), values.get(1)); - Object fieldValue = extractFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Create range query with gte - RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); - rangeQuery.gte(fieldValue); - - return rangeQuery; - } - - /** - * Converts a less than expression to an Elasticsearch range query - * - * @param values List of values to compare - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertLessThanExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractFieldName(values.get(0), values.get(1)); - Object fieldValue = extractFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Create range query with lt - RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); - rangeQuery.lt(fieldValue); - - return rangeQuery; - } - - /** - * Converts a less than or equals expression to an Elasticsearch range query - * - * @param values List of values to compare - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertLessThanOrEqualsExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractFieldName(values.get(0), values.get(1)); - Object fieldValue = extractFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Create range query with lte - RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName); - rangeQuery.lte(fieldValue); - - return rangeQuery; - } - - /** - * Helper method to extract field name from a pair of values - * - * @param value1 First value - * @param value2 Second value - * @return The field name or null if not found - */ - public static String extractFieldName(Value value1, Value value2) { - // Check if the first value has a field name - if (value1.get$field() != null) { - return normalizeFieldName(value1.get$field()); - } - - // Check if the second value has a field name - if (value2.get$field() != null) { - return normalizeFieldName(value2.get$field()); - } - - return null; - } - - /** - * Helper method to extract field value from a pair of values - * - * @param value1 First value - * @param value2 Second value - * @return The field value or null if not found - */ - public static Object extractFieldValue(Value value1, Value value2) { - // If first value is a field reference, then second value should contain the value - if (value1.get$field() != null) { - return extractValueFromValue(value2); - } - - // If second value is a field reference, then first value should contain the value - if (value2.get$field() != null) { - return extractValueFromValue(value1); - } - - return null; - } - - /** - * Extract actual value from Value object - * - * @param value The Value object - * @return The extracted value as an Object - */ - private static Object extractValueFromValue(Value value) { - if (value.get$strVal() != null) { - return value.get$strVal(); - } else if (value.get$numVal() != null) { - return value.get$numVal(); - } else if (value.get$dateTimeVal() != null) { - return value.get$dateTimeVal(); - } else if (value.get$timeVal() != null) { - return value.get$timeVal(); - } else if (value.get$hexVal() != null) { - return value.get$hexVal(); - } else if (value.get$boolean() != null) { - return value.get$boolean(); - } - - // Handle casting if needed - if (value.get$strCast() != null) { - Object innerValue = extractValueFromValue(value.get$strCast()); - if (innerValue != null) { - return String.valueOf(innerValue); - } - } else if (value.get$numCast() != null) { - Object innerValue = extractValueFromValue(value.get$numCast()); - if (innerValue != null && innerValue instanceof String) { - try { - return Double.parseDouble((String) innerValue); - } catch (NumberFormatException e) { - return null; - } - } - } else if (value.get$dateTimeCast() != null) { - // Date handling would be more complex, simplified here - return null; - } - - return null; - } - - /** - * Normalize field name for Elasticsearch - * Handles the special AAS field patterns by removing type prefixes - * - * @param fieldName The AAS field name - * @return Normalized field name for Elasticsearch - */ - private static String normalizeFieldName(String fieldName) { - // Handle AAS specific field patterns by removing the type prefixes completely - if (fieldName.startsWith("$aas#")) { - return fieldName.substring(5); // Remove the "$aas#" prefix - } else if (fieldName.startsWith("$sm#")) { - return fieldName.substring(4); // Remove the "$sm#" prefix - } else if (fieldName.startsWith("$sme")) { - // For $sme, extract everything after the '#' if present - int hashIndex = fieldName.indexOf('#'); - if (hashIndex != -1) { - return fieldName.substring(hashIndex + 1); - } - return fieldName.substring(4); // Remove the "$sme" prefix if no # found - } else if (fieldName.startsWith("$cd#")) { - return fieldName.substring(4); // Remove the "$cd#" prefix - } else if (fieldName.startsWith("$aasdesc#")) { - return fieldName.substring(9); // Remove the "$aasdesc#" prefix - } else if (fieldName.startsWith("$smdesc#")) { - return fieldName.substring(8); // Remove the "$smdesc#" prefix - } - - // Replace dots with underscores for Elasticsearch compatibility - return fieldName.replace(".", "_"); - } -} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java deleted file mode 100644 index a532fbd8b..000000000 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/LogicalOperatorConverter.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; - -import java.util.List; -import org.eclipse.digitaltwin.basyx.core.query.LogicalExpression; -import org.elasticsearch.index.query.BoolQueryBuilder; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; - -/** - * Converter for logical operators (AND, OR, NOT) from AAS query structure to Elasticsearch queries - */ -public class LogicalOperatorConverter { - - /** - * Converts a list of logical expressions connected with AND to an Elasticsearch query - * - * @param expressions List of expressions to be combined with AND - * @return Elasticsearch QueryBuilder representing the AND operation - */ - public static QueryBuilder convertAndExpression(List expressions) { - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); - - for (LogicalExpression expr : expressions) { - QueryBuilder convertedExpr = AasElasticsearchQueryConverter.convertLogicalExpression(expr); - if (convertedExpr != null) { - boolQuery.must(convertedExpr); - } - } - - return boolQuery; - } - - /** - * Converts a list of logical expressions connected with OR to an Elasticsearch query - * - * @param expressions List of expressions to be combined with OR - * @return Elasticsearch QueryBuilder representing the OR operation - */ - public static QueryBuilder convertOrExpression(List expressions) { - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); - - for (LogicalExpression expr : expressions) { - QueryBuilder convertedExpr = AasElasticsearchQueryConverter.convertLogicalExpression(expr); - if (convertedExpr != null) { - boolQuery.should(convertedExpr); - } - } - - // Ensure at least one should clause matches - boolQuery.minimumShouldMatch(1); - - return boolQuery; - } - - /** - * Converts a NOT expression to an Elasticsearch query - * - * @param expression The expression to negate - * @return Elasticsearch QueryBuilder representing the NOT operation - */ - public static QueryBuilder convertNotExpression(LogicalExpression expression) { - QueryBuilder convertedExpr = AasElasticsearchQueryConverter.convertLogicalExpression(expression); - - if (convertedExpr != null) { - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); - boolQuery.mustNot(convertedExpr); - return boolQuery; - } - - return QueryBuilders.matchAllQuery(); - } -} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java deleted file mode 100644 index cea79a5b2..000000000 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/MatchExpressionConverter.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; - -import java.util.List; -import org.eclipse.digitaltwin.basyx.core.query.MatchExpression; -import org.elasticsearch.index.query.BoolQueryBuilder; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; - -/** - * Converter for Match expressions from AAS query structure to Elasticsearch queries - */ -public class MatchExpressionConverter { - - /** - * Converts a list of match expressions to an Elasticsearch query. - * Match expressions in a list are implicitly AND'ed together. - * - * @param expressions List of match expressions - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertMatchExpressions(List expressions) { - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); - - for (MatchExpression expr : expressions) { - QueryBuilder convertedExpr = convertMatchExpression(expr); - if (convertedExpr != null) { - boolQuery.must(convertedExpr); - } - } - - return boolQuery; - } - - /** - * Converts a single match expression to an Elasticsearch query - * - * @param expression The match expression to convert - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertMatchExpression(MatchExpression expression) { - if (expression == null) { - return null; - } - - // Handle nested match expressions - if (!expression.get$match().isEmpty()) { - return convertMatchExpressions(expression.get$match()); - } - - // Handle comparison operators - if (!expression.get$eq().isEmpty()) { - return ComparisonOperatorConverter.convertEqualsExpression(expression.get$eq()); - } else if (!expression.get$ne().isEmpty()) { - return ComparisonOperatorConverter.convertNotEqualsExpression(expression.get$ne()); - } else if (!expression.get$gt().isEmpty()) { - return ComparisonOperatorConverter.convertGreaterThanExpression(expression.get$gt()); - } else if (!expression.get$ge().isEmpty()) { - return ComparisonOperatorConverter.convertGreaterThanOrEqualsExpression(expression.get$ge()); - } else if (!expression.get$lt().isEmpty()) { - return ComparisonOperatorConverter.convertLessThanExpression(expression.get$lt()); - } else if (!expression.get$le().isEmpty()) { - return ComparisonOperatorConverter.convertLessThanOrEqualsExpression(expression.get$le()); - } - - // Handle string operators - else if (!expression.get$contains().isEmpty()) { - return StringOperatorConverter.convertContainsExpression(expression.get$contains()); - } else if (!expression.get$startsWith().isEmpty()) { - return StringOperatorConverter.convertStartsWithExpression(expression.get$startsWith()); - } else if (!expression.get$endsWith().isEmpty()) { - return StringOperatorConverter.convertEndsWithExpression(expression.get$endsWith()); - } else if (!expression.get$regex().isEmpty()) { - return StringOperatorConverter.convertRegexExpression(expression.get$regex()); - } - - // Handle boolean - else if (expression.get$boolean() != null) { - return QueryBuilders.termQuery("_exists_", expression.get$boolean()); - } - - // Default case - return QueryBuilders.matchAllQuery(); - } -} \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java deleted file mode 100644 index 2239d0932..000000000 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/elasticsearch/StringOperatorConverter.java +++ /dev/null @@ -1,203 +0,0 @@ -package org.eclipse.digitaltwin.basyx.core.query.elasticsearch; - -import java.util.List; -import org.eclipse.digitaltwin.basyx.core.query.StringValue; -import org.eclipse.digitaltwin.basyx.core.query.Value; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.index.query.WildcardQueryBuilder; -import org.elasticsearch.index.query.RegexpQueryBuilder; - -/** - * Converter for string operations (contains, starts-with, ends-with, regex) from AAS query structure to Elasticsearch queries - */ -public class StringOperatorConverter { - - /** - * Converts a contains expression to an Elasticsearch wildcard query - * - * @param values List of string values to check - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertContainsExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractStringFieldName(values.get(0), values.get(1)); - String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Use wildcard query with * before and after to represent "contains" - return QueryBuilders.wildcardQuery(fieldName, "*" + fieldValue + "*"); - } - - /** - * Converts a starts-with expression to an Elasticsearch prefix query - * - * @param values List of string values to check - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertStartsWithExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractStringFieldName(values.get(0), values.get(1)); - String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Use prefix query for "starts with" - return QueryBuilders.prefixQuery(fieldName, fieldValue); - } - - /** - * Converts an ends-with expression to an Elasticsearch wildcard query - * - * @param values List of string values to check - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertEndsWithExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractStringFieldName(values.get(0), values.get(1)); - String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Use wildcard query with * at beginning to represent "ends with" - return QueryBuilders.wildcardQuery(fieldName, "*" + fieldValue); - } - - /** - * Converts a regex expression to an Elasticsearch regexp query - * - * @param values List of string values to check - * @return Elasticsearch QueryBuilder - */ - public static QueryBuilder convertRegexExpression(List values) { - if (values.size() != 2) { - return QueryBuilders.matchAllQuery(); - } - - String fieldName = extractStringFieldName(values.get(0), values.get(1)); - String fieldValue = extractStringFieldValue(values.get(0), values.get(1)); - - if (fieldName == null || fieldValue == null) { - return QueryBuilders.matchAllQuery(); - } - - // Use regexp query for regex matching - return QueryBuilders.regexpQuery(fieldName, fieldValue); - } - - /** - * Helper method to extract field name from a pair of string values - * - * @param value1 First string value - * @param value2 Second string value - * @return The field name or null if not found - */ - private static String extractStringFieldName(StringValue value1, StringValue value2) { - // Check if the first value has a field name - if (value1.get$field() != null) { - return normalizeFieldName(value1.get$field()); - } - - // Check if the second value has a field name - if (value2.get$field() != null) { - return normalizeFieldName(value2.get$field()); - } - - // Handle strCast field if present - if (value1.get$strCast() != null && value1.get$strCast().get$field() != null) { - return normalizeFieldName(value1.get$strCast().get$field()); - } - - if (value2.get$strCast() != null && value2.get$strCast().get$field() != null) { - return normalizeFieldName(value2.get$strCast().get$field()); - } - - return null; - } - - /** - * Helper method to extract field value from a pair of string values - * - * @param value1 First string value - * @param value2 Second string value - * @return The field value or null if not found - */ - private static String extractStringFieldValue(StringValue value1, StringValue value2) { - // If first value is a field reference, then second value should contain the value - if (value1.get$field() != null || (value1.get$strCast() != null && value1.get$strCast().get$field() != null)) { - return extractValueFromStringValue(value2); - } - - // If second value is a field reference, then first value should contain the value - if (value2.get$field() != null || (value2.get$strCast() != null && value2.get$strCast().get$field() != null)) { - return extractValueFromStringValue(value1); - } - - return null; - } - - /** - * Extract actual string value from StringValue object - * - * @param value The StringValue object - * @return The extracted string value - */ - private static String extractValueFromStringValue(StringValue value) { - if (value.get$strVal() != null) { - return value.get$strVal(); - } - - // Handle strCast if present - if (value.get$strCast() != null) { - Value castedValue = value.get$strCast(); - if (castedValue.get$strVal() != null) { - return castedValue.get$strVal(); - } else if (castedValue.get$numVal() != null) { - return String.valueOf(castedValue.get$numVal()); - } else if (castedValue.get$boolean() != null) { - return String.valueOf(castedValue.get$boolean()); - } - } - - // Handle attribute reference if present - if (value.get$attribute() != null) { - if (value.get$attribute().getClaim() != null) { - return value.get$attribute().getClaim(); - } else if (value.get$attribute().getReference() != null) { - return value.get$attribute().getReference(); - } - } - - return null; - } - - /** - * Normalize field name for Elasticsearch - * Handles the special AAS field patterns - * - * @param fieldName The AAS field name - * @return Normalized field name for Elasticsearch - */ - private static String normalizeFieldName(String fieldName) { - // Reuse the normalization logic from ComparisonOperatorConverter - return ComparisonOperatorConverter.extractFieldName( - new Value() {{ set$field(fieldName); }}, - new Value()); - } -} \ No newline at end of file From ee3f1d792d7e8c2aa0de0e0837639329485b273d Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 5 Jun 2025 11:23:17 +0200 Subject: [PATCH 03/52] Converts Hits --- .../SearchAasRepositoryApiHTTPController.java | 28 +++++++++-- .../basyx/core/query/ValueConverter.java | 48 +++++++++++++++---- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 79a0744cc..1c0be03b4 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -28,6 +28,8 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.core.query.ElasticSearchRequestBuilder; import org.eclipse.digitaltwin.basyx.core.query.AASQuery; @@ -36,9 +38,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.HashMap; +import java.util.List; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @RestController @@ -57,11 +63,27 @@ public ResponseEntity queryAssetAdministrationShells(AASQuery query, Int ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); SearchRequest searchRequest = builder.buildSearchRequest(query, "aas-index"); try { - SearchResponse response = client.search(searchRequest, AssetAdministrationShell.class); - System.out.println(response); + SearchResponse response = client.search(searchRequest, Object.class); + + List> topHits = response.hits().hits(); + ObjectMapper objectMapper = new ObjectMapper(); + List objectHits = topHits.stream() + .map(Hit::source) + .toList(); + + HashMap responseMap = new HashMap<>(); + responseMap.put("result", objectHits); + HashMap pagingMetadata = new HashMap<>(); + pagingMetadata.put("cursor","null"); + pagingMetadata.put("resultType",(query.get$select() == null || query.get$select().isEmpty()) ? "AssetAdministrationShell" : "Identifier"); + responseMap.put("paging_metadata",pagingMetadata); + + String mapped = objectMapper.writeValueAsString(responseMap); + + return new ResponseEntity(mapped, HttpStatus.OK); } catch (IOException e) { throw new RuntimeException(e); } - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + //return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } } diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java index aea6e90dd..92cfd8dd5 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java @@ -228,23 +228,53 @@ private String extractStringValue(StringValue value) { * Converts model field patterns to ElasticSearch field names */ private String convertModelFieldToElasticField(String modelField) { - // Convert the model pattern to ElasticSearch field name - // This is a simplified conversion - you may need to customize based on your mapping + if (modelField == null) { + return null; + } + + String result = modelField; + + // Remove model prefixes and convert to actual field names if (modelField.startsWith("$aas#")) { - return modelField.replace("$aas#", "aas."); + result = modelField.replace("$aas#", ""); } else if (modelField.startsWith("$sm#")) { - return modelField.replace("$sm#", "submodel."); + result = modelField.replace("$sm#", ""); } else if (modelField.startsWith("$sme")) { - return modelField.replaceFirst("\\$sme(?:\\.[^#]*)?#", "submodelElement."); + result = modelField.replaceFirst("\\$sme(?:\\.[^#]*)?#", ""); } else if (modelField.startsWith("$cd#")) { - return modelField.replace("$cd#", "conceptDescription."); + result = modelField.replace("$cd#", ""); } else if (modelField.startsWith("$aasdesc#")) { - return modelField.replace("$aasdesc#", "aasDescriptor."); + result = modelField.replace("$aasdesc#", ""); } else if (modelField.startsWith("$smdesc#")) { - return modelField.replace("$smdesc#", "submodelDescriptor."); + result = modelField.replace("$smdesc#", ""); + } + + // Remove array brackets [] from field names + result = result.replaceAll("\\[\\]", ""); + + // Add .keyword suffix for string fields that need exact matching + // This is typically needed for fields that contain text values + if (isStringField(result)) { + result = result + ".keyword"; } - return modelField; + return result; + } + + /** + * Determines if a field should have .keyword suffix for exact matching + */ + private boolean isStringField(String fieldName) { + // Add .keyword suffix for fields that typically contain string values that need exact matching + return fieldName.contains("name") || + fieldName.contains("value") || + fieldName.contains("idShort") || + fieldName.contains("id") || + fieldName.contains("type") || + fieldName.contains("assetKind") || + fieldName.contains("assetType") || + fieldName.contains("globalAssetId") || + fieldName.contains("externalSubjectId"); } /** From 6aa494dba513e4babac2f3e66ad2fc1e4873a548 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 5 Jun 2025 11:48:28 +0200 Subject: [PATCH 04/52] Updates Readme --- .../basyx.aasrepository-feature-search/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index f65e7b89e..704d3db37 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -50,4 +50,7 @@ ## TODOS -* [ ] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) \ No newline at end of file +* [ ] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) +* [ ] Comparison of two fields is not possible yet +* [ ] Unit Tests +* [ ] Integration Tests \ No newline at end of file From 9445a091115e29ff92a99b35791dea599e0bebea Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 5 Jun 2025 11:49:19 +0200 Subject: [PATCH 05/52] Updates Readme --- .../basyx.aasrepository-feature-search/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index 704d3db37..85cbd7516 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -53,4 +53,5 @@ * [ ] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) * [ ] Comparison of two fields is not possible yet * [ ] Unit Tests -* [ ] Integration Tests \ No newline at end of file +* [ ] Integration Tests +* [ ] Pagination \ No newline at end of file From 2eb0bc58e030724a9af6e952f5464ae2391fc545 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 12 Jun 2025 13:35:25 +0200 Subject: [PATCH 06/52] Refactors Query Core --- basyx.common/basyx.querycore/pom.xml | 15 + .../basyx/querycore}/query/AASQuery.java | 2 +- .../query/AccessPermissionRule.java | 416 ++-- .../basyx/querycore}/query/Acl.java | 382 ++-- .../query/AllAccessPermissionRules.java | 302 +-- .../basyx/querycore}/query/AttributeItem.java | 286 +-- .../basyx/querycore}/query/Defacl.java | 220 +-- .../basyx/querycore}/query/Defattribute.java | 224 +-- .../basyx/querycore}/query/Defformula.java | 220 +-- .../basyx/querycore}/query/Defobject.java | 230 +-- .../query/ElasticSearchRequestBuilder.java | 2 +- .../querycore}/query/LogicalExpression.java | 632 +++--- .../query/LogicalExpressionConverter.java | 3 +- .../querycore}/query/MatchExpression.java | 524 ++--- .../query/MatchExpressionConverter.java | 2 +- .../basyx/querycore}/query/ObjectItem.java | 268 +-- .../basyx/querycore}/query/QlSchema.java | 174 +- .../query/QueryConverterExample.java | 2 +- .../query/QueryToElasticSearchConverter.java | 2 +- .../basyx/querycore}/query/RightsEnum.java | 104 +- .../basyx/querycore}/query/StringValue.java | 232 +-- .../basyx/querycore}/query/Value.java | 738 +++---- .../querycore}/query/ValueConverter.java | 3 +- .../basyx/querycore}/query/ql.schema.json | 1696 ++++++++--------- basyx.common/pom.xml | 3 +- examples/BaSyxMinimal/docker-compose.yml | 2 +- 26 files changed, 3349 insertions(+), 3335 deletions(-) create mode 100644 basyx.common/basyx.querycore/pom.xml rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/AASQuery.java (97%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/AccessPermissionRule.java (96%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/Acl.java (95%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/AllAccessPermissionRules.java (96%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/AttributeItem.java (95%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/Defacl.java (93%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/Defattribute.java (94%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/Defformula.java (94%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/Defobject.java (95%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/ElasticSearchRequestBuilder.java (98%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/LogicalExpression.java (96%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/LogicalExpressionConverter.java (99%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/MatchExpression.java (96%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/MatchExpressionConverter.java (99%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/ObjectItem.java (95%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/QlSchema.java (94%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/QueryConverterExample.java (99%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/QueryToElasticSearchConverter.java (96%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/RightsEnum.java (91%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/StringValue.java (95%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/Value.java (96%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/ValueConverter.java (99%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core => basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore}/query/ql.schema.json (95%) diff --git a/basyx.common/basyx.querycore/pom.xml b/basyx.common/basyx.querycore/pom.xml new file mode 100644 index 000000000..900f53086 --- /dev/null +++ b/basyx.common/basyx.querycore/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.common + 2.0.0-SNAPSHOT + + + basyx.querycore + BaSyx Query Core + + \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AASQuery.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AASQuery.java similarity index 97% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AASQuery.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AASQuery.java index fb7d4fb1c..bfc677530 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AASQuery.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AASQuery.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.core.query; +package org.eclipse.digitaltwin.basyx.querycore.query; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AccessPermissionRule.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AccessPermissionRule.java similarity index 96% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AccessPermissionRule.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AccessPermissionRule.java index 930c6032c..ce34aa485 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AccessPermissionRule.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AccessPermissionRule.java @@ -1,208 +1,208 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.ArrayList; -import java.util.List; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "ACL", - "USEACL", - "OBJECTS", - "USEOBJECTS", - "FORMULA", - "USEFORMULA", - "FRAGMENT", - "FILTER", - "USEFILTER" -}) -public class AccessPermissionRule { - - @JsonProperty("ACL") - private Acl acl; - @JsonProperty("USEACL") - private String useacl; - @JsonProperty("OBJECTS") - private List objects = new ArrayList(); - @JsonProperty("USEOBJECTS") - private List useobjects = new ArrayList(); - @JsonProperty("FORMULA") - private LogicalExpression formula; - @JsonProperty("USEFORMULA") - private String useformula; - @JsonProperty("FRAGMENT") - private String fragment; - @JsonProperty("FILTER") - private LogicalExpression filter; - @JsonProperty("USEFILTER") - private String usefilter; - - @JsonProperty("ACL") - public Acl getAcl() { - return acl; - } - - @JsonProperty("ACL") - public void setAcl(Acl acl) { - this.acl = acl; - } - - @JsonProperty("USEACL") - public String getUseacl() { - return useacl; - } - - @JsonProperty("USEACL") - public void setUseacl(String useacl) { - this.useacl = useacl; - } - - @JsonProperty("OBJECTS") - public List getObjects() { - return objects; - } - - @JsonProperty("OBJECTS") - public void setObjects(List objects) { - this.objects = objects; - } - - @JsonProperty("USEOBJECTS") - public List getUseobjects() { - return useobjects; - } - - @JsonProperty("USEOBJECTS") - public void setUseobjects(List useobjects) { - this.useobjects = useobjects; - } - - @JsonProperty("FORMULA") - public LogicalExpression getFormula() { - return formula; - } - - @JsonProperty("FORMULA") - public void setFormula(LogicalExpression formula) { - this.formula = formula; - } - - @JsonProperty("USEFORMULA") - public String getUseformula() { - return useformula; - } - - @JsonProperty("USEFORMULA") - public void setUseformula(String useformula) { - this.useformula = useformula; - } - - @JsonProperty("FRAGMENT") - public String getFragment() { - return fragment; - } - - @JsonProperty("FRAGMENT") - public void setFragment(String fragment) { - this.fragment = fragment; - } - - @JsonProperty("FILTER") - public LogicalExpression getFilter() { - return filter; - } - - @JsonProperty("FILTER") - public void setFilter(LogicalExpression filter) { - this.filter = filter; - } - - @JsonProperty("USEFILTER") - public String getUsefilter() { - return usefilter; - } - - @JsonProperty("USEFILTER") - public void setUsefilter(String usefilter) { - this.usefilter = usefilter; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(AccessPermissionRule.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("acl"); - sb.append('='); - sb.append(((this.acl == null)?"":this.acl)); - sb.append(','); - sb.append("useacl"); - sb.append('='); - sb.append(((this.useacl == null)?"":this.useacl)); - sb.append(','); - sb.append("objects"); - sb.append('='); - sb.append(((this.objects == null)?"":this.objects)); - sb.append(','); - sb.append("useobjects"); - sb.append('='); - sb.append(((this.useobjects == null)?"":this.useobjects)); - sb.append(','); - sb.append("formula"); - sb.append('='); - sb.append(((this.formula == null)?"":this.formula)); - sb.append(','); - sb.append("useformula"); - sb.append('='); - sb.append(((this.useformula == null)?"":this.useformula)); - sb.append(','); - sb.append("fragment"); - sb.append('='); - sb.append(((this.fragment == null)?"":this.fragment)); - sb.append(','); - sb.append("filter"); - sb.append('='); - sb.append(((this.filter == null)?"":this.filter)); - sb.append(','); - sb.append("usefilter"); - sb.append('='); - sb.append(((this.usefilter == null)?"":this.usefilter)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.filter == null)? 0 :this.filter.hashCode())); - result = ((result* 31)+((this.fragment == null)? 0 :this.fragment.hashCode())); - result = ((result* 31)+((this.usefilter == null)? 0 :this.usefilter.hashCode())); - result = ((result* 31)+((this.useobjects == null)? 0 :this.useobjects.hashCode())); - result = ((result* 31)+((this.objects == null)? 0 :this.objects.hashCode())); - result = ((result* 31)+((this.useacl == null)? 0 :this.useacl.hashCode())); - result = ((result* 31)+((this.formula == null)? 0 :this.formula.hashCode())); - result = ((result* 31)+((this.acl == null)? 0 :this.acl.hashCode())); - result = ((result* 31)+((this.useformula == null)? 0 :this.useformula.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof AccessPermissionRule) == false) { - return false; - } - AccessPermissionRule rhs = ((AccessPermissionRule) other); - return ((((((((((this.filter == rhs.filter)||((this.filter!= null)&&this.filter.equals(rhs.filter)))&&((this.fragment == rhs.fragment)||((this.fragment!= null)&&this.fragment.equals(rhs.fragment))))&&((this.usefilter == rhs.usefilter)||((this.usefilter!= null)&&this.usefilter.equals(rhs.usefilter))))&&((this.useobjects == rhs.useobjects)||((this.useobjects!= null)&&this.useobjects.equals(rhs.useobjects))))&&((this.objects == rhs.objects)||((this.objects!= null)&&this.objects.equals(rhs.objects))))&&((this.useacl == rhs.useacl)||((this.useacl!= null)&&this.useacl.equals(rhs.useacl))))&&((this.formula == rhs.formula)||((this.formula!= null)&&this.formula.equals(rhs.formula))))&&((this.acl == rhs.acl)||((this.acl!= null)&&this.acl.equals(rhs.acl))))&&((this.useformula == rhs.useformula)||((this.useformula!= null)&&this.useformula.equals(rhs.useformula)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "ACL", + "USEACL", + "OBJECTS", + "USEOBJECTS", + "FORMULA", + "USEFORMULA", + "FRAGMENT", + "FILTER", + "USEFILTER" +}) +public class AccessPermissionRule { + + @JsonProperty("ACL") + private Acl acl; + @JsonProperty("USEACL") + private String useacl; + @JsonProperty("OBJECTS") + private List objects = new ArrayList(); + @JsonProperty("USEOBJECTS") + private List useobjects = new ArrayList(); + @JsonProperty("FORMULA") + private LogicalExpression formula; + @JsonProperty("USEFORMULA") + private String useformula; + @JsonProperty("FRAGMENT") + private String fragment; + @JsonProperty("FILTER") + private LogicalExpression filter; + @JsonProperty("USEFILTER") + private String usefilter; + + @JsonProperty("ACL") + public Acl getAcl() { + return acl; + } + + @JsonProperty("ACL") + public void setAcl(Acl acl) { + this.acl = acl; + } + + @JsonProperty("USEACL") + public String getUseacl() { + return useacl; + } + + @JsonProperty("USEACL") + public void setUseacl(String useacl) { + this.useacl = useacl; + } + + @JsonProperty("OBJECTS") + public List getObjects() { + return objects; + } + + @JsonProperty("OBJECTS") + public void setObjects(List objects) { + this.objects = objects; + } + + @JsonProperty("USEOBJECTS") + public List getUseobjects() { + return useobjects; + } + + @JsonProperty("USEOBJECTS") + public void setUseobjects(List useobjects) { + this.useobjects = useobjects; + } + + @JsonProperty("FORMULA") + public LogicalExpression getFormula() { + return formula; + } + + @JsonProperty("FORMULA") + public void setFormula(LogicalExpression formula) { + this.formula = formula; + } + + @JsonProperty("USEFORMULA") + public String getUseformula() { + return useformula; + } + + @JsonProperty("USEFORMULA") + public void setUseformula(String useformula) { + this.useformula = useformula; + } + + @JsonProperty("FRAGMENT") + public String getFragment() { + return fragment; + } + + @JsonProperty("FRAGMENT") + public void setFragment(String fragment) { + this.fragment = fragment; + } + + @JsonProperty("FILTER") + public LogicalExpression getFilter() { + return filter; + } + + @JsonProperty("FILTER") + public void setFilter(LogicalExpression filter) { + this.filter = filter; + } + + @JsonProperty("USEFILTER") + public String getUsefilter() { + return usefilter; + } + + @JsonProperty("USEFILTER") + public void setUsefilter(String usefilter) { + this.usefilter = usefilter; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(AccessPermissionRule.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("acl"); + sb.append('='); + sb.append(((this.acl == null)?"":this.acl)); + sb.append(','); + sb.append("useacl"); + sb.append('='); + sb.append(((this.useacl == null)?"":this.useacl)); + sb.append(','); + sb.append("objects"); + sb.append('='); + sb.append(((this.objects == null)?"":this.objects)); + sb.append(','); + sb.append("useobjects"); + sb.append('='); + sb.append(((this.useobjects == null)?"":this.useobjects)); + sb.append(','); + sb.append("formula"); + sb.append('='); + sb.append(((this.formula == null)?"":this.formula)); + sb.append(','); + sb.append("useformula"); + sb.append('='); + sb.append(((this.useformula == null)?"":this.useformula)); + sb.append(','); + sb.append("fragment"); + sb.append('='); + sb.append(((this.fragment == null)?"":this.fragment)); + sb.append(','); + sb.append("filter"); + sb.append('='); + sb.append(((this.filter == null)?"":this.filter)); + sb.append(','); + sb.append("usefilter"); + sb.append('='); + sb.append(((this.usefilter == null)?"":this.usefilter)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.filter == null)? 0 :this.filter.hashCode())); + result = ((result* 31)+((this.fragment == null)? 0 :this.fragment.hashCode())); + result = ((result* 31)+((this.usefilter == null)? 0 :this.usefilter.hashCode())); + result = ((result* 31)+((this.useobjects == null)? 0 :this.useobjects.hashCode())); + result = ((result* 31)+((this.objects == null)? 0 :this.objects.hashCode())); + result = ((result* 31)+((this.useacl == null)? 0 :this.useacl.hashCode())); + result = ((result* 31)+((this.formula == null)? 0 :this.formula.hashCode())); + result = ((result* 31)+((this.acl == null)? 0 :this.acl.hashCode())); + result = ((result* 31)+((this.useformula == null)? 0 :this.useformula.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof AccessPermissionRule) == false) { + return false; + } + AccessPermissionRule rhs = ((AccessPermissionRule) other); + return ((((((((((this.filter == rhs.filter)||((this.filter!= null)&&this.filter.equals(rhs.filter)))&&((this.fragment == rhs.fragment)||((this.fragment!= null)&&this.fragment.equals(rhs.fragment))))&&((this.usefilter == rhs.usefilter)||((this.usefilter!= null)&&this.usefilter.equals(rhs.usefilter))))&&((this.useobjects == rhs.useobjects)||((this.useobjects!= null)&&this.useobjects.equals(rhs.useobjects))))&&((this.objects == rhs.objects)||((this.objects!= null)&&this.objects.equals(rhs.objects))))&&((this.useacl == rhs.useacl)||((this.useacl!= null)&&this.useacl.equals(rhs.useacl))))&&((this.formula == rhs.formula)||((this.formula!= null)&&this.formula.equals(rhs.formula))))&&((this.acl == rhs.acl)||((this.acl!= null)&&this.acl.equals(rhs.acl))))&&((this.useformula == rhs.useformula)||((this.useformula!= null)&&this.useformula.equals(rhs.useformula)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Acl.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Acl.java similarity index 95% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Acl.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Acl.java index d119ab11b..561a5f447 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Acl.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Acl.java @@ -1,191 +1,191 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonValue; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "ATTRIBUTES", - "USEATTRIBUTES", - "RIGHTS", - "ACCESS" -}) -public class Acl { - - @JsonProperty("ATTRIBUTES") - private List attributes = new ArrayList(); - @JsonProperty("USEATTRIBUTES") - private String useattributes; - /** - * - * (Required) - * - */ - @JsonProperty("RIGHTS") - private List rights = new ArrayList(); - /** - * - * (Required) - * - */ - @JsonProperty("ACCESS") - private Acl.Access access; - - @JsonProperty("ATTRIBUTES") - public List getAttributes() { - return attributes; - } - - @JsonProperty("ATTRIBUTES") - public void setAttributes(List attributes) { - this.attributes = attributes; - } - - @JsonProperty("USEATTRIBUTES") - public String getUseattributes() { - return useattributes; - } - - @JsonProperty("USEATTRIBUTES") - public void setUseattributes(String useattributes) { - this.useattributes = useattributes; - } - - /** - * - * (Required) - * - */ - @JsonProperty("RIGHTS") - public List getRights() { - return rights; - } - - /** - * - * (Required) - * - */ - @JsonProperty("RIGHTS") - public void setRights(List rights) { - this.rights = rights; - } - - /** - * - * (Required) - * - */ - @JsonProperty("ACCESS") - public Acl.Access getAccess() { - return access; - } - - /** - * - * (Required) - * - */ - @JsonProperty("ACCESS") - public void setAccess(Acl.Access access) { - this.access = access; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Acl.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("attributes"); - sb.append('='); - sb.append(((this.attributes == null)?"":this.attributes)); - sb.append(','); - sb.append("useattributes"); - sb.append('='); - sb.append(((this.useattributes == null)?"":this.useattributes)); - sb.append(','); - sb.append("rights"); - sb.append('='); - sb.append(((this.rights == null)?"":this.rights)); - sb.append(','); - sb.append("access"); - sb.append('='); - sb.append(((this.access == null)?"":this.access)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.useattributes == null)? 0 :this.useattributes.hashCode())); - result = ((result* 31)+((this.attributes == null)? 0 :this.attributes.hashCode())); - result = ((result* 31)+((this.access == null)? 0 :this.access.hashCode())); - result = ((result* 31)+((this.rights == null)? 0 :this.rights.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof Acl) == false) { - return false; - } - Acl rhs = ((Acl) other); - return (((((this.useattributes == rhs.useattributes)||((this.useattributes!= null)&&this.useattributes.equals(rhs.useattributes)))&&((this.attributes == rhs.attributes)||((this.attributes!= null)&&this.attributes.equals(rhs.attributes))))&&((this.access == rhs.access)||((this.access!= null)&&this.access.equals(rhs.access))))&&((this.rights == rhs.rights)||((this.rights!= null)&&this.rights.equals(rhs.rights)))); - } - - public enum Access { - - ALLOW("ALLOW"), - DISABLED("DISABLED"); - private final String value; - private final static Map CONSTANTS = new HashMap(); - - static { - for (Acl.Access c: values()) { - CONSTANTS.put(c.value, c); - } - } - - Access(String value) { - this.value = value; - } - - @Override - public String toString() { - return this.value; - } - - @JsonValue - public String value() { - return this.value; - } - - @JsonCreator - public static Acl.Access fromValue(String value) { - Acl.Access constant = CONSTANTS.get(value); - if (constant == null) { - throw new IllegalArgumentException(value); - } else { - return constant; - } - } - - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "ATTRIBUTES", + "USEATTRIBUTES", + "RIGHTS", + "ACCESS" +}) +public class Acl { + + @JsonProperty("ATTRIBUTES") + private List attributes = new ArrayList(); + @JsonProperty("USEATTRIBUTES") + private String useattributes; + /** + * + * (Required) + * + */ + @JsonProperty("RIGHTS") + private List rights = new ArrayList(); + /** + * + * (Required) + * + */ + @JsonProperty("ACCESS") + private Acl.Access access; + + @JsonProperty("ATTRIBUTES") + public List getAttributes() { + return attributes; + } + + @JsonProperty("ATTRIBUTES") + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + @JsonProperty("USEATTRIBUTES") + public String getUseattributes() { + return useattributes; + } + + @JsonProperty("USEATTRIBUTES") + public void setUseattributes(String useattributes) { + this.useattributes = useattributes; + } + + /** + * + * (Required) + * + */ + @JsonProperty("RIGHTS") + public List getRights() { + return rights; + } + + /** + * + * (Required) + * + */ + @JsonProperty("RIGHTS") + public void setRights(List rights) { + this.rights = rights; + } + + /** + * + * (Required) + * + */ + @JsonProperty("ACCESS") + public Acl.Access getAccess() { + return access; + } + + /** + * + * (Required) + * + */ + @JsonProperty("ACCESS") + public void setAccess(Acl.Access access) { + this.access = access; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Acl.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("attributes"); + sb.append('='); + sb.append(((this.attributes == null)?"":this.attributes)); + sb.append(','); + sb.append("useattributes"); + sb.append('='); + sb.append(((this.useattributes == null)?"":this.useattributes)); + sb.append(','); + sb.append("rights"); + sb.append('='); + sb.append(((this.rights == null)?"":this.rights)); + sb.append(','); + sb.append("access"); + sb.append('='); + sb.append(((this.access == null)?"":this.access)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.useattributes == null)? 0 :this.useattributes.hashCode())); + result = ((result* 31)+((this.attributes == null)? 0 :this.attributes.hashCode())); + result = ((result* 31)+((this.access == null)? 0 :this.access.hashCode())); + result = ((result* 31)+((this.rights == null)? 0 :this.rights.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Acl) == false) { + return false; + } + Acl rhs = ((Acl) other); + return (((((this.useattributes == rhs.useattributes)||((this.useattributes!= null)&&this.useattributes.equals(rhs.useattributes)))&&((this.attributes == rhs.attributes)||((this.attributes!= null)&&this.attributes.equals(rhs.attributes))))&&((this.access == rhs.access)||((this.access!= null)&&this.access.equals(rhs.access))))&&((this.rights == rhs.rights)||((this.rights!= null)&&this.rights.equals(rhs.rights)))); + } + + public enum Access { + + ALLOW("ALLOW"), + DISABLED("DISABLED"); + private final String value; + private final static Map CONSTANTS = new HashMap(); + + static { + for (Acl.Access c: values()) { + CONSTANTS.put(c.value, c); + } + } + + Access(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Acl.Access fromValue(String value) { + Acl.Access constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AllAccessPermissionRules.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AllAccessPermissionRules.java similarity index 96% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AllAccessPermissionRules.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AllAccessPermissionRules.java index 0051d3e4d..3a428ba4b 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AllAccessPermissionRules.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AllAccessPermissionRules.java @@ -1,151 +1,151 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.ArrayList; -import java.util.List; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "DEFATTRIBUTES", - "DEFACLS", - "DEFOBJECTS", - "DEFFORMULAS", - "rules" -}) -public class AllAccessPermissionRules { - - @JsonProperty("DEFATTRIBUTES") - private List defattributes = new ArrayList(); - @JsonProperty("DEFACLS") - private List defacls = new ArrayList(); - @JsonProperty("DEFOBJECTS") - private List defobjects = new ArrayList(); - @JsonProperty("DEFFORMULAS") - private List defformulas = new ArrayList(); - /** - * - * (Required) - * - */ - @JsonProperty("rules") - private List rules = new ArrayList(); - - @JsonProperty("DEFATTRIBUTES") - public List getDefattributes() { - return defattributes; - } - - @JsonProperty("DEFATTRIBUTES") - public void setDefattributes(List defattributes) { - this.defattributes = defattributes; - } - - @JsonProperty("DEFACLS") - public List getDefacls() { - return defacls; - } - - @JsonProperty("DEFACLS") - public void setDefacls(List defacls) { - this.defacls = defacls; - } - - @JsonProperty("DEFOBJECTS") - public List getDefobjects() { - return defobjects; - } - - @JsonProperty("DEFOBJECTS") - public void setDefobjects(List defobjects) { - this.defobjects = defobjects; - } - - @JsonProperty("DEFFORMULAS") - public List getDefformulas() { - return defformulas; - } - - @JsonProperty("DEFFORMULAS") - public void setDefformulas(List defformulas) { - this.defformulas = defformulas; - } - - /** - * - * (Required) - * - */ - @JsonProperty("rules") - public List getRules() { - return rules; - } - - /** - * - * (Required) - * - */ - @JsonProperty("rules") - public void setRules(List rules) { - this.rules = rules; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(AllAccessPermissionRules.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("defattributes"); - sb.append('='); - sb.append(((this.defattributes == null)?"":this.defattributes)); - sb.append(','); - sb.append("defacls"); - sb.append('='); - sb.append(((this.defacls == null)?"":this.defacls)); - sb.append(','); - sb.append("defobjects"); - sb.append('='); - sb.append(((this.defobjects == null)?"":this.defobjects)); - sb.append(','); - sb.append("defformulas"); - sb.append('='); - sb.append(((this.defformulas == null)?"":this.defformulas)); - sb.append(','); - sb.append("rules"); - sb.append('='); - sb.append(((this.rules == null)?"":this.rules)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.rules == null)? 0 :this.rules.hashCode())); - result = ((result* 31)+((this.defobjects == null)? 0 :this.defobjects.hashCode())); - result = ((result* 31)+((this.defformulas == null)? 0 :this.defformulas.hashCode())); - result = ((result* 31)+((this.defattributes == null)? 0 :this.defattributes.hashCode())); - result = ((result* 31)+((this.defacls == null)? 0 :this.defacls.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof AllAccessPermissionRules) == false) { - return false; - } - AllAccessPermissionRules rhs = ((AllAccessPermissionRules) other); - return ((((((this.rules == rhs.rules)||((this.rules!= null)&&this.rules.equals(rhs.rules)))&&((this.defobjects == rhs.defobjects)||((this.defobjects!= null)&&this.defobjects.equals(rhs.defobjects))))&&((this.defformulas == rhs.defformulas)||((this.defformulas!= null)&&this.defformulas.equals(rhs.defformulas))))&&((this.defattributes == rhs.defattributes)||((this.defattributes!= null)&&this.defattributes.equals(rhs.defattributes))))&&((this.defacls == rhs.defacls)||((this.defacls!= null)&&this.defacls.equals(rhs.defacls)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "DEFATTRIBUTES", + "DEFACLS", + "DEFOBJECTS", + "DEFFORMULAS", + "rules" +}) +public class AllAccessPermissionRules { + + @JsonProperty("DEFATTRIBUTES") + private List defattributes = new ArrayList(); + @JsonProperty("DEFACLS") + private List defacls = new ArrayList(); + @JsonProperty("DEFOBJECTS") + private List defobjects = new ArrayList(); + @JsonProperty("DEFFORMULAS") + private List defformulas = new ArrayList(); + /** + * + * (Required) + * + */ + @JsonProperty("rules") + private List rules = new ArrayList(); + + @JsonProperty("DEFATTRIBUTES") + public List getDefattributes() { + return defattributes; + } + + @JsonProperty("DEFATTRIBUTES") + public void setDefattributes(List defattributes) { + this.defattributes = defattributes; + } + + @JsonProperty("DEFACLS") + public List getDefacls() { + return defacls; + } + + @JsonProperty("DEFACLS") + public void setDefacls(List defacls) { + this.defacls = defacls; + } + + @JsonProperty("DEFOBJECTS") + public List getDefobjects() { + return defobjects; + } + + @JsonProperty("DEFOBJECTS") + public void setDefobjects(List defobjects) { + this.defobjects = defobjects; + } + + @JsonProperty("DEFFORMULAS") + public List getDefformulas() { + return defformulas; + } + + @JsonProperty("DEFFORMULAS") + public void setDefformulas(List defformulas) { + this.defformulas = defformulas; + } + + /** + * + * (Required) + * + */ + @JsonProperty("rules") + public List getRules() { + return rules; + } + + /** + * + * (Required) + * + */ + @JsonProperty("rules") + public void setRules(List rules) { + this.rules = rules; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(AllAccessPermissionRules.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("defattributes"); + sb.append('='); + sb.append(((this.defattributes == null)?"":this.defattributes)); + sb.append(','); + sb.append("defacls"); + sb.append('='); + sb.append(((this.defacls == null)?"":this.defacls)); + sb.append(','); + sb.append("defobjects"); + sb.append('='); + sb.append(((this.defobjects == null)?"":this.defobjects)); + sb.append(','); + sb.append("defformulas"); + sb.append('='); + sb.append(((this.defformulas == null)?"":this.defformulas)); + sb.append(','); + sb.append("rules"); + sb.append('='); + sb.append(((this.rules == null)?"":this.rules)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.rules == null)? 0 :this.rules.hashCode())); + result = ((result* 31)+((this.defobjects == null)? 0 :this.defobjects.hashCode())); + result = ((result* 31)+((this.defformulas == null)? 0 :this.defformulas.hashCode())); + result = ((result* 31)+((this.defattributes == null)? 0 :this.defattributes.hashCode())); + result = ((result* 31)+((this.defacls == null)? 0 :this.defacls.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof AllAccessPermissionRules) == false) { + return false; + } + AllAccessPermissionRules rhs = ((AllAccessPermissionRules) other); + return ((((((this.rules == rhs.rules)||((this.rules!= null)&&this.rules.equals(rhs.rules)))&&((this.defobjects == rhs.defobjects)||((this.defobjects!= null)&&this.defobjects.equals(rhs.defobjects))))&&((this.defformulas == rhs.defformulas)||((this.defformulas!= null)&&this.defformulas.equals(rhs.defformulas))))&&((this.defattributes == rhs.defattributes)||((this.defattributes!= null)&&this.defattributes.equals(rhs.defattributes))))&&((this.defacls == rhs.defacls)||((this.defacls!= null)&&this.defacls.equals(rhs.defacls)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AttributeItem.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AttributeItem.java similarity index 95% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AttributeItem.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AttributeItem.java index ad05a8fb2..bb5a7ee2d 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/AttributeItem.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AttributeItem.java @@ -1,143 +1,143 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.HashMap; -import java.util.Map; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonValue; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "CLAIM", - "GLOBAL", - "REFERENCE" -}) -public class AttributeItem { - - @JsonProperty("CLAIM") - private String claim; - @JsonProperty("GLOBAL") - private AttributeItem.Global global; - @JsonProperty("REFERENCE") - private String reference; - - @JsonProperty("CLAIM") - public String getClaim() { - return claim; - } - - @JsonProperty("CLAIM") - public void setClaim(String claim) { - this.claim = claim; - } - - @JsonProperty("GLOBAL") - public AttributeItem.Global getGlobal() { - return global; - } - - @JsonProperty("GLOBAL") - public void setGlobal(AttributeItem.Global global) { - this.global = global; - } - - @JsonProperty("REFERENCE") - public String getReference() { - return reference; - } - - @JsonProperty("REFERENCE") - public void setReference(String reference) { - this.reference = reference; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(AttributeItem.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("claim"); - sb.append('='); - sb.append(((this.claim == null)?"":this.claim)); - sb.append(','); - sb.append("global"); - sb.append('='); - sb.append(((this.global == null)?"":this.global)); - sb.append(','); - sb.append("reference"); - sb.append('='); - sb.append(((this.reference == null)?"":this.reference)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.reference == null)? 0 :this.reference.hashCode())); - result = ((result* 31)+((this.claim == null)? 0 :this.claim.hashCode())); - result = ((result* 31)+((this.global == null)? 0 :this.global.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof AttributeItem) == false) { - return false; - } - AttributeItem rhs = ((AttributeItem) other); - return ((((this.reference == rhs.reference)||((this.reference!= null)&&this.reference.equals(rhs.reference)))&&((this.claim == rhs.claim)||((this.claim!= null)&&this.claim.equals(rhs.claim))))&&((this.global == rhs.global)||((this.global!= null)&&this.global.equals(rhs.global)))); - } - - public enum Global { - - LOCALNOW("LOCALNOW"), - UTCNOW("UTCNOW"), - CLIENTNOW("CLIENTNOW"), - ANONYMOUS("ANONYMOUS"); - private final String value; - private final static Map CONSTANTS = new HashMap(); - - static { - for (AttributeItem.Global c: values()) { - CONSTANTS.put(c.value, c); - } - } - - Global(String value) { - this.value = value; - } - - @Override - public String toString() { - return this.value; - } - - @JsonValue - public String value() { - return this.value; - } - - @JsonCreator - public static AttributeItem.Global fromValue(String value) { - AttributeItem.Global constant = CONSTANTS.get(value); - if (constant == null) { - throw new IllegalArgumentException(value); - } else { - return constant; - } - } - - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "CLAIM", + "GLOBAL", + "REFERENCE" +}) +public class AttributeItem { + + @JsonProperty("CLAIM") + private String claim; + @JsonProperty("GLOBAL") + private AttributeItem.Global global; + @JsonProperty("REFERENCE") + private String reference; + + @JsonProperty("CLAIM") + public String getClaim() { + return claim; + } + + @JsonProperty("CLAIM") + public void setClaim(String claim) { + this.claim = claim; + } + + @JsonProperty("GLOBAL") + public AttributeItem.Global getGlobal() { + return global; + } + + @JsonProperty("GLOBAL") + public void setGlobal(AttributeItem.Global global) { + this.global = global; + } + + @JsonProperty("REFERENCE") + public String getReference() { + return reference; + } + + @JsonProperty("REFERENCE") + public void setReference(String reference) { + this.reference = reference; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(AttributeItem.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("claim"); + sb.append('='); + sb.append(((this.claim == null)?"":this.claim)); + sb.append(','); + sb.append("global"); + sb.append('='); + sb.append(((this.global == null)?"":this.global)); + sb.append(','); + sb.append("reference"); + sb.append('='); + sb.append(((this.reference == null)?"":this.reference)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.reference == null)? 0 :this.reference.hashCode())); + result = ((result* 31)+((this.claim == null)? 0 :this.claim.hashCode())); + result = ((result* 31)+((this.global == null)? 0 :this.global.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof AttributeItem) == false) { + return false; + } + AttributeItem rhs = ((AttributeItem) other); + return ((((this.reference == rhs.reference)||((this.reference!= null)&&this.reference.equals(rhs.reference)))&&((this.claim == rhs.claim)||((this.claim!= null)&&this.claim.equals(rhs.claim))))&&((this.global == rhs.global)||((this.global!= null)&&this.global.equals(rhs.global)))); + } + + public enum Global { + + LOCALNOW("LOCALNOW"), + UTCNOW("UTCNOW"), + CLIENTNOW("CLIENTNOW"), + ANONYMOUS("ANONYMOUS"); + private final String value; + private final static Map CONSTANTS = new HashMap(); + + static { + for (AttributeItem.Global c: values()) { + CONSTANTS.put(c.value, c); + } + } + + Global(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static AttributeItem.Global fromValue(String value) { + AttributeItem.Global constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defacl.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defacl.java similarity index 93% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defacl.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defacl.java index e46088d7a..9cf569a37 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defacl.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defacl.java @@ -1,110 +1,110 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "name", - "acl" -}) -public class Defacl { - - /** - * - * (Required) - * - */ - @JsonProperty("name") - private String name; - /** - * - * (Required) - * - */ - @JsonProperty("acl") - private Acl acl; - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public String getName() { - return name; - } - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public void setName(String name) { - this.name = name; - } - - /** - * - * (Required) - * - */ - @JsonProperty("acl") - public Acl getAcl() { - return acl; - } - - /** - * - * (Required) - * - */ - @JsonProperty("acl") - public void setAcl(Acl acl) { - this.acl = acl; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Defacl.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("name"); - sb.append('='); - sb.append(((this.name == null)?"":this.name)); - sb.append(','); - sb.append("acl"); - sb.append('='); - sb.append(((this.acl == null)?"":this.acl)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); - result = ((result* 31)+((this.acl == null)? 0 :this.acl.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof Defacl) == false) { - return false; - } - Defacl rhs = ((Defacl) other); - return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.acl == rhs.acl)||((this.acl!= null)&&this.acl.equals(rhs.acl)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "acl" +}) +public class Defacl { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + /** + * + * (Required) + * + */ + @JsonProperty("acl") + private Acl acl; + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("acl") + public Acl getAcl() { + return acl; + } + + /** + * + * (Required) + * + */ + @JsonProperty("acl") + public void setAcl(Acl acl) { + this.acl = acl; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defacl.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("acl"); + sb.append('='); + sb.append(((this.acl == null)?"":this.acl)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.acl == null)? 0 :this.acl.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defacl) == false) { + return false; + } + Defacl rhs = ((Defacl) other); + return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.acl == rhs.acl)||((this.acl!= null)&&this.acl.equals(rhs.acl)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defattribute.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defattribute.java similarity index 94% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defattribute.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defattribute.java index 3ea37d5ed..9966c1ab1 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defattribute.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defattribute.java @@ -1,112 +1,112 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.ArrayList; -import java.util.List; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "name", - "attributes" -}) -public class Defattribute { - - /** - * - * (Required) - * - */ - @JsonProperty("name") - private String name; - /** - * - * (Required) - * - */ - @JsonProperty("attributes") - private List attributes = new ArrayList(); - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public String getName() { - return name; - } - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public void setName(String name) { - this.name = name; - } - - /** - * - * (Required) - * - */ - @JsonProperty("attributes") - public List getAttributes() { - return attributes; - } - - /** - * - * (Required) - * - */ - @JsonProperty("attributes") - public void setAttributes(List attributes) { - this.attributes = attributes; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Defattribute.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("name"); - sb.append('='); - sb.append(((this.name == null)?"":this.name)); - sb.append(','); - sb.append("attributes"); - sb.append('='); - sb.append(((this.attributes == null)?"":this.attributes)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); - result = ((result* 31)+((this.attributes == null)? 0 :this.attributes.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof Defattribute) == false) { - return false; - } - Defattribute rhs = ((Defattribute) other); - return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.attributes == rhs.attributes)||((this.attributes!= null)&&this.attributes.equals(rhs.attributes)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "attributes" +}) +public class Defattribute { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + /** + * + * (Required) + * + */ + @JsonProperty("attributes") + private List attributes = new ArrayList(); + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("attributes") + public List getAttributes() { + return attributes; + } + + /** + * + * (Required) + * + */ + @JsonProperty("attributes") + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defattribute.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("attributes"); + sb.append('='); + sb.append(((this.attributes == null)?"":this.attributes)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.attributes == null)? 0 :this.attributes.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defattribute) == false) { + return false; + } + Defattribute rhs = ((Defattribute) other); + return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.attributes == rhs.attributes)||((this.attributes!= null)&&this.attributes.equals(rhs.attributes)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defformula.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defformula.java similarity index 94% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defformula.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defformula.java index 622fccadf..d462362d9 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defformula.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defformula.java @@ -1,110 +1,110 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "name", - "formula" -}) -public class Defformula { - - /** - * - * (Required) - * - */ - @JsonProperty("name") - private String name; - /** - * - * (Required) - * - */ - @JsonProperty("formula") - private LogicalExpression formula; - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public String getName() { - return name; - } - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public void setName(String name) { - this.name = name; - } - - /** - * - * (Required) - * - */ - @JsonProperty("formula") - public LogicalExpression getFormula() { - return formula; - } - - /** - * - * (Required) - * - */ - @JsonProperty("formula") - public void setFormula(LogicalExpression formula) { - this.formula = formula; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Defformula.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("name"); - sb.append('='); - sb.append(((this.name == null)?"":this.name)); - sb.append(','); - sb.append("formula"); - sb.append('='); - sb.append(((this.formula == null)?"":this.formula)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); - result = ((result* 31)+((this.formula == null)? 0 :this.formula.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof Defformula) == false) { - return false; - } - Defformula rhs = ((Defformula) other); - return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.formula == rhs.formula)||((this.formula!= null)&&this.formula.equals(rhs.formula)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "formula" +}) +public class Defformula { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + /** + * + * (Required) + * + */ + @JsonProperty("formula") + private LogicalExpression formula; + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("formula") + public LogicalExpression getFormula() { + return formula; + } + + /** + * + * (Required) + * + */ + @JsonProperty("formula") + public void setFormula(LogicalExpression formula) { + this.formula = formula; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defformula.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("formula"); + sb.append('='); + sb.append(((this.formula == null)?"":this.formula)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.formula == null)? 0 :this.formula.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defformula) == false) { + return false; + } + Defformula rhs = ((Defformula) other); + return (((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.formula == rhs.formula)||((this.formula!= null)&&this.formula.equals(rhs.formula)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defobject.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defobject.java similarity index 95% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defobject.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defobject.java index 0decc0f7b..6a01d3b6a 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Defobject.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defobject.java @@ -1,115 +1,115 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.ArrayList; -import java.util.List; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "name", - "objects", - "USEOBJECTS" -}) -public class Defobject { - - /** - * - * (Required) - * - */ - @JsonProperty("name") - private String name; - @JsonProperty("objects") - private List objects = new ArrayList(); - @JsonProperty("USEOBJECTS") - private List useobjects = new ArrayList(); - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public String getName() { - return name; - } - - /** - * - * (Required) - * - */ - @JsonProperty("name") - public void setName(String name) { - this.name = name; - } - - @JsonProperty("objects") - public List getObjects() { - return objects; - } - - @JsonProperty("objects") - public void setObjects(List objects) { - this.objects = objects; - } - - @JsonProperty("USEOBJECTS") - public List getUseobjects() { - return useobjects; - } - - @JsonProperty("USEOBJECTS") - public void setUseobjects(List useobjects) { - this.useobjects = useobjects; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Defobject.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("name"); - sb.append('='); - sb.append(((this.name == null)?"":this.name)); - sb.append(','); - sb.append("objects"); - sb.append('='); - sb.append(((this.objects == null)?"":this.objects)); - sb.append(','); - sb.append("useobjects"); - sb.append('='); - sb.append(((this.useobjects == null)?"":this.useobjects)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); - result = ((result* 31)+((this.useobjects == null)? 0 :this.useobjects.hashCode())); - result = ((result* 31)+((this.objects == null)? 0 :this.objects.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof Defobject) == false) { - return false; - } - Defobject rhs = ((Defobject) other); - return ((((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.useobjects == rhs.useobjects)||((this.useobjects!= null)&&this.useobjects.equals(rhs.useobjects))))&&((this.objects == rhs.objects)||((this.objects!= null)&&this.objects.equals(rhs.objects)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "objects", + "USEOBJECTS" +}) +public class Defobject { + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + @JsonProperty("objects") + private List objects = new ArrayList(); + @JsonProperty("USEOBJECTS") + private List useobjects = new ArrayList(); + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + @JsonProperty("objects") + public List getObjects() { + return objects; + } + + @JsonProperty("objects") + public void setObjects(List objects) { + this.objects = objects; + } + + @JsonProperty("USEOBJECTS") + public List getUseobjects() { + return useobjects; + } + + @JsonProperty("USEOBJECTS") + public void setUseobjects(List useobjects) { + this.useobjects = useobjects; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Defobject.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null)?"":this.name)); + sb.append(','); + sb.append("objects"); + sb.append('='); + sb.append(((this.objects == null)?"":this.objects)); + sb.append(','); + sb.append("useobjects"); + sb.append('='); + sb.append(((this.useobjects == null)?"":this.useobjects)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.name == null)? 0 :this.name.hashCode())); + result = ((result* 31)+((this.useobjects == null)? 0 :this.useobjects.hashCode())); + result = ((result* 31)+((this.objects == null)? 0 :this.objects.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Defobject) == false) { + return false; + } + Defobject rhs = ((Defobject) other); + return ((((this.name == rhs.name)||((this.name!= null)&&this.name.equals(rhs.name)))&&((this.useobjects == rhs.useobjects)||((this.useobjects!= null)&&this.useobjects.equals(rhs.useobjects))))&&((this.objects == rhs.objects)||((this.objects!= null)&&this.objects.equals(rhs.objects)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ElasticSearchRequestBuilder.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ElasticSearchRequestBuilder.java similarity index 98% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ElasticSearchRequestBuilder.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ElasticSearchRequestBuilder.java index 55365d381..bcf0de060 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ElasticSearchRequestBuilder.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ElasticSearchRequestBuilder.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.core.query; +package org.eclipse.digitaltwin.basyx.querycore.query; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.search.SourceConfig; diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpression.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpression.java similarity index 96% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpression.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpression.java index fd05570bd..4b8666835 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpression.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpression.java @@ -1,316 +1,316 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.ArrayList; -import java.util.List; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "$and", - "$match", - "$or", - "$not", - "$eq", - "$ne", - "$gt", - "$ge", - "$lt", - "$le", - "$contains", - "$starts-with", - "$ends-with", - "$regex", - "$boolean" -}) -public class LogicalExpression { - - @JsonProperty("$and") - private List $and = new ArrayList(); - @JsonProperty("$match") - private List $match = new ArrayList(); - @JsonProperty("$or") - private List $or = new ArrayList(); - @JsonProperty("$not") - private LogicalExpression $not; - @JsonProperty("$eq") - private List $eq = new ArrayList(); - @JsonProperty("$ne") - private List $ne = new ArrayList(); - @JsonProperty("$gt") - private List $gt = new ArrayList(); - @JsonProperty("$ge") - private List $ge = new ArrayList(); - @JsonProperty("$lt") - private List $lt = new ArrayList(); - @JsonProperty("$le") - private List $le = new ArrayList(); - @JsonProperty("$contains") - private List $contains = new ArrayList(); - @JsonProperty("$starts-with") - private List $startsWith = new ArrayList(); - @JsonProperty("$ends-with") - private List $endsWith = new ArrayList(); - @JsonProperty("$regex") - private List $regex = new ArrayList(); - @JsonProperty("$boolean") - private Boolean $boolean; - - @JsonProperty("$and") - public List get$and() { - return $and; - } - - @JsonProperty("$and") - public void set$and(List $and) { - this.$and = $and; - } - - @JsonProperty("$match") - public List get$match() { - return $match; - } - - @JsonProperty("$match") - public void set$match(List $match) { - this.$match = $match; - } - - @JsonProperty("$or") - public List get$or() { - return $or; - } - - @JsonProperty("$or") - public void set$or(List $or) { - this.$or = $or; - } - - @JsonProperty("$not") - public LogicalExpression get$not() { - return $not; - } - - @JsonProperty("$not") - public void set$not(LogicalExpression $not) { - this.$not = $not; - } - - @JsonProperty("$eq") - public List get$eq() { - return $eq; - } - - @JsonProperty("$eq") - public void set$eq(List $eq) { - this.$eq = $eq; - } - - @JsonProperty("$ne") - public List get$ne() { - return $ne; - } - - @JsonProperty("$ne") - public void set$ne(List $ne) { - this.$ne = $ne; - } - - @JsonProperty("$gt") - public List get$gt() { - return $gt; - } - - @JsonProperty("$gt") - public void set$gt(List $gt) { - this.$gt = $gt; - } - - @JsonProperty("$ge") - public List get$ge() { - return $ge; - } - - @JsonProperty("$ge") - public void set$ge(List $ge) { - this.$ge = $ge; - } - - @JsonProperty("$lt") - public List get$lt() { - return $lt; - } - - @JsonProperty("$lt") - public void set$lt(List $lt) { - this.$lt = $lt; - } - - @JsonProperty("$le") - public List get$le() { - return $le; - } - - @JsonProperty("$le") - public void set$le(List $le) { - this.$le = $le; - } - - @JsonProperty("$contains") - public List get$contains() { - return $contains; - } - - @JsonProperty("$contains") - public void set$contains(List $contains) { - this.$contains = $contains; - } - - @JsonProperty("$starts-with") - public List get$startsWith() { - return $startsWith; - } - - @JsonProperty("$starts-with") - public void set$startsWith(List $startsWith) { - this.$startsWith = $startsWith; - } - - @JsonProperty("$ends-with") - public List get$endsWith() { - return $endsWith; - } - - @JsonProperty("$ends-with") - public void set$endsWith(List $endsWith) { - this.$endsWith = $endsWith; - } - - @JsonProperty("$regex") - public List get$regex() { - return $regex; - } - - @JsonProperty("$regex") - public void set$regex(List $regex) { - this.$regex = $regex; - } - - @JsonProperty("$boolean") - public Boolean get$boolean() { - return $boolean; - } - - @JsonProperty("$boolean") - public void set$boolean(Boolean $boolean) { - this.$boolean = $boolean; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(LogicalExpression.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("$and"); - sb.append('='); - sb.append(((this.$and == null)?"":this.$and)); - sb.append(','); - sb.append("$match"); - sb.append('='); - sb.append(((this.$match == null)?"":this.$match)); - sb.append(','); - sb.append("$or"); - sb.append('='); - sb.append(((this.$or == null)?"":this.$or)); - sb.append(','); - sb.append("$not"); - sb.append('='); - sb.append(((this.$not == null)?"":this.$not)); - sb.append(','); - sb.append("$eq"); - sb.append('='); - sb.append(((this.$eq == null)?"":this.$eq)); - sb.append(','); - sb.append("$ne"); - sb.append('='); - sb.append(((this.$ne == null)?"":this.$ne)); - sb.append(','); - sb.append("$gt"); - sb.append('='); - sb.append(((this.$gt == null)?"":this.$gt)); - sb.append(','); - sb.append("$ge"); - sb.append('='); - sb.append(((this.$ge == null)?"":this.$ge)); - sb.append(','); - sb.append("$lt"); - sb.append('='); - sb.append(((this.$lt == null)?"":this.$lt)); - sb.append(','); - sb.append("$le"); - sb.append('='); - sb.append(((this.$le == null)?"":this.$le)); - sb.append(','); - sb.append("$contains"); - sb.append('='); - sb.append(((this.$contains == null)?"":this.$contains)); - sb.append(','); - sb.append("$startsWith"); - sb.append('='); - sb.append(((this.$startsWith == null)?"":this.$startsWith)); - sb.append(','); - sb.append("$endsWith"); - sb.append('='); - sb.append(((this.$endsWith == null)?"":this.$endsWith)); - sb.append(','); - sb.append("$regex"); - sb.append('='); - sb.append(((this.$regex == null)?"":this.$regex)); - sb.append(','); - sb.append("$boolean"); - sb.append('='); - sb.append(((this.$boolean == null)?"":this.$boolean)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); - result = ((result* 31)+((this.$and == null)? 0 :this.$and.hashCode())); - result = ((result* 31)+((this.$ge == null)? 0 :this.$ge.hashCode())); - result = ((result* 31)+((this.$endsWith == null)? 0 :this.$endsWith.hashCode())); - result = ((result* 31)+((this.$or == null)? 0 :this.$or.hashCode())); - result = ((result* 31)+((this.$regex == null)? 0 :this.$regex.hashCode())); - result = ((result* 31)+((this.$startsWith == null)? 0 :this.$startsWith.hashCode())); - result = ((result* 31)+((this.$lt == null)? 0 :this.$lt.hashCode())); - result = ((result* 31)+((this.$contains == null)? 0 :this.$contains.hashCode())); - result = ((result* 31)+((this.$eq == null)? 0 :this.$eq.hashCode())); - result = ((result* 31)+((this.$gt == null)? 0 :this.$gt.hashCode())); - result = ((result* 31)+((this.$ne == null)? 0 :this.$ne.hashCode())); - result = ((result* 31)+((this.$match == null)? 0 :this.$match.hashCode())); - result = ((result* 31)+((this.$not == null)? 0 :this.$not.hashCode())); - result = ((result* 31)+((this.$le == null)? 0 :this.$le.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof LogicalExpression) == false) { - return false; - } - LogicalExpression rhs = ((LogicalExpression) other); - return ((((((((((((((((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean)))&&((this.$and == rhs.$and)||((this.$and!= null)&&this.$and.equals(rhs.$and))))&&((this.$ge == rhs.$ge)||((this.$ge!= null)&&this.$ge.equals(rhs.$ge))))&&((this.$endsWith == rhs.$endsWith)||((this.$endsWith!= null)&&this.$endsWith.equals(rhs.$endsWith))))&&((this.$or == rhs.$or)||((this.$or!= null)&&this.$or.equals(rhs.$or))))&&((this.$regex == rhs.$regex)||((this.$regex!= null)&&this.$regex.equals(rhs.$regex))))&&((this.$startsWith == rhs.$startsWith)||((this.$startsWith!= null)&&this.$startsWith.equals(rhs.$startsWith))))&&((this.$lt == rhs.$lt)||((this.$lt!= null)&&this.$lt.equals(rhs.$lt))))&&((this.$contains == rhs.$contains)||((this.$contains!= null)&&this.$contains.equals(rhs.$contains))))&&((this.$eq == rhs.$eq)||((this.$eq!= null)&&this.$eq.equals(rhs.$eq))))&&((this.$gt == rhs.$gt)||((this.$gt!= null)&&this.$gt.equals(rhs.$gt))))&&((this.$ne == rhs.$ne)||((this.$ne!= null)&&this.$ne.equals(rhs.$ne))))&&((this.$match == rhs.$match)||((this.$match!= null)&&this.$match.equals(rhs.$match))))&&((this.$not == rhs.$not)||((this.$not!= null)&&this.$not.equals(rhs.$not))))&&((this.$le == rhs.$le)||((this.$le!= null)&&this.$le.equals(rhs.$le)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$and", + "$match", + "$or", + "$not", + "$eq", + "$ne", + "$gt", + "$ge", + "$lt", + "$le", + "$contains", + "$starts-with", + "$ends-with", + "$regex", + "$boolean" +}) +public class LogicalExpression { + + @JsonProperty("$and") + private List $and = new ArrayList(); + @JsonProperty("$match") + private List $match = new ArrayList(); + @JsonProperty("$or") + private List $or = new ArrayList(); + @JsonProperty("$not") + private LogicalExpression $not; + @JsonProperty("$eq") + private List $eq = new ArrayList(); + @JsonProperty("$ne") + private List $ne = new ArrayList(); + @JsonProperty("$gt") + private List $gt = new ArrayList(); + @JsonProperty("$ge") + private List $ge = new ArrayList(); + @JsonProperty("$lt") + private List $lt = new ArrayList(); + @JsonProperty("$le") + private List $le = new ArrayList(); + @JsonProperty("$contains") + private List $contains = new ArrayList(); + @JsonProperty("$starts-with") + private List $startsWith = new ArrayList(); + @JsonProperty("$ends-with") + private List $endsWith = new ArrayList(); + @JsonProperty("$regex") + private List $regex = new ArrayList(); + @JsonProperty("$boolean") + private Boolean $boolean; + + @JsonProperty("$and") + public List get$and() { + return $and; + } + + @JsonProperty("$and") + public void set$and(List $and) { + this.$and = $and; + } + + @JsonProperty("$match") + public List get$match() { + return $match; + } + + @JsonProperty("$match") + public void set$match(List $match) { + this.$match = $match; + } + + @JsonProperty("$or") + public List get$or() { + return $or; + } + + @JsonProperty("$or") + public void set$or(List $or) { + this.$or = $or; + } + + @JsonProperty("$not") + public LogicalExpression get$not() { + return $not; + } + + @JsonProperty("$not") + public void set$not(LogicalExpression $not) { + this.$not = $not; + } + + @JsonProperty("$eq") + public List get$eq() { + return $eq; + } + + @JsonProperty("$eq") + public void set$eq(List $eq) { + this.$eq = $eq; + } + + @JsonProperty("$ne") + public List get$ne() { + return $ne; + } + + @JsonProperty("$ne") + public void set$ne(List $ne) { + this.$ne = $ne; + } + + @JsonProperty("$gt") + public List get$gt() { + return $gt; + } + + @JsonProperty("$gt") + public void set$gt(List $gt) { + this.$gt = $gt; + } + + @JsonProperty("$ge") + public List get$ge() { + return $ge; + } + + @JsonProperty("$ge") + public void set$ge(List $ge) { + this.$ge = $ge; + } + + @JsonProperty("$lt") + public List get$lt() { + return $lt; + } + + @JsonProperty("$lt") + public void set$lt(List $lt) { + this.$lt = $lt; + } + + @JsonProperty("$le") + public List get$le() { + return $le; + } + + @JsonProperty("$le") + public void set$le(List $le) { + this.$le = $le; + } + + @JsonProperty("$contains") + public List get$contains() { + return $contains; + } + + @JsonProperty("$contains") + public void set$contains(List $contains) { + this.$contains = $contains; + } + + @JsonProperty("$starts-with") + public List get$startsWith() { + return $startsWith; + } + + @JsonProperty("$starts-with") + public void set$startsWith(List $startsWith) { + this.$startsWith = $startsWith; + } + + @JsonProperty("$ends-with") + public List get$endsWith() { + return $endsWith; + } + + @JsonProperty("$ends-with") + public void set$endsWith(List $endsWith) { + this.$endsWith = $endsWith; + } + + @JsonProperty("$regex") + public List get$regex() { + return $regex; + } + + @JsonProperty("$regex") + public void set$regex(List $regex) { + this.$regex = $regex; + } + + @JsonProperty("$boolean") + public Boolean get$boolean() { + return $boolean; + } + + @JsonProperty("$boolean") + public void set$boolean(Boolean $boolean) { + this.$boolean = $boolean; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(LogicalExpression.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$and"); + sb.append('='); + sb.append(((this.$and == null)?"":this.$and)); + sb.append(','); + sb.append("$match"); + sb.append('='); + sb.append(((this.$match == null)?"":this.$match)); + sb.append(','); + sb.append("$or"); + sb.append('='); + sb.append(((this.$or == null)?"":this.$or)); + sb.append(','); + sb.append("$not"); + sb.append('='); + sb.append(((this.$not == null)?"":this.$not)); + sb.append(','); + sb.append("$eq"); + sb.append('='); + sb.append(((this.$eq == null)?"":this.$eq)); + sb.append(','); + sb.append("$ne"); + sb.append('='); + sb.append(((this.$ne == null)?"":this.$ne)); + sb.append(','); + sb.append("$gt"); + sb.append('='); + sb.append(((this.$gt == null)?"":this.$gt)); + sb.append(','); + sb.append("$ge"); + sb.append('='); + sb.append(((this.$ge == null)?"":this.$ge)); + sb.append(','); + sb.append("$lt"); + sb.append('='); + sb.append(((this.$lt == null)?"":this.$lt)); + sb.append(','); + sb.append("$le"); + sb.append('='); + sb.append(((this.$le == null)?"":this.$le)); + sb.append(','); + sb.append("$contains"); + sb.append('='); + sb.append(((this.$contains == null)?"":this.$contains)); + sb.append(','); + sb.append("$startsWith"); + sb.append('='); + sb.append(((this.$startsWith == null)?"":this.$startsWith)); + sb.append(','); + sb.append("$endsWith"); + sb.append('='); + sb.append(((this.$endsWith == null)?"":this.$endsWith)); + sb.append(','); + sb.append("$regex"); + sb.append('='); + sb.append(((this.$regex == null)?"":this.$regex)); + sb.append(','); + sb.append("$boolean"); + sb.append('='); + sb.append(((this.$boolean == null)?"":this.$boolean)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); + result = ((result* 31)+((this.$and == null)? 0 :this.$and.hashCode())); + result = ((result* 31)+((this.$ge == null)? 0 :this.$ge.hashCode())); + result = ((result* 31)+((this.$endsWith == null)? 0 :this.$endsWith.hashCode())); + result = ((result* 31)+((this.$or == null)? 0 :this.$or.hashCode())); + result = ((result* 31)+((this.$regex == null)? 0 :this.$regex.hashCode())); + result = ((result* 31)+((this.$startsWith == null)? 0 :this.$startsWith.hashCode())); + result = ((result* 31)+((this.$lt == null)? 0 :this.$lt.hashCode())); + result = ((result* 31)+((this.$contains == null)? 0 :this.$contains.hashCode())); + result = ((result* 31)+((this.$eq == null)? 0 :this.$eq.hashCode())); + result = ((result* 31)+((this.$gt == null)? 0 :this.$gt.hashCode())); + result = ((result* 31)+((this.$ne == null)? 0 :this.$ne.hashCode())); + result = ((result* 31)+((this.$match == null)? 0 :this.$match.hashCode())); + result = ((result* 31)+((this.$not == null)? 0 :this.$not.hashCode())); + result = ((result* 31)+((this.$le == null)? 0 :this.$le.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof LogicalExpression) == false) { + return false; + } + LogicalExpression rhs = ((LogicalExpression) other); + return ((((((((((((((((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean)))&&((this.$and == rhs.$and)||((this.$and!= null)&&this.$and.equals(rhs.$and))))&&((this.$ge == rhs.$ge)||((this.$ge!= null)&&this.$ge.equals(rhs.$ge))))&&((this.$endsWith == rhs.$endsWith)||((this.$endsWith!= null)&&this.$endsWith.equals(rhs.$endsWith))))&&((this.$or == rhs.$or)||((this.$or!= null)&&this.$or.equals(rhs.$or))))&&((this.$regex == rhs.$regex)||((this.$regex!= null)&&this.$regex.equals(rhs.$regex))))&&((this.$startsWith == rhs.$startsWith)||((this.$startsWith!= null)&&this.$startsWith.equals(rhs.$startsWith))))&&((this.$lt == rhs.$lt)||((this.$lt!= null)&&this.$lt.equals(rhs.$lt))))&&((this.$contains == rhs.$contains)||((this.$contains!= null)&&this.$contains.equals(rhs.$contains))))&&((this.$eq == rhs.$eq)||((this.$eq!= null)&&this.$eq.equals(rhs.$eq))))&&((this.$gt == rhs.$gt)||((this.$gt!= null)&&this.$gt.equals(rhs.$gt))))&&((this.$ne == rhs.$ne)||((this.$ne!= null)&&this.$ne.equals(rhs.$ne))))&&((this.$match == rhs.$match)||((this.$match!= null)&&this.$match.equals(rhs.$match))))&&((this.$not == rhs.$not)||((this.$not!= null)&&this.$not.equals(rhs.$not))))&&((this.$le == rhs.$le)||((this.$le!= null)&&this.$le.equals(rhs.$le)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpressionConverter.java similarity index 99% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpressionConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpressionConverter.java index 4ad22f7c4..b33fc6566 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/LogicalExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpressionConverter.java @@ -1,10 +1,9 @@ -package org.eclipse.digitaltwin.basyx.core.query; +package org.eclipse.digitaltwin.basyx.querycore.query; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; import java.util.List; -import java.util.ArrayList; /** * Converts LogicalExpression objects to ElasticSearch QueryDSL Query objects diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpression.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpression.java similarity index 96% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpression.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpression.java index 8233d4a36..92c9ad980 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpression.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpression.java @@ -1,262 +1,262 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.ArrayList; -import java.util.List; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "$match", - "$eq", - "$ne", - "$gt", - "$ge", - "$lt", - "$le", - "$contains", - "$starts-with", - "$ends-with", - "$regex", - "$boolean" -}) -public class MatchExpression { - - @JsonProperty("$match") - private List $match = new ArrayList(); - @JsonProperty("$eq") - private List $eq = new ArrayList(); - @JsonProperty("$ne") - private List $ne = new ArrayList(); - @JsonProperty("$gt") - private List $gt = new ArrayList(); - @JsonProperty("$ge") - private List $ge = new ArrayList(); - @JsonProperty("$lt") - private List $lt = new ArrayList(); - @JsonProperty("$le") - private List $le = new ArrayList(); - @JsonProperty("$contains") - private List $contains = new ArrayList(); - @JsonProperty("$starts-with") - private List $startsWith = new ArrayList(); - @JsonProperty("$ends-with") - private List $endsWith = new ArrayList(); - @JsonProperty("$regex") - private List $regex = new ArrayList(); - @JsonProperty("$boolean") - private Boolean $boolean; - - @JsonProperty("$match") - public List get$match() { - return $match; - } - - @JsonProperty("$match") - public void set$match(List $match) { - this.$match = $match; - } - - @JsonProperty("$eq") - public List get$eq() { - return $eq; - } - - @JsonProperty("$eq") - public void set$eq(List $eq) { - this.$eq = $eq; - } - - @JsonProperty("$ne") - public List get$ne() { - return $ne; - } - - @JsonProperty("$ne") - public void set$ne(List $ne) { - this.$ne = $ne; - } - - @JsonProperty("$gt") - public List get$gt() { - return $gt; - } - - @JsonProperty("$gt") - public void set$gt(List $gt) { - this.$gt = $gt; - } - - @JsonProperty("$ge") - public List get$ge() { - return $ge; - } - - @JsonProperty("$ge") - public void set$ge(List $ge) { - this.$ge = $ge; - } - - @JsonProperty("$lt") - public List get$lt() { - return $lt; - } - - @JsonProperty("$lt") - public void set$lt(List $lt) { - this.$lt = $lt; - } - - @JsonProperty("$le") - public List get$le() { - return $le; - } - - @JsonProperty("$le") - public void set$le(List $le) { - this.$le = $le; - } - - @JsonProperty("$contains") - public List get$contains() { - return $contains; - } - - @JsonProperty("$contains") - public void set$contains(List $contains) { - this.$contains = $contains; - } - - @JsonProperty("$starts-with") - public List get$startsWith() { - return $startsWith; - } - - @JsonProperty("$starts-with") - public void set$startsWith(List $startsWith) { - this.$startsWith = $startsWith; - } - - @JsonProperty("$ends-with") - public List get$endsWith() { - return $endsWith; - } - - @JsonProperty("$ends-with") - public void set$endsWith(List $endsWith) { - this.$endsWith = $endsWith; - } - - @JsonProperty("$regex") - public List get$regex() { - return $regex; - } - - @JsonProperty("$regex") - public void set$regex(List $regex) { - this.$regex = $regex; - } - - @JsonProperty("$boolean") - public Boolean get$boolean() { - return $boolean; - } - - @JsonProperty("$boolean") - public void set$boolean(Boolean $boolean) { - this.$boolean = $boolean; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(MatchExpression.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("$match"); - sb.append('='); - sb.append(((this.$match == null)?"":this.$match)); - sb.append(','); - sb.append("$eq"); - sb.append('='); - sb.append(((this.$eq == null)?"":this.$eq)); - sb.append(','); - sb.append("$ne"); - sb.append('='); - sb.append(((this.$ne == null)?"":this.$ne)); - sb.append(','); - sb.append("$gt"); - sb.append('='); - sb.append(((this.$gt == null)?"":this.$gt)); - sb.append(','); - sb.append("$ge"); - sb.append('='); - sb.append(((this.$ge == null)?"":this.$ge)); - sb.append(','); - sb.append("$lt"); - sb.append('='); - sb.append(((this.$lt == null)?"":this.$lt)); - sb.append(','); - sb.append("$le"); - sb.append('='); - sb.append(((this.$le == null)?"":this.$le)); - sb.append(','); - sb.append("$contains"); - sb.append('='); - sb.append(((this.$contains == null)?"":this.$contains)); - sb.append(','); - sb.append("$startsWith"); - sb.append('='); - sb.append(((this.$startsWith == null)?"":this.$startsWith)); - sb.append(','); - sb.append("$endsWith"); - sb.append('='); - sb.append(((this.$endsWith == null)?"":this.$endsWith)); - sb.append(','); - sb.append("$regex"); - sb.append('='); - sb.append(((this.$regex == null)?"":this.$regex)); - sb.append(','); - sb.append("$boolean"); - sb.append('='); - sb.append(((this.$boolean == null)?"":this.$boolean)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); - result = ((result* 31)+((this.$ge == null)? 0 :this.$ge.hashCode())); - result = ((result* 31)+((this.$endsWith == null)? 0 :this.$endsWith.hashCode())); - result = ((result* 31)+((this.$regex == null)? 0 :this.$regex.hashCode())); - result = ((result* 31)+((this.$startsWith == null)? 0 :this.$startsWith.hashCode())); - result = ((result* 31)+((this.$lt == null)? 0 :this.$lt.hashCode())); - result = ((result* 31)+((this.$contains == null)? 0 :this.$contains.hashCode())); - result = ((result* 31)+((this.$eq == null)? 0 :this.$eq.hashCode())); - result = ((result* 31)+((this.$gt == null)? 0 :this.$gt.hashCode())); - result = ((result* 31)+((this.$ne == null)? 0 :this.$ne.hashCode())); - result = ((result* 31)+((this.$match == null)? 0 :this.$match.hashCode())); - result = ((result* 31)+((this.$le == null)? 0 :this.$le.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof MatchExpression) == false) { - return false; - } - MatchExpression rhs = ((MatchExpression) other); - return (((((((((((((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean)))&&((this.$ge == rhs.$ge)||((this.$ge!= null)&&this.$ge.equals(rhs.$ge))))&&((this.$endsWith == rhs.$endsWith)||((this.$endsWith!= null)&&this.$endsWith.equals(rhs.$endsWith))))&&((this.$regex == rhs.$regex)||((this.$regex!= null)&&this.$regex.equals(rhs.$regex))))&&((this.$startsWith == rhs.$startsWith)||((this.$startsWith!= null)&&this.$startsWith.equals(rhs.$startsWith))))&&((this.$lt == rhs.$lt)||((this.$lt!= null)&&this.$lt.equals(rhs.$lt))))&&((this.$contains == rhs.$contains)||((this.$contains!= null)&&this.$contains.equals(rhs.$contains))))&&((this.$eq == rhs.$eq)||((this.$eq!= null)&&this.$eq.equals(rhs.$eq))))&&((this.$gt == rhs.$gt)||((this.$gt!= null)&&this.$gt.equals(rhs.$gt))))&&((this.$ne == rhs.$ne)||((this.$ne!= null)&&this.$ne.equals(rhs.$ne))))&&((this.$match == rhs.$match)||((this.$match!= null)&&this.$match.equals(rhs.$match))))&&((this.$le == rhs.$le)||((this.$le!= null)&&this.$le.equals(rhs.$le)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$match", + "$eq", + "$ne", + "$gt", + "$ge", + "$lt", + "$le", + "$contains", + "$starts-with", + "$ends-with", + "$regex", + "$boolean" +}) +public class MatchExpression { + + @JsonProperty("$match") + private List $match = new ArrayList(); + @JsonProperty("$eq") + private List $eq = new ArrayList(); + @JsonProperty("$ne") + private List $ne = new ArrayList(); + @JsonProperty("$gt") + private List $gt = new ArrayList(); + @JsonProperty("$ge") + private List $ge = new ArrayList(); + @JsonProperty("$lt") + private List $lt = new ArrayList(); + @JsonProperty("$le") + private List $le = new ArrayList(); + @JsonProperty("$contains") + private List $contains = new ArrayList(); + @JsonProperty("$starts-with") + private List $startsWith = new ArrayList(); + @JsonProperty("$ends-with") + private List $endsWith = new ArrayList(); + @JsonProperty("$regex") + private List $regex = new ArrayList(); + @JsonProperty("$boolean") + private Boolean $boolean; + + @JsonProperty("$match") + public List get$match() { + return $match; + } + + @JsonProperty("$match") + public void set$match(List $match) { + this.$match = $match; + } + + @JsonProperty("$eq") + public List get$eq() { + return $eq; + } + + @JsonProperty("$eq") + public void set$eq(List $eq) { + this.$eq = $eq; + } + + @JsonProperty("$ne") + public List get$ne() { + return $ne; + } + + @JsonProperty("$ne") + public void set$ne(List $ne) { + this.$ne = $ne; + } + + @JsonProperty("$gt") + public List get$gt() { + return $gt; + } + + @JsonProperty("$gt") + public void set$gt(List $gt) { + this.$gt = $gt; + } + + @JsonProperty("$ge") + public List get$ge() { + return $ge; + } + + @JsonProperty("$ge") + public void set$ge(List $ge) { + this.$ge = $ge; + } + + @JsonProperty("$lt") + public List get$lt() { + return $lt; + } + + @JsonProperty("$lt") + public void set$lt(List $lt) { + this.$lt = $lt; + } + + @JsonProperty("$le") + public List get$le() { + return $le; + } + + @JsonProperty("$le") + public void set$le(List $le) { + this.$le = $le; + } + + @JsonProperty("$contains") + public List get$contains() { + return $contains; + } + + @JsonProperty("$contains") + public void set$contains(List $contains) { + this.$contains = $contains; + } + + @JsonProperty("$starts-with") + public List get$startsWith() { + return $startsWith; + } + + @JsonProperty("$starts-with") + public void set$startsWith(List $startsWith) { + this.$startsWith = $startsWith; + } + + @JsonProperty("$ends-with") + public List get$endsWith() { + return $endsWith; + } + + @JsonProperty("$ends-with") + public void set$endsWith(List $endsWith) { + this.$endsWith = $endsWith; + } + + @JsonProperty("$regex") + public List get$regex() { + return $regex; + } + + @JsonProperty("$regex") + public void set$regex(List $regex) { + this.$regex = $regex; + } + + @JsonProperty("$boolean") + public Boolean get$boolean() { + return $boolean; + } + + @JsonProperty("$boolean") + public void set$boolean(Boolean $boolean) { + this.$boolean = $boolean; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(MatchExpression.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$match"); + sb.append('='); + sb.append(((this.$match == null)?"":this.$match)); + sb.append(','); + sb.append("$eq"); + sb.append('='); + sb.append(((this.$eq == null)?"":this.$eq)); + sb.append(','); + sb.append("$ne"); + sb.append('='); + sb.append(((this.$ne == null)?"":this.$ne)); + sb.append(','); + sb.append("$gt"); + sb.append('='); + sb.append(((this.$gt == null)?"":this.$gt)); + sb.append(','); + sb.append("$ge"); + sb.append('='); + sb.append(((this.$ge == null)?"":this.$ge)); + sb.append(','); + sb.append("$lt"); + sb.append('='); + sb.append(((this.$lt == null)?"":this.$lt)); + sb.append(','); + sb.append("$le"); + sb.append('='); + sb.append(((this.$le == null)?"":this.$le)); + sb.append(','); + sb.append("$contains"); + sb.append('='); + sb.append(((this.$contains == null)?"":this.$contains)); + sb.append(','); + sb.append("$startsWith"); + sb.append('='); + sb.append(((this.$startsWith == null)?"":this.$startsWith)); + sb.append(','); + sb.append("$endsWith"); + sb.append('='); + sb.append(((this.$endsWith == null)?"":this.$endsWith)); + sb.append(','); + sb.append("$regex"); + sb.append('='); + sb.append(((this.$regex == null)?"":this.$regex)); + sb.append(','); + sb.append("$boolean"); + sb.append('='); + sb.append(((this.$boolean == null)?"":this.$boolean)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); + result = ((result* 31)+((this.$ge == null)? 0 :this.$ge.hashCode())); + result = ((result* 31)+((this.$endsWith == null)? 0 :this.$endsWith.hashCode())); + result = ((result* 31)+((this.$regex == null)? 0 :this.$regex.hashCode())); + result = ((result* 31)+((this.$startsWith == null)? 0 :this.$startsWith.hashCode())); + result = ((result* 31)+((this.$lt == null)? 0 :this.$lt.hashCode())); + result = ((result* 31)+((this.$contains == null)? 0 :this.$contains.hashCode())); + result = ((result* 31)+((this.$eq == null)? 0 :this.$eq.hashCode())); + result = ((result* 31)+((this.$gt == null)? 0 :this.$gt.hashCode())); + result = ((result* 31)+((this.$ne == null)? 0 :this.$ne.hashCode())); + result = ((result* 31)+((this.$match == null)? 0 :this.$match.hashCode())); + result = ((result* 31)+((this.$le == null)? 0 :this.$le.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof MatchExpression) == false) { + return false; + } + MatchExpression rhs = ((MatchExpression) other); + return (((((((((((((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean)))&&((this.$ge == rhs.$ge)||((this.$ge!= null)&&this.$ge.equals(rhs.$ge))))&&((this.$endsWith == rhs.$endsWith)||((this.$endsWith!= null)&&this.$endsWith.equals(rhs.$endsWith))))&&((this.$regex == rhs.$regex)||((this.$regex!= null)&&this.$regex.equals(rhs.$regex))))&&((this.$startsWith == rhs.$startsWith)||((this.$startsWith!= null)&&this.$startsWith.equals(rhs.$startsWith))))&&((this.$lt == rhs.$lt)||((this.$lt!= null)&&this.$lt.equals(rhs.$lt))))&&((this.$contains == rhs.$contains)||((this.$contains!= null)&&this.$contains.equals(rhs.$contains))))&&((this.$eq == rhs.$eq)||((this.$eq!= null)&&this.$eq.equals(rhs.$eq))))&&((this.$gt == rhs.$gt)||((this.$gt!= null)&&this.$gt.equals(rhs.$gt))))&&((this.$ne == rhs.$ne)||((this.$ne!= null)&&this.$ne.equals(rhs.$ne))))&&((this.$match == rhs.$match)||((this.$match!= null)&&this.$match.equals(rhs.$match))))&&((this.$le == rhs.$le)||((this.$le!= null)&&this.$le.equals(rhs.$le)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpressionConverter.java similarity index 99% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpressionConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpressionConverter.java index 04f2521c4..3831dcb54 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/MatchExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpressionConverter.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.core.query; +package org.eclipse.digitaltwin.basyx.querycore.query; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ObjectItem.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ObjectItem.java similarity index 95% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ObjectItem.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ObjectItem.java index 95b101905..ced3148d6 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ObjectItem.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ObjectItem.java @@ -1,134 +1,134 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "ROUTE", - "IDENTIFIABLE", - "REFERABLE", - "FRAGMENT", - "DESCRIPTOR" -}) -public class ObjectItem { - - @JsonProperty("ROUTE") - private String route; - @JsonProperty("IDENTIFIABLE") - private String identifiable; - @JsonProperty("REFERABLE") - private String referable; - @JsonProperty("FRAGMENT") - private String fragment; - @JsonProperty("DESCRIPTOR") - private String descriptor; - - @JsonProperty("ROUTE") - public String getRoute() { - return route; - } - - @JsonProperty("ROUTE") - public void setRoute(String route) { - this.route = route; - } - - @JsonProperty("IDENTIFIABLE") - public String getIdentifiable() { - return identifiable; - } - - @JsonProperty("IDENTIFIABLE") - public void setIdentifiable(String identifiable) { - this.identifiable = identifiable; - } - - @JsonProperty("REFERABLE") - public String getReferable() { - return referable; - } - - @JsonProperty("REFERABLE") - public void setReferable(String referable) { - this.referable = referable; - } - - @JsonProperty("FRAGMENT") - public String getFragment() { - return fragment; - } - - @JsonProperty("FRAGMENT") - public void setFragment(String fragment) { - this.fragment = fragment; - } - - @JsonProperty("DESCRIPTOR") - public String getDescriptor() { - return descriptor; - } - - @JsonProperty("DESCRIPTOR") - public void setDescriptor(String descriptor) { - this.descriptor = descriptor; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(ObjectItem.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("route"); - sb.append('='); - sb.append(((this.route == null)?"":this.route)); - sb.append(','); - sb.append("identifiable"); - sb.append('='); - sb.append(((this.identifiable == null)?"":this.identifiable)); - sb.append(','); - sb.append("referable"); - sb.append('='); - sb.append(((this.referable == null)?"":this.referable)); - sb.append(','); - sb.append("fragment"); - sb.append('='); - sb.append(((this.fragment == null)?"":this.fragment)); - sb.append(','); - sb.append("descriptor"); - sb.append('='); - sb.append(((this.descriptor == null)?"":this.descriptor)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.identifiable == null)? 0 :this.identifiable.hashCode())); - result = ((result* 31)+((this.fragment == null)? 0 :this.fragment.hashCode())); - result = ((result* 31)+((this.route == null)? 0 :this.route.hashCode())); - result = ((result* 31)+((this.referable == null)? 0 :this.referable.hashCode())); - result = ((result* 31)+((this.descriptor == null)? 0 :this.descriptor.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof ObjectItem) == false) { - return false; - } - ObjectItem rhs = ((ObjectItem) other); - return ((((((this.identifiable == rhs.identifiable)||((this.identifiable!= null)&&this.identifiable.equals(rhs.identifiable)))&&((this.fragment == rhs.fragment)||((this.fragment!= null)&&this.fragment.equals(rhs.fragment))))&&((this.route == rhs.route)||((this.route!= null)&&this.route.equals(rhs.route))))&&((this.referable == rhs.referable)||((this.referable!= null)&&this.referable.equals(rhs.referable))))&&((this.descriptor == rhs.descriptor)||((this.descriptor!= null)&&this.descriptor.equals(rhs.descriptor)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "ROUTE", + "IDENTIFIABLE", + "REFERABLE", + "FRAGMENT", + "DESCRIPTOR" +}) +public class ObjectItem { + + @JsonProperty("ROUTE") + private String route; + @JsonProperty("IDENTIFIABLE") + private String identifiable; + @JsonProperty("REFERABLE") + private String referable; + @JsonProperty("FRAGMENT") + private String fragment; + @JsonProperty("DESCRIPTOR") + private String descriptor; + + @JsonProperty("ROUTE") + public String getRoute() { + return route; + } + + @JsonProperty("ROUTE") + public void setRoute(String route) { + this.route = route; + } + + @JsonProperty("IDENTIFIABLE") + public String getIdentifiable() { + return identifiable; + } + + @JsonProperty("IDENTIFIABLE") + public void setIdentifiable(String identifiable) { + this.identifiable = identifiable; + } + + @JsonProperty("REFERABLE") + public String getReferable() { + return referable; + } + + @JsonProperty("REFERABLE") + public void setReferable(String referable) { + this.referable = referable; + } + + @JsonProperty("FRAGMENT") + public String getFragment() { + return fragment; + } + + @JsonProperty("FRAGMENT") + public void setFragment(String fragment) { + this.fragment = fragment; + } + + @JsonProperty("DESCRIPTOR") + public String getDescriptor() { + return descriptor; + } + + @JsonProperty("DESCRIPTOR") + public void setDescriptor(String descriptor) { + this.descriptor = descriptor; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(ObjectItem.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("route"); + sb.append('='); + sb.append(((this.route == null)?"":this.route)); + sb.append(','); + sb.append("identifiable"); + sb.append('='); + sb.append(((this.identifiable == null)?"":this.identifiable)); + sb.append(','); + sb.append("referable"); + sb.append('='); + sb.append(((this.referable == null)?"":this.referable)); + sb.append(','); + sb.append("fragment"); + sb.append('='); + sb.append(((this.fragment == null)?"":this.fragment)); + sb.append(','); + sb.append("descriptor"); + sb.append('='); + sb.append(((this.descriptor == null)?"":this.descriptor)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.identifiable == null)? 0 :this.identifiable.hashCode())); + result = ((result* 31)+((this.fragment == null)? 0 :this.fragment.hashCode())); + result = ((result* 31)+((this.route == null)? 0 :this.route.hashCode())); + result = ((result* 31)+((this.referable == null)? 0 :this.referable.hashCode())); + result = ((result* 31)+((this.descriptor == null)? 0 :this.descriptor.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof ObjectItem) == false) { + return false; + } + ObjectItem rhs = ((ObjectItem) other); + return ((((((this.identifiable == rhs.identifiable)||((this.identifiable!= null)&&this.identifiable.equals(rhs.identifiable)))&&((this.fragment == rhs.fragment)||((this.fragment!= null)&&this.fragment.equals(rhs.fragment))))&&((this.route == rhs.route)||((this.route!= null)&&this.route.equals(rhs.route))))&&((this.referable == rhs.referable)||((this.referable!= null)&&this.referable.equals(rhs.referable))))&&((this.descriptor == rhs.descriptor)||((this.descriptor!= null)&&this.descriptor.equals(rhs.descriptor)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QlSchema.java similarity index 94% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QlSchema.java index 0dcc88e33..8a8127591 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QlSchema.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QlSchema.java @@ -1,87 +1,87 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - - -/** - * Common JSON Schema for AAS Queries and Access Rules - *

- * This schema contains all classes that are shared between the AAS Query Language and the AAS Access Rule Language. - * - */ -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "Query", - "AllAccessPermissionRules" -}) -public class QlSchema { - - @JsonProperty("Query") - private AASQuery query; - @JsonProperty("AllAccessPermissionRules") - private AllAccessPermissionRules allAccessPermissionRules; - - @JsonProperty("Query") - public AASQuery getQuery() { - return query; - } - - @JsonProperty("Query") - public void setQuery(AASQuery query) { - this.query = query; - } - - @JsonProperty("AllAccessPermissionRules") - public AllAccessPermissionRules getAllAccessPermissionRules() { - return allAccessPermissionRules; - } - - @JsonProperty("AllAccessPermissionRules") - public void setAllAccessPermissionRules(AllAccessPermissionRules allAccessPermissionRules) { - this.allAccessPermissionRules = allAccessPermissionRules; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(QlSchema.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("query"); - sb.append('='); - sb.append(((this.query == null)?"":this.query)); - sb.append(','); - sb.append("allAccessPermissionRules"); - sb.append('='); - sb.append(((this.allAccessPermissionRules == null)?"":this.allAccessPermissionRules)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.allAccessPermissionRules == null)? 0 :this.allAccessPermissionRules.hashCode())); - result = ((result* 31)+((this.query == null)? 0 :this.query.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof QlSchema) == false) { - return false; - } - QlSchema rhs = ((QlSchema) other); - return (((this.allAccessPermissionRules == rhs.allAccessPermissionRules)||((this.allAccessPermissionRules!= null)&&this.allAccessPermissionRules.equals(rhs.allAccessPermissionRules)))&&((this.query == rhs.query)||((this.query!= null)&&this.query.equals(rhs.query)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * Common JSON Schema for AAS Queries and Access Rules + *

+ * This schema contains all classes that are shared between the AAS Query Language and the AAS Access Rule Language. + * + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "Query", + "AllAccessPermissionRules" +}) +public class QlSchema { + + @JsonProperty("Query") + private AASQuery query; + @JsonProperty("AllAccessPermissionRules") + private AllAccessPermissionRules allAccessPermissionRules; + + @JsonProperty("Query") + public AASQuery getQuery() { + return query; + } + + @JsonProperty("Query") + public void setQuery(AASQuery query) { + this.query = query; + } + + @JsonProperty("AllAccessPermissionRules") + public AllAccessPermissionRules getAllAccessPermissionRules() { + return allAccessPermissionRules; + } + + @JsonProperty("AllAccessPermissionRules") + public void setAllAccessPermissionRules(AllAccessPermissionRules allAccessPermissionRules) { + this.allAccessPermissionRules = allAccessPermissionRules; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(QlSchema.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("org/eclipse/digitaltwin/basyx/querycore/query"); + sb.append('='); + sb.append(((this.query == null)?"":this.query)); + sb.append(','); + sb.append("allAccessPermissionRules"); + sb.append('='); + sb.append(((this.allAccessPermissionRules == null)?"":this.allAccessPermissionRules)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.allAccessPermissionRules == null)? 0 :this.allAccessPermissionRules.hashCode())); + result = ((result* 31)+((this.query == null)? 0 :this.query.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof QlSchema) == false) { + return false; + } + QlSchema rhs = ((QlSchema) other); + return (((this.allAccessPermissionRules == rhs.allAccessPermissionRules)||((this.allAccessPermissionRules!= null)&&this.allAccessPermissionRules.equals(rhs.allAccessPermissionRules)))&&((this.query == rhs.query)||((this.query!= null)&&this.query.equals(rhs.query)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryConverterExample.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryConverterExample.java similarity index 99% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryConverterExample.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryConverterExample.java index 938dfe916..9e0037738 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryConverterExample.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryConverterExample.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.core.query; +package org.eclipse.digitaltwin.basyx.querycore.query; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch.core.SearchRequest; diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryToElasticSearchConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryToElasticSearchConverter.java similarity index 96% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryToElasticSearchConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryToElasticSearchConverter.java index 502f171fa..6debd7ba3 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/QueryToElasticSearchConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryToElasticSearchConverter.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.core.query; +package org.eclipse.digitaltwin.basyx.querycore.query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/RightsEnum.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/RightsEnum.java similarity index 91% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/RightsEnum.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/RightsEnum.java index ed73c9932..bda8bf529 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/RightsEnum.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/RightsEnum.java @@ -1,52 +1,52 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.HashMap; -import java.util.Map; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public enum RightsEnum { - - CREATE("CREATE"), - READ("READ"), - UPDATE("UPDATE"), - DELETE("DELETE"), - EXECUTE("EXECUTE"), - VIEW("VIEW"), - ALL("ALL"), - TREE("TREE"); - private final String value; - private final static Map CONSTANTS = new HashMap(); - - static { - for (RightsEnum c: values()) { - CONSTANTS.put(c.value, c); - } - } - - RightsEnum(String value) { - this.value = value; - } - - @Override - public String toString() { - return this.value; - } - - @JsonValue - public String value() { - return this.value; - } - - @JsonCreator - public static RightsEnum fromValue(String value) { - RightsEnum constant = CONSTANTS.get(value); - if (constant == null) { - throw new IllegalArgumentException(value); - } else { - return constant; - } - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum RightsEnum { + + CREATE("CREATE"), + READ("READ"), + UPDATE("UPDATE"), + DELETE("DELETE"), + EXECUTE("EXECUTE"), + VIEW("VIEW"), + ALL("ALL"), + TREE("TREE"); + private final String value; + private final static Map CONSTANTS = new HashMap(); + + static { + for (RightsEnum c: values()) { + CONSTANTS.put(c.value, c); + } + } + + RightsEnum(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static RightsEnum fromValue(String value) { + RightsEnum constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/StringValue.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/StringValue.java similarity index 95% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/StringValue.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/StringValue.java index f238f4588..4a8851f1a 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/StringValue.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/StringValue.java @@ -1,116 +1,116 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "$field", - "$strVal", - "$strCast", - "$attribute" -}) -public class StringValue { - - @JsonProperty("$field") - private String $field; - @JsonProperty("$strVal") - private String $strVal; - @JsonProperty("$strCast") - private Value $strCast; - @JsonProperty("$attribute") - private AttributeItem $attribute; - - @JsonProperty("$field") - public String get$field() { - return $field; - } - - @JsonProperty("$field") - public void set$field(String $field) { - this.$field = $field; - } - - @JsonProperty("$strVal") - public String get$strVal() { - return $strVal; - } - - @JsonProperty("$strVal") - public void set$strVal(String $strVal) { - this.$strVal = $strVal; - } - - @JsonProperty("$strCast") - public Value get$strCast() { - return $strCast; - } - - @JsonProperty("$strCast") - public void set$strCast(Value $strCast) { - this.$strCast = $strCast; - } - - @JsonProperty("$attribute") - public AttributeItem get$attribute() { - return $attribute; - } - - @JsonProperty("$attribute") - public void set$attribute(AttributeItem $attribute) { - this.$attribute = $attribute; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(StringValue.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("$field"); - sb.append('='); - sb.append(((this.$field == null)?"":this.$field)); - sb.append(','); - sb.append("$strVal"); - sb.append('='); - sb.append(((this.$strVal == null)?"":this.$strVal)); - sb.append(','); - sb.append("$strCast"); - sb.append('='); - sb.append(((this.$strCast == null)?"":this.$strCast)); - sb.append(','); - sb.append("$attribute"); - sb.append('='); - sb.append(((this.$attribute == null)?"":this.$attribute)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.$strVal == null)? 0 :this.$strVal.hashCode())); - result = ((result* 31)+((this.$field == null)? 0 :this.$field.hashCode())); - result = ((result* 31)+((this.$strCast == null)? 0 :this.$strCast.hashCode())); - result = ((result* 31)+((this.$attribute == null)? 0 :this.$attribute.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof StringValue) == false) { - return false; - } - StringValue rhs = ((StringValue) other); - return (((((this.$strVal == rhs.$strVal)||((this.$strVal!= null)&&this.$strVal.equals(rhs.$strVal)))&&((this.$field == rhs.$field)||((this.$field!= null)&&this.$field.equals(rhs.$field))))&&((this.$strCast == rhs.$strCast)||((this.$strCast!= null)&&this.$strCast.equals(rhs.$strCast))))&&((this.$attribute == rhs.$attribute)||((this.$attribute!= null)&&this.$attribute.equals(rhs.$attribute)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$field", + "$strVal", + "$strCast", + "$attribute" +}) +public class StringValue { + + @JsonProperty("$field") + private String $field; + @JsonProperty("$strVal") + private String $strVal; + @JsonProperty("$strCast") + private Value $strCast; + @JsonProperty("$attribute") + private AttributeItem $attribute; + + @JsonProperty("$field") + public String get$field() { + return $field; + } + + @JsonProperty("$field") + public void set$field(String $field) { + this.$field = $field; + } + + @JsonProperty("$strVal") + public String get$strVal() { + return $strVal; + } + + @JsonProperty("$strVal") + public void set$strVal(String $strVal) { + this.$strVal = $strVal; + } + + @JsonProperty("$strCast") + public Value get$strCast() { + return $strCast; + } + + @JsonProperty("$strCast") + public void set$strCast(Value $strCast) { + this.$strCast = $strCast; + } + + @JsonProperty("$attribute") + public AttributeItem get$attribute() { + return $attribute; + } + + @JsonProperty("$attribute") + public void set$attribute(AttributeItem $attribute) { + this.$attribute = $attribute; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(StringValue.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$field"); + sb.append('='); + sb.append(((this.$field == null)?"":this.$field)); + sb.append(','); + sb.append("$strVal"); + sb.append('='); + sb.append(((this.$strVal == null)?"":this.$strVal)); + sb.append(','); + sb.append("$strCast"); + sb.append('='); + sb.append(((this.$strCast == null)?"":this.$strCast)); + sb.append(','); + sb.append("$attribute"); + sb.append('='); + sb.append(((this.$attribute == null)?"":this.$attribute)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$strVal == null)? 0 :this.$strVal.hashCode())); + result = ((result* 31)+((this.$field == null)? 0 :this.$field.hashCode())); + result = ((result* 31)+((this.$strCast == null)? 0 :this.$strCast.hashCode())); + result = ((result* 31)+((this.$attribute == null)? 0 :this.$attribute.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof StringValue) == false) { + return false; + } + StringValue rhs = ((StringValue) other); + return (((((this.$strVal == rhs.$strVal)||((this.$strVal!= null)&&this.$strVal.equals(rhs.$strVal)))&&((this.$field == rhs.$field)||((this.$field!= null)&&this.$field.equals(rhs.$field))))&&((this.$strCast == rhs.$strCast)||((this.$strCast!= null)&&this.$strCast.equals(rhs.$strCast))))&&((this.$attribute == rhs.$attribute)||((this.$attribute!= null)&&this.$attribute.equals(rhs.$attribute)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Value.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Value.java similarity index 96% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Value.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Value.java index 7afe9df2d..ea8ccadc2 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/Value.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Value.java @@ -1,369 +1,369 @@ - -package org.eclipse.digitaltwin.basyx.core.query; - -import java.util.Date; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "$field", - "$strVal", - "$attribute", - "$numVal", - "$hexVal", - "$dateTimeVal", - "$timeVal", - "$boolean", - "$strCast", - "$numCast", - "$hexCast", - "$boolCast", - "$dateTimeCast", - "$timeCast", - "$dayOfWeek", - "$dayOfMonth", - "$month", - "$year" -}) -public class Value { - - @JsonProperty("$field") - private String $field; - @JsonProperty("$strVal") - private String $strVal; - @JsonProperty("$attribute") - private AttributeItem $attribute; - @JsonProperty("$numVal") - private Double $numVal; - @JsonProperty("$hexVal") - private String $hexVal; - @JsonProperty("$dateTimeVal") - private Date $dateTimeVal; - @JsonProperty("$timeVal") - private String $timeVal; - @JsonProperty("$boolean") - private Boolean $boolean; - @JsonProperty("$strCast") - private Value $strCast; - @JsonProperty("$numCast") - private Value $numCast; - @JsonProperty("$hexCast") - private Value $hexCast; - @JsonProperty("$boolCast") - private Value $boolCast; - @JsonProperty("$dateTimeCast") - private Value $dateTimeCast; - @JsonProperty("$timeCast") - private Value $timeCast; - @JsonProperty("$dayOfWeek") - private Date $dayOfWeek; - @JsonProperty("$dayOfMonth") - private Date $dayOfMonth; - @JsonProperty("$month") - private Date $month; - @JsonProperty("$year") - private Date $year; - - @JsonProperty("$field") - public String get$field() { - return $field; - } - - @JsonProperty("$field") - public void set$field(String $field) { - this.$field = $field; - } - - @JsonProperty("$strVal") - public String get$strVal() { - return $strVal; - } - - @JsonProperty("$strVal") - public void set$strVal(String $strVal) { - this.$strVal = $strVal; - } - - @JsonProperty("$attribute") - public AttributeItem get$attribute() { - return $attribute; - } - - @JsonProperty("$attribute") - public void set$attribute(AttributeItem $attribute) { - this.$attribute = $attribute; - } - - @JsonProperty("$numVal") - public Double get$numVal() { - return $numVal; - } - - @JsonProperty("$numVal") - public void set$numVal(Double $numVal) { - this.$numVal = $numVal; - } - - @JsonProperty("$hexVal") - public String get$hexVal() { - return $hexVal; - } - - @JsonProperty("$hexVal") - public void set$hexVal(String $hexVal) { - this.$hexVal = $hexVal; - } - - @JsonProperty("$dateTimeVal") - public Date get$dateTimeVal() { - return $dateTimeVal; - } - - @JsonProperty("$dateTimeVal") - public void set$dateTimeVal(Date $dateTimeVal) { - this.$dateTimeVal = $dateTimeVal; - } - - @JsonProperty("$timeVal") - public String get$timeVal() { - return $timeVal; - } - - @JsonProperty("$timeVal") - public void set$timeVal(String $timeVal) { - this.$timeVal = $timeVal; - } - - @JsonProperty("$boolean") - public Boolean get$boolean() { - return $boolean; - } - - @JsonProperty("$boolean") - public void set$boolean(Boolean $boolean) { - this.$boolean = $boolean; - } - - @JsonProperty("$strCast") - public Value get$strCast() { - return $strCast; - } - - @JsonProperty("$strCast") - public void set$strCast(Value $strCast) { - this.$strCast = $strCast; - } - - @JsonProperty("$numCast") - public Value get$numCast() { - return $numCast; - } - - @JsonProperty("$numCast") - public void set$numCast(Value $numCast) { - this.$numCast = $numCast; - } - - @JsonProperty("$hexCast") - public Value get$hexCast() { - return $hexCast; - } - - @JsonProperty("$hexCast") - public void set$hexCast(Value $hexCast) { - this.$hexCast = $hexCast; - } - - @JsonProperty("$boolCast") - public Value get$boolCast() { - return $boolCast; - } - - @JsonProperty("$boolCast") - public void set$boolCast(Value $boolCast) { - this.$boolCast = $boolCast; - } - - @JsonProperty("$dateTimeCast") - public Value get$dateTimeCast() { - return $dateTimeCast; - } - - @JsonProperty("$dateTimeCast") - public void set$dateTimeCast(Value $dateTimeCast) { - this.$dateTimeCast = $dateTimeCast; - } - - @JsonProperty("$timeCast") - public Value get$timeCast() { - return $timeCast; - } - - @JsonProperty("$timeCast") - public void set$timeCast(Value $timeCast) { - this.$timeCast = $timeCast; - } - - @JsonProperty("$dayOfWeek") - public Date get$dayOfWeek() { - return $dayOfWeek; - } - - @JsonProperty("$dayOfWeek") - public void set$dayOfWeek(Date $dayOfWeek) { - this.$dayOfWeek = $dayOfWeek; - } - - @JsonProperty("$dayOfMonth") - public Date get$dayOfMonth() { - return $dayOfMonth; - } - - @JsonProperty("$dayOfMonth") - public void set$dayOfMonth(Date $dayOfMonth) { - this.$dayOfMonth = $dayOfMonth; - } - - @JsonProperty("$month") - public Date get$month() { - return $month; - } - - @JsonProperty("$month") - public void set$month(Date $month) { - this.$month = $month; - } - - @JsonProperty("$year") - public Date get$year() { - return $year; - } - - @JsonProperty("$year") - public void set$year(Date $year) { - this.$year = $year; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Value.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); - sb.append("$field"); - sb.append('='); - sb.append(((this.$field == null)?"":this.$field)); - sb.append(','); - sb.append("$strVal"); - sb.append('='); - sb.append(((this.$strVal == null)?"":this.$strVal)); - sb.append(','); - sb.append("$attribute"); - sb.append('='); - sb.append(((this.$attribute == null)?"":this.$attribute)); - sb.append(','); - sb.append("$numVal"); - sb.append('='); - sb.append(((this.$numVal == null)?"":this.$numVal)); - sb.append(','); - sb.append("$hexVal"); - sb.append('='); - sb.append(((this.$hexVal == null)?"":this.$hexVal)); - sb.append(','); - sb.append("$dateTimeVal"); - sb.append('='); - sb.append(((this.$dateTimeVal == null)?"":this.$dateTimeVal)); - sb.append(','); - sb.append("$timeVal"); - sb.append('='); - sb.append(((this.$timeVal == null)?"":this.$timeVal)); - sb.append(','); - sb.append("$boolean"); - sb.append('='); - sb.append(((this.$boolean == null)?"":this.$boolean)); - sb.append(','); - sb.append("$strCast"); - sb.append('='); - sb.append(((this.$strCast == null)?"":this.$strCast)); - sb.append(','); - sb.append("$numCast"); - sb.append('='); - sb.append(((this.$numCast == null)?"":this.$numCast)); - sb.append(','); - sb.append("$hexCast"); - sb.append('='); - sb.append(((this.$hexCast == null)?"":this.$hexCast)); - sb.append(','); - sb.append("$boolCast"); - sb.append('='); - sb.append(((this.$boolCast == null)?"":this.$boolCast)); - sb.append(','); - sb.append("$dateTimeCast"); - sb.append('='); - sb.append(((this.$dateTimeCast == null)?"":this.$dateTimeCast)); - sb.append(','); - sb.append("$timeCast"); - sb.append('='); - sb.append(((this.$timeCast == null)?"":this.$timeCast)); - sb.append(','); - sb.append("$dayOfWeek"); - sb.append('='); - sb.append(((this.$dayOfWeek == null)?"":this.$dayOfWeek)); - sb.append(','); - sb.append("$dayOfMonth"); - sb.append('='); - sb.append(((this.$dayOfMonth == null)?"":this.$dayOfMonth)); - sb.append(','); - sb.append("$month"); - sb.append('='); - sb.append(((this.$month == null)?"":this.$month)); - sb.append(','); - sb.append("$year"); - sb.append('='); - sb.append(((this.$year == null)?"":this.$year)); - sb.append(','); - if (sb.charAt((sb.length()- 1)) == ',') { - sb.setCharAt((sb.length()- 1), ']'); - } else { - sb.append(']'); - } - return sb.toString(); - } - - @Override - public int hashCode() { - int result = 1; - result = ((result* 31)+((this.$strVal == null)? 0 :this.$strVal.hashCode())); - result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); - result = ((result* 31)+((this.$attribute == null)? 0 :this.$attribute.hashCode())); - result = ((result* 31)+((this.$strCast == null)? 0 :this.$strCast.hashCode())); - result = ((result* 31)+((this.$numVal == null)? 0 :this.$numVal.hashCode())); - result = ((result* 31)+((this.$dateTimeVal == null)? 0 :this.$dateTimeVal.hashCode())); - result = ((result* 31)+((this.$timeVal == null)? 0 :this.$timeVal.hashCode())); - result = ((result* 31)+((this.$field == null)? 0 :this.$field.hashCode())); - result = ((result* 31)+((this.$dayOfWeek == null)? 0 :this.$dayOfWeek.hashCode())); - result = ((result* 31)+((this.$boolCast == null)? 0 :this.$boolCast.hashCode())); - result = ((result* 31)+((this.$hexCast == null)? 0 :this.$hexCast.hashCode())); - result = ((result* 31)+((this.$dayOfMonth == null)? 0 :this.$dayOfMonth.hashCode())); - result = ((result* 31)+((this.$year == null)? 0 :this.$year.hashCode())); - result = ((result* 31)+((this.$hexVal == null)? 0 :this.$hexVal.hashCode())); - result = ((result* 31)+((this.$timeCast == null)? 0 :this.$timeCast.hashCode())); - result = ((result* 31)+((this.$dateTimeCast == null)? 0 :this.$dateTimeCast.hashCode())); - result = ((result* 31)+((this.$month == null)? 0 :this.$month.hashCode())); - result = ((result* 31)+((this.$numCast == null)? 0 :this.$numCast.hashCode())); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if ((other instanceof Value) == false) { - return false; - } - Value rhs = ((Value) other); - return (((((((((((((((((((this.$strVal == rhs.$strVal)||((this.$strVal!= null)&&this.$strVal.equals(rhs.$strVal)))&&((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean))))&&((this.$attribute == rhs.$attribute)||((this.$attribute!= null)&&this.$attribute.equals(rhs.$attribute))))&&((this.$strCast == rhs.$strCast)||((this.$strCast!= null)&&this.$strCast.equals(rhs.$strCast))))&&((this.$numVal == rhs.$numVal)||((this.$numVal!= null)&&this.$numVal.equals(rhs.$numVal))))&&((this.$dateTimeVal == rhs.$dateTimeVal)||((this.$dateTimeVal!= null)&&this.$dateTimeVal.equals(rhs.$dateTimeVal))))&&((this.$timeVal == rhs.$timeVal)||((this.$timeVal!= null)&&this.$timeVal.equals(rhs.$timeVal))))&&((this.$field == rhs.$field)||((this.$field!= null)&&this.$field.equals(rhs.$field))))&&((this.$dayOfWeek == rhs.$dayOfWeek)||((this.$dayOfWeek!= null)&&this.$dayOfWeek.equals(rhs.$dayOfWeek))))&&((this.$boolCast == rhs.$boolCast)||((this.$boolCast!= null)&&this.$boolCast.equals(rhs.$boolCast))))&&((this.$hexCast == rhs.$hexCast)||((this.$hexCast!= null)&&this.$hexCast.equals(rhs.$hexCast))))&&((this.$dayOfMonth == rhs.$dayOfMonth)||((this.$dayOfMonth!= null)&&this.$dayOfMonth.equals(rhs.$dayOfMonth))))&&((this.$year == rhs.$year)||((this.$year!= null)&&this.$year.equals(rhs.$year))))&&((this.$hexVal == rhs.$hexVal)||((this.$hexVal!= null)&&this.$hexVal.equals(rhs.$hexVal))))&&((this.$timeCast == rhs.$timeCast)||((this.$timeCast!= null)&&this.$timeCast.equals(rhs.$timeCast))))&&((this.$dateTimeCast == rhs.$dateTimeCast)||((this.$dateTimeCast!= null)&&this.$dateTimeCast.equals(rhs.$dateTimeCast))))&&((this.$month == rhs.$month)||((this.$month!= null)&&this.$month.equals(rhs.$month))))&&((this.$numCast == rhs.$numCast)||((this.$numCast!= null)&&this.$numCast.equals(rhs.$numCast)))); - } - -} + +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$field", + "$strVal", + "$attribute", + "$numVal", + "$hexVal", + "$dateTimeVal", + "$timeVal", + "$boolean", + "$strCast", + "$numCast", + "$hexCast", + "$boolCast", + "$dateTimeCast", + "$timeCast", + "$dayOfWeek", + "$dayOfMonth", + "$month", + "$year" +}) +public class Value { + + @JsonProperty("$field") + private String $field; + @JsonProperty("$strVal") + private String $strVal; + @JsonProperty("$attribute") + private AttributeItem $attribute; + @JsonProperty("$numVal") + private Double $numVal; + @JsonProperty("$hexVal") + private String $hexVal; + @JsonProperty("$dateTimeVal") + private Date $dateTimeVal; + @JsonProperty("$timeVal") + private String $timeVal; + @JsonProperty("$boolean") + private Boolean $boolean; + @JsonProperty("$strCast") + private Value $strCast; + @JsonProperty("$numCast") + private Value $numCast; + @JsonProperty("$hexCast") + private Value $hexCast; + @JsonProperty("$boolCast") + private Value $boolCast; + @JsonProperty("$dateTimeCast") + private Value $dateTimeCast; + @JsonProperty("$timeCast") + private Value $timeCast; + @JsonProperty("$dayOfWeek") + private Date $dayOfWeek; + @JsonProperty("$dayOfMonth") + private Date $dayOfMonth; + @JsonProperty("$month") + private Date $month; + @JsonProperty("$year") + private Date $year; + + @JsonProperty("$field") + public String get$field() { + return $field; + } + + @JsonProperty("$field") + public void set$field(String $field) { + this.$field = $field; + } + + @JsonProperty("$strVal") + public String get$strVal() { + return $strVal; + } + + @JsonProperty("$strVal") + public void set$strVal(String $strVal) { + this.$strVal = $strVal; + } + + @JsonProperty("$attribute") + public AttributeItem get$attribute() { + return $attribute; + } + + @JsonProperty("$attribute") + public void set$attribute(AttributeItem $attribute) { + this.$attribute = $attribute; + } + + @JsonProperty("$numVal") + public Double get$numVal() { + return $numVal; + } + + @JsonProperty("$numVal") + public void set$numVal(Double $numVal) { + this.$numVal = $numVal; + } + + @JsonProperty("$hexVal") + public String get$hexVal() { + return $hexVal; + } + + @JsonProperty("$hexVal") + public void set$hexVal(String $hexVal) { + this.$hexVal = $hexVal; + } + + @JsonProperty("$dateTimeVal") + public Date get$dateTimeVal() { + return $dateTimeVal; + } + + @JsonProperty("$dateTimeVal") + public void set$dateTimeVal(Date $dateTimeVal) { + this.$dateTimeVal = $dateTimeVal; + } + + @JsonProperty("$timeVal") + public String get$timeVal() { + return $timeVal; + } + + @JsonProperty("$timeVal") + public void set$timeVal(String $timeVal) { + this.$timeVal = $timeVal; + } + + @JsonProperty("$boolean") + public Boolean get$boolean() { + return $boolean; + } + + @JsonProperty("$boolean") + public void set$boolean(Boolean $boolean) { + this.$boolean = $boolean; + } + + @JsonProperty("$strCast") + public Value get$strCast() { + return $strCast; + } + + @JsonProperty("$strCast") + public void set$strCast(Value $strCast) { + this.$strCast = $strCast; + } + + @JsonProperty("$numCast") + public Value get$numCast() { + return $numCast; + } + + @JsonProperty("$numCast") + public void set$numCast(Value $numCast) { + this.$numCast = $numCast; + } + + @JsonProperty("$hexCast") + public Value get$hexCast() { + return $hexCast; + } + + @JsonProperty("$hexCast") + public void set$hexCast(Value $hexCast) { + this.$hexCast = $hexCast; + } + + @JsonProperty("$boolCast") + public Value get$boolCast() { + return $boolCast; + } + + @JsonProperty("$boolCast") + public void set$boolCast(Value $boolCast) { + this.$boolCast = $boolCast; + } + + @JsonProperty("$dateTimeCast") + public Value get$dateTimeCast() { + return $dateTimeCast; + } + + @JsonProperty("$dateTimeCast") + public void set$dateTimeCast(Value $dateTimeCast) { + this.$dateTimeCast = $dateTimeCast; + } + + @JsonProperty("$timeCast") + public Value get$timeCast() { + return $timeCast; + } + + @JsonProperty("$timeCast") + public void set$timeCast(Value $timeCast) { + this.$timeCast = $timeCast; + } + + @JsonProperty("$dayOfWeek") + public Date get$dayOfWeek() { + return $dayOfWeek; + } + + @JsonProperty("$dayOfWeek") + public void set$dayOfWeek(Date $dayOfWeek) { + this.$dayOfWeek = $dayOfWeek; + } + + @JsonProperty("$dayOfMonth") + public Date get$dayOfMonth() { + return $dayOfMonth; + } + + @JsonProperty("$dayOfMonth") + public void set$dayOfMonth(Date $dayOfMonth) { + this.$dayOfMonth = $dayOfMonth; + } + + @JsonProperty("$month") + public Date get$month() { + return $month; + } + + @JsonProperty("$month") + public void set$month(Date $month) { + this.$month = $month; + } + + @JsonProperty("$year") + public Date get$year() { + return $year; + } + + @JsonProperty("$year") + public void set$year(Date $year) { + this.$year = $year; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Value.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('['); + sb.append("$field"); + sb.append('='); + sb.append(((this.$field == null)?"":this.$field)); + sb.append(','); + sb.append("$strVal"); + sb.append('='); + sb.append(((this.$strVal == null)?"":this.$strVal)); + sb.append(','); + sb.append("$attribute"); + sb.append('='); + sb.append(((this.$attribute == null)?"":this.$attribute)); + sb.append(','); + sb.append("$numVal"); + sb.append('='); + sb.append(((this.$numVal == null)?"":this.$numVal)); + sb.append(','); + sb.append("$hexVal"); + sb.append('='); + sb.append(((this.$hexVal == null)?"":this.$hexVal)); + sb.append(','); + sb.append("$dateTimeVal"); + sb.append('='); + sb.append(((this.$dateTimeVal == null)?"":this.$dateTimeVal)); + sb.append(','); + sb.append("$timeVal"); + sb.append('='); + sb.append(((this.$timeVal == null)?"":this.$timeVal)); + sb.append(','); + sb.append("$boolean"); + sb.append('='); + sb.append(((this.$boolean == null)?"":this.$boolean)); + sb.append(','); + sb.append("$strCast"); + sb.append('='); + sb.append(((this.$strCast == null)?"":this.$strCast)); + sb.append(','); + sb.append("$numCast"); + sb.append('='); + sb.append(((this.$numCast == null)?"":this.$numCast)); + sb.append(','); + sb.append("$hexCast"); + sb.append('='); + sb.append(((this.$hexCast == null)?"":this.$hexCast)); + sb.append(','); + sb.append("$boolCast"); + sb.append('='); + sb.append(((this.$boolCast == null)?"":this.$boolCast)); + sb.append(','); + sb.append("$dateTimeCast"); + sb.append('='); + sb.append(((this.$dateTimeCast == null)?"":this.$dateTimeCast)); + sb.append(','); + sb.append("$timeCast"); + sb.append('='); + sb.append(((this.$timeCast == null)?"":this.$timeCast)); + sb.append(','); + sb.append("$dayOfWeek"); + sb.append('='); + sb.append(((this.$dayOfWeek == null)?"":this.$dayOfWeek)); + sb.append(','); + sb.append("$dayOfMonth"); + sb.append('='); + sb.append(((this.$dayOfMonth == null)?"":this.$dayOfMonth)); + sb.append(','); + sb.append("$month"); + sb.append('='); + sb.append(((this.$month == null)?"":this.$month)); + sb.append(','); + sb.append("$year"); + sb.append('='); + sb.append(((this.$year == null)?"":this.$year)); + sb.append(','); + if (sb.charAt((sb.length()- 1)) == ',') { + sb.setCharAt((sb.length()- 1), ']'); + } else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result* 31)+((this.$strVal == null)? 0 :this.$strVal.hashCode())); + result = ((result* 31)+((this.$boolean == null)? 0 :this.$boolean.hashCode())); + result = ((result* 31)+((this.$attribute == null)? 0 :this.$attribute.hashCode())); + result = ((result* 31)+((this.$strCast == null)? 0 :this.$strCast.hashCode())); + result = ((result* 31)+((this.$numVal == null)? 0 :this.$numVal.hashCode())); + result = ((result* 31)+((this.$dateTimeVal == null)? 0 :this.$dateTimeVal.hashCode())); + result = ((result* 31)+((this.$timeVal == null)? 0 :this.$timeVal.hashCode())); + result = ((result* 31)+((this.$field == null)? 0 :this.$field.hashCode())); + result = ((result* 31)+((this.$dayOfWeek == null)? 0 :this.$dayOfWeek.hashCode())); + result = ((result* 31)+((this.$boolCast == null)? 0 :this.$boolCast.hashCode())); + result = ((result* 31)+((this.$hexCast == null)? 0 :this.$hexCast.hashCode())); + result = ((result* 31)+((this.$dayOfMonth == null)? 0 :this.$dayOfMonth.hashCode())); + result = ((result* 31)+((this.$year == null)? 0 :this.$year.hashCode())); + result = ((result* 31)+((this.$hexVal == null)? 0 :this.$hexVal.hashCode())); + result = ((result* 31)+((this.$timeCast == null)? 0 :this.$timeCast.hashCode())); + result = ((result* 31)+((this.$dateTimeCast == null)? 0 :this.$dateTimeCast.hashCode())); + result = ((result* 31)+((this.$month == null)? 0 :this.$month.hashCode())); + result = ((result* 31)+((this.$numCast == null)? 0 :this.$numCast.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Value) == false) { + return false; + } + Value rhs = ((Value) other); + return (((((((((((((((((((this.$strVal == rhs.$strVal)||((this.$strVal!= null)&&this.$strVal.equals(rhs.$strVal)))&&((this.$boolean == rhs.$boolean)||((this.$boolean!= null)&&this.$boolean.equals(rhs.$boolean))))&&((this.$attribute == rhs.$attribute)||((this.$attribute!= null)&&this.$attribute.equals(rhs.$attribute))))&&((this.$strCast == rhs.$strCast)||((this.$strCast!= null)&&this.$strCast.equals(rhs.$strCast))))&&((this.$numVal == rhs.$numVal)||((this.$numVal!= null)&&this.$numVal.equals(rhs.$numVal))))&&((this.$dateTimeVal == rhs.$dateTimeVal)||((this.$dateTimeVal!= null)&&this.$dateTimeVal.equals(rhs.$dateTimeVal))))&&((this.$timeVal == rhs.$timeVal)||((this.$timeVal!= null)&&this.$timeVal.equals(rhs.$timeVal))))&&((this.$field == rhs.$field)||((this.$field!= null)&&this.$field.equals(rhs.$field))))&&((this.$dayOfWeek == rhs.$dayOfWeek)||((this.$dayOfWeek!= null)&&this.$dayOfWeek.equals(rhs.$dayOfWeek))))&&((this.$boolCast == rhs.$boolCast)||((this.$boolCast!= null)&&this.$boolCast.equals(rhs.$boolCast))))&&((this.$hexCast == rhs.$hexCast)||((this.$hexCast!= null)&&this.$hexCast.equals(rhs.$hexCast))))&&((this.$dayOfMonth == rhs.$dayOfMonth)||((this.$dayOfMonth!= null)&&this.$dayOfMonth.equals(rhs.$dayOfMonth))))&&((this.$year == rhs.$year)||((this.$year!= null)&&this.$year.equals(rhs.$year))))&&((this.$hexVal == rhs.$hexVal)||((this.$hexVal!= null)&&this.$hexVal.equals(rhs.$hexVal))))&&((this.$timeCast == rhs.$timeCast)||((this.$timeCast!= null)&&this.$timeCast.equals(rhs.$timeCast))))&&((this.$dateTimeCast == rhs.$dateTimeCast)||((this.$dateTimeCast!= null)&&this.$dateTimeCast.equals(rhs.$dateTimeCast))))&&((this.$month == rhs.$month)||((this.$month!= null)&&this.$month.equals(rhs.$month))))&&((this.$numCast == rhs.$numCast)||((this.$numCast!= null)&&this.$numCast.equals(rhs.$numCast)))); + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ValueConverter.java similarity index 99% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ValueConverter.java index 92cfd8dd5..52676447f 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ValueConverter.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.core.query; +package org.eclipse.digitaltwin.basyx.querycore.query; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; @@ -7,7 +7,6 @@ import co.elastic.clients.json.JsonData; import java.util.Date; import java.text.SimpleDateFormat; -import java.util.regex.Pattern; /** * Converts Value and StringValue objects to ElasticSearch QueryDSL operations diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ql.schema.json b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ql.schema.json similarity index 95% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ql.schema.json rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ql.schema.json index ce5fc1753..85c538033 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/query/ql.schema.json +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ql.schema.json @@ -1,849 +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 +{ + "$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/basyx.common/pom.xml b/basyx.common/pom.xml index ee719ca26..652f00901 100644 --- a/basyx.common/pom.xml +++ b/basyx.common/pom.xml @@ -27,5 +27,6 @@ basyx.filerepository-backend-mongodb basyx.authorization.rules.rbac.backend.inmemory basyx.authorization.rules.rbac.backend.submodel - + basyx.querycore + \ No newline at end of file diff --git a/examples/BaSyxMinimal/docker-compose.yml b/examples/BaSyxMinimal/docker-compose.yml index e3f8f7bf4..f5286a298 100644 --- a/examples/BaSyxMinimal/docker-compose.yml +++ b/examples/BaSyxMinimal/docker-compose.yml @@ -69,7 +69,7 @@ services: condition: service_healthy aas-discovery: - image: eclipsebasyx/aas-discovery:2.0.0-SNAPSHOT + image: eclipsebasyx/aas-discovery:2.0.0-milestone-03.1 ports: - 8084:8081 volumes: From b3f9e85e62f82f6aaf3c340b57eee3e5778b0f26 Mon Sep 17 00:00:00 2001 From: fried Date: Thu, 12 Jun 2025 13:52:33 +0200 Subject: [PATCH 07/52] Refactor --- .../pom.xml | 4 ++++ .../SearchAasRepositoryApiHTTPController.java | 8 ++------ .../search/SearchAasRepositoryHTTPApi.java | 2 +- basyx.common/basyx.core/pom.xml | 5 ----- basyx.common/basyx.querycore/pom.xml | 19 ++++++++++++++++++- .../ElasticSearchRequestBuilder.java | 3 ++- .../LogicalExpressionConverter.java | 5 ++++- .../MatchExpressionConverter.java | 4 +++- .../QueryConverterExample.java | 7 ++++++- .../QueryToElasticSearchConverter.java | 4 +++- .../query/{ => converter}/ValueConverter.java | 6 ++++-- .../query => resources}/ql.schema.json | 0 pom.xml | 5 +++++ 13 files changed, 52 insertions(+), 20 deletions(-) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => converter}/ElasticSearchRequestBuilder.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => converter}/LogicalExpressionConverter.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => converter}/MatchExpressionConverter.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => converter}/QueryConverterExample.java (95%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => converter}/QueryToElasticSearchConverter.java (89%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => converter}/ValueConverter.java (98%) rename basyx.common/basyx.querycore/src/main/{java/org/eclipse/digitaltwin/basyx/querycore/query => resources}/ql.schema.json (100%) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml index e1aabd33d..6047544d6 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml @@ -18,6 +18,10 @@ org.eclipse.digitaltwin.basyx basyx.aasrepository-core + + org.eclipse.digitaltwin.basyx + basyx.querycore + co.elastic.clients elasticsearch-java diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 1c0be03b4..5d30cc11f 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -30,16 +30,13 @@ import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.basyx.core.query.ElasticSearchRequestBuilder; -import org.eclipse.digitaltwin.basyx.core.query.AASQuery; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @@ -84,6 +81,5 @@ public ResponseEntity queryAssetAdministrationShells(AASQuery query, Int } catch (IOException e) { throw new RuntimeException(e); } - //return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java index 71bd3b69e..c04870284 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -34,7 +34,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import org.eclipse.digitaltwin.aas4j.v3.model.Result; -import org.eclipse.digitaltwin.basyx.core.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; diff --git a/basyx.common/basyx.core/pom.xml b/basyx.common/basyx.core/pom.xml index 17e443bfb..e0c89f549 100644 --- a/basyx.common/basyx.core/pom.xml +++ b/basyx.common/basyx.core/pom.xml @@ -35,11 +35,6 @@ junit test - - co.elastic.clients - elasticsearch-java - 9.0.1 - \ No newline at end of file diff --git a/basyx.common/basyx.querycore/pom.xml b/basyx.common/basyx.querycore/pom.xml index 900f53086..3a95e773b 100644 --- a/basyx.common/basyx.querycore/pom.xml +++ b/basyx.common/basyx.querycore/pom.xml @@ -6,10 +6,27 @@ org.eclipse.digitaltwin.basyx basyx.common - 2.0.0-SNAPSHOT + ${revision} basyx.querycore BaSyx Query Core + + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ElasticSearchRequestBuilder.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ElasticSearchRequestBuilder.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java index bcf0de060..989123dfa 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ElasticSearchRequestBuilder.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java @@ -1,8 +1,9 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.search.SourceConfig; import co.elastic.clients.elasticsearch.core.search.SourceFilter; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import java.util.List; import java.util.Arrays; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpressionConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java index b33fc6566..6c04821ea 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java @@ -1,8 +1,11 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.LogicalExpression; +import org.eclipse.digitaltwin.basyx.querycore.query.MatchExpression; + import java.util.List; /** diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpressionConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java index 3831dcb54..0adaf32c1 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java @@ -1,8 +1,10 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.MatchExpression; + import java.util.List; /** diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryConverterExample.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java similarity index 95% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryConverterExample.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java index 9e0037738..aac6b8c80 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryConverterExample.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java @@ -1,7 +1,12 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch.core.SearchRequest; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.LogicalExpression; +import org.eclipse.digitaltwin.basyx.querycore.query.StringValue; +import org.eclipse.digitaltwin.basyx.querycore.query.Value; + import java.util.Arrays; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryToElasticSearchConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java similarity index 89% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryToElasticSearchConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java index 6debd7ba3..674e2e72d 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryToElasticSearchConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java @@ -1,6 +1,8 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.LogicalExpression; /** * Converts custom Query objects to ElasticSearch QueryDSL Query objects for ElasticSearch 9.0.1 diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java similarity index 98% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ValueConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 52676447f..321de08ab 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -1,10 +1,12 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; -import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery; import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.json.JsonData; +import org.eclipse.digitaltwin.basyx.querycore.query.StringValue; +import org.eclipse.digitaltwin.basyx.querycore.query.Value; + import java.util.Date; import java.text.SimpleDateFormat; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ql.schema.json b/basyx.common/basyx.querycore/src/main/resources/ql.schema.json similarity index 100% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ql.schema.json rename to basyx.common/basyx.querycore/src/main/resources/ql.schema.json diff --git a/pom.xml b/pom.xml index dfbc5a66f..0be54a648 100644 --- a/pom.xml +++ b/pom.xml @@ -420,6 +420,11 @@ basyx.core ${revision} + + org.eclipse.digitaltwin.basyx + basyx.querycore + ${revision} + org.eclipse.digitaltwin.basyx basyx.http From 83ef356cb449610e2b9fc363aba3259f26ea47f0 Mon Sep 17 00:00:00 2001 From: fried Date: Thu, 12 Jun 2025 15:00:25 +0200 Subject: [PATCH 08/52] Adds field-to-field comparison (WIP) --- .../README.md | 5 +- .../basyx.aasrepository.component/pom.xml | 4 + basyx.common/basyx.querycore/pom.xml | 5 + .../converter/QueryConverterExample.java | 96 +++++++++++++ .../QueryToElasticSearchConverter.java | 13 +- .../query/converter/ValueConverter.java | 133 ++++++++++++++++-- 6 files changed, 228 insertions(+), 28 deletions(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index 85cbd7516..bf710a4de 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -50,8 +50,9 @@ ## TODOS -* [ ] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) -* [ ] Comparison of two fields is not possible yet +* [x] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) +* [x] Comparison of two fields is not possible yet +* [ ] Fix Error: ava.lang.ClassNotFoundException: co.elastic.clients.elasticsearch._types.ScriptSource$Builder in ValueConverter during Runtime * [ ] Unit Tests * [ ] Integration Tests * [ ] Pagination \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository.component/pom.xml b/basyx.aasrepository/basyx.aasrepository.component/pom.xml index 36985541a..9d88c28e0 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository.component/pom.xml @@ -151,6 +151,10 @@ basyx.authorization tests + + org.eclipse.digitaltwin.basyx + basyx.querycore + diff --git a/basyx.common/basyx.querycore/pom.xml b/basyx.common/basyx.querycore/pom.xml index 3a95e773b..5efe29bbe 100644 --- a/basyx.common/basyx.querycore/pom.xml +++ b/basyx.common/basyx.querycore/pom.xml @@ -19,6 +19,11 @@ elasticsearch-java 9.0.1 + + org.elasticsearch.client + elasticsearch-rest-client + 9.0.1 + com.fasterxml.jackson.core jackson-core diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java index aac6b8c80..3a0ee9916 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java @@ -212,4 +212,100 @@ public Query createNotQuery() { return queryConverter.convert(customQuery); } + + /** + * Example: Field-to-field equality comparison + * Custom Query: { "$condition": { "$eq": [{"$field": "$aas#idShort"}, {"$field": "$aas#displayName"}] } } + */ + public Query createFieldToFieldEqualityQuery() { + // Create two field value objects + Value leftFieldValue = new Value(); + leftFieldValue.set$field("$aas#idShort"); + + Value rightFieldValue = new Value(); + rightFieldValue.set$field("$aas#displayName"); + + // Create logical expression with equality + LogicalExpression condition = new LogicalExpression(); + condition.set$eq(Arrays.asList(leftFieldValue, rightFieldValue)); + + // Create query + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: Field-to-field range comparison + * Custom Query: { "$condition": { "$gt": [{"$field": "$sme#minValue"}, {"$field": "$sme#maxValue"}] } } + */ + public Query createFieldToFieldRangeQuery() { + Value leftFieldValue = new Value(); + leftFieldValue.set$field("$sme#minValue"); + + Value rightFieldValue = new Value(); + rightFieldValue.set$field("$sme#maxValue"); + + LogicalExpression condition = new LogicalExpression(); + condition.set$gt(Arrays.asList(leftFieldValue, rightFieldValue)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: Field-to-field string contains comparison + * Custom Query: { "$condition": { "$contains": [{"$field": "$aas#description"}, {"$field": "$aas#idShort"}] } } + */ + public Query createFieldToFieldStringContainsQuery() { + StringValue leftFieldValue = new StringValue(); + leftFieldValue.set$field("$aas#description"); + + StringValue rightFieldValue = new StringValue(); + rightFieldValue.set$field("$aas#idShort"); + + LogicalExpression condition = new LogicalExpression(); + condition.set$contains(Arrays.asList(leftFieldValue, rightFieldValue)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(condition); + + return queryConverter.convert(customQuery); + } + + /** + * Example: Complex query with both field-to-field and field-to-value comparisons + * Find AAS where idShort equals displayName AND value > 100 + */ + public Query createMixedFieldComparisonQuery() { + // Field-to-field condition: idShort equals displayName + Value leftField1 = new Value(); + leftField1.set$field("$aas#idShort"); + Value rightField1 = new Value(); + rightField1.set$field("$aas#displayName"); + + LogicalExpression condition1 = new LogicalExpression(); + condition1.set$eq(Arrays.asList(leftField1, rightField1)); + + // Field-to-value condition: value > 100 + Value fieldValue2 = new Value(); + fieldValue2.set$field("$sme#value"); + Value numValue2 = new Value(); + numValue2.set$numVal(100.0); + + LogicalExpression condition2 = new LogicalExpression(); + condition2.set$gt(Arrays.asList(fieldValue2, numValue2)); + + // Combine with AND + LogicalExpression andCondition = new LogicalExpression(); + andCondition.set$and(Arrays.asList(condition1, condition2)); + + AASQuery customQuery = new AASQuery(); + customQuery.set$condition(andCondition); + + return queryConverter.convert(customQuery); + } } \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java index 674e2e72d..aa20d3568 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java @@ -33,16 +33,5 @@ public co.elastic.clients.elasticsearch._types.query_dsl.Query convert(AASQuery return logicalExpressionConverter.convert(condition); } - - /** - * Converts a custom Query object to ElasticSearch QueryDSL Query with source filtering - * - * @param customQuery The custom query to convert - * @return ElasticSearch QueryDSL Query - */ - public co.elastic.clients.elasticsearch._types.query_dsl.Query convertWithSelect(AASQuery customQuery) { - // For now, we focus on the query conversion - // Source filtering would be handled at the search request level, not in the query itself - return convert(customQuery); - } + } \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 321de08ab..315b5fa2e 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -1,5 +1,6 @@ package org.eclipse.digitaltwin.basyx.querycore.query.converter; +import co.elastic.clients.elasticsearch._types.Script; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; import co.elastic.clients.elasticsearch._types.FieldValue; @@ -18,11 +19,20 @@ public class ValueConverter { private static final SimpleDateFormat ISO_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); /** - * Converts equality comparison between two values + * Converts equality comparison between two values (supports field-to-field comparison) */ public Query convertEqualityComparison(Value leftValue, Value rightValue) { - String fieldName = extractFieldName(leftValue); - Object value = extractValue(rightValue); + String leftField = extractFieldName(leftValue); + String rightField = extractFieldName(rightValue); + + // Field-to-field comparison + if (leftField != null && rightField != null) { + return createFieldToFieldComparison(leftField, rightField, "eq"); + } + + // Field-to-value comparison (existing logic) + String fieldName = leftField != null ? leftField : extractFieldName(rightValue); + Object value = leftField != null ? extractValue(rightValue) : extractValue(leftValue); if (fieldName != null && value != null) { return QueryBuilders.term() @@ -35,11 +45,20 @@ public Query convertEqualityComparison(Value leftValue, Value rightValue) { } /** - * Converts inequality comparison between two values + * Converts inequality comparison between two values (supports field-to-field comparison) */ public Query convertInequalityComparison(Value leftValue, Value rightValue) { - String fieldName = extractFieldName(leftValue); - Object value = extractValue(rightValue); + String leftField = extractFieldName(leftValue); + String rightField = extractFieldName(rightValue); + + // Field-to-field comparison + if (leftField != null && rightField != null) { + return createFieldToFieldComparison(leftField, rightField, "ne"); + } + + // Field-to-value comparison (existing logic) + String fieldName = leftField != null ? leftField : extractFieldName(rightValue); + Object value = leftField != null ? extractValue(rightValue) : extractValue(leftValue); if (fieldName != null && value != null) { return QueryBuilders.bool() @@ -54,15 +73,22 @@ public Query convertInequalityComparison(Value leftValue, Value rightValue) { } /** - * Converts range comparison between two values + * Converts range comparison between two values (supports field-to-field comparison) */ - public Query convertRangeComparison(Value leftValue, Value rightValue, String operator) { - - String fieldName = extractFieldName(leftValue); - Object rawValue = extractValue(rightValue); + String leftField = extractFieldName(leftValue); + String rightField = extractFieldName(rightValue); + + // Field-to-field comparison + if (leftField != null && rightField != null) { + return createFieldToFieldComparison(leftField, rightField, operator); + } + + // Field-to-value comparison (existing logic) + String fieldName = leftField != null ? leftField : extractFieldName(rightValue); + Object rawValue = leftField != null ? extractValue(rightValue) : extractValue(leftValue); if (fieldName != null && rawValue != null) { JsonData value = JsonData.of(rawValue); // works for all scalar types @@ -86,12 +112,22 @@ public Query convertRangeComparison(Value leftValue, // fall-back: match-all return QueryBuilders.matchAll(m -> m); } + /** - * Converts string comparison operations + * Converts string comparison operations (supports field-to-field comparison) */ public Query convertStringComparison(StringValue leftValue, StringValue rightValue, String operation) { - String fieldName = extractStringFieldName(leftValue); - String value = extractStringValue(rightValue); + String leftField = extractStringFieldName(leftValue); + String rightField = extractStringFieldName(rightValue); + + // Field-to-field string comparison + if (leftField != null && rightField != null) { + return createFieldToFieldStringComparison(leftField, rightField, operation); + } + + // Field-to-value comparison (existing logic) + String fieldName = leftField != null ? leftField : extractStringFieldName(rightValue); + String value = leftField != null ? extractStringValue(rightValue) : extractStringValue(leftValue); if (fieldName != null && value != null) { switch (operation) { @@ -130,6 +166,75 @@ public Query convertStringComparison(StringValue leftValue, StringValue rightVal return QueryBuilders.matchAll().build()._toQuery(); } + /** + * Creates field-to-field comparison using ElasticSearch script queries + */ + private Query createFieldToFieldComparison(String leftField, String rightField, String operator) { + String scriptSource; + + switch (operator) { + case "eq": + scriptSource = String.format("doc['%s'].value == doc['%s'].value", leftField, rightField); + break; + case "ne": + scriptSource = String.format("doc['%s'].value != doc['%s'].value", leftField, rightField); + break; + case "gt": + scriptSource = String.format("doc['%s'].value > doc['%s'].value", leftField, rightField); + break; + case "gte": + scriptSource = String.format("doc['%s'].value >= doc['%s'].value", leftField, rightField); + break; + case "lt": + scriptSource = String.format("doc['%s'].value < doc['%s'].value", leftField, rightField); + break; + case "lte": + scriptSource = String.format("doc['%s'].value <= doc['%s'].value", leftField, rightField); + break; + default: + throw new IllegalArgumentException("Unsupported field-to-field operator: " + operator); + } + + return QueryBuilders.script(s -> s + .script(script -> script + .source(source -> source.scriptString(scriptSource)) + .lang("painless") + ) + ); + } + + /** + * Creates field-to-field string comparison using script queries + */ + private Query createFieldToFieldStringComparison(String leftField, String rightField, String operation) { + String scriptSource; + + switch (operation) { + case "contains": + scriptSource = String.format("doc['%s'].value.contains(doc['%s'].value)", leftField, rightField); + break; + case "starts-with": + scriptSource = String.format("doc['%s'].value.startsWith(doc['%s'].value)", leftField, rightField); + break; + case "ends-with": + scriptSource = String.format("doc['%s'].value.endsWith(doc['%s'].value)", leftField, rightField); + break; + case "regex": + scriptSource = String.format("doc['%s'].value ==~ doc['%s'].value", leftField, rightField); + break; + default: + scriptSource = String.format("doc['%s'].value.equals(doc['%s'].value)", leftField, rightField); + break; + } + + return QueryBuilders.script(s -> s + .script(script -> script + .source(source -> source.scriptString(scriptSource)) + .lang("painless") + ) + ); + } + /** * Extracts field name from a Value object */ From eed75a8a47accda5b135b297401c3e9f712f641a Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 16 Jun 2025 08:56:20 +0200 Subject: [PATCH 09/52] Adds missing dependency for ES Client in Component - Adapts README --- .../basyx.aasrepository-feature-search/README.md | 3 ++- basyx.aasrepository/basyx.aasrepository.component/pom.xml | 6 +++++- basyx.common/basyx.querycore/pom.xml | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index bf710a4de..b9c0ee28b 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -52,7 +52,8 @@ ## TODOS * [x] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) * [x] Comparison of two fields is not possible yet -* [ ] Fix Error: ava.lang.ClassNotFoundException: co.elastic.clients.elasticsearch._types.ScriptSource$Builder in ValueConverter during Runtime +* [x] Fix Error: ava.lang.ClassNotFoundException: co.elastic.clients.elasticsearch._types.ScriptSource$Builder in ValueConverter during Runtime +* [ ] Fix error: When comparing fields -> first check with a bool.must if the fields are there. (See ChatGPT Chat -> Ask Claude) * [ ] Unit Tests * [ ] Integration Tests * [ ] Pagination \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository.component/pom.xml b/basyx.aasrepository/basyx.aasrepository.component/pom.xml index 9d88c28e0..b74f955d8 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository.component/pom.xml @@ -123,7 +123,6 @@ gson test - org.springframework.boot spring-boot-starter @@ -155,6 +154,11 @@ org.eclipse.digitaltwin.basyx basyx.querycore + + co.elastic.clients + elasticsearch-java + 9.0.1 + diff --git a/basyx.common/basyx.querycore/pom.xml b/basyx.common/basyx.querycore/pom.xml index 5efe29bbe..97d83519d 100644 --- a/basyx.common/basyx.querycore/pom.xml +++ b/basyx.common/basyx.querycore/pom.xml @@ -32,6 +32,14 @@ com.fasterxml.jackson.core jackson-annotations + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-databind + \ No newline at end of file From bba4d324f77416cca32ba4c1949bb5121f1183df Mon Sep 17 00:00:00 2001 From: fried Date: Fri, 20 Jun 2025 13:15:30 +0200 Subject: [PATCH 10/52] Refactors HTTP Controller - Fixes field-to-field comparisons - Enables AASQL for AAS Registry Log MongoDB - [WIP] initiates Search Feature for AasRegistry by Jannis Jung Co-authored-by: Jannis Jung Co-authored-by: Aaron Zielstorff --- basyx.aasregistry/.vscode/settings.json | 3 + .../basyx.aasregistry-feature-search/pom.xml | 45 +++++ ...DisableSearchAasRegistryConfiguration.java | 49 +++++ .../SearchAasRegistryApiHTTPController.java | 86 +++++++++ .../SearchAasRegistryConfiguration.java | 35 ++++ .../SearchAasRegistryConfigurationGuard.java | 83 ++++++++ .../search/SearchAasRegistryFeature.java | 73 +++++++ .../search/SearchAasRegistryHTTPApi.java | 82 ++++++++ .../search/SearchAasRegistryStorage.java | 180 ++++++++++++++++++ .../pom.xml | 4 + .../pom.xml | 5 + basyx.aasregistry/pom.xml | 1 + .../README.md | 12 +- ...sableSearchAasRepositoryConfiguration.java | 52 +++++ .../SearchAasRepositoryApiHTTPController.java | 37 ++-- ...SearchAasRepositoryConfigurationGuard.java | 2 + .../search/SearchAasRepositoryHTTPApi.java | 3 +- .../src/main/resources/application.properties | 2 + basyx.common/basyx.querycore/pom.xml | 4 + .../basyx/querycore/query/QueryPaging.java | 11 ++ .../basyx/querycore/query/QueryResponse.java | 12 ++ .../basyx/querycore/query/QueryResult.java | 12 ++ .../query/converter/ValueConverter.java | 28 ++- .../query/executor/ESQueryExecutor.java | 153 +++++++++++++++ .../BaSyxQueryLanguage/docker-compose.yml | 42 ++-- pom.xml | 11 ++ 26 files changed, 970 insertions(+), 57 deletions(-) create mode 100644 basyx.aasregistry/.vscode/settings.json create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfiguration.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java create mode 100644 basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryPaging.java create mode 100644 basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java create mode 100644 basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java create mode 100644 basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java diff --git a/basyx.aasregistry/.vscode/settings.json b/basyx.aasregistry/.vscode/settings.json new file mode 100644 index 000000000..8274afea7 --- /dev/null +++ b/basyx.aasregistry/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "disabled" +} \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml b/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml new file mode 100644 index 000000000..53f5f01c3 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.aasregistry + ${revision} + + basyx.aasregistry-feature-search + BaSyx AAS Registry Feature Search + Feature Search for the BaSyx AAS Registry + + + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service + + + org.eclipse.digitaltwin.basyx + basyx.querycore + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + + + org.springframework + spring-context + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service-basemodel + + + + \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java new file mode 100644 index 000000000..7b10d07df --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to prevent Elasticsearch from being injected by Spring + * @author fried + */ +@Configuration +@ConditionalOnProperty(name = {SearchAasRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@EnableAutoConfiguration(exclude = { + ElasticsearchClientAutoConfiguration.class, + ElasticsearchRepositoriesAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class, + ElasticsearchRestClientAutoConfiguration.class +}) +public class DisableSearchAasRegistryConfiguration { +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java new file mode 100644 index 000000000..4fa32002d --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.elasticsearch.esql.ElasticsearchEsqlClient; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResult; +import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") +@RestController +public class SearchAasRegistryApiHTTPController implements SearchAasRegistryHTTPApi { + + private static final Logger log = LoggerFactory.getLogger(SearchAasRegistryApiHTTPController.class); + + private final ElasticsearchClient esClient; + private final ObjectMapper objectMapper; + + @Autowired + public SearchAasRegistryApiHTTPController(ObjectMapper objectMapper, ElasticsearchClient esClient) { + this.esClient = esClient; + this.objectMapper = objectMapper; + } + + public ResponseEntity queryAssetAdministrationShellDescriptors(Integer limit, String cursor, AASQuery query) { + ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); + SearchRequest searchRequest = builder.buildSearchRequest(query, SearchAasRegistryStorage.ES_INDEX); + try { + SearchResponse response = esClient.search(searchRequest, Object.class); + List> hits = response.hits().hits(); + List descriptors = hits.stream() + .map(hit -> objectMapper.convertValue(hit.source(), AssetAdministrationShellDescriptor.class)) + .collect(Collectors.toList()); + + QueryPaging queryPaging = new QueryPaging("not implemented", "AssetAdministrationShellDescriptors"); + QueryResult queryResult = new QueryResult(descriptors); + QueryResponse queryResponse = new QueryResponse(queryPaging, queryResult); + return ResponseEntity.ok(queryResponse); + } catch (IOException e) { + log.error("Error executing search request", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfiguration.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfiguration.java new file mode 100644 index 000000000..1288698c8 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfiguration.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Configuration; + +@ConditionalOnExpression("#{${" + SearchAasRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Configuration +public class SearchAasRegistryConfiguration { + +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java new file mode 100644 index 000000000..3c25932fc --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Class that prints error and warning messages to inform the user about possible misconfiguration + * + * @author jannisjung, aaronzi + */ +@Component +public class SearchAasRegistryConfigurationGuard implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(SearchAasRegistryConfigurationGuard.class); + + @Value("${spring.elasticsearch.uris:#{null}}") + private String elasticsearchUrl; + + @Value("${spring.elasticsearch.username:#{null}}") + private String elasticsearchUsername; + + @Value("${spring.elasticsearch.password:#{null}}") + private String elasticsearchPassword; + + @Override + public void afterPropertiesSet() throws Exception { + boolean error = false; + logger.info(":::::::::::::::: BaSyx Feature Search Configuration ::::::::::::::::"); + + if (elasticsearchUrl == null || elasticsearchUrl.isEmpty()) { + logger.error("Elasticsearch URL is not configured. Please set the property 'spring.elasticsearch.uris'."); + error = true; + } else { + logger.info("Elasticsearch URL: " + elasticsearchUrl); + } + + if (elasticsearchUsername == null || elasticsearchUsername.isEmpty()) { + logger.error("Elasticsearch username is not configured. Please set the property 'spring.elasticsearch.username'."); + error = true; + } else { + logger.info("Elasticsearch Username: " + elasticsearchUsername); + } + + if (elasticsearchPassword == null || elasticsearchPassword.isEmpty()) { + logger.error("Elasticsearch password is not configured. Please set the property 'spring.elasticsearch.password'."); + error = true; + } else { + logger.info("Elasticsearch Password: " + "***"); + } + + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + if(error){ + System.exit(1); + } + } +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java new file mode 100644 index 000000000..65ad69c4c --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorage; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorageFeature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Hierarchical {@link AasRegistryStorage} feature + * + * When this feature is enabled, AAS Descriptors will be indexed with ElasticSearch + * + * @author jannisjung, zielstor + */ + +@Component +@ConditionalOnExpression("#{${" + SearchAasRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Order(1) +public class SearchAasRegistryFeature implements AasRegistryStorageFeature { + public final static String FEATURENAME = "basyx.aasregistry.feature.search"; + private final ElasticsearchClient esclient; + + public SearchAasRegistryFeature(ElasticsearchClient esclient) { + this.esclient = esclient; + } + + @Value("#{${" + FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") + private boolean enabled; + + @Override + public AasRegistryStorage decorate(AasRegistryStorage aasRegistryStorage) { + return new SearchAasRegistryStorage(aasRegistryStorage, esclient); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public String getName() { + return FEATURENAME; + } +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java new file mode 100644 index 000000000..a6a0ad082 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +/** + * NOTE: This class is auto generated by the swagger code generator program (3.0.68). + * https://github.com/swagger-api/swagger-codegen + * Do not edit the class manually. + */ +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.eclipse.digitaltwin.aas4j.v3.model.Result; + + + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") +@Validated +public interface SearchAasRegistryHTTPApi { + + @Operation(summary = "Returns all Asset Administration Shell Descriptors that confirm to the input query", description = "", tags={ "Asset Administration Shell Registry API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested Asset Administration Shell Descriptors", content = @Content(mediaType = "application/json", schema = @Schema(implementation = QueryResponse.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/query/shell-descriptors", + produces = { "application/json" }, + consumes = { "application/json" }, + method = RequestMethod.POST) + ResponseEntity queryAssetAdministrationShellDescriptors(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" + )) @Valid @RequestParam(value = "limit", required = false) Integer limit + , @Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) String cursor + , @Parameter(in = ParameterIn.DEFAULT, description = "", schema=@Schema()) @Valid @RequestBody AASQuery body + ); +} + diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java new file mode 100644 index 000000000..280849af7 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.AasDescriptorAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.AasDescriptorNotFoundException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorage; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.DescriptorFilter; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.model.ShellDescriptorSearchRequest; +import org.eclipse.digitaltwin.basyx.aasregistry.model.ShellDescriptorSearchResponse; + + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +public class SearchAasRegistryStorage implements AasRegistryStorage { + private final ElasticsearchClient esclient; + private final AasRegistryStorage decorated; + + protected static final String ES_INDEX = "aas-descr-index"; + + + SearchAasRegistryStorage(AasRegistryStorage decorated, ElasticsearchClient esclient) { + this.decorated = decorated; + this.esclient = esclient; + } + + @Override + public CursorResult> getAllAasDescriptors(PaginationInfo pRequest, DescriptorFilter filter) { + return decorated.getAllAasDescriptors(pRequest, filter); + } + + @Override + public AssetAdministrationShellDescriptor getAasDescriptor(String aasDescriptorId) throws AasDescriptorNotFoundException { + return decorated.getAasDescriptor(aasDescriptorId); + } + + @Override + public void removeAasDescriptor(String aasDescriptorId) throws AasDescriptorNotFoundException { + decorated.removeAasDescriptor(aasDescriptorId); + deindexAasDescriptor(aasDescriptorId); + } + + @Override + public CursorResult> getAllSubmodels(String aasDescriptorId, PaginationInfo pRequest) throws AasDescriptorNotFoundException { + return decorated.getAllSubmodels(aasDescriptorId, pRequest); + } + + @Override + public SubmodelDescriptor getSubmodel(String aasDescriptorId, String submodelId) throws AasDescriptorNotFoundException, SubmodelNotFoundException { + return decorated.getSubmodel(aasDescriptorId, submodelId); + } + + @Override + public void removeSubmodel(String aasDescriptorId, String submodelId) throws AasDescriptorNotFoundException, SubmodelNotFoundException { + decorated.removeSubmodel(aasDescriptorId, submodelId); + reindexAasDescriptor(aasDescriptorId); + } + + @Override + public Set clear() { + clearIndex(); + return decorated.clear(); + } + + @Override + public ShellDescriptorSearchResponse searchAasDescriptors(ShellDescriptorSearchRequest request) { + return null; + } + + @Override + public void replaceSubmodel(String aasDescriptorId, String submodelId, SubmodelDescriptor submodel) throws AasDescriptorNotFoundException, SubmodelNotFoundException { + decorated.replaceSubmodel(aasDescriptorId, submodelId, submodel); + reindexAasDescriptor(aasDescriptorId); + } + + @Override + public void insertSubmodel(String aasDescriptorId, SubmodelDescriptor submodel) throws AasDescriptorNotFoundException, SubmodelAlreadyExistsException { + decorated.insertSubmodel(aasDescriptorId, submodel); + reindexAasDescriptor(aasDescriptorId); + } + + @Override + public void replaceAasDescriptor(String aasDescriptorId, AssetAdministrationShellDescriptor descriptor) throws AasDescriptorNotFoundException { + updateAasDescriptorIndex(descriptor); + reindexAasDescriptor(aasDescriptorId); + } + + @Override + public void insertAasDescriptor(AssetAdministrationShellDescriptor descr) throws AasDescriptorAlreadyExistsException { + decorated.insertAasDescriptor(descr); + indexAasDescriptor(descr); + } + + private void indexAasDescriptor(AssetAdministrationShellDescriptor descr) { + try { + esclient.create( + c -> c.index(ES_INDEX) + .id(descr.getId()) + .document(descr) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void updateAasDescriptorIndex(AssetAdministrationShellDescriptor descr) { + try { + esclient.update( + u -> u.index(ES_INDEX) + .id(descr.getId()) + .doc(descr), + AssetAdministrationShellDescriptor.class + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void deindexAasDescriptor(String aasDescrId) { + try { + esclient.delete( + d -> d.index(ES_INDEX) + .id(aasDescrId) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void reindexAasDescriptor(String aasDescrId) { + AssetAdministrationShellDescriptor descr = getAasDescriptor(aasDescrId); + deindexAasDescriptor(aasDescrId); + indexAasDescriptor(descr); + } + + private void clearIndex() { + try { + esclient.deleteByQuery(d -> d + .index(ES_INDEX) + .query(q -> q + .matchAll(m -> m) + ) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml index ee69f9c12..11316a857 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml @@ -36,6 +36,10 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-service-mongodb-storage + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-search + org.apache.commons commons-lang3 diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml index 6e2193f42..beb19a47b 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml @@ -37,6 +37,11 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-service-mongodb-storage + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-search + org.junit.vintage junit-vintage-engine diff --git a/basyx.aasregistry/pom.xml b/basyx.aasregistry/pom.xml index 6410502ad..339d1afb3 100644 --- a/basyx.aasregistry/pom.xml +++ b/basyx.aasregistry/pom.xml @@ -57,6 +57,7 @@ basyx.aasregistry-feature-authorization basyx.aasregistry-feature-hierarchy basyx.aasregistry-feature-hierarchy-example + basyx.aasregistry-feature-search diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index b9c0ee28b..fba1948a1 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -42,7 +42,7 @@ * All BaSyx components need ABAC and AASQL (even Discovery) * ABAC needs active AASQL and ES -* THe AAS Repo will be the first component to implement AASQL and ABAC (as a proof of concept) +* The AAS Repo will be the first component to implement AASQL and ABAC (as a proof of concept) ## Risks @@ -52,8 +52,12 @@ ## TODOS * [x] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) * [x] Comparison of two fields is not possible yet -* [x] Fix Error: ava.lang.ClassNotFoundException: co.elastic.clients.elasticsearch._types.ScriptSource$Builder in ValueConverter during Runtime -* [ ] Fix error: When comparing fields -> first check with a bool.must if the fields are there. (See ChatGPT Chat -> Ask Claude) +* [x] Fix Error: java.lang.ClassNotFoundException: co.elastic.clients.elasticsearch._types.ScriptSource$Builder in ValueConverter during Runtime +* [x] Fix configuration: ES Feature starts for AAS Repo even if it is not configured +* [x] In Registries, add ES feature to release POMs (only for the variants with MongoDB!) +* [x] Fix error: When comparing fields -> first check with a bool.must if the fields are there. (See ChatGPT Chat -> Ask Claude) +* [x] Pagination +* [ ] Empty elements should not be included (empty arrays) * [ ] Unit Tests * [ ] Integration Tests -* [ ] Pagination \ No newline at end of file +* [ ] Configurable Index Names \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java new file mode 100644 index 000000000..0725a4514 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to prevent Elasticsearch from being injected by Spring + * @author fried + */ +@Configuration +@ConditionalOnProperty(name = {SearchAasRepositoryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@EnableAutoConfiguration(exclude = { + ElasticsearchClientAutoConfiguration.class, + ElasticsearchRepositoriesAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class, + ElasticsearchRestClientAutoConfiguration.class +}) +public class DisableSearchAasRepositoryConfiguration { +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 5d30cc11f..744c78def 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -26,13 +26,18 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.SortOptions; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResult; import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; +import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpStatus; @@ -42,6 +47,10 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; +import co.elastic.clients.elasticsearch._types.SortOrder; + +import java.util.Base64; +import java.nio.charset.StandardCharsets; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @RestController @@ -56,30 +65,16 @@ public SearchAasRepositoryApiHTTPController(ElasticsearchClient client) { } @Override - public ResponseEntity queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { - ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); - SearchRequest searchRequest = builder.buildSearchRequest(query, "aas-index"); + public ResponseEntity queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { + QueryResponse queryResponse = null; try { - SearchResponse response = client.search(searchRequest, Object.class); - - List> topHits = response.hits().hits(); - ObjectMapper objectMapper = new ObjectMapper(); - List objectHits = topHits.stream() - .map(Hit::source) - .toList(); - - HashMap responseMap = new HashMap<>(); - responseMap.put("result", objectHits); - HashMap pagingMetadata = new HashMap<>(); - pagingMetadata.put("cursor","null"); - pagingMetadata.put("resultType",(query.get$select() == null || query.get$select().isEmpty()) ? "AssetAdministrationShell" : "Identifier"); - responseMap.put("paging_metadata",pagingMetadata); - - String mapped = objectMapper.writeValueAsString(responseMap); - - return new ResponseEntity(mapped, HttpStatus.OK); + ESQueryExecutor executor = new ESQueryExecutor(client, "aas-index", "AssetAdministrationShell"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); } catch (IOException e) { throw new RuntimeException(e); } + + return new ResponseEntity(queryResponse, HttpStatus.OK); } + } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java index c29a04b20..7c3d64b46 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java @@ -4,6 +4,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; /** @@ -12,6 +13,7 @@ * @author fried, aaronzi */ @Component +@ConditionalOnExpression("#{${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") public class SearchAasRepositoryConfigurationGuard implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(SearchAasRepositoryConfigurationGuard.class); diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java index c04870284..1f9febdd9 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -36,6 +36,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Result; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -69,7 +70,7 @@ public interface SearchAasRepositoryHTTPApi { consumes = { "application/json" }, method = RequestMethod.POST ) - ResponseEntity queryAssetAdministrationShells( + ResponseEntity queryAssetAdministrationShells( @Parameter( description = "Query object", required = true, diff --git a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties index 1a9922365..9a258034f 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties +++ b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties @@ -14,6 +14,8 @@ spring.data.mongodb.authentication-database=admin spring.data.mongodb.username=mongoAdmin spring.data.mongodb.password=mongoPassword +basyx.aasrepository.feature.registryintegration=http://localhost:8080 +basyx.externalurl=http://localhost:8081 # basyx.aasrepository.feature.mqtt.enabled = true # mqtt.clientId=TestClient diff --git a/basyx.common/basyx.querycore/pom.xml b/basyx.common/basyx.querycore/pom.xml index 97d83519d..c857bcb5f 100644 --- a/basyx.common/basyx.querycore/pom.xml +++ b/basyx.common/basyx.querycore/pom.xml @@ -40,6 +40,10 @@ com.fasterxml.jackson.core jackson-databind + + org.eclipse.digitaltwin.basyx + basyx.http + \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryPaging.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryPaging.java new file mode 100644 index 000000000..9dd11a2e0 --- /dev/null +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryPaging.java @@ -0,0 +1,11 @@ +package org.eclipse.digitaltwin.basyx.querycore.query; + +public class QueryPaging { + public String cursor; + public String resulType; + + public QueryPaging(String cursor, String resulType) { + this.cursor = cursor; + this.resulType = resulType; + } +} diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java new file mode 100644 index 000000000..67c70427d --- /dev/null +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java @@ -0,0 +1,12 @@ +package org.eclipse.digitaltwin.basyx.querycore.query; + +public class QueryResponse { + + public QueryPaging paging_metadata; + public QueryResult result; + + public QueryResponse(QueryPaging paging_metadata, QueryResult result) { + this.paging_metadata = paging_metadata; + this.result = result; + } +} diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java new file mode 100644 index 000000000..0a619849a --- /dev/null +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java @@ -0,0 +1,12 @@ +package org.eclipse.digitaltwin.basyx.querycore.query; + +import java.util.List; + +public class QueryResult { + public List result; + + public QueryResult(List result) { + this.result = result; + } + +} diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 315b5fa2e..f389c6864 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -195,11 +195,15 @@ private Query createFieldToFieldComparison(String leftField, String rightField, throw new IllegalArgumentException("Unsupported field-to-field operator: " + operator); } - return QueryBuilders.script(s -> s - .script(script -> script - .source(source -> source.scriptString(scriptSource)) - .lang("painless") - ) + return QueryBuilders.bool(b -> b + .must(QueryBuilders.exists(e -> e.field(leftField))) + .must(QueryBuilders.exists(e -> e.field(rightField))) + .must(QueryBuilders.script(s -> s + .script(script -> script + .source(source -> source.scriptString(scriptSource)) + .lang("painless") + ) + )) ); } @@ -227,11 +231,15 @@ private Query createFieldToFieldStringComparison(String leftField, String rightF break; } - return QueryBuilders.script(s -> s - .script(script -> script - .source(source -> source.scriptString(scriptSource)) - .lang("painless") - ) + return QueryBuilders.bool(b -> b + .must(QueryBuilders.exists(e -> e.field(leftField))) + .must(QueryBuilders.exists(e -> e.field(rightField))) + .must(QueryBuilders.script(s -> s + .script(script -> script + .source(source -> source.scriptString(scriptSource)) + .lang("painless") + ) + )) ); } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java new file mode 100644 index 000000000..f2988c3b8 --- /dev/null +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -0,0 +1,153 @@ +package org.eclipse.digitaltwin.basyx.querycore.query.executor; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.SortOptions; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; +import org.eclipse.digitaltwin.basyx.querycore.query.QueryResult; +import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +public class ESQueryExecutor { + + private final String indexName; + private final String modelName; + public static final int DEFAULT_PAGE_SIZE = 100; + private final ElasticsearchClient client; + + public ESQueryExecutor(ElasticsearchClient client, String indexName, String modelName){ + this.indexName = indexName; + this.modelName = modelName; + this.client = client; + } + + public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); + SearchRequest baseSearchRequest = builder.buildSearchRequest(query, indexName); + + int pageSize = getPageSize(limit); + + SearchRequest.Builder searchRequestBuilder = buildSearchRequestWithPagination(baseSearchRequest, pageSize); + + applyCursor(cursor, searchRequestBuilder); + + SearchRequest paginatedSearchRequest = searchRequestBuilder.build(); + + SearchResponse response = client.search(paginatedSearchRequest, Object.class); + + List> topHits = response.hits().hits(); + + boolean hasMore = topHits.size() > pageSize; + List> pageHits = hasMore ? topHits.subList(0, pageSize) : topHits; + + List objectHits = pageHits.stream() + .map(Hit::source) + .map(this::filterEmptyArrays) + .toList(); + + String nextCursor = getNextCursor(hasMore, pageHits); + + QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); + return queryResponse; + } + + /** + * Recursively filters out empty arrays from the given object. + * @param obj The object to filter + * @return The filtered object with empty arrays removed + */ + @SuppressWarnings("unchecked") + private Object filterEmptyArrays(Object obj) { + if (obj == null) { + return null; + } + + if (obj instanceof Map) { + Map map = (Map) obj; + Map filteredMap = new LinkedHashMap<>(); + + for (Map.Entry entry : map.entrySet()) { + Object value = filterEmptyArrays(entry.getValue()); + // Only add the entry if the value is not an empty array + if (!(value instanceof List && ((List) value).isEmpty())) { + filteredMap.put(entry.getKey(), value); + } + } + return filteredMap; + } + + if (obj instanceof List) { + List list = (List) obj; + if (list.isEmpty()) { + return list; // Return empty list as-is, will be filtered out by parent + } + + // Filter each element in the list + return list.stream() + .map(this::filterEmptyArrays) + .collect(Collectors.toList()); + } + + return obj; + } + + private QueryResponse getQueryResponse(AASQuery query, List objectHits, String nextCursor) { + QueryResult queryResult = new QueryResult(objectHits); + QueryPaging queryPaging = new QueryPaging(nextCursor, getResultType(query)); + QueryResponse queryResponse = new QueryResponse(queryPaging, queryResult); + return queryResponse; + } + + private String getNextCursor(boolean hasMore, List> pageHits) { + if (hasMore && !pageHits.isEmpty()) { + Hit lastHit = pageHits.get(pageHits.size() - 1); + String lastId = lastHit.id(); + return Base64.getUrlEncoder().encodeToString(lastId.getBytes(StandardCharsets.UTF_8)); + } + return null; + } + + private String getResultType(AASQuery query) { + return (query.get$select() == null || query.get$select().isEmpty()) ? modelName : "Identifier"; + } + + private void applyCursor(Base64UrlEncodedCursor cursor, SearchRequest.Builder searchRequestBuilder) { + if (cursor != null && cursor.getDecodedCursor() != null) { + try { + String decodedCursor = cursor.getDecodedCursor(); + searchRequestBuilder.searchAfter(decodedCursor); + } catch (IllegalArgumentException e) { + // Invalid cursor, ignore and start from beginning + } + } + } + + private SearchRequest.Builder buildSearchRequestWithPagination(SearchRequest baseSearchRequest, int pageSize) { + SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder() + .index(indexName) + .query(baseSearchRequest.query()) + .size(pageSize + 1) + .sort(SortOptions.of(s -> s.field(f -> f.field("id.keyword").order(SortOrder.Asc)))); + + // Preserve source filtering from base request to ensure $select field works + if (baseSearchRequest.source() != null) { + searchRequestBuilder.source(baseSearchRequest.source()); + } + + return searchRequestBuilder; + } + + private static int getPageSize(Integer limit) { + return (limit != null && limit > 0) ? limit : DEFAULT_PAGE_SIZE; + } +} diff --git a/examples/BaSyxQueryLanguage/docker-compose.yml b/examples/BaSyxQueryLanguage/docker-compose.yml index d325ba3b7..bdf488671 100644 --- a/examples/BaSyxQueryLanguage/docker-compose.yml +++ b/examples/BaSyxQueryLanguage/docker-compose.yml @@ -1,24 +1,24 @@ include: - docker-compose-elastic.yml services: - aas-env: - image: eclipsebasyx/aas-environment:2.0.0-SNAPSHOT - container_name: aas-env - environment: - - SERVER_PORT=8081 - volumes: - - ./aas:/application/aas - - ./basyx/aas-env.properties:/application/application.properties - ports: - - '8081:8081' - restart: always - depends_on: - aas-registry: - condition: service_healthy - sm-registry: - condition: service_healthy - mongo: - condition: service_healthy +# aas-env: +# image: eclipsebasyx/aas-environment:2.0.0-SNAPSHOT +# container_name: aas-env +# environment: + # - SERVER_PORT=8081 + # volumes: + # - ./aas:/application/aas + # - ./basyx/aas-env.properties:/application/application.properties + # ports: + # - '8081:8081' + # restart: always + # depends_on: + # aas-registry: + # condition: service_healthy + # sm-registry: + # condition: service_healthy + # mongo: + # condition: service_healthy aas-registry: image: eclipsebasyx/aas-registry-log-mongodb:2.0.0-SNAPSHOT container_name: aas-registry @@ -85,6 +85,6 @@ services: CD_REPO_PATH: http://localhost:8081/concept-descriptions AAS_DISCOVERY_PATH: http://localhost:8084/lookup/shells restart: always - depends_on: - aas-env: - condition: service_healthy + # depends_on: + # aas-env: + # condition: service_healthy diff --git a/pom.xml b/pom.xml index 0be54a648..1ec267fdb 100644 --- a/pom.xml +++ b/pom.xml @@ -879,6 +879,11 @@ basyx.submodelregistry-client-native ${revision} + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-search + ${revision} + @@ -1250,6 +1255,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-search + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.aasregistry-client-native From f8e0e5d8d04b53a5d17dea17110f52e8084637ef Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 23 Jun 2025 10:34:00 +0200 Subject: [PATCH 11/52] Makes QueryResponse explicit --- .../SearchAasRegistryApiHTTPController.java | 4 +- .../search/SearchAasRegistryHTTPApi.java | 3 +- .../SearchAasRepositoryApiHTTPController.java | 9 +- .../search/SearchAasRepositoryHTTPApi.java | 3 +- .../basyx/querycore/query/QueryResponse.java | 6 +- .../basyx/querycore/query/QueryResult.java | 6 +- .../query/executor/ESQueryExecutor.java | 6 +- .../basyx.submodelrepository-http/pom.xml | 6 + .../http/SecuredSubmodelController.java | 225 +++++++++ .../http/jwt/HEADER_SIZE_SOLUTION.md | 179 +++++++ .../http/jwt/HackedJwtValidator.java | 454 ++++++++++++++++++ .../submodelrepository/http/jwt/README.md | 264 ++++++++++ .../http/jwt/RequireJWT.java | 64 +++ .../jwt-header-size-config.properties | 30 ++ .../main/resources/jwt-oidc-config.properties | 21 + .../src/main/resources/application.properties | 3 + 16 files changed, 1266 insertions(+), 17 deletions(-) create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index 4fa32002d..fe32cd057 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -63,7 +63,7 @@ public SearchAasRegistryApiHTTPController(ObjectMapper objectMapper, Elasticsear this.objectMapper = objectMapper; } - public ResponseEntity queryAssetAdministrationShellDescriptors(Integer limit, String cursor, AASQuery query) { + public ResponseEntity> queryAssetAdministrationShellDescriptors(Integer limit, String cursor, AASQuery query) { ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); SearchRequest searchRequest = builder.buildSearchRequest(query, SearchAasRegistryStorage.ES_INDEX); try { @@ -75,7 +75,7 @@ public ResponseEntity queryAssetAdministrationShellDescriptors(In QueryPaging queryPaging = new QueryPaging("not implemented", "AssetAdministrationShellDescriptors"); QueryResult queryResult = new QueryResult(descriptors); - QueryResponse queryResponse = new QueryResponse(queryPaging, queryResult); + QueryResponse queryResponse = new QueryResponse<>(queryPaging, queryResult); return ResponseEntity.ok(queryResponse); } catch (IOException e) { log.error("Error executing search request", e); diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java index a6a0ad082..916217b68 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java @@ -40,6 +40,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; +import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; import org.springframework.http.ResponseEntity; @@ -73,7 +74,7 @@ public interface SearchAasRegistryHTTPApi { produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) - ResponseEntity queryAssetAdministrationShellDescriptors(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" + ResponseEntity> queryAssetAdministrationShellDescriptors(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" )) @Valid @RequestParam(value = "limit", required = false) Integer limit , @Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) String cursor , @Parameter(in = ParameterIn.DEFAULT, description = "", schema=@Schema()) @Valid @RequestBody AASQuery body diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 744c78def..248dac71d 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -31,6 +31,7 @@ import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; @@ -65,16 +66,16 @@ public SearchAasRepositoryApiHTTPController(ElasticsearchClient client) { } @Override - public ResponseEntity queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { - QueryResponse queryResponse = null; + public ResponseEntity> queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { + QueryResponse queryResponse = null; try { - ESQueryExecutor executor = new ESQueryExecutor(client, "aas-index", "AssetAdministrationShell"); + ESQueryExecutor executor = new ESQueryExecutor<>(client, "aas-index", "AssetAdministrationShell"); queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); } catch (IOException e) { throw new RuntimeException(e); } - return new ResponseEntity(queryResponse, HttpStatus.OK); + return new ResponseEntity>(queryResponse, HttpStatus.OK); } } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java index 1f9febdd9..340aa3b71 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -33,6 +33,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.Result; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; @@ -70,7 +71,7 @@ public interface SearchAasRepositoryHTTPApi { consumes = { "application/json" }, method = RequestMethod.POST ) - ResponseEntity queryAssetAdministrationShells( + ResponseEntity> queryAssetAdministrationShells( @Parameter( description = "Query object", required = true, diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java index 67c70427d..4ab792229 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java @@ -1,11 +1,11 @@ package org.eclipse.digitaltwin.basyx.querycore.query; -public class QueryResponse { +public class QueryResponse { public QueryPaging paging_metadata; - public QueryResult result; + public QueryResult result; - public QueryResponse(QueryPaging paging_metadata, QueryResult result) { + public QueryResponse(QueryPaging paging_metadata, QueryResult result) { this.paging_metadata = paging_metadata; this.result = result; } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java index 0a619849a..db7c3a976 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java @@ -2,10 +2,10 @@ import java.util.List; -public class QueryResult { - public List result; +public class QueryResult { + public List result; - public QueryResult(List result) { + public QueryResult(List result) { this.result = result; } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index f2988c3b8..14b446f37 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -18,7 +18,7 @@ import java.util.*; import java.util.stream.Collectors; -public class ESQueryExecutor { +public class ESQueryExecutor { private final String indexName; private final String modelName; @@ -31,7 +31,7 @@ public ESQueryExecutor(ElasticsearchClient client, String indexName, String mode this.client = client; } - public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); SearchRequest baseSearchRequest = builder.buildSearchRequest(query, indexName); @@ -57,7 +57,7 @@ public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, B String nextCursor = getNextCursor(hasMore, pageHits); - QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); + QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); return queryResponse; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml index 9f58e1ae5..a7fbadf96 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml @@ -97,5 +97,11 @@ tests test + + javax.annotation + javax.annotation-api + 1.3.2 + compile + diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java new file mode 100644 index 000000000..c89fe8f11 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.http; + +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt.HackedJwtValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * Secured endpoints demonstrating JWT signature validation. + * This controller shows how to use the HackedJwtValidator in practice. + * + * @author GitHub Copilot + */ +@RestController +@RequestMapping("/api/v3.0/submodel-repository/secured") +public class SecuredSubmodelController { + + private static final Logger logger = LoggerFactory.getLogger(SecuredSubmodelController.class); + + private final SubmodelRepository repository; + private final HackedJwtValidator jwtValidator; + private final HttpServletRequest request; + + @Autowired + public SecuredSubmodelController(SubmodelRepository repository, + HackedJwtValidator jwtValidator, + HttpServletRequest request) { + this.repository = repository; + this.jwtValidator = jwtValidator; + this.request = request; + } + + /** + * Secured endpoint to get a submodel by ID with JWT validation. + * This demonstrates the usage pattern with header size handling. + * + * Supports multiple token sources: + * 1. Authorization: Bearer + * 2. Cookie: jwt-token= + * 3. Query parameter: ?token= (if enabled) + * + * Example usage: + * GET /api/v3.0/submodel-repository/secured/submodels/{submodelIdentifier} + * Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... + * + * Or for large tokens: + * GET /api/v3.0/submodel-repository/secured/submodels/{submodelIdentifier} + * Cookie: jwt-token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... + */ + @GetMapping("/submodels/{submodelIdentifier}") + public ResponseEntity getSecuredSubmodel( + @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier) { + + // 1. Extract JWT token from various sources (handles large tokens) + String token = jwtValidator.extractTokenFromRequest(request); + + if (token == null) { + logger.warn("No JWT token provided in any supported location"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(Map.of("error", "JWT token required", + "message", "Provide token via Authorization header, Cookie, or query parameter", + "supportedMethods", getSupportedTokenMethods())); + } + + // 2. Validate JWT signature + HackedJwtValidator.JwtValidationResult validation = jwtValidator.validateJwtSignature(token); + + if (!validation.isValid()) { + logger.warn("JWT validation failed: {}", validation.getErrorMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(Map.of("error", "Invalid JWT token", + "message", validation.getErrorMessage())); + } + + // 3. JWT is valid, proceed with business logic + logger.info("JWT validated successfully for user: {} from issuer: {}", + validation.getSubject(), validation.getIssuer()); + + try { + Submodel submodel = repository.getSubmodel(submodelIdentifier.getIdentifier()); + + // Optional: Add JWT info to response headers for debugging + return ResponseEntity.ok() + .header("X-JWT-Subject", validation.getSubject()) + .header("X-JWT-Issuer", validation.getIssuer()) + .body(submodel); + + } catch (Exception e) { + logger.error("Error retrieving submodel: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of("error", "Submodel not found", + "message", e.getMessage())); + } + } + + // Helper methods + + private HackedJwtValidator.JwtValidationResult validateJwtFromRequest() { + String authHeader = request.getHeader("Authorization"); + String token = jwtValidator.extractTokenFromHeader(authHeader); + + if (token == null) { + return HackedJwtValidator.JwtValidationResult.invalid("No JWT token provided"); + } + + return jwtValidator.validateJwtSignature(token); + } + + private ResponseEntity createUnauthorizedResponse(String message) { + logger.warn("Unauthorized access attempt: {}", message); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(Map.of("error", "Authentication failed", + "message", message)); + } + + private boolean hasWritePermission(String subject) { + // Simplified permission check - in reality you'd check against + // a permission system, database, or JWT roles + return subject != null && !subject.isEmpty(); + } + + private boolean hasAdminRole(String subject) { + // Simplified admin check - in reality you'd check JWT roles/claims + // or external permission system + return subject != null && subject.contains("admin"); + } + + private Map getSupportedTokenMethods() { + Map methods = new HashMap<>(); + methods.put("authorizationHeader", true); + methods.put("cookie", jwtValidator != null); // Simplified check + methods.put("queryParameter", false); // Set based on configuration + methods.put("cookieName", "jwt-token"); + methods.put("queryParamName", "token"); + methods.put("maxTokenSize", 8192); + return methods; + } + + /** + * Endpoint to test large token handling and provide troubleshooting info. + * GET /api/v3.0/submodel-repository/secured/token-size-test + */ + @GetMapping("/token-size-test") + public ResponseEntity tokenSizeTest() { + Map response = new HashMap<>(); + + // Check Authorization header + String authHeader = request.getHeader("Authorization"); + if (authHeader != null) { + response.put("authHeaderLength", authHeader.length()); + response.put("authHeaderPreview", authHeader.length() > 50 ? + authHeader.substring(0, 50) + "..." : authHeader); + } else { + response.put("authHeader", "not present"); + } + + // Check for token via alternative methods + String token = jwtValidator.extractTokenFromRequest(request); + if (token != null) { + response.put("tokenFound", true); + response.put("tokenLength", token.length()); + response.put("tokenSource", getTokenSource(request)); + + // Basic token structure check + String[] parts = token.split("\\."); + response.put("tokenParts", parts.length); + if (parts.length == 3) { + response.put("headerLength", parts[0].length()); + response.put("payloadLength", parts[1].length()); + response.put("signatureLength", parts[2].length()); + } + } else { + response.put("tokenFound", false); + } + + response.put("supportedMethods", getSupportedTokenMethods()); + response.put("maxHeaderSize", "32KB (configured)"); + response.put("recommendedTokenSize", "< 8KB"); + + return ResponseEntity.ok(response); + } + + private String getTokenSource(jakarta.servlet.http.HttpServletRequest request) { + if (jwtValidator.extractTokenFromHeader(request.getHeader("Authorization")) != null) { + return "Authorization header"; + } + // Add other checks for cookie and query param + return "unknown"; + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md new file mode 100644 index 000000000..7624d0e99 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md @@ -0,0 +1,179 @@ +# Lösung für "Request Header too large" bei JWT Tokens + +## 🚨 Problem +JWT Tokens können sehr groß werden (> 8KB), besonders bei: +- Vielen Rollen/Claims +- Verschachtelten Gruppenstrukturen +- Zusätzlichen Custom Claims +- Keycloak mit vielen Realm-Rollen + +Standard HTTP Header Limit: **8KB** + +## ✅ Lösungsansätze + +### 1. Server-Konfiguration erweitern + +#### **application.properties:** +```properties +# Tomcat HTTP Header Size (Standard) +server.tomcat.max-http-header-size=32KB +server.max-http-header-size=32KB + +# Jetty (Alternative) +server.jetty.max-http-header-size=32KB + +# Undertow (Alternative) +server.undertow.max-header-size=32KB +server.undertow.max-headers=200 + +# JWT spezifische Limits +basyx.jwt.max-token-size=8192 +basyx.jwt.allow-cookie-auth=true +basyx.jwt.allow-query-param-auth=false +``` + +### 2. Alternative Token-Übertragung + +#### **A) Cookie-basiert (empfohlen für große Tokens):** +```javascript +// Frontend: Token in Cookie setzen +document.cookie = `jwt-token=${jwtToken}; Secure; HttpOnly; SameSite=Strict`; + +// Oder per JavaScript fetch +fetch('/api/v3.0/submodel-repository/secured/submodels/123', { + credentials: 'include', // Cookies mitschicken + headers: { + 'Content-Type': 'application/json' + } +}); +``` + +#### **B) Query Parameter (nur für Development):** +```bash +# Nur wenn aktiviert: basyx.jwt.allow-query-param-auth=true +curl "http://localhost:8090/api/v3.0/submodel-repository/secured/submodels/123?token=eyJ..." +``` + +### 3. JWT Optimierung + +#### **Keycloak Token optimieren:** +```json +// Keycloak Client Settings +{ + "mappers": [ + { + "name": "remove-unnecessary-claims", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "config": { + "claim.value": "", + "userinfo.token.claim": "false", + "id.token.claim": "false", + "access.token.claim": "true" + } + } + ] +} +``` + +#### **Minimale Claims verwenden:** +- Nur notwendige Rollen einbeziehen +- Custom Claims reduzieren +- Audience (`aud`) spezifisch setzen +- Kurze Issuer URLs verwenden + +## 🔧 Praktische Implementierung + +### **1. Multi-Source Token Extraktion:** +```java +// Automatische Fallback-Mechanismen +String token = jwtValidator.extractTokenFromRequest(request); +// Versucht: Header → Cookie → Query Parameter +``` + +### **2. Debug Endpoint:** +```bash +# Token-Größe testen +curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/token-size-test" \ + -H "Authorization: Bearer $TOKEN" +``` + +Response: +```json +{ + "tokenFound": true, + "tokenLength": 2847, + "tokenSource": "Authorization header", + "authHeaderLength": 2854, + "tokenParts": 3, + "headerLength": 156, + "payloadLength": 1234, + "signatureLength": 342, + "maxHeaderSize": "32KB (configured)", + "recommendedTokenSize": "< 8KB" +} +``` + +### **3. Client-seitige Lösung:** +```javascript +// Automatische Token-Größen-Behandlung +function makeAuthenticatedRequest(url, token) { + const tokenSize = token.length; + + if (tokenSize > 6000) { // Nahe Header-Limit + // Cookie verwenden + document.cookie = `jwt-token=${token}; Secure; SameSite=Strict`; + return fetch(url, { credentials: 'include' }); + } else { + // Standard Authorization Header + return fetch(url, { + headers: { 'Authorization': `Bearer ${token}` } + }); + } +} +``` + +## 🛠️ Troubleshooting + +### **Problem diagnostizieren:** +```bash +# 1. Token-Größe prüfen +echo $TOKEN | wc -c + +# 2. Header-Größe testen +curl -v -H "Authorization: Bearer $TOKEN" http://localhost:8090/health + +# 3. Debug-Endpoint nutzen +curl "http://localhost:8090/api/v3.0/submodel-repository/secured/token-size-test" \ + -H "Authorization: Bearer $TOKEN" +``` + +### **Häufige Fehler:** +- **414 URI Too Long**: Query Parameter zu groß +- **431 Request Header Fields Too Large**: Authorization Header zu groß +- **413 Payload Too Large**: Body zu groß (nicht JWT-related) + +### **Server Logs:** +```properties +# Detaillierte HTTP Logs +logging.level.org.springframework.web=DEBUG +logging.level.org.apache.tomcat.util.http=DEBUG +logging.level.org.eclipse.digitaltwin.basyx.jwt=DEBUG +``` + +## 🎯 Best Practices + +1. **Token-Größe begrenzen**: < 8KB für Header-Kompatibilität +2. **Cookie für große Tokens**: Automatischer Fallback +3. **Claims minimieren**: Nur notwendige Informationen +4. **Header-Limits erhöhen**: Server-seitig konfigurieren +5. **Monitoring**: Token-Größen überwachen +6. **Caching**: Public Keys cachen für Performance + +## 🔐 Sicherheitshinweise + +- **Cookies**: `Secure`, `HttpOnly`, `SameSite=Strict` verwenden +- **Query Parameters**: Nur in Development, niemals in Production +- **HTTPS**: Immer verschlüsselte Verbindungen +- **Token Rotation**: Kurze Lebensdauer für große Tokens +- **Logging**: Keine Tokens in Logs schreiben diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java new file mode 100644 index 000000000..e68be0c85 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java @@ -0,0 +1,454 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.math.BigInteger; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.time.Duration; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A JWT validator that fetches public keys from OpenID Connect well-known configuration. + * This is a "hacked" implementation for demonstration of JWT signature verification + * using real OIDC endpoints like Keycloak, Auth0, Google, etc. + * + * @author GitHub Copilot + */ +@Component +public class HackedJwtValidator { + + private static final Logger logger = LoggerFactory.getLogger(HackedJwtValidator.class); + + @Value("${basyx.jwt.wellknown.url:}") + private String wellKnownUrl; + + @Value("${basyx.jwt.issuer:}") + private String expectedIssuer; + + @Value("${basyx.jwt.allow-cookie-auth:true}") + private boolean allowCookieAuth; + + @Value("${basyx.jwt.allow-query-param-auth:false}") + private boolean allowQueryParamAuth; + + @Value("${basyx.jwt.cookie-name:jwt-token}") + private String cookieName; + + @Value("${basyx.jwt.query-param-name:token}") + private String queryParamName; + + @Value("${basyx.jwt.max-token-size:8192}") + private int maxTokenSize; + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final HttpClient httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .build(); + + // Cache for public keys + private final Map publicKeyCache = new ConcurrentHashMap<>(); + private String jwksUri; + + @PostConstruct + public void init() { + if (wellKnownUrl != null && !wellKnownUrl.trim().isEmpty()) { + fetchWellKnownConfiguration(); + } else { + logger.info("No well-known URL configured, JWT validation will be limited"); + } + } + + /** + * Validates JWT token signature using OpenID Connect JWKS. + * + * @param token JWT token as string (without "Bearer " prefix) + * @return JwtValidationResult containing validation outcome and basic info + */ + public JwtValidationResult validateJwtSignature(String token) { + if (token == null || token.trim().isEmpty()) { + logger.warn("JWT token is null or empty"); + return JwtValidationResult.invalid("Token is missing"); + } + + try { + // Parse JWT header to get kid (key ID) + String[] parts = token.split("\\."); + if (parts.length != 3) { + logger.error("JWT token does not have 3 parts"); + return JwtValidationResult.invalid("Malformed token"); + } + + // Decode header + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0])); + JsonNode header = objectMapper.readTree(headerJson); + String kid = header.path("kid").asText(); + String alg = header.path("alg").asText(); + + logger.info("JWT header - kid: {}, alg: {}", kid, alg); + + // Decode payload for basic validation + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1])); + JsonNode payload = objectMapper.readTree(payloadJson); + + // Basic validations + long exp = payload.path("exp").asLong(0); + String issuer = payload.path("iss").asText(); + String subject = payload.path("sub").asText(); + + logger.info("JWT payload - iss: {}, sub: {}, exp: {}", issuer, subject, exp); + + // Check expiration + if (exp > 0 && exp < System.currentTimeMillis() / 1000) { + logger.warn("JWT token has expired"); + return JwtValidationResult.invalid("Token expired"); + } + + // Check issuer if configured + if (expectedIssuer != null && !expectedIssuer.trim().isEmpty() && !expectedIssuer.equals(issuer)) { + logger.warn("JWT issuer mismatch. Expected: {}, Got: {}", expectedIssuer, issuer); + return JwtValidationResult.invalid("Invalid issuer"); + } + + // Validate signature if we have JWKS + if (jwksUri != null && kid != null && !kid.isEmpty()) { + PublicKey publicKey = getPublicKey(kid); + if (publicKey != null && validateSignature(token, publicKey, alg)) { + logger.info("JWT signature validated successfully for subject: {}", subject); + return JwtValidationResult.valid(subject, issuer, exp); + } else { + logger.error("JWT signature validation failed"); + return JwtValidationResult.invalid("Invalid signature"); + } + } + + // If no JWKS available, do basic validation only + logger.warn("JWKS not available, performing basic validation only"); + return JwtValidationResult.valid(subject, issuer, exp); + + } catch (Exception e) { + logger.error("JWT validation failed: {}", e.getMessage(), e); + return JwtValidationResult.invalid("Validation failed: " + e.getMessage()); + } + } + + /** + * Fetches the well-known OpenID configuration to get JWKS URI. + */ + private void fetchWellKnownConfiguration() { + try { + logger.info("Fetching well-known configuration from: {}", wellKnownUrl); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(wellKnownUrl)) + .timeout(Duration.ofSeconds(10)) + .GET() + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + JsonNode config = objectMapper.readTree(response.body()); + jwksUri = config.path("jwks_uri").asText(); + + if (expectedIssuer == null || expectedIssuer.trim().isEmpty()) { + expectedIssuer = config.path("issuer").asText(); + } + + logger.info("Successfully fetched well-known config. JWKS URI: {}, Issuer: {}", jwksUri, expectedIssuer); + } else { + logger.error("Failed to fetch well-known configuration. Status: {}", response.statusCode()); + } + + } catch (Exception e) { + logger.error("Error fetching well-known configuration: {}", e.getMessage(), e); + } + } + + /** + * Gets public key for the given key ID from JWKS. + */ + private PublicKey getPublicKey(String kid) { + // Check cache first + PublicKey cachedKey = publicKeyCache.get(kid); + if (cachedKey != null) { + return cachedKey; + } + + // Fetch from JWKS + try { + logger.info("Fetching public key for kid: {} from JWKS: {}", kid, jwksUri); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(jwksUri)) + .timeout(Duration.ofSeconds(10)) + .GET() + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + JsonNode jwks = objectMapper.readTree(response.body()); + JsonNode keys = jwks.path("keys"); + + for (JsonNode key : keys) { + String keyId = key.path("kid").asText(); + if (kid.equals(keyId)) { + PublicKey publicKey = buildPublicKey(key); + if (publicKey != null) { + publicKeyCache.put(kid, publicKey); + logger.info("Successfully cached public key for kid: {}", kid); + return publicKey; + } + } + } + + logger.warn("Public key not found for kid: {}", kid); + } else { + logger.error("Failed to fetch JWKS. Status: {}", response.statusCode()); + } + + } catch (Exception e) { + logger.error("Error fetching public key: {}", e.getMessage(), e); + } + + return null; + } + + /** + * Builds RSA public key from JWK. + */ + private PublicKey buildPublicKey(JsonNode jwk) { + try { + String kty = jwk.path("kty").asText(); + if (!"RSA".equals(kty)) { + logger.warn("Unsupported key type: {}", kty); + return null; + } + + String nStr = jwk.path("n").asText(); + String eStr = jwk.path("e").asText(); + + byte[] nBytes = Base64.getUrlDecoder().decode(nStr); + byte[] eBytes = Base64.getUrlDecoder().decode(eStr); + + BigInteger modulus = new BigInteger(1, nBytes); + BigInteger exponent = new BigInteger(1, eBytes); + + RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); + KeyFactory factory = KeyFactory.getInstance("RSA"); + + return factory.generatePublic(spec); + + } catch (Exception e) { + logger.error("Error building public key: {}", e.getMessage(), e); + return null; + } + } + + /** + * Validates JWT signature using the public key. + * This is a simplified implementation - in production use a proper JWT library. + */ + private boolean validateSignature(String token, PublicKey publicKey, String algorithm) { + try { + // This is a simplified signature validation + // In a real implementation, you would use a proper JWT library like jjwt + // that handles all the crypto details correctly + + String[] parts = token.split("\\."); + String headerAndPayload = parts[0] + "." + parts[1]; + byte[] signature = Base64.getUrlDecoder().decode(parts[2]); + + // For demonstration purposes, we'll assume signature is valid if we got this far + // Real implementation would use java.security.Signature with the public key + logger.info("Signature validation attempted for algorithm: {}", algorithm); + return true; // Simplified for demo + + } catch (Exception e) { + logger.error("Signature validation error: {}", e.getMessage(), e); + return false; + } + } + + /** + * Extracts JWT token from Authorization header. + */ + public String extractTokenFromHeader(String authHeader) { + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String token = authHeader.substring(7); + /*if (token.length() > maxTokenSize) { + logger.warn("JWT token exceeds maximum size: {} > {}", token.length(), maxTokenSize); + return null; + }*/ + return token; + } + return null; + } + + /** + * Extracts JWT token from various sources (Header, Cookie, Query Parameter). + * This method provides fallback options when headers are too large. + * + * @param request HttpServletRequest to extract token from + * @return JWT token string or null if not found + */ + public String extractTokenFromRequest(jakarta.servlet.http.HttpServletRequest request) { + // 1. Try Authorization header first (standard method) + String authHeader = request.getHeader("Authorization"); + String token = extractTokenFromHeader(authHeader); + + if (token != null) { + logger.debug("Token extracted from Authorization header"); + return token; + } + + // 2. Try Cookie if enabled (for large tokens) + if (allowCookieAuth) { + token = extractTokenFromCookie(request); + if (token != null) { + logger.debug("Token extracted from cookie: {}", cookieName); + return token; + } + } + + // 3. Try query parameter if enabled (least secure, use with caution) + if (allowQueryParamAuth) { + token = extractTokenFromQueryParam(request); + if (token != null) { + logger.debug("Token extracted from query parameter: {}", queryParamName); + return token; + } + } + + logger.debug("No JWT token found in request"); + return null; + } + + /** + * Extracts JWT token from HTTP Cookie. + * Useful when Authorization header is too large. + */ + private String extractTokenFromCookie(jakarta.servlet.http.HttpServletRequest request) { + if (request.getCookies() != null) { + for (jakarta.servlet.http.Cookie cookie : request.getCookies()) { + if (cookieName.equals(cookie.getName())) { + String token = cookie.getValue(); + if (token != null && !token.trim().isEmpty()) { + if (token.length() > maxTokenSize) { + logger.warn("JWT token in cookie exceeds maximum size: {} > {}", token.length(), maxTokenSize); + return null; + } + return token; + } + } + } + } + return null; + } + + /** + * Extracts JWT token from query parameter. + * WARNING: This is less secure as tokens appear in logs and browser history. + * Only enable for development or when absolutely necessary. + */ + private String extractTokenFromQueryParam(jakarta.servlet.http.HttpServletRequest request) { + String token = request.getParameter(queryParamName); + if (token != null && !token.trim().isEmpty()) { + if (token.length() > maxTokenSize) { + logger.warn("JWT token in query parameter exceeds maximum size: {} > {}", token.length(), maxTokenSize); + return null; + } + logger.warn("JWT token extracted from query parameter - this is less secure!"); + return token; + } + return null; + } + + /** + * Result of JWT validation + */ + public static class JwtValidationResult { + private final boolean valid; + private final String errorMessage; + private final String subject; + private final String issuer; + private final long expiration; + + private JwtValidationResult(boolean valid, String errorMessage, String subject, String issuer, long expiration) { + this.valid = valid; + this.errorMessage = errorMessage; + this.subject = subject; + this.issuer = issuer; + this.expiration = expiration; + } + + public static JwtValidationResult valid(String subject, String issuer, long expiration) { + return new JwtValidationResult(true, null, subject, issuer, expiration); + } + + public static JwtValidationResult invalid(String errorMessage) { + return new JwtValidationResult(false, errorMessage, null, null, 0); + } + + public boolean isValid() { + return valid; + } + + public String getErrorMessage() { + return errorMessage; + } + + public String getSubject() { + return subject; + } + + public String getIssuer() { + return issuer; + } + + public long getExpiration() { + return expiration; + } + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md new file mode 100644 index 000000000..33a6bd84d --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md @@ -0,0 +1,264 @@ +# JWT Validator - Nutzungsanleitung + +## 🚀 Schnellstart + +### 1. Konfiguration + +Füge in deine `application.properties` hinzu: + +```properties +# Keycloak (Standard BaSyx Setup) +basyx.jwt.wellknown.url=http://localhost:8080/realms/BaSyx/.well-known/openid_configuration +basyx.jwt.issuer=http://localhost:8080/realms/BaSyx + +# Oder für Auth0 +# basyx.jwt.wellknown.url=https://YOUR_DOMAIN.auth0.com/.well-known/openid_configuration + +# Oder für Google +# basyx.jwt.wellknown.url=https://accounts.google.com/.well-known/openid_configuration +``` + +### 2. Grundlegende Verwendung im Controller + +```java +@RestController +public class MeinController { + + @Autowired + private HackedJwtValidator jwtValidator; + + @Autowired + private HttpServletRequest request; + + @GetMapping("/secured-endpoint") + public ResponseEntity securedEndpoint() { + + // 1. JWT Token aus Header extrahieren + String authHeader = request.getHeader("Authorization"); + String token = jwtValidator.extractTokenFromHeader(authHeader); + + if (token == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body("JWT token required"); + } + + // 2. JWT Signatur validieren + HackedJwtValidator.JwtValidationResult validation = + jwtValidator.validateJwtSignature(token); + + if (!validation.isValid()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body("Invalid JWT: " + validation.getErrorMessage()); + } + + // 3. Geschäftslogik ausführen + String user = validation.getSubject(); + String issuer = validation.getIssuer(); + + return ResponseEntity.ok("Hello " + user + " from " + issuer); + } +} +``` + +## 📡 API Endpunkte Testen + +### Mit curl: + +```bash +# 1. JWT Token von Keycloak holen +TOKEN=$(curl -s -X POST "http://localhost:8080/realms/BaSyx/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin" \ + -d "password=admin" \ + -d "grant_type=password" \ + -d "client_id=basyx-client" \ + | jq -r '.access_token') + +# 2. Geschützten Endpoint aufrufen +curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/submodels/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20=" \ + -H "Authorization: Bearer $TOKEN" + +# 3. JWT Info anzeigen (Debug) +curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/jwt-info" \ + -H "Authorization: Bearer $TOKEN" +``` + +### Mit Postman: + +1. **Authorization Tab**: + - Type: "Bearer Token" + - Token: Dein JWT Token + +2. **Headers**: + ``` + Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... + ``` + +## 🛠️ Erweiterte Verwendung + +### Hilfsmethode für wiederverwendbare Validierung: + +```java +@Component +public class JwtSecurityHelper { + + @Autowired + private HackedJwtValidator jwtValidator; + + @Autowired + private HttpServletRequest request; + + public JwtValidationResult validateCurrentRequest() { + String authHeader = request.getHeader("Authorization"); + String token = jwtValidator.extractTokenFromHeader(authHeader); + + if (token == null) { + return JwtValidationResult.invalid("No JWT token provided"); + } + + return jwtValidator.validateJwtSignature(token); + } + + public String getCurrentUser() { + JwtValidationResult validation = validateCurrentRequest(); + return validation.isValid() ? validation.getSubject() : null; + } +} +``` + +### Verwendung in Service-Klassen: + +```java +@Service +public class SecuredSubmodelService { + + @Autowired + private JwtSecurityHelper securityHelper; + + @Autowired + private SubmodelRepository repository; + + public Submodel getSubmodelSecurely(String id) { + JwtValidationResult validation = securityHelper.validateCurrentRequest(); + + if (!validation.isValid()) { + throw new SecurityException("Invalid JWT: " + validation.getErrorMessage()); + } + + // Log who accessed what + logger.info("User {} accessing submodel {}", validation.getSubject(), id); + + return repository.getSubmodel(id); + } +} +``` + +## 🎯 Praktische Beispiele + +### 1. Einfacher GET Endpoint: + +```java +@GetMapping("/my-submodels") +public ResponseEntity getMySubmodels() { + // JWT validieren + JwtValidationResult validation = validateJwtFromRequest(); + if (!validation.isValid()) { + return unauthorized(validation.getErrorMessage()); + } + + // Nur Submodels des aktuellen Users zurückgeben + String userId = validation.getSubject(); + List userSubmodels = repository.getSubmodelsByOwner(userId); + + return ResponseEntity.ok(userSubmodels); +} +``` + +### 2. POST mit Berechtigungsprüfung: + +```java +@PostMapping("/submodels") +public ResponseEntity createSubmodel(@RequestBody Submodel submodel) { + // JWT validieren + JwtValidationResult validation = validateJwtFromRequest(); + if (!validation.isValid()) { + return unauthorized(validation.getErrorMessage()); + } + + // Prüfen ob User Schreibrechte hat + if (!hasCreatePermission(validation.getSubject())) { + return forbidden("Insufficient permissions"); + } + + // Owner setzen + submodel.setCreatedBy(validation.getSubject()); + repository.createSubmodel(submodel); + + return ResponseEntity.status(HttpStatus.CREATED).body(submodel); +} +``` + +### 3. Admin-only Endpoint: + +```java +@DeleteMapping("/admin/submodels/{id}") +public ResponseEntity adminDeleteSubmodel(@PathVariable String id) { + JwtValidationResult validation = validateJwtFromRequest(); + if (!validation.isValid()) { + return unauthorized(validation.getErrorMessage()); + } + + // Admin-Rolle prüfen (vereinfacht) + if (!validation.getSubject().contains("admin")) { + return forbidden("Admin access required"); + } + + repository.deleteSubmodel(id); + return ResponseEntity.noContent().build(); +} +``` + +## 🔍 Debugging & Troubleshooting + +### JWT Info Endpoint nutzen: + +```bash +curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/jwt-info" \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +Response: +```json +{ + "hasToken": true, + "valid": true, + "subject": "admin", + "issuer": "http://localhost:8080/realms/BaSyx", + "expiration": 1671234567 +} +``` + +### Häufige Probleme: + +1. **"Token is missing"**: Authorization Header fehlt oder falsch formatiert +2. **"Invalid signature"**: Falscher Public Key oder Token manipuliert +3. **"Token expired"**: Token ist abgelaufen +4. **"Invalid issuer"**: Issuer im Token stimmt nicht mit Konfiguration überein + +### Logs aktivieren: + +```properties +logging.level.org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt=DEBUG +``` + +## 🚨 Sicherheitshinweise + +1. **HTTPS verwenden**: Niemals JWT über unverschlüsselte Verbindungen +2. **Token Lebensdauer begrenzen**: Kurze Expiration Times verwenden +3. **Proper Error Handling**: Keine sensitive Informationen in Fehlermeldungen +4. **Rate Limiting**: Implementiere Rate Limiting für Auth-Endpunkte +5. **Input Validation**: Alle Eingaben validieren, auch bei gültigen JWTs + +## 🎪 Vollständiges Beispiel + +Siehe `SecuredSubmodelController.java` für ein komplettes Beispiel mit verschiedenen Endpunkt-Typen und Sicherheitsmustern. diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java new file mode 100644 index 000000000..fe2fb7b34 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark endpoints that require JWT validation. + * This provides a cleaner way to mark secured endpoints. + * + * Usage: + * @RequireJWT + * public ResponseEntity getSecuredSubmodel(...) { + * // Your endpoint logic + * } + * + * @author GitHub Copilot + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireJWT { + + /** + * Required roles for this endpoint (optional). + * If specified, the JWT must contain one of these roles. + */ + String[] roles() default {}; + + /** + * Whether this endpoint requires admin privileges. + */ + boolean requireAdmin() default false; + + /** + * Custom permission required for this endpoint. + */ + String permission() default ""; +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties new file mode 100644 index 000000000..c4d831b79 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties @@ -0,0 +1,30 @@ +# HTTP Header Size Configuration for JWT Tokens + +# Server Configuration (Tomcat) +server.tomcat.max-http-header-size=32KB +server.tomcat.max-http-post-size=10MB +server.max-http-header-size=32KB + +# Alternative: Jetty Configuration +server.jetty.max-http-header-size=32KB +server.jetty.max-http-post-size=10MB + +# Undertow Configuration +server.undertow.max-header-size=32KB +server.undertow.max-headers=200 + +# Spring Boot HTTP configurations +server.max-http-request-header-size=32KB +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB + +# Additional JWT configurations +basyx.jwt.max-token-size=8192 +basyx.jwt.allow-cookie-auth=true +basyx.jwt.allow-query-param-auth=true +basyx.jwt.cookie-name=jwt-token +basyx.jwt.query-param-name=token + +# Logging for debugging +logging.level.org.springframework.web=DEBUG +logging.level.org.apache.tomcat=INFO diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties new file mode 100644 index 000000000..d4d66ca26 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties @@ -0,0 +1,21 @@ +# Example JWT validation configuration using OpenID Connect well-known endpoints + +# Keycloak example (adjust to your Keycloak instance) +# basyx.jwt.wellknown.url=http://localhost:8080/realms/BaSyx/.well-known/openid_configuration +# basyx.jwt.issuer=http://localhost:8080/realms/BaSyx + +# Auth0 example +# basyx.jwt.wellknown.url=https://YOUR_DOMAIN.auth0.com/.well-known/openid_configuration +# basyx.jwt.issuer=https://YOUR_DOMAIN.auth0.com/ + +# Google example +# basyx.jwt.wellknown.url=https://accounts.google.com/.well-known/openid_configuration +# basyx.jwt.issuer=https://accounts.google.com + +# Microsoft Azure AD example +# basyx.jwt.wellknown.url=https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0/.well-known/openid_configuration +# basyx.jwt.issuer=https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0 + +# For testing with a local Keycloak (default BaSyx setup) +basyx.jwt.wellknown.url=http://localhost:8080/realms/BaSyx/.well-known/openid_configuration +basyx.jwt.issuer=http://localhost:8080/realms/BaSyx diff --git a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties index 4b40a24a4..fab0840ce 100644 --- a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties +++ b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties @@ -3,6 +3,9 @@ server.port=8081 spring.application.name=Submodel Repository basyx.smrepo.name = sm-repo basyx.backend = InMemory +basyx.jwt.wellknown.url=https://www.admin-shell-io.com/50001/.well-known/openid-configuration +basyx.jwt.issuer=https://127.0.0.1:50001 +server.max-http-request-header-size=128KB # basyx.submodelrepository.feature.registryintegration=http://localhost:8060 # basyx.externalurl=http://localhost:8081 From 1b375e39702f7b31239dee413e7f90454ef1dea4 Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 23 Jun 2025 11:02:08 +0200 Subject: [PATCH 12/52] Revert "Makes QueryResponse explicit" This reverts commit f8e0e5d8d04b53a5d17dea17110f52e8084637ef. --- .../SearchAasRegistryApiHTTPController.java | 4 +- .../search/SearchAasRegistryHTTPApi.java | 3 +- .../SearchAasRepositoryApiHTTPController.java | 9 +- .../search/SearchAasRepositoryHTTPApi.java | 3 +- .../basyx/querycore/query/QueryResponse.java | 6 +- .../basyx/querycore/query/QueryResult.java | 6 +- .../query/executor/ESQueryExecutor.java | 6 +- .../basyx.submodelrepository-http/pom.xml | 6 - .../http/SecuredSubmodelController.java | 225 --------- .../http/jwt/HEADER_SIZE_SOLUTION.md | 179 ------- .../http/jwt/HackedJwtValidator.java | 454 ------------------ .../submodelrepository/http/jwt/README.md | 264 ---------- .../http/jwt/RequireJWT.java | 64 --- .../jwt-header-size-config.properties | 30 -- .../main/resources/jwt-oidc-config.properties | 21 - .../src/main/resources/application.properties | 3 - 16 files changed, 17 insertions(+), 1266 deletions(-) delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index fe32cd057..4fa32002d 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -63,7 +63,7 @@ public SearchAasRegistryApiHTTPController(ObjectMapper objectMapper, Elasticsear this.objectMapper = objectMapper; } - public ResponseEntity> queryAssetAdministrationShellDescriptors(Integer limit, String cursor, AASQuery query) { + public ResponseEntity queryAssetAdministrationShellDescriptors(Integer limit, String cursor, AASQuery query) { ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); SearchRequest searchRequest = builder.buildSearchRequest(query, SearchAasRegistryStorage.ES_INDEX); try { @@ -75,7 +75,7 @@ public ResponseEntity> queryAs QueryPaging queryPaging = new QueryPaging("not implemented", "AssetAdministrationShellDescriptors"); QueryResult queryResult = new QueryResult(descriptors); - QueryResponse queryResponse = new QueryResponse<>(queryPaging, queryResult); + QueryResponse queryResponse = new QueryResponse(queryPaging, queryResult); return ResponseEntity.ok(queryResponse); } catch (IOException e) { log.error("Error executing search request", e); diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java index 916217b68..a6a0ad082 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java @@ -40,7 +40,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; -import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; import org.springframework.http.ResponseEntity; @@ -74,7 +73,7 @@ public interface SearchAasRegistryHTTPApi { produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) - ResponseEntity> queryAssetAdministrationShellDescriptors(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" + ResponseEntity queryAssetAdministrationShellDescriptors(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" )) @Valid @RequestParam(value = "limit", required = false) Integer limit , @Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) String cursor , @Parameter(in = ParameterIn.DEFAULT, description = "", schema=@Schema()) @Valid @RequestBody AASQuery body diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 248dac71d..744c78def 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -31,7 +31,6 @@ import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; @@ -66,16 +65,16 @@ public SearchAasRepositoryApiHTTPController(ElasticsearchClient client) { } @Override - public ResponseEntity> queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { - QueryResponse queryResponse = null; + public ResponseEntity queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { + QueryResponse queryResponse = null; try { - ESQueryExecutor executor = new ESQueryExecutor<>(client, "aas-index", "AssetAdministrationShell"); + ESQueryExecutor executor = new ESQueryExecutor(client, "aas-index", "AssetAdministrationShell"); queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); } catch (IOException e) { throw new RuntimeException(e); } - return new ResponseEntity>(queryResponse, HttpStatus.OK); + return new ResponseEntity(queryResponse, HttpStatus.OK); } } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java index 340aa3b71..1f9febdd9 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -33,7 +33,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.Result; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; @@ -71,7 +70,7 @@ public interface SearchAasRepositoryHTTPApi { consumes = { "application/json" }, method = RequestMethod.POST ) - ResponseEntity> queryAssetAdministrationShells( + ResponseEntity queryAssetAdministrationShells( @Parameter( description = "Query object", required = true, diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java index 4ab792229..67c70427d 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java @@ -1,11 +1,11 @@ package org.eclipse.digitaltwin.basyx.querycore.query; -public class QueryResponse { +public class QueryResponse { public QueryPaging paging_metadata; - public QueryResult result; + public QueryResult result; - public QueryResponse(QueryPaging paging_metadata, QueryResult result) { + public QueryResponse(QueryPaging paging_metadata, QueryResult result) { this.paging_metadata = paging_metadata; this.result = result; } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java index db7c3a976..0a619849a 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java @@ -2,10 +2,10 @@ import java.util.List; -public class QueryResult { - public List result; +public class QueryResult { + public List result; - public QueryResult(List result) { + public QueryResult(List result) { this.result = result; } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index 14b446f37..f2988c3b8 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -18,7 +18,7 @@ import java.util.*; import java.util.stream.Collectors; -public class ESQueryExecutor { +public class ESQueryExecutor { private final String indexName; private final String modelName; @@ -31,7 +31,7 @@ public ESQueryExecutor(ElasticsearchClient client, String indexName, String mode this.client = client; } - public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); SearchRequest baseSearchRequest = builder.buildSearchRequest(query, indexName); @@ -57,7 +57,7 @@ public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit String nextCursor = getNextCursor(hasMore, pageHits); - QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); + QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); return queryResponse; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml index a7fbadf96..9f58e1ae5 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml @@ -97,11 +97,5 @@ tests test - - javax.annotation - javax.annotation-api - 1.3.2 - compile - diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java deleted file mode 100644 index c89fe8f11..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SecuredSubmodelController.java +++ /dev/null @@ -1,225 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository.http; - -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt.HackedJwtValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; - -/** - * Secured endpoints demonstrating JWT signature validation. - * This controller shows how to use the HackedJwtValidator in practice. - * - * @author GitHub Copilot - */ -@RestController -@RequestMapping("/api/v3.0/submodel-repository/secured") -public class SecuredSubmodelController { - - private static final Logger logger = LoggerFactory.getLogger(SecuredSubmodelController.class); - - private final SubmodelRepository repository; - private final HackedJwtValidator jwtValidator; - private final HttpServletRequest request; - - @Autowired - public SecuredSubmodelController(SubmodelRepository repository, - HackedJwtValidator jwtValidator, - HttpServletRequest request) { - this.repository = repository; - this.jwtValidator = jwtValidator; - this.request = request; - } - - /** - * Secured endpoint to get a submodel by ID with JWT validation. - * This demonstrates the usage pattern with header size handling. - * - * Supports multiple token sources: - * 1. Authorization: Bearer - * 2. Cookie: jwt-token= - * 3. Query parameter: ?token= (if enabled) - * - * Example usage: - * GET /api/v3.0/submodel-repository/secured/submodels/{submodelIdentifier} - * Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... - * - * Or for large tokens: - * GET /api/v3.0/submodel-repository/secured/submodels/{submodelIdentifier} - * Cookie: jwt-token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... - */ - @GetMapping("/submodels/{submodelIdentifier}") - public ResponseEntity getSecuredSubmodel( - @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier) { - - // 1. Extract JWT token from various sources (handles large tokens) - String token = jwtValidator.extractTokenFromRequest(request); - - if (token == null) { - logger.warn("No JWT token provided in any supported location"); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "JWT token required", - "message", "Provide token via Authorization header, Cookie, or query parameter", - "supportedMethods", getSupportedTokenMethods())); - } - - // 2. Validate JWT signature - HackedJwtValidator.JwtValidationResult validation = jwtValidator.validateJwtSignature(token); - - if (!validation.isValid()) { - logger.warn("JWT validation failed: {}", validation.getErrorMessage()); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "Invalid JWT token", - "message", validation.getErrorMessage())); - } - - // 3. JWT is valid, proceed with business logic - logger.info("JWT validated successfully for user: {} from issuer: {}", - validation.getSubject(), validation.getIssuer()); - - try { - Submodel submodel = repository.getSubmodel(submodelIdentifier.getIdentifier()); - - // Optional: Add JWT info to response headers for debugging - return ResponseEntity.ok() - .header("X-JWT-Subject", validation.getSubject()) - .header("X-JWT-Issuer", validation.getIssuer()) - .body(submodel); - - } catch (Exception e) { - logger.error("Error retrieving submodel: {}", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(Map.of("error", "Submodel not found", - "message", e.getMessage())); - } - } - - // Helper methods - - private HackedJwtValidator.JwtValidationResult validateJwtFromRequest() { - String authHeader = request.getHeader("Authorization"); - String token = jwtValidator.extractTokenFromHeader(authHeader); - - if (token == null) { - return HackedJwtValidator.JwtValidationResult.invalid("No JWT token provided"); - } - - return jwtValidator.validateJwtSignature(token); - } - - private ResponseEntity createUnauthorizedResponse(String message) { - logger.warn("Unauthorized access attempt: {}", message); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "Authentication failed", - "message", message)); - } - - private boolean hasWritePermission(String subject) { - // Simplified permission check - in reality you'd check against - // a permission system, database, or JWT roles - return subject != null && !subject.isEmpty(); - } - - private boolean hasAdminRole(String subject) { - // Simplified admin check - in reality you'd check JWT roles/claims - // or external permission system - return subject != null && subject.contains("admin"); - } - - private Map getSupportedTokenMethods() { - Map methods = new HashMap<>(); - methods.put("authorizationHeader", true); - methods.put("cookie", jwtValidator != null); // Simplified check - methods.put("queryParameter", false); // Set based on configuration - methods.put("cookieName", "jwt-token"); - methods.put("queryParamName", "token"); - methods.put("maxTokenSize", 8192); - return methods; - } - - /** - * Endpoint to test large token handling and provide troubleshooting info. - * GET /api/v3.0/submodel-repository/secured/token-size-test - */ - @GetMapping("/token-size-test") - public ResponseEntity tokenSizeTest() { - Map response = new HashMap<>(); - - // Check Authorization header - String authHeader = request.getHeader("Authorization"); - if (authHeader != null) { - response.put("authHeaderLength", authHeader.length()); - response.put("authHeaderPreview", authHeader.length() > 50 ? - authHeader.substring(0, 50) + "..." : authHeader); - } else { - response.put("authHeader", "not present"); - } - - // Check for token via alternative methods - String token = jwtValidator.extractTokenFromRequest(request); - if (token != null) { - response.put("tokenFound", true); - response.put("tokenLength", token.length()); - response.put("tokenSource", getTokenSource(request)); - - // Basic token structure check - String[] parts = token.split("\\."); - response.put("tokenParts", parts.length); - if (parts.length == 3) { - response.put("headerLength", parts[0].length()); - response.put("payloadLength", parts[1].length()); - response.put("signatureLength", parts[2].length()); - } - } else { - response.put("tokenFound", false); - } - - response.put("supportedMethods", getSupportedTokenMethods()); - response.put("maxHeaderSize", "32KB (configured)"); - response.put("recommendedTokenSize", "< 8KB"); - - return ResponseEntity.ok(response); - } - - private String getTokenSource(jakarta.servlet.http.HttpServletRequest request) { - if (jwtValidator.extractTokenFromHeader(request.getHeader("Authorization")) != null) { - return "Authorization header"; - } - // Add other checks for cookie and query param - return "unknown"; - } -} diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md deleted file mode 100644 index 7624d0e99..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HEADER_SIZE_SOLUTION.md +++ /dev/null @@ -1,179 +0,0 @@ -# Lösung für "Request Header too large" bei JWT Tokens - -## 🚨 Problem -JWT Tokens können sehr groß werden (> 8KB), besonders bei: -- Vielen Rollen/Claims -- Verschachtelten Gruppenstrukturen -- Zusätzlichen Custom Claims -- Keycloak mit vielen Realm-Rollen - -Standard HTTP Header Limit: **8KB** - -## ✅ Lösungsansätze - -### 1. Server-Konfiguration erweitern - -#### **application.properties:** -```properties -# Tomcat HTTP Header Size (Standard) -server.tomcat.max-http-header-size=32KB -server.max-http-header-size=32KB - -# Jetty (Alternative) -server.jetty.max-http-header-size=32KB - -# Undertow (Alternative) -server.undertow.max-header-size=32KB -server.undertow.max-headers=200 - -# JWT spezifische Limits -basyx.jwt.max-token-size=8192 -basyx.jwt.allow-cookie-auth=true -basyx.jwt.allow-query-param-auth=false -``` - -### 2. Alternative Token-Übertragung - -#### **A) Cookie-basiert (empfohlen für große Tokens):** -```javascript -// Frontend: Token in Cookie setzen -document.cookie = `jwt-token=${jwtToken}; Secure; HttpOnly; SameSite=Strict`; - -// Oder per JavaScript fetch -fetch('/api/v3.0/submodel-repository/secured/submodels/123', { - credentials: 'include', // Cookies mitschicken - headers: { - 'Content-Type': 'application/json' - } -}); -``` - -#### **B) Query Parameter (nur für Development):** -```bash -# Nur wenn aktiviert: basyx.jwt.allow-query-param-auth=true -curl "http://localhost:8090/api/v3.0/submodel-repository/secured/submodels/123?token=eyJ..." -``` - -### 3. JWT Optimierung - -#### **Keycloak Token optimieren:** -```json -// Keycloak Client Settings -{ - "mappers": [ - { - "name": "remove-unnecessary-claims", - "protocol": "openid-connect", - "protocolMapper": "oidc-hardcoded-claim-mapper", - "config": { - "claim.value": "", - "userinfo.token.claim": "false", - "id.token.claim": "false", - "access.token.claim": "true" - } - } - ] -} -``` - -#### **Minimale Claims verwenden:** -- Nur notwendige Rollen einbeziehen -- Custom Claims reduzieren -- Audience (`aud`) spezifisch setzen -- Kurze Issuer URLs verwenden - -## 🔧 Praktische Implementierung - -### **1. Multi-Source Token Extraktion:** -```java -// Automatische Fallback-Mechanismen -String token = jwtValidator.extractTokenFromRequest(request); -// Versucht: Header → Cookie → Query Parameter -``` - -### **2. Debug Endpoint:** -```bash -# Token-Größe testen -curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/token-size-test" \ - -H "Authorization: Bearer $TOKEN" -``` - -Response: -```json -{ - "tokenFound": true, - "tokenLength": 2847, - "tokenSource": "Authorization header", - "authHeaderLength": 2854, - "tokenParts": 3, - "headerLength": 156, - "payloadLength": 1234, - "signatureLength": 342, - "maxHeaderSize": "32KB (configured)", - "recommendedTokenSize": "< 8KB" -} -``` - -### **3. Client-seitige Lösung:** -```javascript -// Automatische Token-Größen-Behandlung -function makeAuthenticatedRequest(url, token) { - const tokenSize = token.length; - - if (tokenSize > 6000) { // Nahe Header-Limit - // Cookie verwenden - document.cookie = `jwt-token=${token}; Secure; SameSite=Strict`; - return fetch(url, { credentials: 'include' }); - } else { - // Standard Authorization Header - return fetch(url, { - headers: { 'Authorization': `Bearer ${token}` } - }); - } -} -``` - -## 🛠️ Troubleshooting - -### **Problem diagnostizieren:** -```bash -# 1. Token-Größe prüfen -echo $TOKEN | wc -c - -# 2. Header-Größe testen -curl -v -H "Authorization: Bearer $TOKEN" http://localhost:8090/health - -# 3. Debug-Endpoint nutzen -curl "http://localhost:8090/api/v3.0/submodel-repository/secured/token-size-test" \ - -H "Authorization: Bearer $TOKEN" -``` - -### **Häufige Fehler:** -- **414 URI Too Long**: Query Parameter zu groß -- **431 Request Header Fields Too Large**: Authorization Header zu groß -- **413 Payload Too Large**: Body zu groß (nicht JWT-related) - -### **Server Logs:** -```properties -# Detaillierte HTTP Logs -logging.level.org.springframework.web=DEBUG -logging.level.org.apache.tomcat.util.http=DEBUG -logging.level.org.eclipse.digitaltwin.basyx.jwt=DEBUG -``` - -## 🎯 Best Practices - -1. **Token-Größe begrenzen**: < 8KB für Header-Kompatibilität -2. **Cookie für große Tokens**: Automatischer Fallback -3. **Claims minimieren**: Nur notwendige Informationen -4. **Header-Limits erhöhen**: Server-seitig konfigurieren -5. **Monitoring**: Token-Größen überwachen -6. **Caching**: Public Keys cachen für Performance - -## 🔐 Sicherheitshinweise - -- **Cookies**: `Secure`, `HttpOnly`, `SameSite=Strict` verwenden -- **Query Parameters**: Nur in Development, niemals in Production -- **HTTPS**: Immer verschlüsselte Verbindungen -- **Token Rotation**: Kurze Lebensdauer für große Tokens -- **Logging**: Keine Tokens in Logs schreiben diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java deleted file mode 100644 index e68be0c85..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/HackedJwtValidator.java +++ /dev/null @@ -1,454 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.io.IOException; -import java.math.BigInteger; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.spec.RSAPublicKeySpec; -import java.time.Duration; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * A JWT validator that fetches public keys from OpenID Connect well-known configuration. - * This is a "hacked" implementation for demonstration of JWT signature verification - * using real OIDC endpoints like Keycloak, Auth0, Google, etc. - * - * @author GitHub Copilot - */ -@Component -public class HackedJwtValidator { - - private static final Logger logger = LoggerFactory.getLogger(HackedJwtValidator.class); - - @Value("${basyx.jwt.wellknown.url:}") - private String wellKnownUrl; - - @Value("${basyx.jwt.issuer:}") - private String expectedIssuer; - - @Value("${basyx.jwt.allow-cookie-auth:true}") - private boolean allowCookieAuth; - - @Value("${basyx.jwt.allow-query-param-auth:false}") - private boolean allowQueryParamAuth; - - @Value("${basyx.jwt.cookie-name:jwt-token}") - private String cookieName; - - @Value("${basyx.jwt.query-param-name:token}") - private String queryParamName; - - @Value("${basyx.jwt.max-token-size:8192}") - private int maxTokenSize; - - private final ObjectMapper objectMapper = new ObjectMapper(); - private final HttpClient httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(10)) - .build(); - - // Cache for public keys - private final Map publicKeyCache = new ConcurrentHashMap<>(); - private String jwksUri; - - @PostConstruct - public void init() { - if (wellKnownUrl != null && !wellKnownUrl.trim().isEmpty()) { - fetchWellKnownConfiguration(); - } else { - logger.info("No well-known URL configured, JWT validation will be limited"); - } - } - - /** - * Validates JWT token signature using OpenID Connect JWKS. - * - * @param token JWT token as string (without "Bearer " prefix) - * @return JwtValidationResult containing validation outcome and basic info - */ - public JwtValidationResult validateJwtSignature(String token) { - if (token == null || token.trim().isEmpty()) { - logger.warn("JWT token is null or empty"); - return JwtValidationResult.invalid("Token is missing"); - } - - try { - // Parse JWT header to get kid (key ID) - String[] parts = token.split("\\."); - if (parts.length != 3) { - logger.error("JWT token does not have 3 parts"); - return JwtValidationResult.invalid("Malformed token"); - } - - // Decode header - String headerJson = new String(Base64.getUrlDecoder().decode(parts[0])); - JsonNode header = objectMapper.readTree(headerJson); - String kid = header.path("kid").asText(); - String alg = header.path("alg").asText(); - - logger.info("JWT header - kid: {}, alg: {}", kid, alg); - - // Decode payload for basic validation - String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1])); - JsonNode payload = objectMapper.readTree(payloadJson); - - // Basic validations - long exp = payload.path("exp").asLong(0); - String issuer = payload.path("iss").asText(); - String subject = payload.path("sub").asText(); - - logger.info("JWT payload - iss: {}, sub: {}, exp: {}", issuer, subject, exp); - - // Check expiration - if (exp > 0 && exp < System.currentTimeMillis() / 1000) { - logger.warn("JWT token has expired"); - return JwtValidationResult.invalid("Token expired"); - } - - // Check issuer if configured - if (expectedIssuer != null && !expectedIssuer.trim().isEmpty() && !expectedIssuer.equals(issuer)) { - logger.warn("JWT issuer mismatch. Expected: {}, Got: {}", expectedIssuer, issuer); - return JwtValidationResult.invalid("Invalid issuer"); - } - - // Validate signature if we have JWKS - if (jwksUri != null && kid != null && !kid.isEmpty()) { - PublicKey publicKey = getPublicKey(kid); - if (publicKey != null && validateSignature(token, publicKey, alg)) { - logger.info("JWT signature validated successfully for subject: {}", subject); - return JwtValidationResult.valid(subject, issuer, exp); - } else { - logger.error("JWT signature validation failed"); - return JwtValidationResult.invalid("Invalid signature"); - } - } - - // If no JWKS available, do basic validation only - logger.warn("JWKS not available, performing basic validation only"); - return JwtValidationResult.valid(subject, issuer, exp); - - } catch (Exception e) { - logger.error("JWT validation failed: {}", e.getMessage(), e); - return JwtValidationResult.invalid("Validation failed: " + e.getMessage()); - } - } - - /** - * Fetches the well-known OpenID configuration to get JWKS URI. - */ - private void fetchWellKnownConfiguration() { - try { - logger.info("Fetching well-known configuration from: {}", wellKnownUrl); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(wellKnownUrl)) - .timeout(Duration.ofSeconds(10)) - .GET() - .build(); - - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() == 200) { - JsonNode config = objectMapper.readTree(response.body()); - jwksUri = config.path("jwks_uri").asText(); - - if (expectedIssuer == null || expectedIssuer.trim().isEmpty()) { - expectedIssuer = config.path("issuer").asText(); - } - - logger.info("Successfully fetched well-known config. JWKS URI: {}, Issuer: {}", jwksUri, expectedIssuer); - } else { - logger.error("Failed to fetch well-known configuration. Status: {}", response.statusCode()); - } - - } catch (Exception e) { - logger.error("Error fetching well-known configuration: {}", e.getMessage(), e); - } - } - - /** - * Gets public key for the given key ID from JWKS. - */ - private PublicKey getPublicKey(String kid) { - // Check cache first - PublicKey cachedKey = publicKeyCache.get(kid); - if (cachedKey != null) { - return cachedKey; - } - - // Fetch from JWKS - try { - logger.info("Fetching public key for kid: {} from JWKS: {}", kid, jwksUri); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(jwksUri)) - .timeout(Duration.ofSeconds(10)) - .GET() - .build(); - - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() == 200) { - JsonNode jwks = objectMapper.readTree(response.body()); - JsonNode keys = jwks.path("keys"); - - for (JsonNode key : keys) { - String keyId = key.path("kid").asText(); - if (kid.equals(keyId)) { - PublicKey publicKey = buildPublicKey(key); - if (publicKey != null) { - publicKeyCache.put(kid, publicKey); - logger.info("Successfully cached public key for kid: {}", kid); - return publicKey; - } - } - } - - logger.warn("Public key not found for kid: {}", kid); - } else { - logger.error("Failed to fetch JWKS. Status: {}", response.statusCode()); - } - - } catch (Exception e) { - logger.error("Error fetching public key: {}", e.getMessage(), e); - } - - return null; - } - - /** - * Builds RSA public key from JWK. - */ - private PublicKey buildPublicKey(JsonNode jwk) { - try { - String kty = jwk.path("kty").asText(); - if (!"RSA".equals(kty)) { - logger.warn("Unsupported key type: {}", kty); - return null; - } - - String nStr = jwk.path("n").asText(); - String eStr = jwk.path("e").asText(); - - byte[] nBytes = Base64.getUrlDecoder().decode(nStr); - byte[] eBytes = Base64.getUrlDecoder().decode(eStr); - - BigInteger modulus = new BigInteger(1, nBytes); - BigInteger exponent = new BigInteger(1, eBytes); - - RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); - KeyFactory factory = KeyFactory.getInstance("RSA"); - - return factory.generatePublic(spec); - - } catch (Exception e) { - logger.error("Error building public key: {}", e.getMessage(), e); - return null; - } - } - - /** - * Validates JWT signature using the public key. - * This is a simplified implementation - in production use a proper JWT library. - */ - private boolean validateSignature(String token, PublicKey publicKey, String algorithm) { - try { - // This is a simplified signature validation - // In a real implementation, you would use a proper JWT library like jjwt - // that handles all the crypto details correctly - - String[] parts = token.split("\\."); - String headerAndPayload = parts[0] + "." + parts[1]; - byte[] signature = Base64.getUrlDecoder().decode(parts[2]); - - // For demonstration purposes, we'll assume signature is valid if we got this far - // Real implementation would use java.security.Signature with the public key - logger.info("Signature validation attempted for algorithm: {}", algorithm); - return true; // Simplified for demo - - } catch (Exception e) { - logger.error("Signature validation error: {}", e.getMessage(), e); - return false; - } - } - - /** - * Extracts JWT token from Authorization header. - */ - public String extractTokenFromHeader(String authHeader) { - if (authHeader != null && authHeader.startsWith("Bearer ")) { - String token = authHeader.substring(7); - /*if (token.length() > maxTokenSize) { - logger.warn("JWT token exceeds maximum size: {} > {}", token.length(), maxTokenSize); - return null; - }*/ - return token; - } - return null; - } - - /** - * Extracts JWT token from various sources (Header, Cookie, Query Parameter). - * This method provides fallback options when headers are too large. - * - * @param request HttpServletRequest to extract token from - * @return JWT token string or null if not found - */ - public String extractTokenFromRequest(jakarta.servlet.http.HttpServletRequest request) { - // 1. Try Authorization header first (standard method) - String authHeader = request.getHeader("Authorization"); - String token = extractTokenFromHeader(authHeader); - - if (token != null) { - logger.debug("Token extracted from Authorization header"); - return token; - } - - // 2. Try Cookie if enabled (for large tokens) - if (allowCookieAuth) { - token = extractTokenFromCookie(request); - if (token != null) { - logger.debug("Token extracted from cookie: {}", cookieName); - return token; - } - } - - // 3. Try query parameter if enabled (least secure, use with caution) - if (allowQueryParamAuth) { - token = extractTokenFromQueryParam(request); - if (token != null) { - logger.debug("Token extracted from query parameter: {}", queryParamName); - return token; - } - } - - logger.debug("No JWT token found in request"); - return null; - } - - /** - * Extracts JWT token from HTTP Cookie. - * Useful when Authorization header is too large. - */ - private String extractTokenFromCookie(jakarta.servlet.http.HttpServletRequest request) { - if (request.getCookies() != null) { - for (jakarta.servlet.http.Cookie cookie : request.getCookies()) { - if (cookieName.equals(cookie.getName())) { - String token = cookie.getValue(); - if (token != null && !token.trim().isEmpty()) { - if (token.length() > maxTokenSize) { - logger.warn("JWT token in cookie exceeds maximum size: {} > {}", token.length(), maxTokenSize); - return null; - } - return token; - } - } - } - } - return null; - } - - /** - * Extracts JWT token from query parameter. - * WARNING: This is less secure as tokens appear in logs and browser history. - * Only enable for development or when absolutely necessary. - */ - private String extractTokenFromQueryParam(jakarta.servlet.http.HttpServletRequest request) { - String token = request.getParameter(queryParamName); - if (token != null && !token.trim().isEmpty()) { - if (token.length() > maxTokenSize) { - logger.warn("JWT token in query parameter exceeds maximum size: {} > {}", token.length(), maxTokenSize); - return null; - } - logger.warn("JWT token extracted from query parameter - this is less secure!"); - return token; - } - return null; - } - - /** - * Result of JWT validation - */ - public static class JwtValidationResult { - private final boolean valid; - private final String errorMessage; - private final String subject; - private final String issuer; - private final long expiration; - - private JwtValidationResult(boolean valid, String errorMessage, String subject, String issuer, long expiration) { - this.valid = valid; - this.errorMessage = errorMessage; - this.subject = subject; - this.issuer = issuer; - this.expiration = expiration; - } - - public static JwtValidationResult valid(String subject, String issuer, long expiration) { - return new JwtValidationResult(true, null, subject, issuer, expiration); - } - - public static JwtValidationResult invalid(String errorMessage) { - return new JwtValidationResult(false, errorMessage, null, null, 0); - } - - public boolean isValid() { - return valid; - } - - public String getErrorMessage() { - return errorMessage; - } - - public String getSubject() { - return subject; - } - - public String getIssuer() { - return issuer; - } - - public long getExpiration() { - return expiration; - } - } -} diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md deleted file mode 100644 index 33a6bd84d..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/README.md +++ /dev/null @@ -1,264 +0,0 @@ -# JWT Validator - Nutzungsanleitung - -## 🚀 Schnellstart - -### 1. Konfiguration - -Füge in deine `application.properties` hinzu: - -```properties -# Keycloak (Standard BaSyx Setup) -basyx.jwt.wellknown.url=http://localhost:8080/realms/BaSyx/.well-known/openid_configuration -basyx.jwt.issuer=http://localhost:8080/realms/BaSyx - -# Oder für Auth0 -# basyx.jwt.wellknown.url=https://YOUR_DOMAIN.auth0.com/.well-known/openid_configuration - -# Oder für Google -# basyx.jwt.wellknown.url=https://accounts.google.com/.well-known/openid_configuration -``` - -### 2. Grundlegende Verwendung im Controller - -```java -@RestController -public class MeinController { - - @Autowired - private HackedJwtValidator jwtValidator; - - @Autowired - private HttpServletRequest request; - - @GetMapping("/secured-endpoint") - public ResponseEntity securedEndpoint() { - - // 1. JWT Token aus Header extrahieren - String authHeader = request.getHeader("Authorization"); - String token = jwtValidator.extractTokenFromHeader(authHeader); - - if (token == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body("JWT token required"); - } - - // 2. JWT Signatur validieren - HackedJwtValidator.JwtValidationResult validation = - jwtValidator.validateJwtSignature(token); - - if (!validation.isValid()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body("Invalid JWT: " + validation.getErrorMessage()); - } - - // 3. Geschäftslogik ausführen - String user = validation.getSubject(); - String issuer = validation.getIssuer(); - - return ResponseEntity.ok("Hello " + user + " from " + issuer); - } -} -``` - -## 📡 API Endpunkte Testen - -### Mit curl: - -```bash -# 1. JWT Token von Keycloak holen -TOKEN=$(curl -s -X POST "http://localhost:8080/realms/BaSyx/protocol/openid-connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "username=admin" \ - -d "password=admin" \ - -d "grant_type=password" \ - -d "client_id=basyx-client" \ - | jq -r '.access_token') - -# 2. Geschützten Endpoint aufrufen -curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/submodels/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20=" \ - -H "Authorization: Bearer $TOKEN" - -# 3. JWT Info anzeigen (Debug) -curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/jwt-info" \ - -H "Authorization: Bearer $TOKEN" -``` - -### Mit Postman: - -1. **Authorization Tab**: - - Type: "Bearer Token" - - Token: Dein JWT Token - -2. **Headers**: - ``` - Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... - ``` - -## 🛠️ Erweiterte Verwendung - -### Hilfsmethode für wiederverwendbare Validierung: - -```java -@Component -public class JwtSecurityHelper { - - @Autowired - private HackedJwtValidator jwtValidator; - - @Autowired - private HttpServletRequest request; - - public JwtValidationResult validateCurrentRequest() { - String authHeader = request.getHeader("Authorization"); - String token = jwtValidator.extractTokenFromHeader(authHeader); - - if (token == null) { - return JwtValidationResult.invalid("No JWT token provided"); - } - - return jwtValidator.validateJwtSignature(token); - } - - public String getCurrentUser() { - JwtValidationResult validation = validateCurrentRequest(); - return validation.isValid() ? validation.getSubject() : null; - } -} -``` - -### Verwendung in Service-Klassen: - -```java -@Service -public class SecuredSubmodelService { - - @Autowired - private JwtSecurityHelper securityHelper; - - @Autowired - private SubmodelRepository repository; - - public Submodel getSubmodelSecurely(String id) { - JwtValidationResult validation = securityHelper.validateCurrentRequest(); - - if (!validation.isValid()) { - throw new SecurityException("Invalid JWT: " + validation.getErrorMessage()); - } - - // Log who accessed what - logger.info("User {} accessing submodel {}", validation.getSubject(), id); - - return repository.getSubmodel(id); - } -} -``` - -## 🎯 Praktische Beispiele - -### 1. Einfacher GET Endpoint: - -```java -@GetMapping("/my-submodels") -public ResponseEntity getMySubmodels() { - // JWT validieren - JwtValidationResult validation = validateJwtFromRequest(); - if (!validation.isValid()) { - return unauthorized(validation.getErrorMessage()); - } - - // Nur Submodels des aktuellen Users zurückgeben - String userId = validation.getSubject(); - List userSubmodels = repository.getSubmodelsByOwner(userId); - - return ResponseEntity.ok(userSubmodels); -} -``` - -### 2. POST mit Berechtigungsprüfung: - -```java -@PostMapping("/submodels") -public ResponseEntity createSubmodel(@RequestBody Submodel submodel) { - // JWT validieren - JwtValidationResult validation = validateJwtFromRequest(); - if (!validation.isValid()) { - return unauthorized(validation.getErrorMessage()); - } - - // Prüfen ob User Schreibrechte hat - if (!hasCreatePermission(validation.getSubject())) { - return forbidden("Insufficient permissions"); - } - - // Owner setzen - submodel.setCreatedBy(validation.getSubject()); - repository.createSubmodel(submodel); - - return ResponseEntity.status(HttpStatus.CREATED).body(submodel); -} -``` - -### 3. Admin-only Endpoint: - -```java -@DeleteMapping("/admin/submodels/{id}") -public ResponseEntity adminDeleteSubmodel(@PathVariable String id) { - JwtValidationResult validation = validateJwtFromRequest(); - if (!validation.isValid()) { - return unauthorized(validation.getErrorMessage()); - } - - // Admin-Rolle prüfen (vereinfacht) - if (!validation.getSubject().contains("admin")) { - return forbidden("Admin access required"); - } - - repository.deleteSubmodel(id); - return ResponseEntity.noContent().build(); -} -``` - -## 🔍 Debugging & Troubleshooting - -### JWT Info Endpoint nutzen: - -```bash -curl -X GET "http://localhost:8090/api/v3.0/submodel-repository/secured/jwt-info" \ - -H "Authorization: Bearer YOUR_TOKEN" -``` - -Response: -```json -{ - "hasToken": true, - "valid": true, - "subject": "admin", - "issuer": "http://localhost:8080/realms/BaSyx", - "expiration": 1671234567 -} -``` - -### Häufige Probleme: - -1. **"Token is missing"**: Authorization Header fehlt oder falsch formatiert -2. **"Invalid signature"**: Falscher Public Key oder Token manipuliert -3. **"Token expired"**: Token ist abgelaufen -4. **"Invalid issuer"**: Issuer im Token stimmt nicht mit Konfiguration überein - -### Logs aktivieren: - -```properties -logging.level.org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt=DEBUG -``` - -## 🚨 Sicherheitshinweise - -1. **HTTPS verwenden**: Niemals JWT über unverschlüsselte Verbindungen -2. **Token Lebensdauer begrenzen**: Kurze Expiration Times verwenden -3. **Proper Error Handling**: Keine sensitive Informationen in Fehlermeldungen -4. **Rate Limiting**: Implementiere Rate Limiting für Auth-Endpunkte -5. **Input Validation**: Alle Eingaben validieren, auch bei gültigen JWTs - -## 🎪 Vollständiges Beispiel - -Siehe `SecuredSubmodelController.java` für ein komplettes Beispiel mit verschiedenen Endpunkt-Typen und Sicherheitsmustern. diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java deleted file mode 100644 index fe2fb7b34..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/jwt/RequireJWT.java +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.jwt; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark endpoints that require JWT validation. - * This provides a cleaner way to mark secured endpoints. - * - * Usage: - * @RequireJWT - * public ResponseEntity getSecuredSubmodel(...) { - * // Your endpoint logic - * } - * - * @author GitHub Copilot - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface RequireJWT { - - /** - * Required roles for this endpoint (optional). - * If specified, the JWT must contain one of these roles. - */ - String[] roles() default {}; - - /** - * Whether this endpoint requires admin privileges. - */ - boolean requireAdmin() default false; - - /** - * Custom permission required for this endpoint. - */ - String permission() default ""; -} diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties deleted file mode 100644 index c4d831b79..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-header-size-config.properties +++ /dev/null @@ -1,30 +0,0 @@ -# HTTP Header Size Configuration for JWT Tokens - -# Server Configuration (Tomcat) -server.tomcat.max-http-header-size=32KB -server.tomcat.max-http-post-size=10MB -server.max-http-header-size=32KB - -# Alternative: Jetty Configuration -server.jetty.max-http-header-size=32KB -server.jetty.max-http-post-size=10MB - -# Undertow Configuration -server.undertow.max-header-size=32KB -server.undertow.max-headers=200 - -# Spring Boot HTTP configurations -server.max-http-request-header-size=32KB -spring.servlet.multipart.max-file-size=10MB -spring.servlet.multipart.max-request-size=10MB - -# Additional JWT configurations -basyx.jwt.max-token-size=8192 -basyx.jwt.allow-cookie-auth=true -basyx.jwt.allow-query-param-auth=true -basyx.jwt.cookie-name=jwt-token -basyx.jwt.query-param-name=token - -# Logging for debugging -logging.level.org.springframework.web=DEBUG -logging.level.org.apache.tomcat=INFO diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties deleted file mode 100644 index d4d66ca26..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/resources/jwt-oidc-config.properties +++ /dev/null @@ -1,21 +0,0 @@ -# Example JWT validation configuration using OpenID Connect well-known endpoints - -# Keycloak example (adjust to your Keycloak instance) -# basyx.jwt.wellknown.url=http://localhost:8080/realms/BaSyx/.well-known/openid_configuration -# basyx.jwt.issuer=http://localhost:8080/realms/BaSyx - -# Auth0 example -# basyx.jwt.wellknown.url=https://YOUR_DOMAIN.auth0.com/.well-known/openid_configuration -# basyx.jwt.issuer=https://YOUR_DOMAIN.auth0.com/ - -# Google example -# basyx.jwt.wellknown.url=https://accounts.google.com/.well-known/openid_configuration -# basyx.jwt.issuer=https://accounts.google.com - -# Microsoft Azure AD example -# basyx.jwt.wellknown.url=https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0/.well-known/openid_configuration -# basyx.jwt.issuer=https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0 - -# For testing with a local Keycloak (default BaSyx setup) -basyx.jwt.wellknown.url=http://localhost:8080/realms/BaSyx/.well-known/openid_configuration -basyx.jwt.issuer=http://localhost:8080/realms/BaSyx diff --git a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties index fab0840ce..4b40a24a4 100644 --- a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties +++ b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties @@ -3,9 +3,6 @@ server.port=8081 spring.application.name=Submodel Repository basyx.smrepo.name = sm-repo basyx.backend = InMemory -basyx.jwt.wellknown.url=https://www.admin-shell-io.com/50001/.well-known/openid-configuration -basyx.jwt.issuer=https://127.0.0.1:50001 -server.max-http-request-header-size=128KB # basyx.submodelrepository.feature.registryintegration=http://localhost:8060 # basyx.externalurl=http://localhost:8081 From 8935bbf46c28ceef5d46a28db922e22dc9a99422 Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 23 Jun 2025 11:41:08 +0200 Subject: [PATCH 13/52] Makes indizes Configurable --- .../SearchAasRegistryApiHTTPController.java | 32 +- .../SearchAasRegistryConfigurationGuard.java | 10 + .../search/SearchAasRegistryFeature.java | 6 +- .../search/SearchAasRegistryHTTPApi.java | 3 +- .../search/SearchAasRegistryStorage.java | 13 +- .../pom.xml | 5 + .../pom.xml | 5 + .../basyx.aasregistry-service/pom.xml | 430 +++++++++--------- .../README.md | 4 +- .../feature/search/SearchAasRepository.java | 11 +- .../SearchAasRepositoryApiHTTPController.java | 6 +- ...SearchAasRepositoryConfigurationGuard.java | 10 + .../search/SearchAasRepositoryFactory.java | 6 +- .../search/SearchAasRepositoryFeature.java | 8 +- .../src/main/resources/application.properties | 1 + .../converter/QueryConverterExample.java | 311 ------------- .../query/converter/ValueConverter.java | 24 +- 17 files changed, 314 insertions(+), 571 deletions(-) delete mode 100644 basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index 4fa32002d..bc46b7b46 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -32,14 +32,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; import org.eclipse.digitaltwin.basyx.querycore.query.QueryResult; import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; +import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; @@ -55,32 +58,25 @@ public class SearchAasRegistryApiHTTPController implements SearchAasRegistryHTTP private static final Logger log = LoggerFactory.getLogger(SearchAasRegistryApiHTTPController.class); private final ElasticsearchClient esClient; - private final ObjectMapper objectMapper; + + @Value("${" + SearchAasRegistryFeature.FEATURENAME + ".indexname:" + SearchAasRegistryFeature.DEFAULT_INDEX + "}") + private String indexName; @Autowired - public SearchAasRegistryApiHTTPController(ObjectMapper objectMapper, ElasticsearchClient esClient) { + public SearchAasRegistryApiHTTPController(ElasticsearchClient esClient) { this.esClient = esClient; - this.objectMapper = objectMapper; } - public ResponseEntity queryAssetAdministrationShellDescriptors(Integer limit, String cursor, AASQuery query) { - ElasticSearchRequestBuilder builder = new ElasticSearchRequestBuilder(); - SearchRequest searchRequest = builder.buildSearchRequest(query, SearchAasRegistryStorage.ES_INDEX); + public ResponseEntity queryAssetAdministrationShellDescriptors(Integer limit, Base64UrlEncodedCursor cursor, AASQuery query) { + QueryResponse queryResponse; try { - SearchResponse response = esClient.search(searchRequest, Object.class); - List> hits = response.hits().hits(); - List descriptors = hits.stream() - .map(hit -> objectMapper.convertValue(hit.source(), AssetAdministrationShellDescriptor.class)) - .collect(Collectors.toList()); - - QueryPaging queryPaging = new QueryPaging("not implemented", "AssetAdministrationShellDescriptors"); - QueryResult queryResult = new QueryResult(descriptors); - QueryResponse queryResponse = new QueryResponse(queryPaging, queryResult); - return ResponseEntity.ok(queryResponse); + ESQueryExecutor executor = new ESQueryExecutor(esClient, indexName, "AssetAdministrationShellDescriptor"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); } catch (IOException e) { - log.error("Error executing search request", e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + throw new RuntimeException(e); } + + return new ResponseEntity<>(queryResponse, HttpStatus.OK); } } diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java index 3c25932fc..bf450b07d 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java @@ -49,6 +49,9 @@ public class SearchAasRegistryConfigurationGuard implements InitializingBean { @Value("${spring.elasticsearch.password:#{null}}") private String elasticsearchPassword; + @Value("${" + SearchAasRegistryFeature.FEATURENAME + ".indexname:" + SearchAasRegistryFeature.DEFAULT_INDEX + "}") + private String indexName; + @Override public void afterPropertiesSet() throws Exception { boolean error = false; @@ -75,6 +78,13 @@ public void afterPropertiesSet() throws Exception { logger.info("Elasticsearch Password: " + "***"); } + if (indexName == null || indexName.isEmpty()) { + logger.error("Index name is not configured. Please set the property '" + SearchAasRegistryFeature.FEATURENAME + ".indexname'."); + error = true; + } else { + logger.info("Index Name: " + indexName); + } + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); if(error){ System.exit(1); diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java index 65ad69c4c..92d585649 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryFeature.java @@ -47,6 +47,7 @@ @Order(1) public class SearchAasRegistryFeature implements AasRegistryStorageFeature { public final static String FEATURENAME = "basyx.aasregistry.feature.search"; + public static final String DEFAULT_INDEX = "aas-descr-index"; private final ElasticsearchClient esclient; public SearchAasRegistryFeature(ElasticsearchClient esclient) { @@ -56,9 +57,12 @@ public SearchAasRegistryFeature(ElasticsearchClient esclient) { @Value("#{${" + FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") private boolean enabled; + @Value("${" + FEATURENAME + ".indexname:" + DEFAULT_INDEX + "}") + private String indexName; + @Override public AasRegistryStorage decorate(AasRegistryStorage aasRegistryStorage) { - return new SearchAasRegistryStorage(aasRegistryStorage, esclient); + return new SearchAasRegistryStorage(aasRegistryStorage, esclient, indexName); } @Override diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java index a6a0ad082..a540fc9cc 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java @@ -40,6 +40,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; import org.springframework.http.ResponseEntity; @@ -75,7 +76,7 @@ public interface SearchAasRegistryHTTPApi { method = RequestMethod.POST) ResponseEntity queryAssetAdministrationShellDescriptors(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" )) @Valid @RequestParam(value = "limit", required = false) Integer limit - , @Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) String cursor + , @Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) Base64UrlEncodedCursor cursor , @Parameter(in = ParameterIn.DEFAULT, description = "", schema=@Schema()) @Valid @RequestBody AASQuery body ); } diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java index 280849af7..65b331249 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryStorage.java @@ -48,12 +48,13 @@ public class SearchAasRegistryStorage implements AasRegistryStorage { private final ElasticsearchClient esclient; private final AasRegistryStorage decorated; - protected static final String ES_INDEX = "aas-descr-index"; + private final String indexName; - SearchAasRegistryStorage(AasRegistryStorage decorated, ElasticsearchClient esclient) { + SearchAasRegistryStorage(AasRegistryStorage decorated, ElasticsearchClient esclient, String indexName) { this.decorated = decorated; this.esclient = esclient; + this.indexName = indexName; } @Override @@ -126,7 +127,7 @@ public void insertAasDescriptor(AssetAdministrationShellDescriptor descr) throws private void indexAasDescriptor(AssetAdministrationShellDescriptor descr) { try { esclient.create( - c -> c.index(ES_INDEX) + c -> c.index(indexName) .id(descr.getId()) .document(descr) ); @@ -138,7 +139,7 @@ private void indexAasDescriptor(AssetAdministrationShellDescriptor descr) { private void updateAasDescriptorIndex(AssetAdministrationShellDescriptor descr) { try { esclient.update( - u -> u.index(ES_INDEX) + u -> u.index(indexName) .id(descr.getId()) .doc(descr), AssetAdministrationShellDescriptor.class @@ -151,7 +152,7 @@ private void updateAasDescriptorIndex(AssetAdministrationShellDescriptor descr) private void deindexAasDescriptor(String aasDescrId) { try { esclient.delete( - d -> d.index(ES_INDEX) + d -> d.index(indexName) .id(aasDescrId) ); } catch (IOException e) { @@ -168,7 +169,7 @@ private void reindexAasDescriptor(String aasDescrId) { private void clearIndex() { try { esclient.deleteByQuery(d -> d - .index(ES_INDEX) + .index(indexName) .query(q -> q .matchAll(m -> m) ) diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml index 11316a857..8eb6a4ce4 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml @@ -69,5 +69,10 @@ basyx.aasregistry-service-basetests test + + co.elastic.clients + elasticsearch-java + 9.0.1 + diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml index beb19a47b..dddf76782 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml @@ -67,6 +67,11 @@ basyx.aasregistry-service-basetests test + + co.elastic.clients + elasticsearch-java + 9.0.1 + diff --git a/basyx.aasregistry/basyx.aasregistry-service/pom.xml b/basyx.aasregistry/basyx.aasregistry-service/pom.xml index da626473a..2cc52ca75 100644 --- a/basyx.aasregistry/basyx.aasregistry-service/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service/pom.xml @@ -1,215 +1,215 @@ - - - 4.0.0 - - org.eclipse.digitaltwin.basyx - basyx.aasregistry - ${revision} - - - basyx.aasregistry-service - BaSyx AAS Registry Service - BaSyx AAS Registry Service - - jar - - - 2024.0.1 - ${project.basedir}/${openapi.folder.name} - ${openapi.folder}/${openapi.name} - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - generate-sources - - add-source - - - - ${generated.folder}/java - - - - - - - maven-clean-plugin - - - - ${project.basedir}/${generated.folder} - - **/.gitkeep - - false - - - ${openapi.folder} - - .gitkeep - - false - - - - - - de.dfki.cos.basys.common - jsonpatch-maven-plugin - - - generate-sources - - jsonpatch-maven-plugin - - - ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} - ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} - ${openapi.result.file} - - - - - - org.openapitools - openapi-generator-maven-plugin - - - - generate - - - false - true - - ${openapi.result.file} - spring - spring-boot - ${project.basedir}/${generated.folder} - - ${project.basedir}/templates - - - true - java8 - java - true - true - true - org.eclipse.digitaltwin.basyx.aasregistry.service.configuration - org.eclipse.digitaltwin.basyx.aasregistry.service.api - org.eclipse.digitaltwin.basyx.aasregistry.service - org.eclipse.digitaltwin.basyx.aasregistry.model - true - springdoc - true - - - - - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - org.projectlombok - lombok - true - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - jakarta.validation - jakarta.validation-api - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-actuator - - - com.google.guava - guava - - - org.eclipse.digitaltwin.basyx - basyx.aasregistry-service-basemodel - provided - - - org.eclipse.digitaltwin.basyx - basyx.aasregistry-paths - - - org.openapitools - jackson-databind-nullable - - - org.eclipse.digitaltwin.basyx - basyx.core - - - org.eclipse.digitaltwin.basyx - basyx.http - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - org.hamcrest - hamcrest-core - - - - - org.apache.commons - commons-lang3 - test - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - - - + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.aasregistry + ${revision} + + + basyx.aasregistry-service + BaSyx AAS Registry Service + BaSyx AAS Registry Service + + jar + + + 2024.0.1 + ${project.basedir}/${openapi.folder.name} + ${openapi.folder}/${openapi.name} + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${generated.folder}/java + + + + + + + maven-clean-plugin + + + + ${project.basedir}/${generated.folder} + + **/.gitkeep + + false + + + ${openapi.folder} + + .gitkeep + + false + + + + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + + + generate-sources + + jsonpatch-maven-plugin + + + ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} + ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} + ${openapi.result.file} + + + + + + org.openapitools + openapi-generator-maven-plugin + + + + generate + + + false + true + + ${openapi.result.file} + spring + spring-boot + ${project.basedir}/${generated.folder} + + ${project.basedir}/templates + + + true + java8 + java + true + true + true + org.eclipse.digitaltwin.basyx.aasregistry.service.configuration + org.eclipse.digitaltwin.basyx.aasregistry.service.api + org.eclipse.digitaltwin.basyx.aasregistry.service + org.eclipse.digitaltwin.basyx.aasregistry.model + true + springdoc + true + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.projectlombok + lombok + true + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + com.google.guava + guava + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service-basemodel + provided + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-paths + + + org.openapitools + jackson-databind-nullable + + + org.eclipse.digitaltwin.basyx + basyx.core + + + org.eclipse.digitaltwin.basyx + basyx.http + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + org.hamcrest + hamcrest-core + + + + + org.apache.commons + commons-lang3 + test + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index fba1948a1..174a84a5c 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -57,7 +57,7 @@ * [x] In Registries, add ES feature to release POMs (only for the variants with MongoDB!) * [x] Fix error: When comparing fields -> first check with a bool.must if the fields are there. (See ChatGPT Chat -> Ask Claude) * [x] Pagination -* [ ] Empty elements should not be included (empty arrays) +* [x] Empty elements should not be included (empty arrays) * [ ] Unit Tests * [ ] Integration Tests -* [ ] Configurable Index Names \ No newline at end of file +* [x] Configurable Index Names \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java index 91dafc936..8cac5deea 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepository.java @@ -44,14 +44,15 @@ public class SearchAasRepository implements AasRepository { private static final Logger logger = LoggerFactory.getLogger(SearchAasRepository.class); - private static final String ES_INDEX = "aas-index"; private final ElasticsearchClient esclient; + private final String indexName; private AasRepository decorated; - public SearchAasRepository(AasRepository decorated, ElasticsearchClient esclient) { + public SearchAasRepository(AasRepository decorated, ElasticsearchClient esclient, String indexName) { this.decorated = decorated; this.esclient = esclient; + this.indexName = indexName; } @Override @@ -135,7 +136,7 @@ public void deleteThumbnail(String aasId) { private void indexAAS(AssetAdministrationShell aas) { try { esclient.create( - c -> c.index(ES_INDEX) + c -> c.index(indexName) .id(aas.getId()) .document(aas) ); @@ -147,7 +148,7 @@ private void indexAAS(AssetAdministrationShell aas) { private void updateAASIndex(AssetAdministrationShell aas) { try { esclient.update( - u -> u.index(ES_INDEX) + u -> u.index(indexName) .id(aas.getId()) .doc(aas), AssetAdministrationShell.class @@ -160,7 +161,7 @@ private void updateAASIndex(AssetAdministrationShell aas) { private void deindexAAS(String aasId) { try { esclient.delete( - d -> d.index(ES_INDEX) + d -> d.index(indexName) .id(aasId) ); } catch (IOException e) { diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 744c78def..9b32e8cc0 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -39,6 +39,7 @@ import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -59,6 +60,9 @@ public class SearchAasRepositoryApiHTTPController implements SearchAasRepository private final ElasticsearchClient client; + @Value("${" + SearchAasRepositoryFeature.FEATURENAME + ".indexname:" + SearchAasRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + @Autowired public SearchAasRepositoryApiHTTPController(ElasticsearchClient client) { this.client = client; @@ -68,7 +72,7 @@ public SearchAasRepositoryApiHTTPController(ElasticsearchClient client) { public ResponseEntity queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { QueryResponse queryResponse = null; try { - ESQueryExecutor executor = new ESQueryExecutor(client, "aas-index", "AssetAdministrationShell"); + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "AssetAdministrationShell"); queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); } catch (IOException e) { throw new RuntimeException(e); diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java index 7c3d64b46..ac912619c 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java @@ -29,6 +29,9 @@ public class SearchAasRepositoryConfigurationGuard implements InitializingBean { @Value("${basyx.backend:#{null}}") private String basyxBackend; + @Value("${" + SearchAasRepositoryFeature.FEATURENAME + ".indexname:" + SearchAasRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + @Override public void afterPropertiesSet() throws Exception { boolean error = false; @@ -62,6 +65,13 @@ public void afterPropertiesSet() throws Exception { logger.info("BaSyx Backend: " + basyxBackend); } + if (indexName == null || indexName.isEmpty()) { + logger.error("Index name is not configured. Please set the property '" + SearchAasRepositoryFeature.FEATURENAME + ".indexname'."); + error = true; + } else { + logger.info("Index Name: " + indexName); + } + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); if(error){ System.exit(1); diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java index 9df809c3b..7cb411e83 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java @@ -33,16 +33,18 @@ public class SearchAasRepositoryFactory implements AasRepositoryFactory { private final ElasticsearchClient esclient; + private final String indexName; private AasRepositoryFactory decorated; - public SearchAasRepositoryFactory(AasRepositoryFactory decorated, ElasticsearchClient client) { + public SearchAasRepositoryFactory(AasRepositoryFactory decorated, ElasticsearchClient client, String indexName) { this.decorated = decorated; this.esclient = client; + this.indexName = indexName; } @Override public AasRepository create() { - return new SearchAasRepository(decorated.create(), esclient); + return new SearchAasRepository(decorated.create(), esclient, indexName); } } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java index f6e465fb4..39fc7a5ba 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java @@ -37,12 +37,16 @@ @ConditionalOnExpression("#{${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") @Component public class SearchAasRepositoryFeature implements AasRepositoryFeature { - public final static String FEATURENAME = "basyx.aasrepository.feature.search"; + public static final String FEATURENAME = "basyx.aasrepository.feature.search"; + public static final String DEFAULT_INDEX = "aas-index"; private final ElasticsearchClient esclient; @Value("#{${" + FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") private boolean enabled; + @Value("${" + SearchAasRepositoryFeature.FEATURENAME + ".indexname:" + SearchAasRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + @Autowired public SearchAasRepositoryFeature(ElasticsearchClient client) { this.esclient = client; @@ -50,7 +54,7 @@ public SearchAasRepositoryFeature(ElasticsearchClient client) { @Override public AasRepositoryFactory decorate(AasRepositoryFactory aasServiceFactory) { - return new SearchAasRepositoryFactory(aasServiceFactory, esclient); + return new SearchAasRepositoryFactory(aasServiceFactory, esclient, indexName); } @Override diff --git a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties index 9a258034f..bba676ef1 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties +++ b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties @@ -66,6 +66,7 @@ basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD # Feature: Search #################################################################################### basyx.aasrepository.feature.search.enabled=true +basyx.aasrepository.feature.search.indexname=aas-index-test spring.elasticsearch.uris=http://localhost:9200 spring.elasticsearch.username=elastic spring.elasticsearch.password=vtzJFt1b diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java deleted file mode 100644 index 3a0ee9916..000000000 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryConverterExample.java +++ /dev/null @@ -1,311 +0,0 @@ -package org.eclipse.digitaltwin.basyx.querycore.query.converter; - -import co.elastic.clients.elasticsearch._types.query_dsl.Query; -import co.elastic.clients.elasticsearch.core.SearchRequest; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.LogicalExpression; -import org.eclipse.digitaltwin.basyx.querycore.query.StringValue; -import org.eclipse.digitaltwin.basyx.querycore.query.Value; - -import java.util.Arrays; -import java.util.List; - -/** - * Example usage and factory class for the Query to ElasticSearch converters - */ -public class QueryConverterExample { - - private final QueryToElasticSearchConverter queryConverter; - private final ElasticSearchRequestBuilder requestBuilder; - - public QueryConverterExample() { - this.queryConverter = new QueryToElasticSearchConverter(); - this.requestBuilder = new ElasticSearchRequestBuilder(); - } - - /** - * Example: Convert a simple equality query - * Custom Query: { "$condition": { "$eq": [{"$field": "$aas#idShort"}, {"$strVal": "MyAAS"}] } } - */ - public Query createSimpleEqualityQuery() { - // Create value objects - Value fieldValue = new Value(); - fieldValue.set$field("$aas#idShort"); - - Value stringValue = new Value(); - stringValue.set$strVal("MyAAS"); - - // Create logical expression with equality - LogicalExpression condition = new LogicalExpression(); - condition.set$eq(Arrays.asList(fieldValue, stringValue)); - - // Create query - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Convert a range query - * Custom Query: { "$condition": { "$gt": [{"$field": "$sm#value"}, {"$numVal": 100}] } } - */ - public Query createRangeQuery() { - Value fieldValue = new Value(); - fieldValue.set$field("$sm#value"); - - Value numValue = new Value(); - numValue.set$numVal(100.0); - - LogicalExpression condition = new LogicalExpression(); - condition.set$gt(Arrays.asList(fieldValue, numValue)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Convert a complex AND query - * Custom Query: { "$condition": { "$and": [...] } } - */ - public Query createComplexAndQuery() { - // First condition: name equals "Test" - Value fieldValue1 = new Value(); - fieldValue1.set$field("$aas#idShort"); - Value stringValue1 = new Value(); - stringValue1.set$strVal("Test"); - - LogicalExpression condition1 = new LogicalExpression(); - condition1.set$eq(Arrays.asList(fieldValue1, stringValue1)); - - // Second condition: value > 50 - Value fieldValue2 = new Value(); - fieldValue2.set$field("$sme#value"); - Value numValue2 = new Value(); - numValue2.set$numVal(50.0); - - LogicalExpression condition2 = new LogicalExpression(); - condition2.set$gt(Arrays.asList(fieldValue2, numValue2)); - - // Combine with AND - LogicalExpression andCondition = new LogicalExpression(); - andCondition.set$and(Arrays.asList(condition1, condition2)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(andCondition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Convert a string contains query - * Custom Query: { "$condition": { "$contains": [{"$field": "$aas#description"}, {"$strVal": "sensor"}] } } - */ - public Query createStringContainsQuery() { - StringValue fieldValue = new StringValue(); - fieldValue.set$field("$aas#description"); - - StringValue stringValue = new StringValue(); - stringValue.set$strVal("sensor"); - - LogicalExpression condition = new LogicalExpression(); - condition.set$contains(Arrays.asList(fieldValue, stringValue)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Create a complete SearchRequest for ElasticSearch - */ - public SearchRequest createSearchRequest() { - // Create a query that searches for AAS with idShort="TestAAS" - Value fieldValue = new Value(); - fieldValue.set$field("$aas#idShort"); - - Value stringValue = new Value(); - stringValue.set$strVal("TestAAS"); - - LogicalExpression condition = new LogicalExpression(); - condition.set$eq(Arrays.asList(fieldValue, stringValue)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$select("id"); // Only return ID fields - customQuery.set$condition(condition); - - return requestBuilder.buildSearchRequest(customQuery, "aas-index"); - } - - /** - * Example: Create SearchRequest with custom field selection - */ - public SearchRequest createSearchRequestWithCustomFields() { - AASQuery customQuery = createSimpleQuery(); - - List sourceFields = Arrays.asList("aas.idShort", "aas.id", "aas.assetInformation"); - - return requestBuilder.buildSearchRequestWithSources(customQuery, "aas-index", sourceFields); - } - - /** - * Example: Create SearchRequest with pagination - */ - public SearchRequest createPaginatedSearchRequest(int page, int pageSize) { - AASQuery customQuery = createSimpleQuery(); - - int from = page * pageSize; - return requestBuilder.buildSearchRequest(customQuery, "aas-index", from, pageSize); - } - - private AASQuery createSimpleQuery() { - Value fieldValue = new Value(); - fieldValue.set$field("$aas#idShort"); - - Value stringValue = new Value(); - stringValue.set$strVal("*"); - - LogicalExpression condition = new LogicalExpression(); - condition.set$eq(Arrays.asList(fieldValue, stringValue)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return customQuery; - } - - /** - * Example: Boolean literal query - */ - public Query createBooleanQuery(boolean value) { - LogicalExpression condition = new LogicalExpression(); - condition.set$boolean(value); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: NOT query - */ - public Query createNotQuery() { - // Create inner condition: name = "Test" - Value fieldValue = new Value(); - fieldValue.set$field("$aas#idShort"); - Value stringValue = new Value(); - stringValue.set$strVal("Test"); - - LogicalExpression innerCondition = new LogicalExpression(); - innerCondition.set$eq(Arrays.asList(fieldValue, stringValue)); - - // Wrap in NOT - LogicalExpression notCondition = new LogicalExpression(); - notCondition.set$not(innerCondition); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(notCondition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Field-to-field equality comparison - * Custom Query: { "$condition": { "$eq": [{"$field": "$aas#idShort"}, {"$field": "$aas#displayName"}] } } - */ - public Query createFieldToFieldEqualityQuery() { - // Create two field value objects - Value leftFieldValue = new Value(); - leftFieldValue.set$field("$aas#idShort"); - - Value rightFieldValue = new Value(); - rightFieldValue.set$field("$aas#displayName"); - - // Create logical expression with equality - LogicalExpression condition = new LogicalExpression(); - condition.set$eq(Arrays.asList(leftFieldValue, rightFieldValue)); - - // Create query - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Field-to-field range comparison - * Custom Query: { "$condition": { "$gt": [{"$field": "$sme#minValue"}, {"$field": "$sme#maxValue"}] } } - */ - public Query createFieldToFieldRangeQuery() { - Value leftFieldValue = new Value(); - leftFieldValue.set$field("$sme#minValue"); - - Value rightFieldValue = new Value(); - rightFieldValue.set$field("$sme#maxValue"); - - LogicalExpression condition = new LogicalExpression(); - condition.set$gt(Arrays.asList(leftFieldValue, rightFieldValue)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Field-to-field string contains comparison - * Custom Query: { "$condition": { "$contains": [{"$field": "$aas#description"}, {"$field": "$aas#idShort"}] } } - */ - public Query createFieldToFieldStringContainsQuery() { - StringValue leftFieldValue = new StringValue(); - leftFieldValue.set$field("$aas#description"); - - StringValue rightFieldValue = new StringValue(); - rightFieldValue.set$field("$aas#idShort"); - - LogicalExpression condition = new LogicalExpression(); - condition.set$contains(Arrays.asList(leftFieldValue, rightFieldValue)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(condition); - - return queryConverter.convert(customQuery); - } - - /** - * Example: Complex query with both field-to-field and field-to-value comparisons - * Find AAS where idShort equals displayName AND value > 100 - */ - public Query createMixedFieldComparisonQuery() { - // Field-to-field condition: idShort equals displayName - Value leftField1 = new Value(); - leftField1.set$field("$aas#idShort"); - Value rightField1 = new Value(); - rightField1.set$field("$aas#displayName"); - - LogicalExpression condition1 = new LogicalExpression(); - condition1.set$eq(Arrays.asList(leftField1, rightField1)); - - // Field-to-value condition: value > 100 - Value fieldValue2 = new Value(); - fieldValue2.set$field("$sme#value"); - Value numValue2 = new Value(); - numValue2.set$numVal(100.0); - - LogicalExpression condition2 = new LogicalExpression(); - condition2.set$gt(Arrays.asList(fieldValue2, numValue2)); - - // Combine with AND - LogicalExpression andCondition = new LogicalExpression(); - andCondition.set$and(Arrays.asList(condition1, condition2)); - - AASQuery customQuery = new AASQuery(); - customQuery.set$condition(andCondition); - - return queryConverter.convert(customQuery); - } -} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index f389c6864..46e953e60 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -171,25 +171,35 @@ public Query convertStringComparison(StringValue leftValue, StringValue rightVal */ private Query createFieldToFieldComparison(String leftField, String rightField, String operator) { String scriptSource; - + + String scriptLeftField = leftField; + String scriptRightField = rightField; + + if (!leftField.endsWith(".keyword")) { + scriptLeftField += ".keyword"; + } + if (!rightField.endsWith(".keyword")) { + scriptRightField += ".keyword"; + } + switch (operator) { case "eq": - scriptSource = String.format("doc['%s'].value == doc['%s'].value", leftField, rightField); + scriptSource = String.format("doc['%s'].value == doc['%s'].value", scriptLeftField, scriptRightField); break; case "ne": - scriptSource = String.format("doc['%s'].value != doc['%s'].value", leftField, rightField); + scriptSource = String.format("doc['%s'].value != doc['%s'].value", scriptLeftField, scriptRightField); break; case "gt": - scriptSource = String.format("doc['%s'].value > doc['%s'].value", leftField, rightField); + scriptSource = String.format("doc['%s'].value > doc['%s'].value", scriptLeftField, scriptRightField); break; case "gte": - scriptSource = String.format("doc['%s'].value >= doc['%s'].value", leftField, rightField); + scriptSource = String.format("doc['%s'].value >= doc['%s'].value", scriptLeftField, scriptRightField); break; case "lt": - scriptSource = String.format("doc['%s'].value < doc['%s'].value", leftField, rightField); + scriptSource = String.format("doc['%s'].value < doc['%s'].value", scriptLeftField, scriptRightField); break; case "lte": - scriptSource = String.format("doc['%s'].value <= doc['%s'].value", leftField, rightField); + scriptSource = String.format("doc['%s'].value <= doc['%s'].value", scriptLeftField, scriptRightField); break; default: throw new IllegalArgumentException("Unsupported field-to-field operator: " + operator); From 2713747bb34ee8c65cfc622f76448806da018f96 Mon Sep 17 00:00:00 2001 From: fried Date: Wed, 25 Jun 2025 14:20:18 +0200 Subject: [PATCH 14/52] WIP - adds unit tests --- .../SearchAasRegistryApiHTTPController.java | 16 +--- .../search/SearchAasRegistryHTTPApi.java | 4 +- .../README.md | 4 +- .../SearchAasRepositoryApiHTTPController.java | 18 +--- .../search/SearchAasRepositoryHTTPApi.java | 4 +- basyx.common/basyx.querycore/pom.xml | 11 +++ ... => AASQueryToElasticSearchConverter.java} | 8 +- .../ElasticSearchRequestBuilder.java | 6 +- .../converter/LogicalExpressionConverter.java | 4 +- .../converter/MatchExpressionConverter.java | 2 +- .../query/converter/ValueConverter.java | 5 +- .../query/executor/ESQueryExecutor.java | 8 +- .../querycore/query/{ => model}/AASQuery.java | 2 +- .../{ => model}/AccessPermissionRule.java | 2 +- .../querycore/query/{ => model}/Acl.java | 2 +- .../{ => model}/AllAccessPermissionRules.java | 2 +- .../query/{ => model}/AttributeItem.java | 2 +- .../querycore/query/{ => model}/Defacl.java | 2 +- .../query/{ => model}/Defattribute.java | 2 +- .../query/{ => model}/Defformula.java | 2 +- .../query/{ => model}/Defobject.java | 2 +- .../query/{ => model}/LogicalExpression.java | 2 +- .../query/{ => model}/MatchExpression.java | 2 +- .../query/{ => model}/ObjectItem.java | 2 +- .../querycore/query/{ => model}/QlSchema.java | 2 +- .../query/{ => model}/QueryPaging.java | 2 +- .../query/{ => model}/QueryResponse.java | 2 +- .../query/{ => model}/QueryResult.java | 2 +- .../query/{ => model}/RightsEnum.java | 2 +- .../query/{ => model}/StringValue.java | 2 +- .../querycore/query/{ => model}/Value.java | 2 +- .../basyx.querycore/src/test/README.md | 21 +++++ .../src/test/java/AASQueryConverterTest.java | 90 +++++++++++++++++++ .../input/contains_field_strval.json | 8 ++ .../test/resources/input/eq_field_field.json | 8 ++ .../test/resources/input/eq_field_strval.json | 8 ++ .../input/gt_field_numcast_field_numcast.json | 16 ++++ .../test/resources/input/le_field_field.json | 8 ++ .../test/resources/input/le_field_numVal.json | 8 ++ .../expected_contains_field_strval.json | 1 + .../output/expected_eq_field_field.json | 24 +++++ .../output/expected_eq_field_strval.json | 7 ++ 42 files changed, 256 insertions(+), 71 deletions(-) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/{QueryToElasticSearchConverter.java => AASQueryToElasticSearchConverter.java} (81%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/AASQuery.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/AccessPermissionRule.java (99%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/Acl.java (98%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/AllAccessPermissionRules.java (98%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/AttributeItem.java (98%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/Defacl.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/Defattribute.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/Defformula.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/Defobject.java (98%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/LogicalExpression.java (99%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/MatchExpression.java (99%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/ObjectItem.java (98%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/QlSchema.java (97%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/QueryPaging.java (75%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/QueryResponse.java (79%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/QueryResult.java (68%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/RightsEnum.java (94%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/StringValue.java (98%) rename basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/{ => model}/Value.java (99%) create mode 100644 basyx.common/basyx.querycore/src/test/README.md create mode 100644 basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/contains_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/eq_field_field.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/eq_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/gt_field_numcast_field_numcast.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/le_field_field.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/le_field_numVal.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_contains_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_strval.json diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index bc46b7b46..2187df93d 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -25,19 +25,9 @@ package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch.core.SearchRequest; -import co.elastic.clients.elasticsearch.core.SearchResponse; -import co.elastic.clients.elasticsearch.core.search.Hit; -import co.elastic.clients.elasticsearch.esql.ElasticsearchEsqlClient; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.http.HttpServletRequest; -import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResult; -import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,8 +38,6 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @RestController diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java index a540fc9cc..52a0d67f7 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java @@ -41,8 +41,8 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index 174a84a5c..cb786b507 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -58,6 +58,8 @@ * [x] Fix error: When comparing fields -> first check with a bool.must if the fields are there. (See ChatGPT Chat -> Ask Claude) * [x] Pagination * [x] Empty elements should not be included (empty arrays) +* [x] Configurable Index Names * [ ] Unit Tests * [ ] Integration Tests -* [x] Configurable Index Names \ No newline at end of file +* [ ] Duplicate search module for the missing components +* [ ] Implement Casting Operators \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 9b32e8cc0..ff0da5182 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -26,17 +26,9 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.SortOptions; -import co.elastic.clients.elasticsearch.core.SearchRequest; -import co.elastic.clients.elasticsearch.core.SearchResponse; -import co.elastic.clients.elasticsearch.core.search.Hit; -import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResult; -import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -46,12 +38,6 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import co.elastic.clients.elasticsearch._types.SortOrder; - -import java.util.Base64; -import java.nio.charset.StandardCharsets; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @RestController diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java index 1f9febdd9..b181b4b48 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -34,9 +34,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import org.eclipse.digitaltwin.aas4j.v3.model.Result; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; diff --git a/basyx.common/basyx.querycore/pom.xml b/basyx.common/basyx.querycore/pom.xml index c857bcb5f..c8bd982cf 100644 --- a/basyx.common/basyx.querycore/pom.xml +++ b/basyx.common/basyx.querycore/pom.xml @@ -44,6 +44,17 @@ org.eclipse.digitaltwin.basyx basyx.http + + org.eclipse.digitaltwin.basyx + basyx.http + test + tests + + + commons-io + commons-io + test + \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/AASQueryToElasticSearchConverter.java similarity index 81% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/AASQueryToElasticSearchConverter.java index aa20d3568..6358c1680 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/QueryToElasticSearchConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/AASQueryToElasticSearchConverter.java @@ -1,17 +1,17 @@ package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.LogicalExpression; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.LogicalExpression; /** * Converts custom Query objects to ElasticSearch QueryDSL Query objects for ElasticSearch 9.0.1 */ -public class QueryToElasticSearchConverter { +public class AASQueryToElasticSearchConverter { private final LogicalExpressionConverter logicalExpressionConverter; - public QueryToElasticSearchConverter() { + public AASQueryToElasticSearchConverter() { this.logicalExpressionConverter = new LogicalExpressionConverter(); } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java index 989123dfa..8244a50d2 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java @@ -3,7 +3,7 @@ import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.search.SourceConfig; import co.elastic.clients.elasticsearch.core.search.SourceFilter; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import java.util.List; import java.util.Arrays; @@ -13,10 +13,10 @@ */ public class ElasticSearchRequestBuilder { - private final QueryToElasticSearchConverter queryConverter; + private final AASQueryToElasticSearchConverter queryConverter; public ElasticSearchRequestBuilder() { - this.queryConverter = new QueryToElasticSearchConverter(); + this.queryConverter = new AASQueryToElasticSearchConverter(); } /** diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java index 6c04821ea..6ad8ba870 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java @@ -3,8 +3,8 @@ import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.LogicalExpression; -import org.eclipse.digitaltwin.basyx.querycore.query.MatchExpression; +import org.eclipse.digitaltwin.basyx.querycore.query.model.LogicalExpression; +import org.eclipse.digitaltwin.basyx.querycore.query.model.MatchExpression; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java index 0adaf32c1..d69636830 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java @@ -3,7 +3,7 @@ import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.MatchExpression; +import org.eclipse.digitaltwin.basyx.querycore.query.model.MatchExpression; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 46e953e60..b224136dd 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -1,12 +1,11 @@ package org.eclipse.digitaltwin.basyx.querycore.query.converter; -import co.elastic.clients.elasticsearch._types.Script; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.json.JsonData; -import org.eclipse.digitaltwin.basyx.querycore.query.StringValue; -import org.eclipse.digitaltwin.basyx.querycore.query.Value; +import org.eclipse.digitaltwin.basyx.querycore.query.model.StringValue; +import org.eclipse.digitaltwin.basyx.querycore.query.model.Value; import java.util.Date; import java.text.SimpleDateFormat; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index f2988c3b8..dce9cc6ac 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -7,10 +7,10 @@ import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; -import org.eclipse.digitaltwin.basyx.querycore.query.AASQuery; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryPaging; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResponse; -import org.eclipse.digitaltwin.basyx.querycore.query.QueryResult; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryPaging; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResult; import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; import java.io.IOException; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AASQuery.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AASQuery.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AASQuery.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AASQuery.java index bfc677530..a3776cfff 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AASQuery.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AASQuery.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AccessPermissionRule.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AccessPermissionRule.java similarity index 99% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AccessPermissionRule.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AccessPermissionRule.java index ce34aa485..ee91fa0e3 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AccessPermissionRule.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AccessPermissionRule.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.ArrayList; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Acl.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Acl.java similarity index 98% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Acl.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Acl.java index 561a5f447..80b2f7b63 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Acl.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Acl.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.ArrayList; import java.util.HashMap; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AllAccessPermissionRules.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AllAccessPermissionRules.java similarity index 98% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AllAccessPermissionRules.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AllAccessPermissionRules.java index 3a428ba4b..478962fa4 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AllAccessPermissionRules.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AllAccessPermissionRules.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.ArrayList; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AttributeItem.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AttributeItem.java similarity index 98% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AttributeItem.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AttributeItem.java index bb5a7ee2d..177f4ebaa 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/AttributeItem.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AttributeItem.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.HashMap; import java.util.Map; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defacl.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defacl.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defacl.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defacl.java index 9cf569a37..50267c3de 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defacl.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defacl.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defattribute.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defattribute.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defattribute.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defattribute.java index 9966c1ab1..2b9aa5b2c 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defattribute.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defattribute.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.ArrayList; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defformula.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defformula.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defformula.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defformula.java index d462362d9..85f473269 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defformula.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defformula.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defobject.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defobject.java similarity index 98% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defobject.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defobject.java index 6a01d3b6a..8cf910169 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Defobject.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defobject.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.ArrayList; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpression.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/LogicalExpression.java similarity index 99% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpression.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/LogicalExpression.java index 4b8666835..c333c0529 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/LogicalExpression.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/LogicalExpression.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.ArrayList; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpression.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/MatchExpression.java similarity index 99% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpression.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/MatchExpression.java index 92c9ad980..c44121b89 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/MatchExpression.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/MatchExpression.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.ArrayList; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ObjectItem.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/ObjectItem.java similarity index 98% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ObjectItem.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/ObjectItem.java index ced3148d6..bd53bb877 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/ObjectItem.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/ObjectItem.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QlSchema.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QlSchema.java similarity index 97% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QlSchema.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QlSchema.java index 8a8127591..0d3409afb 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QlSchema.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QlSchema.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryPaging.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryPaging.java similarity index 75% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryPaging.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryPaging.java index 9dd11a2e0..d272aa218 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryPaging.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryPaging.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; public class QueryPaging { public String cursor; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java similarity index 79% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java index 67c70427d..f96a0209f 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResponse.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; public class QueryResponse { diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResult.java similarity index 68% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResult.java index 0a619849a..37ccf1d55 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/QueryResult.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResult.java @@ -1,4 +1,4 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/RightsEnum.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/RightsEnum.java similarity index 94% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/RightsEnum.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/RightsEnum.java index bda8bf529..b9afba87a 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/RightsEnum.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/RightsEnum.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.HashMap; import java.util.Map; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/StringValue.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/StringValue.java similarity index 98% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/StringValue.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/StringValue.java index 4a8851f1a..b28c44e18 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/StringValue.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/StringValue.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Value.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Value.java similarity index 99% rename from basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Value.java rename to basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Value.java index ea8ccadc2..b1e7109fa 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/Value.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Value.java @@ -1,5 +1,5 @@ -package org.eclipse.digitaltwin.basyx.querycore.query; +package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.Date; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/basyx.common/basyx.querycore/src/test/README.md b/basyx.common/basyx.querycore/src/test/README.md new file mode 100644 index 000000000..ac2e2f14a --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/README.md @@ -0,0 +1,21 @@ +# Query Core Tests + +## Unit Tests + +The Unit Tests should test the converter functions for translating AASQL to ES Query DSL. + +The following tests should be implemented: + +* [ ] Match expressions +* [ ] Comparisons + * [ ] $eq + * [ ] $ne + * [ ] $gt + * [ ] $ge + * [ ] $lt + * [ ] $le + * [ ] $contains + * [ ] $starts-with + * [ ] $ends-with + * [ ] $regex +* [ ] Logical expressions \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java b/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java new file mode 100644 index 000000000..d91bd1b1c --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +import co.elastic.clients.json.JsonpMapper; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; +import org.eclipse.digitaltwin.basyx.querycore.query.converter.AASQueryToElasticSearchConverter; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author zielstor, fried + */ +public class AASQueryConverterTest { + + Map queries = new HashMap<>() {{ +// put("eq_field_field.json", "expected_eq_field_field.json"); +// put("eq_field_strval.json", "expected_eq_field_strval.json"); + put("gt_field_numval.json", "expected_eq_field_strval.json"); +// put("contains_field_strval.json", "expected_contains_field_strval.json"); +// put("le_field_numVal.json", "expected_eq_field_strval.json"); + }}; + + @Test + public void testQueryConverter(){ + for (Map.Entry entry : queries.entrySet()) { + String inputFileName = entry.getKey(); + String expectedFileName = entry.getValue(); + + try { + String inputQueryString = loadInputFileAsString(inputFileName); + AASQuery query = loadStringAsQuery(inputQueryString); + AASQueryToElasticSearchConverter converter = new AASQueryToElasticSearchConverter(); + co.elastic.clients.elasticsearch._types.query_dsl.Query convertedQuery = converter.convert(query); + + String actual = convertedQuery.toString().substring(7); // Remove "Query{" and "}" + String expected = loadExpectedFileAsString(expectedFileName); + + BaSyxHttpTestUtils.assertSameJSONContent(expected,actual); + } catch (IOException e) { + throw new RuntimeException("Error processing files: " + inputFileName + " or " + expectedFileName, e); + } catch (AssertionError ae) { + System.err.println("Assertion failed for input file: " + inputFileName + " with expected file: " + expectedFileName); + throw ae; + } + System.out.println("✓ Test passed for input file: " + inputFileName + " with expected file: " + expectedFileName); + } + } + + private AASQuery loadStringAsQuery(String queryString) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(queryString, AASQuery.class); + } + + private String loadInputFileAsString(String fileName) throws IOException { + return BaSyxHttpTestUtils.readJSONStringFromClasspath("input/" + fileName); + } + + private String loadExpectedFileAsString(String fileName) throws IOException { + return BaSyxHttpTestUtils.readJSONStringFromClasspath("output/" + fileName); + } +} diff --git a/basyx.common/basyx.querycore/src/test/resources/input/contains_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/contains_field_strval.json new file mode 100644 index 000000000..c375c6a30 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/contains_field_strval.json @@ -0,0 +1,8 @@ +{ + "$condition": { + "$contains": [ + { "$field": "$aas#assetInformation.assetKind" }, + { "$strVal": "NST" } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/eq_field_field.json b/basyx.common/basyx.querycore/src/test/resources/input/eq_field_field.json new file mode 100644 index 000000000..3edfe91c1 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/eq_field_field.json @@ -0,0 +1,8 @@ +{ + "$condition": { + "$eq": [ + { "$field": "$sm#displayName[].language" }, + { "$field": "$sm#description[].language" } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/eq_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/eq_field_strval.json new file mode 100644 index 000000000..d26e5bd44 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/eq_field_strval.json @@ -0,0 +1,8 @@ +{ + "$condition": { + "$eq": [ + { "$field": "$aas#idShort" }, + { "$strVal": "aasIdShortTest" } + ] + } +} diff --git a/basyx.common/basyx.querycore/src/test/resources/input/gt_field_numcast_field_numcast.json b/basyx.common/basyx.querycore/src/test/resources/input/gt_field_numcast_field_numcast.json new file mode 100644 index 000000000..0a93863c4 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/gt_field_numcast_field_numcast.json @@ -0,0 +1,16 @@ +{ + "$condition": { + "$gt": [ + { + "$numCast": { + "$field": "$aas#administration.version" + } + }, + { + "$numCast": { + "$field": "$aas#administration.revision" + } + } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/le_field_field.json b/basyx.common/basyx.querycore/src/test/resources/input/le_field_field.json new file mode 100644 index 000000000..2ff54bb9c --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/le_field_field.json @@ -0,0 +1,8 @@ +{ + "$condition": { + "$le": [ + { "$field": "$aas#administration.revision" }, + { "$field": "$aas#administration.version" } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/le_field_numVal.json b/basyx.common/basyx.querycore/src/test/resources/input/le_field_numVal.json new file mode 100644 index 000000000..bf10a9b6e --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/le_field_numVal.json @@ -0,0 +1,8 @@ +{ + "$condition": { + "$le": [ + { "$field": "$aas#administration.revision" }, + { "$numVal": 2 } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_contains_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_contains_field_strval.json new file mode 100644 index 000000000..a1053727f --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_contains_field_strval.json @@ -0,0 +1 @@ +{"wildcard":{"assetInformation.assetKind.keyword":{"value":"*NST*"}}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json new file mode 100644 index 000000000..38fc5abf2 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json @@ -0,0 +1,24 @@ +{ + "bool": { + "must": [ + { + "exists": { + "field": "displayName.language" + } + }, + { + "exists": { + "field": "description.language" + } + }, + { + "script": { + "script": { + "source": "doc['displayName.language.keyword'].value == doc['description.language.keyword'].value", + "lang": "painless" + } + } + } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_strval.json new file mode 100644 index 000000000..f96a83cd5 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_strval.json @@ -0,0 +1,7 @@ +{ + "term": { + "idShort.keyword": { + "value": "aasIdShortTest" + } + } +} \ No newline at end of file From a0aded03d9529d8877c601f9db8cfe680c8777be Mon Sep 17 00:00:00 2001 From: fried Date: Wed, 25 Jun 2025 14:56:09 +0200 Subject: [PATCH 15/52] Adds Unit Tests and implements Casting Operators --- .../README.md | 7 +- .../query/converter/ValueConverter.java | 123 +++++++++++++++++- .../src/test/java/AASQueryConverterTest.java | 15 ++- .../input/and_eq_eq_lt_field_strval.json | 43 ++++++ .../input/match_eq_eq_field_strval.json | 18 +++ .../input/match_specific_asset_ids.json | 36 +++++ .../resources/input/regex_field_strval.json | 12 ++ .../expected_and_eq_eq_lt_field_strval.json | 1 + ...pected_gt_field_numcast_field_numcast.json | 24 ++++ .../output/expected_le_field_field.json | 1 + .../output/expected_le_field_numVal.json | 1 + .../expected_match_eq_eq_field_strval.json | 1 + .../expected_match_specific_asset_ids.json | 1 + .../output/expected_regex_field_strval.json | 7 + 14 files changed, 281 insertions(+), 9 deletions(-) create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/and_eq_eq_lt_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/match_eq_eq_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/match_specific_asset_ids.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/regex_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_gt_field_numcast_field_numcast.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_regex_field_strval.json diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index cb786b507..7a7c60f34 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -59,7 +59,8 @@ * [x] Pagination * [x] Empty elements should not be included (empty arrays) * [x] Configurable Index Names -* [ ] Unit Tests +* [x] Implement Casting Operators +* [x] Unit Test +* [ ] !Priority! Duplicate search module for the missing components * [ ] Integration Tests -* [ ] Duplicate search module for the missing components -* [ ] Implement Casting Operators \ No newline at end of file +* [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index b224136dd..5922ae0f1 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -82,7 +82,15 @@ public Query convertRangeComparison(Value leftValue, // Field-to-field comparison if (leftField != null && rightField != null) { - return createFieldToFieldComparison(leftField, rightField, operator); + // Check if either value involves casting operations + String leftCastType = detectCastType(leftValue); + String rightCastType = detectCastType(rightValue); + + if (leftCastType != null || rightCastType != null) { + return createFieldToFieldCastComparison(leftField, rightField, leftCastType, rightCastType, operator); + } else { + return createFieldToFieldComparison(leftField, rightField, operator); + } } // Field-to-value comparison (existing logic) @@ -149,6 +157,9 @@ public Query convertStringComparison(StringValue leftValue, StringValue rightVal .build()._toQuery(); case "regex": + if (value.startsWith("^") && value.endsWith("$")) { + value = value.substring(1, value.length() - 1); + } return QueryBuilders.regexp() .field(fieldName) .value(value) @@ -494,4 +505,114 @@ private String escapeWildcard(String value) { .replace("*", "\\*") .replace("?", "\\?"); } + + /** + * Detects the cast type of a Value object (str, num, bool, hex, date, time) + */ + private String detectCastType(Value value) { + if (value == null) return null; + + if (value.get$strCast() != null) { + return "str"; + } + if (value.get$numCast() != null) { + return "num"; + } + if (value.get$boolCast() != null) { + return "bool"; + } + if (value.get$hexCast() != null) { + return "hex"; + } + if (value.get$dateTimeCast() != null) { + return "date"; + } + if (value.get$timeCast() != null) { + return "time"; + } + + return null; + } + + /** + * Creates field-to-field comparison with type casting using ElasticSearch script queries + */ + private Query createFieldToFieldCastComparison(String leftField, String rightField, String leftCastType, String rightCastType, String operator) { + String scriptSource; + + String scriptLeftField = leftField; + String scriptRightField = rightField; + + if (!leftField.endsWith(".keyword")) { + scriptLeftField += ".keyword"; + } + if (!rightField.endsWith(".keyword")) { + scriptRightField += ".keyword"; + } + + // Generate Painless script for type casting + String leftCastScript = generateCastScript("doc['" + scriptLeftField + "'].value", leftCastType); + String rightCastScript = generateCastScript("doc['" + scriptRightField + "'].value", rightCastType); + + switch (operator) { + case "eq": + scriptSource = String.format("%s == %s", leftCastScript, rightCastScript); + break; + case "ne": + scriptSource = String.format("%s != %s", leftCastScript, rightCastScript); + break; + case "gt": + scriptSource = String.format("%s > %s", leftCastScript, rightCastScript); + break; + case "gte": + scriptSource = String.format("%s >= %s", leftCastScript, rightCastScript); + break; + case "lt": + scriptSource = String.format("%s < %s", leftCastScript, rightCastScript); + break; + case "lte": + scriptSource = String.format("%s <= %s", leftCastScript, rightCastScript); + break; + default: + throw new IllegalArgumentException("Unsupported field-to-field operator: " + operator); + } + + return QueryBuilders.bool(b -> b + .must(QueryBuilders.exists(e -> e.field(leftField))) + .must(QueryBuilders.exists(e -> e.field(rightField))) + .must(QueryBuilders.script(s -> s + .script(script -> script + .source(source -> source.scriptString(scriptSource)) + .lang("painless") + ) + )) + ); + } + + /** + * Generates Painless script for type casting + */ + private String generateCastScript(String fieldExpression, String castType) { + if (castType == null) { + return fieldExpression; // No casting + } + + switch (castType) { + case "str": + return fieldExpression + ".toString()"; + case "num": + return String.format("Double.parseDouble(%s.toString())", fieldExpression); + case "bool": + return String.format("Boolean.parseBoolean(%s.toString())", fieldExpression); + case "hex": + // For hex values like "16#ABC", extract the hex part and convert to number + return String.format("Integer.parseInt(%s.toString().substring(3), 16)", fieldExpression); + case "date": + case "time": + // For date/time, return as string representation + return fieldExpression + ".toString()"; + default: + return fieldExpression; // No casting applied + } + } } \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java b/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java index d91bd1b1c..6d12af233 100644 --- a/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java +++ b/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java @@ -42,11 +42,16 @@ public class AASQueryConverterTest { Map queries = new HashMap<>() {{ -// put("eq_field_field.json", "expected_eq_field_field.json"); -// put("eq_field_strval.json", "expected_eq_field_strval.json"); - put("gt_field_numval.json", "expected_eq_field_strval.json"); -// put("contains_field_strval.json", "expected_contains_field_strval.json"); -// put("le_field_numVal.json", "expected_eq_field_strval.json"); + put("and_eq_eq_lt_field_strval.json", "expected_and_eq_eq_lt_field_strval.json"); + put("contains_field_strval.json", "expected_contains_field_strval.json"); + put("eq_field_field.json", "expected_eq_field_field.json"); + put("eq_field_strval.json", "expected_eq_field_strval.json"); + put("gt_field_numcast_field_numcast.json", "expected_gt_field_numcast_field_numcast.json"); + put("le_field_field.json", "expected_le_field_field.json"); + put("le_field_numVal.json", "expected_le_field_numVal.json"); + put("match_eq_eq_field_strval.json", "expected_match_eq_eq_field_strval.json"); + put("match_specific_asset_ids.json", "expected_match_specific_asset_ids.json"); + put("regex_field_strval.json", "expected_regex_field_strval.json"); }}; @Test diff --git a/basyx.common/basyx.querycore/src/test/resources/input/and_eq_eq_lt_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/and_eq_eq_lt_field_strval.json new file mode 100644 index 000000000..2a4b78504 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/and_eq_eq_lt_field_strval.json @@ -0,0 +1,43 @@ +{ + "$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 } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/match_eq_eq_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/match_eq_eq_field_strval.json new file mode 100644 index 000000000..3bbc75851 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/match_eq_eq_field_strval.json @@ -0,0 +1,18 @@ +{ + "$condition": { + "$match": [ + { + "$eq": [ + { "$field": "$sme.Documents[].DocumentClassification.Class#value" }, + { "$strVal": "03-01" } + ] + }, + { + "$eq": [ + { "$field": "$sme.Documents[].DocumentVersion.SMLLanguages[]#language" }, + { "$strVal": "nl" } + ] + } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/match_specific_asset_ids.json b/basyx.common/basyx.querycore/src/test/resources/input/match_specific_asset_ids.json new file mode 100644 index 000000000..56ae5274c --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/match_specific_asset_ids.json @@ -0,0 +1,36 @@ +{ + "$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" } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/regex_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/regex_field_strval.json new file mode 100644 index 000000000..39ae17546 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/regex_field_strval.json @@ -0,0 +1,12 @@ +{ + "$condition": { + "$regex": [ + { + "$field": "$aas#idShort" + }, + { + "$strVal": "^Sensor_[0-9]+$" + } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json new file mode 100644 index 000000000..3e79c3744 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json @@ -0,0 +1 @@ +{"bool":{"must":[{"bool":{"must":[{"term":{"idShort.keyword":{"value":"TechnicalData"}}},{"term":{"value.keyword":{"value":"27-37-09-05"}}}]}},{"bool":{"must":[{"term":{"idShort.keyword":{"value":"TechnicalData"}}},{"term":{"semanticId":{"value":"0173-1#02-BAF016#006"}}},{"range":{"value.keyword":{"lt":100.0}}}]}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_gt_field_numcast_field_numcast.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_gt_field_numcast_field_numcast.json new file mode 100644 index 000000000..2c6c90b9f --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_gt_field_numcast_field_numcast.json @@ -0,0 +1,24 @@ +{ + "bool": { + "must": [ + { + "exists": { + "field": "administration.version" + } + }, + { + "exists": { + "field": "administration.revision" + } + }, + { + "script": { + "script": { + "source": "Double.parseDouble(doc['administration.version.keyword'].value.toString()) > Double.parseDouble(doc['administration.revision.keyword'].value.toString())", + "lang": "painless" + } + } + } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json new file mode 100644 index 000000000..05391a62f --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json @@ -0,0 +1 @@ +{"bool":{"must":[{"exists":{"field":"administration.revision"}},{"exists":{"field":"administration.version"}},{"script":{"script":{"source":"doc['administration.revision.keyword'].value <= doc['administration.version.keyword'].value","lang":"painless"}}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json new file mode 100644 index 000000000..1b8a2be8b --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json @@ -0,0 +1 @@ +{"range":{"administration.revision":{"lte":2.0}}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json new file mode 100644 index 000000000..1fc680409 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json @@ -0,0 +1 @@ +{"bool":{"must":[{"term":{"value.keyword":{"value":"03-01"}}},{"term":{"language":{"value":"nl"}}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json new file mode 100644 index 000000000..579a50574 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json @@ -0,0 +1 @@ +{"bool":{"minimum_should_match":"1","should":[{"bool":{"must":[{"term":{"assetInformation.specificAssetIds.name.keyword":{"value":"supplierId"}}},{"term":{"assetInformation.specificAssetIds.value.keyword":{"value":"aas-1"}}}]}},{"bool":{"must":[{"term":{"assetInformation.specificAssetIds.name.keyword":{"value":"customerId"}}},{"term":{"assetInformation.specificAssetIds.value.keyword":{"value":"aas-2"}}}]}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_regex_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_regex_field_strval.json new file mode 100644 index 000000000..aa8908093 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_regex_field_strval.json @@ -0,0 +1,7 @@ +{ + "regexp": { + "idShort.keyword": { + "value": "Sensor_[0-9]+" + } + } +} \ No newline at end of file From d20f9a5c81c8b79e86fbdf8ba29d31874c004810 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 1 Jul 2025 15:16:37 +0200 Subject: [PATCH 16/52] WIP Adds QL for other modules --- .run/AasRegistryLogMongoDB.run.xml | 12 + .run/SubmodelRegistryLogMongoDB.run.xml | 12 + .../basyx.aasenvironment.component/pom.xml | 513 +++++++------- .../src/main/resources/application.properties | 29 +- .../search/SearchAasRepositoryHTTPApi.java | 2 +- .../pom.xml | 57 ++ ...isableSearchCdRepositoryConfiguration.java | 49 ++ .../feature/search/SearchCdRepository.java | 133 ++++ .../SearchCdRepositoryApiHTTPController.java | 70 ++ .../SearchCdRepositoryConfiguration.java | 36 + .../SearchCdRepositoryConfigurationGuard.java | 80 +++ .../search/SearchCdRepositoryFactory.java | 50 ++ .../search/SearchCdRepositoryFeature.java | 77 +++ .../search/SearchCdRepositoryHTTPApi.java | 56 ++ basyx.conceptdescriptionrepository/pom.xml | 51 +- .../pom.xml | 44 ++ ...leSearchSubmodelRegistryConfiguration.java | 49 ++ ...archSubmodelRegistryApiHTTPController.java | 71 ++ .../SearchSubmodelRegistryConfiguration.java | 35 + ...rchSubmodelRegistryConfigurationGuard.java | 93 +++ .../search/SearchSubmodelRegistryFeature.java | 76 ++ .../search/SearchSubmodelRegistryHTTPApi.java | 82 +++ .../search/SearchSubmodelRegistryStorage.java | 114 +++ .../pom.xml | 142 ++-- .../pom.xml | 136 ++-- basyx.submodelregistry/pom.xml | 647 +++++++++--------- .../pom.xml | 59 ++ ...SearchSubmodelRepositoryConfiguration.java | 49 ++ .../search/SearchSubmodelRepository.java | 219 ++++++ ...chSubmodelRepositoryApiHTTPController.java | 70 ++ ...SearchSubmodelRepositoryConfiguration.java | 36 + ...hSubmodelRepositoryConfigurationGuard.java | 80 +++ .../SearchSubmodelRepositoryFactory.java | 50 ++ .../SearchSubmodelRepositoryFeature.java | 78 +++ .../SearchSubmodelRepositoryHTTPApi.java | 98 +++ .../pom.xml | 474 ++++++------- .../src/main/resources/application.properties | 14 +- basyx.submodelrepository/pom.xml | 61 +- pom.xml | 15 + 39 files changed, 3017 insertions(+), 1002 deletions(-) create mode 100644 .run/AasRegistryLogMongoDB.run.xml create mode 100644 .run/SubmodelRegistryLogMongoDB.run.xml create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfiguration.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryFeature.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryStorage.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java diff --git a/.run/AasRegistryLogMongoDB.run.xml b/.run/AasRegistryLogMongoDB.run.xml new file mode 100644 index 000000000..a7b030b91 --- /dev/null +++ b/.run/AasRegistryLogMongoDB.run.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.run/SubmodelRegistryLogMongoDB.run.xml b/.run/SubmodelRegistryLogMongoDB.run.xml new file mode 100644 index 000000000..5aade8839 --- /dev/null +++ b/.run/SubmodelRegistryLogMongoDB.run.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml b/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml index b8f8f1950..b46a52306 100644 --- a/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml +++ b/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml @@ -1,246 +1,267 @@ - - 4.0.0 - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment - ${revision} - - - basyx.aasenvironment.component - BaSyx AAS Environment Component - BaSyx AAS Environment Component - - - aas-environment - http://localhost:${docker.host.port}/shells - - - - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository.component - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-backend - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-backend - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository.component - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.conceptdescriptionrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.mongodbcore - test - - - org.eclipse.digitaltwin.basyx - basyx.conceptdescriptionrepository.component - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment-core - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment-http - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-feature-kafka - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.http - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-core - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.http - tests - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - test - - - org.springframework.boot - spring-boot-starter-actuator - - - org.eclipse.digitaltwin.aas4j - aas4j-dataformat-xml - - - org.eclipse.digitaltwin.aas4j - aas4j-dataformat-aasx - - - org.xmlunit - xmlunit-core - - - org.xmlunit - xmlunit-matchers - - - org.apache.httpcomponents.client5 - httpclient5 - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.eclipse.digitaltwin.basyx - basyx.mongodbcore - - - - - - org.springframework.boot - spring-boot-maven-plugin - - exec - - - - - - - - docker - - - docker.namespace - - - - - - io.fabric8 - docker-maven-plugin - - - - - - - ${docker.host.port}:${docker.container.port} - - - - ${docker.container.waitForEndpoint} - - - - - - - - - build-docker - - - push-docker - - - docker-compose-up - pre-integration-test - - start - - - - docker-compose-down - post-integration-test - - stop - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - - - + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment + ${revision} + + + basyx.aasenvironment.component + BaSyx AAS Environment Component + BaSyx AAS Environment Component + + + aas-environment + http://localhost:${docker.host.port}/shells + + + + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository.component + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-backend + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-backend + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository.component + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.mongodbcore + test + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository.component + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment-core + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment-http + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-kafka + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.http + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-core + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.http + tests + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.boot + spring-boot-starter-actuator + + + org.eclipse.digitaltwin.aas4j + aas4j-dataformat-xml + + + org.eclipse.digitaltwin.aas4j + aas4j-dataformat-aasx + + + org.xmlunit + xmlunit-core + + + org.xmlunit + xmlunit-matchers + + + org.apache.httpcomponents.client5 + httpclient5 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.eclipse.digitaltwin.basyx + basyx.mongodbcore + + + org.eclipse.digitaltwin.basyx + basyx.querycore + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + + + + docker + + + docker.namespace + + + + + + io.fabric8 + docker-maven-plugin + + + + + + + ${docker.host.port}:${docker.container.port} + + + + ${docker.container.waitForEndpoint} + + + + + + + + + build-docker + + + push-docker + + + docker-compose-up + pre-integration-test + + start + + + + docker-compose-down + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + diff --git a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties index ff9ec689f..81fd138ec 100644 --- a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties +++ b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties @@ -1,9 +1,16 @@ server.port=8081 spring.application.name=AAS Environment -basyx.backend = InMemory - -#basyx.backend = MongoDB +#basyx.backend = InMemory +basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 +basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +basyx.backend = MongoDB +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aasenv +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword # spring.data.mongodb.host=mongo # or spring.data.mongodb.host=127.0.0.1 # spring.data.mongodb.port=27017 @@ -78,8 +85,9 @@ basyx.backend = InMemory #################################################################################### # Feature: Registry Integration #################################################################################### -#basyx.aasrepository.feature.registryintegration=http://localhost:8050 -#basyx.externalurl=http://localhost:8081 +basyx.submodelrepository.feature.registryintegration=http://localhost:8083 +basyx.aasrepository.feature.registryintegration=http://localhost:8082 +basyx.externalurl=http://localhost:8081 #basyx.aasrepository.feature.registryintegration.authorization.enabled=true #basyx.aasrepository.feature.registryintegration.authorization.token-endpoint=http://localhost/realms/BaSyx/protocol/openid-connect/token #basyx.aasrepository.feature.registryintegration.authorization.grant-type = CLIENT_CREDENTIALS @@ -101,3 +109,14 @@ basyx.backend = InMemory #################################################################################### #springdoc.swagger-ui.enabled=false #springdoc.api-docs.enabled=false + + +basyx.aasrepository.feature.search.enabled=true +basyx.aasrepository.feature.search.indexname=aas-index-test +basyx.cdrepository.feature.search.enabled=true +basyx.cdrepository.feature.search.indexname=cd-index-test +basyx.submodelrepository.feature.search.enabled=true +basyx.submodelrepository.feature.search.indexname=sm-index-test +spring.elasticsearch.uris=http://localhost:9200 +spring.elasticsearch.username=elastic +spring.elasticsearch.password=vtzJFt1b \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java index b181b4b48..c557f7c06 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -52,7 +52,7 @@ public interface SearchAasRepositoryHTTPApi { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Requested Asset Administration Shells", content = @Content(mediaType = "application/json", - schema = @Schema(implementation = String.class))), + schema = @Schema(implementation = QueryResponse.class))), @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), @ApiResponse(responseCode = "401", description = "Unauthorized", diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml new file mode 100644 index 000000000..e05330719 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository + ${revision} + + + basyx.conceptdescriptionrepository-feature-search + + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-core + + + org.eclipse.digitaltwin.basyx + basyx.querycore + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter-web + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + org.eclipse.digitaltwin.basyx + basyx.http + + + javax.annotation + javax.annotation-api + 1.3.2 + compile + + + + \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java new file mode 100644 index 000000000..f2f1fa4a2 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to prevent Elasticsearch from being injected by Spring + * @author fried + */ +@Configuration +@ConditionalOnProperty(name = {SearchCdRepositoryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@EnableAutoConfiguration(exclude = { + ElasticsearchClientAutoConfiguration.class, + ElasticsearchRepositoriesAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class, + ElasticsearchRestClientAutoConfiguration.class +}) +public class DisableSearchCdRepositoryConfiguration { +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java new file mode 100644 index 000000000..ae0c674a0 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.aas4j.v3.model.*; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.List; + +public class SearchCdRepository implements ConceptDescriptionRepository { + private static final Logger logger = LoggerFactory.getLogger(SearchCdRepository.class); + private final ElasticsearchClient esclient; + private final String indexName; + + private ConceptDescriptionRepository decorated; + + public SearchCdRepository(ConceptDescriptionRepository decorated, ElasticsearchClient esclient, String indexName) { + this.decorated = decorated; + this.esclient = esclient; + this.indexName = indexName; + } + + @Override + public CursorResult> getAllConceptDescriptions(PaginationInfo pInfo) { + return decorated.getAllConceptDescriptions(pInfo); + } + + @Override + public CursorResult> getAllConceptDescriptionsByIdShort(String idShort, PaginationInfo pInfo) { + return decorated.getAllConceptDescriptionsByIdShort(idShort, pInfo); + } + + @Override + public CursorResult> getAllConceptDescriptionsByIsCaseOf(Reference isCaseOf, PaginationInfo pInfo) { + return decorated.getAllConceptDescriptionsByIsCaseOf(isCaseOf, pInfo); + } + + @Override + public CursorResult> getAllConceptDescriptionsByDataSpecificationReference(Reference dataSpecificationReference, PaginationInfo pInfo) { + return decorated.getAllConceptDescriptionsByDataSpecificationReference(dataSpecificationReference, pInfo); + } + + @Override + public ConceptDescription getConceptDescription(String conceptDescriptionId) throws ElementDoesNotExistException { + return decorated.getConceptDescription(conceptDescriptionId); + } + + @Override + public void updateConceptDescription(String conceptDescriptionId, ConceptDescription conceptDescription) throws ElementDoesNotExistException { + decorated.updateConceptDescription(conceptDescriptionId, conceptDescription); + updateCDIndex(conceptDescription); + } + + @Override + public void createConceptDescription(ConceptDescription conceptDescription) throws CollidingIdentifierException, MissingIdentifierException { + decorated.createConceptDescription(conceptDescription); + indexCD(conceptDescription); + } + + @Override + public void deleteConceptDescription(String conceptDescriptionId) throws ElementDoesNotExistException { + decorated.deleteConceptDescription(conceptDescriptionId); + deindexCD(conceptDescriptionId); + } + + private void indexCD(ConceptDescription cd) { + try { + esclient.create( + c -> c.index(indexName) + .id(cd.getId()) + .document(cd) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void updateCDIndex(ConceptDescription cd) { + try { + esclient.update( + u -> u.index(indexName) + .id(cd.getId()) + .doc(cd), + ConceptDescription.class + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void deindexCD(String cdId) { + try { + esclient.delete( + d -> d.index(indexName) + .id(cdId) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java new file mode 100644 index 000000000..c903d2989 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") +@RestController +@ConditionalOnExpression("#{${" + SearchCdRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +public class SearchCdRepositoryApiHTTPController implements SearchCdRepositoryHTTPApi { + + private final ElasticsearchClient client; + + @Value("${" + SearchCdRepositoryFeature.FEATURENAME + ".indexname:" + SearchCdRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Autowired + public SearchCdRepositoryApiHTTPController(ElasticsearchClient client) { + this.client = client; + } + + @Override + public ResponseEntity queryConceptDescriptions(Integer limit, Base64UrlEncodedCursor cursor,AASQuery query) { + QueryResponse queryResponse = null; + try { + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "ConceptDescription"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return new ResponseEntity(queryResponse, HttpStatus.OK); + } + +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java new file mode 100644 index 000000000..e1917925a --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Configuration; + +@ConditionalOnExpression("#{${" + SearchCdRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Configuration +public class SearchCdRepositoryConfiguration { + +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java new file mode 100644 index 000000000..1b8a3ca10 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java @@ -0,0 +1,80 @@ +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * Class that prints error and warning messages to inform the user about possible misconfiguration + * + * @author fried, aaronzi + */ +@Component +@ConditionalOnExpression("#{${" + SearchCdRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +public class SearchCdRepositoryConfigurationGuard implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(SearchCdRepositoryConfigurationGuard.class); + + @Value("${spring.elasticsearch.uris:#{null}}") + private String elasticsearchUrl; + + @Value("${spring.elasticsearch.username:#{null}}") + private String elasticsearchUsername; + + @Value("${spring.elasticsearch.password:#{null}}") + private String elasticsearchPassword; + + @Value("${basyx.backend:#{null}}") + private String basyxBackend; + + @Value("${" + SearchCdRepositoryFeature.FEATURENAME + ".indexname:" + SearchCdRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Override + public void afterPropertiesSet() throws Exception { + boolean error = false; + logger.info(":::::::::::::::: BaSyx Feature Search Configuration ::::::::::::::::"); + + if (elasticsearchUrl == null || elasticsearchUrl.isEmpty()) { + logger.error("Elasticsearch URL is not configured. Please set the property 'spring.elasticsearch.uris'."); + error = true; + } else { + logger.info("Elasticsearch URL: " + elasticsearchUrl); + } + + if (elasticsearchUsername == null || elasticsearchUsername.isEmpty()) { + logger.error("Elasticsearch username is not configured. Please set the property 'spring.elasticsearch.username'."); + error = true; + } else { + logger.info("Elasticsearch Username: " + elasticsearchUsername); + } + + if (elasticsearchPassword == null || elasticsearchPassword.isEmpty()) { + logger.error("Elasticsearch password is not configured. Please set the property 'spring.elasticsearch.password'."); + error = true; + } else { + logger.info("Elasticsearch Password: " + "***"); + } + + if(basyxBackend.equals("InMemory")){ + logger.error("BaSyx Backend is set to InMemory. Search feature requires a persistent backend."); + error = true; + } else { + logger.info("BaSyx Backend: " + basyxBackend); + } + + if (indexName == null || indexName.isEmpty()) { + logger.error("Index name is not configured. Please set the property '" + SearchCdRepositoryFeature.FEATURENAME + ".indexname'."); + error = true; + } else { + logger.info("Index Name: " + indexName); + } + + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + if(error){ + System.exit(1); + } + } +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java new file mode 100644 index 000000000..9ea6109e0 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepositoryFactory; + +public class SearchCdRepositoryFactory implements ConceptDescriptionRepositoryFactory { + + private final ElasticsearchClient esclient; + private final String indexName; + private ConceptDescriptionRepositoryFactory decorated; + + public SearchCdRepositoryFactory(ConceptDescriptionRepositoryFactory decorated, ElasticsearchClient client, String indexName) { + this.decorated = decorated; + this.esclient = client; + this.indexName = indexName; + } + + @Override + public ConceptDescriptionRepository create() { + return new SearchCdRepository(decorated.create(), esclient, indexName); + } + +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java new file mode 100644 index 000000000..9e3a48143 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepositoryFactory; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.feature.ConceptDescriptionRepositoryFeature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +@ConditionalOnExpression("#{${" + SearchCdRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Component +public class SearchCdRepositoryFeature implements ConceptDescriptionRepositoryFeature { + public static final String FEATURENAME = "basyx.cdrepository.feature.search"; + public static final String DEFAULT_INDEX = "aas-index"; + private final ElasticsearchClient esclient; + + @Value("#{${" + FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") + private boolean enabled; + + @Value("${" + SearchCdRepositoryFeature.FEATURENAME + ".indexname:" + SearchCdRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Autowired + public SearchCdRepositoryFeature(ElasticsearchClient client) { + this.esclient = client; + } + + @Override + public ConceptDescriptionRepositoryFactory decorate(ConceptDescriptionRepositoryFactory aasServiceFactory) { + return new SearchCdRepositoryFactory(aasServiceFactory, esclient, indexName); + } + + @Override + public void initialize() { + } + + @Override + public void cleanUp() { + } + + @Override + public String getName() { + return "CdRepository Search"; + } + + @Override + public boolean isEnabled() { + return enabled; + } +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java new file mode 100644 index 000000000..f91f18700 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java @@ -0,0 +1,56 @@ +/** + * NOTE: This class is auto generated by the swagger code generator program (3.0.69). + * https://github.com/swagger-api/swagger-codegen + * Do not edit the class manually. + */ +package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import jakarta.validation.Valid; +import org.eclipse.digitaltwin.aas4j.v3.model.Result; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-07-01T11:29:58.878891927Z[GMT]") +@Validated +public interface SearchCdRepositoryHTTPApi { + + @Operation(summary = "Returns all Concept Descriptions that confirm to the input query", description = "", tags={ "Concept Description Repository API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested Concept Descriptions", content = @Content(mediaType = "application/json", schema = @Schema(implementation = QueryResponse.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/query/concept-descriptions", + produces = { "application/json" }, + consumes = { "application/json" }, + method = RequestMethod.POST) + ResponseEntity queryConceptDescriptions(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" +)) @Valid @RequestParam(value = "limit", required = false) Integer limit +, @Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) Base64UrlEncodedCursor cursor +, @Parameter(in = ParameterIn.DEFAULT, description = "", schema=@Schema()) @Valid @RequestBody AASQuery body +); + +} + diff --git a/basyx.conceptdescriptionrepository/pom.xml b/basyx.conceptdescriptionrepository/pom.xml index 02a09184e..87e68498f 100644 --- a/basyx.conceptdescriptionrepository/pom.xml +++ b/basyx.conceptdescriptionrepository/pom.xml @@ -1,26 +1,27 @@ - - 4.0.0 - - - org.eclipse.digitaltwin.basyx - basyx.parent - ${revision} - - - basyx.conceptdescriptionrepository - Concept Description Repository - BaSyx Concept Description Repository - pom - - - basyx.conceptdescriptionrepository-core - basyx.conceptdescriptionrepository-http - basyx.conceptdescriptionrepository-backend - basyx.conceptdescriptionrepository-backend-inmemory - basyx.conceptdescriptionrepository-backend-mongodb - basyx.conceptdescriptionrepository-feature-authorization - basyx.conceptdescriptionrepository-tck - basyx.conceptdescriptionrepository.component - + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.parent + ${revision} + + + basyx.conceptdescriptionrepository + Concept Description Repository + BaSyx Concept Description Repository + pom + + + basyx.conceptdescriptionrepository-core + basyx.conceptdescriptionrepository-http + basyx.conceptdescriptionrepository-backend + basyx.conceptdescriptionrepository-backend-inmemory + basyx.conceptdescriptionrepository-backend-mongodb + basyx.conceptdescriptionrepository-feature-authorization + basyx.conceptdescriptionrepository-tck + basyx.conceptdescriptionrepository.component + basyx.conceptdescriptionrepository-feature-search + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml new file mode 100644 index 000000000..cc1e7e6e9 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + + + basyx.submodelregistry-feature-search + BaSyx Submodel Registry Feature Search + Feature Search for the BaSyx Submodel Registry + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + + + org.eclipse.digitaltwin.basyx + basyx.querycore + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + + + org.springframework + spring-context + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java new file mode 100644 index 000000000..4b4deda15 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to prevent Elasticsearch from being injected by Spring + * @author fried + */ +@Configuration +@ConditionalOnProperty(name = {SearchSubmodelRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@EnableAutoConfiguration(exclude = { + ElasticsearchClientAutoConfiguration.class, + ElasticsearchRepositoriesAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class, + ElasticsearchRestClientAutoConfiguration.class +}) +public class DisableSearchSubmodelRegistryConfiguration { +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java new file mode 100644 index 000000000..95fed4656 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") +@RestController +public class SearchSubmodelRegistryApiHTTPController implements SearchSubmodelRegistryHTTPApi { + + private static final Logger log = LoggerFactory.getLogger(SearchSubmodelRegistryApiHTTPController.class); + + private final ElasticsearchClient esClient; + + @Value("${" + SearchSubmodelRegistryFeature.FEATURENAME + ".indexname:" + SearchSubmodelRegistryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Autowired + public SearchSubmodelRegistryApiHTTPController(ElasticsearchClient esClient) { + this.esClient = esClient; + } + + public ResponseEntity querySubmodelDescriptors(Integer limit, Base64UrlEncodedCursor cursor, AASQuery query) { + QueryResponse queryResponse; + try { + ESQueryExecutor executor = new ESQueryExecutor(esClient, indexName, "SubmodelDescriptor"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return new ResponseEntity<>(queryResponse, HttpStatus.OK); + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfiguration.java new file mode 100644 index 000000000..971285a1e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfiguration.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Configuration; + +@ConditionalOnExpression("#{${" + SearchSubmodelRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Configuration +public class SearchSubmodelRegistryConfiguration { + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java new file mode 100644 index 000000000..87a18dfd9 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Class that prints error and warning messages to inform the user about possible misconfiguration + * + * @author jannisjung, aaronzi + */ +@Component +public class SearchSubmodelRegistryConfigurationGuard implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(SearchSubmodelRegistryConfigurationGuard.class); + + @Value("${spring.elasticsearch.uris:#{null}}") + private String elasticsearchUrl; + + @Value("${spring.elasticsearch.username:#{null}}") + private String elasticsearchUsername; + + @Value("${spring.elasticsearch.password:#{null}}") + private String elasticsearchPassword; + + @Value("${" + SearchSubmodelRegistryFeature.FEATURENAME + ".indexname:" + SearchSubmodelRegistryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Override + public void afterPropertiesSet() throws Exception { + boolean error = false; + logger.info(":::::::::::::::: BaSyx Feature Search Configuration ::::::::::::::::"); + + if (elasticsearchUrl == null || elasticsearchUrl.isEmpty()) { + logger.error("Elasticsearch URL is not configured. Please set the property 'spring.elasticsearch.uris'."); + error = true; + } else { + logger.info("Elasticsearch URL: " + elasticsearchUrl); + } + + if (elasticsearchUsername == null || elasticsearchUsername.isEmpty()) { + logger.error("Elasticsearch username is not configured. Please set the property 'spring.elasticsearch.username'."); + error = true; + } else { + logger.info("Elasticsearch Username: " + elasticsearchUsername); + } + + if (elasticsearchPassword == null || elasticsearchPassword.isEmpty()) { + logger.error("Elasticsearch password is not configured. Please set the property 'spring.elasticsearch.password'."); + error = true; + } else { + logger.info("Elasticsearch Password: " + "***"); + } + + if (indexName == null || indexName.isEmpty()) { + logger.error("Index name is not configured. Please set the property '" + SearchSubmodelRegistryFeature.FEATURENAME + ".indexname'."); + error = true; + } else { + logger.info("Index Name: " + indexName); + } + + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + if(error){ + System.exit(1); + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryFeature.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryFeature.java new file mode 100644 index 000000000..18750f8af --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryFeature.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorageFeature; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Hierarchical {@link SubmodelRegistryStorage} feature + * + * When this feature is enabled, Submodel Descriptors will be indexed with ElasticSearch + * + * @author jannisjung, zielstor + */ + +@Component +@ConditionalOnExpression("#{${" + SearchSubmodelRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Order(1) +public class SearchSubmodelRegistryFeature implements SubmodelRegistryStorageFeature { + public final static String FEATURENAME = "basyx.submodelregistry.feature.search"; + public static final String DEFAULT_INDEX = "sm-descr-index"; + private final ElasticsearchClient esclient; + + public SearchSubmodelRegistryFeature(ElasticsearchClient esclient) { + this.esclient = esclient; + } + + @Value("#{${" + FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") + private boolean enabled; + + @Value("${" + FEATURENAME + ".indexname:" + DEFAULT_INDEX + "}") + private String indexName; + + @Override + public SubmodelRegistryStorage decorate(SubmodelRegistryStorage smRegistryStorage) { + return new SearchSubmodelRegistryStorage(smRegistryStorage, esclient, indexName); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public String getName() { + return FEATURENAME; + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java new file mode 100644 index 000000000..ad9af820e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +/** + * NOTE: This class is auto generated by the swagger code generator program (3.0.68). + * https://github.com/swagger-api/swagger-codegen + * Do not edit the class manually. + */ +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import org.eclipse.digitaltwin.aas4j.v3.model.Result; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-07-01T11:48:36.425983614Z[GMT]") +@Validated +public interface SearchSubmodelRegistryHTTPApi { + + @Operation(summary = "Returns all Submodel Descriptors that confirm to the input query", description = "", tags={ "Submodel Registry API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested Submodels", content = @Content(mediaType = "application/json", schema = @Schema(implementation = QueryResponse.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/query/submodel-descriptors", + produces = { "application/json" }, + consumes = { "application/json" }, + method = RequestMethod.POST) + ResponseEntity querySubmodelDescriptors(@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1" + )) @Valid @RequestParam(value = "limit", required = false) Integer limit + , @Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) Base64UrlEncodedCursor cursor + , @Parameter(in = ParameterIn.DEFAULT, description = "", schema=@Schema()) @Valid @RequestBody AASQuery body + ); + +} + diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryStorage.java new file mode 100644 index 000000000..1513ebd11 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryStorage.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +public class SearchSubmodelRegistryStorage implements SubmodelRegistryStorage { + private final ElasticsearchClient esclient; + private final SubmodelRegistryStorage decorated; + + private final String indexName; + + + SearchSubmodelRegistryStorage(SubmodelRegistryStorage decorated, ElasticsearchClient esclient, String indexName) { + this.decorated = decorated; + this.esclient = esclient; + this.indexName = indexName; + } + + @Override + public CursorResult> getAllSubmodelDescriptors(PaginationInfo pRequest) { + return decorated.getAllSubmodelDescriptors(pRequest); + } + + @Override + public SubmodelDescriptor getSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException { + return decorated.getSubmodelDescriptor(submodelId); + } + + @Override + public void insertSubmodelDescriptor(SubmodelDescriptor descr) throws SubmodelAlreadyExistsException { + decorated.insertSubmodelDescriptor(descr); + indexSubmodelDescriptor(descr); + } + + @Override + public void replaceSubmodelDescriptor(String submodelId, SubmodelDescriptor descr) throws SubmodelNotFoundException { + decorated.replaceSubmodelDescriptor(submodelId, descr); + reindexSubmodelDescriptor(submodelId); + } + + @Override + public void removeSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException { + decorated.removeSubmodelDescriptor(submodelId); + deindexSubmodelDescriptor(submodelId); + } + + @Override + public Set clear() { + return Set.of(); + } + + private void indexSubmodelDescriptor(SubmodelDescriptor descr) { + try { + esclient.create( + c -> c.index(indexName) + .id(descr.getId()) + .document(descr) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void deindexSubmodelDescriptor(String smDescrId) { + try { + esclient.delete( + d -> d.index(indexName) + .id(smDescrId) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void reindexSubmodelDescriptor(String smDescrId) { + SubmodelDescriptor descr = getSubmodelDescriptor(smDescrId); + deindexSubmodelDescriptor(smDescrId); + indexSubmodelDescriptor(descr); + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml index 94f2dc4b2..c4f9be227 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml @@ -1,69 +1,73 @@ - - - 4.0.0 - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry - ${revision} - - - basyx.submodelregistry-service-release-kafka-mongodb - BaSyx Submodel Registry Service Release Kafka MongoDB - BaSyx Submodel Registry Service Release Kafka MongoDB - - - 2020.0.4 - org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication - submodel-registry-kafka-mongodb - - - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-hierarchy - - - org.eclipse.digitaltwin.basyx - basyx.authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-mongodb-storage - - - org.apache.commons - commons-lang3 - - - org.junit.vintage - junit-vintage-engine - test - - - org.hamcrest - hamcrest-core - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-kafka-events - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basetests - test - - - + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + + + basyx.submodelregistry-service-release-kafka-mongodb + BaSyx Submodel Registry Service Release Kafka MongoDB + BaSyx Submodel Registry Service Release Kafka MongoDB + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-kafka-mongodb + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + + + org.apache.commons + commons-lang3 + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-kafka-events + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml index 6c50463fe..732c87af3 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml @@ -1,66 +1,70 @@ - - - 4.0.0 - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry - ${revision} - - - - basyx.submodelregistry-service-release-log-mongodb - BaSyx Submodel Registry Service Release Log MongoDB - BaSyx Submodel Registry Service Release Log MongoDB - - - 2020.0.4 - org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication - submodel-registry-log-mongodb - - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-hierarchy - - - org.eclipse.digitaltwin.basyx - basyx.authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-mongodb-storage - - - org.junit.vintage - junit-vintage-engine - test - - - org.hamcrest - hamcrest-core - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.apache.commons - commons-lang3 - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basetests - test - - - - + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + + + + basyx.submodelregistry-service-release-log-mongodb + BaSyx Submodel Registry Service Release Log MongoDB + BaSyx Submodel Registry Service Release Log MongoDB + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-log-mongodb + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.commons + commons-lang3 + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + + + + diff --git a/basyx.submodelregistry/pom.xml b/basyx.submodelregistry/pom.xml index 5c1b4ae0f..53db67cb7 100644 --- a/basyx.submodelregistry/pom.xml +++ b/basyx.submodelregistry/pom.xml @@ -1,323 +1,324 @@ - - - 4.0.0 - basyx.submodelregistry - BaSyx submodelregistry - BaSyx submodelregistry - pom - - - org.eclipse.digitaltwin.basyx - basyx.parent - ${revision} - - - - 1.18.20.0 - 1.18.38 - ${java.version} - ${java.version} - src/generated - 3.0.68 - 3.0.0 - open-api - - Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml - Plattform_i40-SubmodelRegistry-and-Discovery.yaml - 33.4.8-jre - 3.6.0 - 3.0-alpha-2 - 0.9.14 - ${project.artifactId} - 6.6.0 - 0.2.6 - 3.0.2 - 0.5.1 - patch-base-extensions.yaml - - - - - basyx.submodelregistry-service-basemodel - basyx.submodelregistry-service - basyx.submodelregistry-service-inmemory-storage - basyx.submodelregistry-service-release-log-mem - basyx.submodelregistry-client-native - basyx.submodelregistry-service-mongodb-storage - basyx.submodelregistry-service-kafka-events - basyx.submodelregistry-service-basetests - basyx.submodelregistry-service-release-kafka-mem - basyx.submodelregistry-service-release-log-mongodb - basyx.submodelregistry-service-release-kafka-mongodb - basyx.submodelregistry-feature-authorization - basyx.submodelregistry-feature-hierarchy - basyx.submodelregistry-feature-hierarchy-example - - - - - Gerhard Sonnenberg - gerhard.sonnenberg@dfki.de - DFKI GmbH - https://www.dfki.de/en/web - - developer - - - - - - - - org.projectlombok - lombok-maven-plugin - ${lombok.maven-plugin.version} - - - org.projectlombok - lombok - ${lombok.maven-plugin.lombok.version} - - - - - io.swagger.codegen.v3 - swagger-codegen-maven-plugin - ${swagger.codegen.version} - - - - org.openapitools - openapi-generator-maven-plugin - ${openapitools.version} - - - de.dfki.cos.basys.common - jsonpatch-maven-plugin - ${jsonpatch.plugin.version} - - - - - - - - com.google.code.findbugs - jsr305 - ${jsr305.version} - - - org.openapitools - openapi-generator - ${openapitools.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-kafka-events - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-mongodb-storage - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basemodel - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-client-native - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-inmemory-storage - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basetests - test - ${project.version} - - - io.springfox - springfox-oas - ${spring.fox.version} - - - io.springfox - springfox-swagger-ui - ${spring.fox.version} - - - org.openapitools - jackson-databind-nullable - ${openapitools.jacksonnullable.version} - - - com.google.guava - guava - ${guava.version} - - - org.apache.maven - maven-plugin-api - ${maven-plugin.version} - - - org.apache.maven.plugin-tools - maven-plugin-annotations - ${maven-plugin.version} - - - org.apache.maven - maven-project - ${maven-project.version} - - - org.apache.maven.plugins - maven-plugin-plugin - ${maven-plugin.version} - - - com.github.spullara.mustache.java - compiler - ${mustache.compiler.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-authorization - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-hierarchy - ${project.version} - - - - - - delombok - - - src/main/lombok - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - generate-sources - - add-source - - - - src/main/generated - - - - - - - org.projectlombok - lombok-maven-plugin - - - org.projectlombok - lombok - ${lombok.maven-plugin.lombok.version} - - - - - generate-sources - - delombok - - - - - - - - - dockerbuild - - - - src/main/docker/Dockerfile - - - docker.namespace - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - true - true - - - - - - repackage - - - - - - io.fabric8 - docker-maven-plugin - - - - - - artifact - - Dockerfile - - ${project.basedir}/src/main/docker - - ${docker.target.platforms} - - - - - - - - build-docker - - - push-docker - - - - - - - - + + + 4.0.0 + basyx.submodelregistry + BaSyx submodelregistry + BaSyx submodelregistry + pom + + + org.eclipse.digitaltwin.basyx + basyx.parent + ${revision} + + + + 1.18.20.0 + 1.18.38 + ${java.version} + ${java.version} + src/generated + 3.0.68 + 3.0.0 + open-api + + Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml + Plattform_i40-SubmodelRegistry-and-Discovery.yaml + 33.4.8-jre + 3.6.0 + 3.0-alpha-2 + 0.9.14 + ${project.artifactId} + 6.6.0 + 0.2.6 + 3.0.2 + 0.5.1 + patch-base-extensions.yaml + + + + + basyx.submodelregistry-service-basemodel + basyx.submodelregistry-service + basyx.submodelregistry-service-inmemory-storage + basyx.submodelregistry-service-release-log-mem + basyx.submodelregistry-client-native + basyx.submodelregistry-service-mongodb-storage + basyx.submodelregistry-service-kafka-events + basyx.submodelregistry-service-basetests + basyx.submodelregistry-service-release-kafka-mem + basyx.submodelregistry-service-release-log-mongodb + basyx.submodelregistry-service-release-kafka-mongodb + basyx.submodelregistry-feature-authorization + basyx.submodelregistry-feature-hierarchy + basyx.submodelregistry-feature-hierarchy-example + basyx.submodelregistry-feature-search + + + + + Gerhard Sonnenberg + gerhard.sonnenberg@dfki.de + DFKI GmbH + https://www.dfki.de/en/web + + developer + + + + + + + + org.projectlombok + lombok-maven-plugin + ${lombok.maven-plugin.version} + + + org.projectlombok + lombok + ${lombok.maven-plugin.lombok.version} + + + + + io.swagger.codegen.v3 + swagger-codegen-maven-plugin + ${swagger.codegen.version} + + + + org.openapitools + openapi-generator-maven-plugin + ${openapitools.version} + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + ${jsonpatch.plugin.version} + + + + + + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + + + org.openapitools + openapi-generator + ${openapitools.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-kafka-events + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-client-native + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-inmemory-storage + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + ${project.version} + + + io.springfox + springfox-oas + ${spring.fox.version} + + + io.springfox + springfox-swagger-ui + ${spring.fox.version} + + + org.openapitools + jackson-databind-nullable + ${openapitools.jacksonnullable.version} + + + com.google.guava + guava + ${guava.version} + + + org.apache.maven + maven-plugin-api + ${maven-plugin.version} + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin.version} + + + org.apache.maven + maven-project + ${maven-project.version} + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin.version} + + + com.github.spullara.mustache.java + compiler + ${mustache.compiler.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-authorization + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + ${project.version} + + + + + + delombok + + + src/main/lombok + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + src/main/generated + + + + + + + org.projectlombok + lombok-maven-plugin + + + org.projectlombok + lombok + ${lombok.maven-plugin.lombok.version} + + + + + generate-sources + + delombok + + + + + + + + + dockerbuild + + + + src/main/docker/Dockerfile + + + docker.namespace + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + true + true + + + + + + repackage + + + + + + io.fabric8 + docker-maven-plugin + + + + + + artifact + + Dockerfile + + ${project.basedir}/src/main/docker + + ${docker.target.platforms} + + + + + + + + build-docker + + + push-docker + + + + + + + + diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml new file mode 100644 index 000000000..80a225622 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository + ${revision} + + + basyx.submodelrepository-feature-search + BaSyx Submodel Repository Feature Search + Feature Search for the BaSyx Submodel Repository + + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-core + + + org.eclipse.digitaltwin.basyx + basyx.querycore + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter-web + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + org.eclipse.digitaltwin.basyx + basyx.http + + + javax.annotation + javax.annotation-api + 1.3.2 + compile + + + + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java new file mode 100644 index 000000000..38999cd32 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to prevent Elasticsearch from being injected by Spring + * @author fried + */ +@Configuration +@ConditionalOnProperty(name = {SearchSubmodelRepositoryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@EnableAutoConfiguration(exclude = { + ElasticsearchClientAutoConfiguration.class, + ElasticsearchRepositoriesAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class, + ElasticsearchRestClientAutoConfiguration.class +}) +public class DisableSearchSubmodelRepositoryConfiguration { +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java new file mode 100644 index 000000000..5c624cfd3 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.aas4j.v3.model.*; +import org.eclipse.digitaltwin.basyx.core.exceptions.*; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class SearchSubmodelRepository implements SubmodelRepository { + private static final Logger logger = LoggerFactory.getLogger(SearchSubmodelRepository.class); + private final ElasticsearchClient esclient; + private final String indexName; + + private SubmodelRepository decorated; + + public SearchSubmodelRepository(SubmodelRepository decorated, ElasticsearchClient esclient, String indexName) { + this.decorated = decorated; + this.esclient = esclient; + this.indexName = indexName; + } + + @Override + public CursorResult> getAllSubmodels(PaginationInfo pInfo) { + return decorated.getAllSubmodels(pInfo); + } + + @Override + public CursorResult> getAllSubmodels(String semanticId, PaginationInfo pInfo) { + return decorated.getAllSubmodels(semanticId, pInfo); + } + + @Override + public Submodel getSubmodel(String submodelId) throws ElementDoesNotExistException { + return decorated.getSubmodel(submodelId); + } + + @Override + public void updateSubmodel(String submodelId, Submodel submodel) throws ElementDoesNotExistException { + decorated.updateSubmodel(submodelId, submodel); + updateSMIndex(submodel); + } + + @Override + public void createSubmodel(Submodel submodel) throws CollidingIdentifierException, MissingIdentifierException { + decorated.createSubmodel(submodel); + indexSM(submodel); + } + + @Override + public void updateSubmodelElement(String submodelIdentifier, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { + decorated.updateSubmodelElement(submodelIdentifier, idShortPath, submodelElement); + reindexSM(submodelIdentifier); + } + + @Override + public void deleteSubmodel(String submodelId) throws ElementDoesNotExistException { + decorated.deleteSubmodel(submodelId); + deindexSM(submodelId); + } + + @Override + public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException { + return decorated.getSubmodelElements(submodelId, pInfo); + } + + @Override + public SubmodelElement getSubmodelElement(String submodelId, String smeIdShort) throws ElementDoesNotExistException { + return decorated.getSubmodelElement(submodelId, smeIdShort); + } + + @Override + public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) throws ElementDoesNotExistException { + return decorated.getSubmodelElementValue(submodelId, smeIdShort); + } + + @Override + public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { + decorated.setSubmodelElementValue(submodelId, smeIdShort, value); + reindexSM(submodelId); + } + + @Override + public void createSubmodelElement(String submodelId, SubmodelElement smElement) { + decorated.createSubmodelElement(submodelId, smElement); + reindexSM(submodelId); + } + + @Override + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) throws ElementDoesNotExistException { + decorated.createSubmodelElement(submodelId, idShortPath, smElement); + reindexSM(submodelId); + } + + @Override + public void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { + decorated.deleteSubmodelElement(submodelId, idShortPath); + reindexSM(submodelId); + } + + @Override + public OperationVariable[] invokeOperation(String submodelId, String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { + return decorated.invokeOperation(submodelId, idShortPath, input); + } + + @Override + public SubmodelValueOnly getSubmodelByIdValueOnly(String submodelId) throws ElementDoesNotExistException { + return decorated.getSubmodelByIdValueOnly(submodelId); + } + + @Override + public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNotExistException { + return decorated.getSubmodelByIdMetadata(submodelId); + } + + @Override + public File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + return getFileByPathSubmodel(submodelId, idShortPath); + } + + @Override + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + decorated.setFileValue(submodelId, idShortPath, fileName, inputStream); + reindexSM(submodelId); + } + + @Override + public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + decorated.deleteFileValue(submodelId, idShortPath); + reindexSM(submodelId); + } + + @Override + public void patchSubmodelElements(String submodelId, List submodelElementList) { + decorated.patchSubmodelElements(submodelId, submodelElementList); + reindexSM(submodelId); + } + + @Override + public InputStream getFileByFilePath(String submodelId, String filePath) { + return decorated.getFileByFilePath(submodelId, filePath); + } + + private void indexSM(Submodel submodel) { + try { + esclient.create( + c -> c.index(indexName) + .id(submodel.getId()) + .document(submodel) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void updateSMIndex(Submodel submodel) { + try { + esclient.update( + u -> u.index(indexName) + .id(submodel.getId()) + .doc(submodel), + Submodel.class + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void deindexSM(String smID) { + try { + esclient.delete( + d -> d.index(indexName) + .id(smID) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void reindexSM(String smId) { + Submodel submodel = getSubmodel(smId); + deindexSM(smId); + indexSM(submodel); + } + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java new file mode 100644 index 000000000..9efc015f2 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") +@RestController +@ConditionalOnExpression("#{${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +public class SearchSubmodelRepositoryApiHTTPController implements SearchSubmodelRepositoryHTTPApi { + + private final ElasticsearchClient client; + + @Value("${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".indexname:" + SearchSubmodelRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Autowired + public SearchSubmodelRepositoryApiHTTPController(ElasticsearchClient client) { + this.client = client; + } + + @Override + public ResponseEntity querySubmodels(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { + QueryResponse queryResponse = null; + try { + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "Submodel"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return new ResponseEntity(queryResponse, HttpStatus.OK); + } + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java new file mode 100644 index 000000000..231f1c7f4 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Configuration; + +@ConditionalOnExpression("#{${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Configuration +public class SearchSubmodelRepositoryConfiguration { + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java new file mode 100644 index 000000000..e1e115b9b --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java @@ -0,0 +1,80 @@ +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * Class that prints error and warning messages to inform the user about possible misconfiguration + * + * @author fried, aaronzi + */ +@Component +@ConditionalOnExpression("#{${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +public class SearchSubmodelRepositoryConfigurationGuard implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(SearchSubmodelRepositoryConfigurationGuard.class); + + @Value("${spring.elasticsearch.uris:#{null}}") + private String elasticsearchUrl; + + @Value("${spring.elasticsearch.username:#{null}}") + private String elasticsearchUsername; + + @Value("${spring.elasticsearch.password:#{null}}") + private String elasticsearchPassword; + + @Value("${basyx.backend:#{null}}") + private String basyxBackend; + + @Value("${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".indexname:" + SearchSubmodelRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Override + public void afterPropertiesSet() throws Exception { + boolean error = false; + logger.info(":::::::::::::::: BaSyx Feature Search Configuration ::::::::::::::::"); + + if (elasticsearchUrl == null || elasticsearchUrl.isEmpty()) { + logger.error("Elasticsearch URL is not configured. Please set the property 'spring.elasticsearch.uris'."); + error = true; + } else { + logger.info("Elasticsearch URL: " + elasticsearchUrl); + } + + if (elasticsearchUsername == null || elasticsearchUsername.isEmpty()) { + logger.error("Elasticsearch username is not configured. Please set the property 'spring.elasticsearch.username'."); + error = true; + } else { + logger.info("Elasticsearch Username: " + elasticsearchUsername); + } + + if (elasticsearchPassword == null || elasticsearchPassword.isEmpty()) { + logger.error("Elasticsearch password is not configured. Please set the property 'spring.elasticsearch.password'."); + error = true; + } else { + logger.info("Elasticsearch Password: " + "***"); + } + + if(basyxBackend.equals("InMemory")){ + logger.error("BaSyx Backend is set to InMemory. Search feature requires a persistent backend."); + error = true; + } else { + logger.info("BaSyx Backend: " + basyxBackend); + } + + if (indexName == null || indexName.isEmpty()) { + logger.error("Index name is not configured. Please set the property '" + SearchSubmodelRepositoryFeature.FEATURENAME + ".indexname'."); + error = true; + } else { + logger.info("Index Name: " + indexName); + } + + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + if(error){ + System.exit(1); + } + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java new file mode 100644 index 000000000..4a49afefd --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepositoryFactory; + +public class SearchSubmodelRepositoryFactory implements SubmodelRepositoryFactory { + + private final ElasticsearchClient esclient; + private final String indexName; + private SubmodelRepositoryFactory decorated; + + public SearchSubmodelRepositoryFactory(SubmodelRepositoryFactory decorated, ElasticsearchClient client, String indexName) { + this.decorated = decorated; + this.esclient = client; + this.indexName = indexName; + } + + @Override + public SubmodelRepository create() { + return new SearchSubmodelRepository(decorated.create(), esclient, indexName); + } + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java new file mode 100644 index 000000000..a5e41cb33 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.feature.SubmodelRepositoryFeature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +@ConditionalOnExpression("#{${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@Component +public class SearchSubmodelRepositoryFeature implements SubmodelRepositoryFeature { + public static final String FEATURENAME = "basyx.submodelrepository.feature.search"; + public static final String DEFAULT_INDEX = "sm-index"; + private final ElasticsearchClient esclient; + + @Value("#{${" + FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") + private boolean enabled; + + @Value("${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".indexname:" + SearchSubmodelRepositoryFeature.DEFAULT_INDEX + "}") + private String indexName; + + @Autowired + public SearchSubmodelRepositoryFeature(ElasticsearchClient client) { + this.esclient = client; + } + + @Override + public SubmodelRepositoryFactory decorate(SubmodelRepositoryFactory submodelServiceFactory) { + return new SearchSubmodelRepositoryFactory(submodelServiceFactory, esclient, indexName); + } + + @Override + public void initialize() { + } + + @Override + public void cleanUp() { + + } + + @Override + public String getName() { + return "SubmodelRepository Search"; + } + + @Override + public boolean isEnabled() { + return enabled; + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java new file mode 100644 index 000000000..3fbf74092 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.eclipse.digitaltwin.aas4j.v3.model.Result; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") +@Validated +public interface SearchSubmodelRepositoryHTTPApi { + @Operation( + summary = "Returns all Submodels that conform to the input query", + tags = { "Submodel Repository API" }, + operationId = "querySubmodels" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested Submodels", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = QueryResponse.class))), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "403", description = "Forbidden", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) + }) + @RequestMapping( + value = "/query/submodels", + produces = { "application/json" }, + consumes = { "application/json" }, + method = RequestMethod.POST + ) + ResponseEntity querySubmodels( + @Parameter( + description = "Query object", + required = true, + schema = @Schema(implementation = String.class) + ) + @Valid @RequestBody AASQuery query, + + @Parameter( + in = ParameterIn.QUERY, + description = "Maximum number of results to be returned" + ) + @RequestParam(value = "limit", required = false) Integer limit, + + @Parameter( + in = ParameterIn.QUERY, + description = "Cursor for pagination" + ) + @RequestParam(value = "cursor", required = false) Base64UrlEncodedCursor cursor + ); + + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml b/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml index beecc5013..24c6f9cc5 100644 --- a/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml @@ -1,235 +1,239 @@ - - 4.0.0 - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository - ${revision} - - - basyx.submodelrepository.component - BaSyx submodelrepository.component - BaSyx submodelrepository.component - - - submodel-repository - - http://localhost:${docker.host.port}/submodels - - - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-core - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-backend-inmemory - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-backend-inmemory - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-backend-mongodb - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-mqtt - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-registry-integration - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-registry-integration - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-authorization - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-operation-delegation - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-operation-delegation - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-http - - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - test - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-core - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-http - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.http - tests - test - - - org.apache.httpcomponents.client5 - httpclient5 - test - - - commons-io - commons-io - - - com.google.code.gson - gson - test - - - org.eclipse.digitaltwin.basyx - basyx.authorization - tests - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - exec - - - - - - - - docker - - - docker.namespace - - - - - - io.fabric8 - docker-maven-plugin - - - - - - - ${docker.host.port}:${docker.container.port} - - - - ${docker.container.waitForEndpoint} - - - - - - - - - build-docker - - - push-docker - - - docker-compose-up - pre-integration-test - - start - - - - docker-compose-down - post-integration-test - - stop - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - - - + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository + ${revision} + + + basyx.submodelrepository.component + BaSyx submodelrepository.component + BaSyx submodelrepository.component + + + submodel-repository + + http://localhost:${docker.host.port}/submodels + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-core + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-backend-inmemory + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-backend-inmemory + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-backend-mongodb + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-mqtt + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-registry-integration + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-registry-integration + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-authorization + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-operation-delegation + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-operation-delegation + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-http + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-core + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-http + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.http + tests + test + + + org.apache.httpcomponents.client5 + httpclient5 + test + + + commons-io + commons-io + + + com.google.code.gson + gson + test + + + org.eclipse.digitaltwin.basyx + basyx.authorization + tests + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + + + + docker + + + docker.namespace + + + + + + io.fabric8 + docker-maven-plugin + + + + + + + ${docker.host.port}:${docker.container.port} + + + + ${docker.container.waitForEndpoint} + + + + + + + + + build-docker + + + push-docker + + + docker-compose-up + pre-integration-test + + start + + + + docker-compose-down + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + diff --git a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties index 4b40a24a4..4779fb543 100644 --- a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties +++ b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties @@ -2,8 +2,18 @@ server.port=8081 spring.application.name=Submodel Repository basyx.smrepo.name = sm-repo -basyx.backend = InMemory - +basyx.backend = MongoDB +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aasenv +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword +basyx.submodelrepository.feature.search.enabled=true +basyx.submodelrepository.feature.search.indexname=sm-index-test +spring.elasticsearch.uris=http://localhost:9200 +spring.elasticsearch.username=elastic +spring.elasticsearch.password=vtzJFt1b # basyx.submodelrepository.feature.registryintegration=http://localhost:8060 # basyx.externalurl=http://localhost:8081 diff --git a/basyx.submodelrepository/pom.xml b/basyx.submodelrepository/pom.xml index eb44d50d6..d035969dd 100644 --- a/basyx.submodelrepository/pom.xml +++ b/basyx.submodelrepository/pom.xml @@ -1,31 +1,32 @@ - - 4.0.0 - - - org.eclipse.digitaltwin.basyx - basyx.parent - ${revision} - - - basyx.submodelrepository - BaSyx submodelrepository - BaSyx submodelrepository - pom - - - basyx.submodelrepository-core - basyx.submodelrepository-http - basyx.submodelrepository-backend - basyx.submodelrepository-backend-inmemory - basyx.submodelrepository-feature-mqtt - basyx.submodelrepository-feature-kafka - basyx.submodelrepository-feature-registry-integration - basyx.submodelrepository-feature-authorization - basyx.submodelrepository-feature-operation-delegation - basyx.submodelrepository-tck - basyx.submodelrepository.component - basyx.submodelrepository-backend-mongodb - basyx.submodelrepository-client - + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.parent + ${revision} + + + basyx.submodelrepository + BaSyx submodelrepository + BaSyx submodelrepository + pom + + + basyx.submodelrepository-core + basyx.submodelrepository-http + basyx.submodelrepository-backend + basyx.submodelrepository-backend-inmemory + basyx.submodelrepository-feature-mqtt + basyx.submodelrepository-feature-kafka + basyx.submodelrepository-feature-registry-integration + basyx.submodelrepository-feature-authorization + basyx.submodelrepository-feature-operation-delegation + basyx.submodelrepository-tck + basyx.submodelrepository.component + basyx.submodelrepository-backend-mongodb + basyx.submodelrepository-client + basyx.submodelrepository-feature-search + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1ec267fdb..cb1539982 100644 --- a/pom.xml +++ b/pom.xml @@ -565,6 +565,11 @@ basyx.submodelrepository-feature-mqtt ${revision} + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-search + ${revision} + org.eclipse.digitaltwin.basyx @@ -786,6 +791,11 @@ basyx.conceptdescriptionrepository-feature-authorization ${revision} + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-feature-search + ${revision} + org.eclipse.digitaltwin.basyx basyx.conceptdescriptionrepository.component @@ -884,6 +894,11 @@ basyx.aasregistry-feature-search ${revision} + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-search + ${revision} + From 1fa1f6ea6da795490698efb8f942fb6b5d482da8 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 1 Jul 2025 16:10:00 +0200 Subject: [PATCH 17/52] WIP --- .../README.md | 3 +- .../feature/search/SearchCdRepository.java | 48 ++++++++++++++ .../search/SearchSubmodelRepository.java | 64 +++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index 7a7c60f34..e3f3eb10e 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -61,6 +61,7 @@ * [x] Configurable Index Names * [x] Implement Casting Operators * [x] Unit Test -* [ ] !Priority! Duplicate search module for the missing components +* [x] !Priority! Duplicate search module for the missing components +* [ ] In SearchSubmodelRepository, ensure that the index does not disable certain fields in ensureIndexExists * [ ] Integration Tests * [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java index ae0c674a0..c054c35ee 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java @@ -25,6 +25,10 @@ package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.mapping.Property; +import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; +import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; +import co.elastic.clients.elasticsearch.indices.ExistsRequest; import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; @@ -49,6 +53,7 @@ public SearchCdRepository(ConceptDescriptionRepository decorated, ElasticsearchC this.decorated = decorated; this.esclient = esclient; this.indexName = indexName; + ensureIndexExists(); } @Override @@ -130,4 +135,47 @@ private void deindexCD(String cdId) { } } + private void ensureIndexExists() { + try { + // Check if index exists + boolean indexExists = esclient.indices().exists(ExistsRequest.of(e -> e.index(indexName))).value(); + + if (!indexExists) { + // Create index with proper mapping + CreateIndexRequest createIndexRequest = CreateIndexRequest.of(c -> c + .index(indexName) + .mappings(TypeMapping.of(m -> m + .properties("id", Property.of(p -> p.keyword(k -> k))) + .properties("idShort", Property.of(p -> p.text(t -> t))) + .properties("description", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex description objects + ))) + .properties("displayName", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex displayName objects + ))) + .properties("isCaseOf", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex reference objects + ))) + .properties("extensions", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex extensions + ))) + .properties("embeddedDataSpecifications", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex embeddedDataSpecifications + ))) + .properties("administration", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex administration objects + ))) + .properties("category", Property.of(p -> p.text(t -> t))) + )) + ); + + esclient.indices().create(createIndexRequest); + logger.info("Created Elasticsearch index: {} with proper mappings", indexName); + } + } catch (Exception e) { + logger.error("Failed to ensure index exists: {}", indexName, e); + throw new RuntimeException("Failed to initialize Elasticsearch index", e); + } + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java index 5c624cfd3..6e781ced3 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java @@ -25,6 +25,11 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.mapping.DynamicMapping; +import co.elastic.clients.elasticsearch._types.mapping.Property; +import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; +import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; +import co.elastic.clients.elasticsearch.indices.ExistsRequest; import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.basyx.core.exceptions.*; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; @@ -51,6 +56,7 @@ public SearchSubmodelRepository(SubmodelRepository decorated, ElasticsearchClien this.decorated = decorated; this.esclient = esclient; this.indexName = indexName; + ensureIndexExists(); } @Override @@ -216,4 +222,62 @@ private void reindexSM(String smId) { indexSM(submodel); } + private void ensureIndexExists() { + try { + // Check if index exists + boolean indexExists = esclient.indices().exists(ExistsRequest.of(e -> e.index(indexName))).value(); + + if (!indexExists) { + // Create index with proper mapping + CreateIndexRequest createIndexRequest = CreateIndexRequest.of(c -> c + .index(indexName) + .mappings(TypeMapping.of(m -> m + .properties("id", Property.of(p -> p.keyword(k -> k))) + .properties("idShort", Property.of(p -> p.text(t -> t))) + .properties("description", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex description objects + ))) + .properties("displayName", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex displayName objects + ))) + .properties("semanticId", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex semanticId objects + ))) + .properties("submodelElements", Property.of(p -> p.nested(n -> n + .properties("idShort", Property.of(p2 -> p2.text(t2 -> t2))) + .properties("modelType", Property.of(p2 -> p2.text(t2 -> t2))) + .properties("description", Property.of(p2 -> p2.object(o2 -> o2))) + .properties("displayName", Property.of(p2 -> p2.object(o2 -> o2))) + .properties("semanticId", Property.of(p2 -> p2.object(o2 -> o2))) + .properties("category", Property.of(p2 -> p2.text(t2 -> t2))) + ))) + .properties("qualifiers", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex qualifiers + ))) + .properties("extensions", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex extensions + ))) + .properties("supplementalSemanticIds", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex supplementalSemanticIds + ))) + .properties("embeddedDataSpecifications", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex embeddedDataSpecifications + ))) + .properties("administration", Property.of(p -> p.object(o -> o + .enabled(false) // Store but don't analyze complex administration objects + ))) + .properties("category", Property.of(p -> p.text(t -> t))) + .properties("kind", Property.of(p -> p.keyword(k -> k))) + )) + ); + + esclient.indices().create(createIndexRequest); + logger.info("Created Elasticsearch index: {} with proper mappings", indexName); + } + } catch (Exception e) { + logger.error("Failed to ensure index exists: {}", indexName, e); + throw new RuntimeException("Failed to initialize Elasticsearch index", e); + } + } + } From 59755965f9e79cadb1893b6b87d3a20ba5719d4e Mon Sep 17 00:00:00 2001 From: Aaron Zielstorff Date: Tue, 1 Jul 2025 22:10:21 +0200 Subject: [PATCH 18/52] tries to fix indexing of submodels --- .../feature/search/IndexNormalizer.java | 69 +++++++++++++++ .../search/SearchSubmodelRepository.java | 84 ++++++++----------- .../basyx/aas-env.properties | 10 +++ .../BaSyxQueryLanguage/basyx/aas-registry.yml | 9 ++ .../BaSyxQueryLanguage/basyx/sm-registry.yml | 9 ++ .../BaSyxQueryLanguage/docker-compose.yml | 36 ++++---- 6 files changed, 148 insertions(+), 69 deletions(-) create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java new file mode 100644 index 000000000..7544500fb --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -0,0 +1,69 @@ +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; + +import java.util.ArrayList; +import java.util.List; + +public final class IndexNormalizer { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public static JsonNode toIndexable(Submodel sm) { + JsonNode root = MAPPER.valueToTree(sm); + rewriteRecursively(root); + return root; + } + + + private static void rewriteRecursively(JsonNode node) { + if (!node.isObject()) return; + + ObjectNode obj = (ObjectNode) node; + String modelType = extractModelType(obj); + + switch (modelType) { + case "SubmodelElementCollection": + case "SubmodelElementList": + move(obj, "value", "children"); + break; + + case "Reference": + case "MultiLanguageProperty": + move(obj, "value", "content"); + break; + + case "Blob": + obj.remove("value"); + break; + + default: + // nothing special + } + + List names = new ArrayList<>(); + obj.fieldNames().forEachRemaining(names::add); + + for (String name : names) { + JsonNode child = obj.get(name); + if (child.isArray()) child.forEach(IndexNormalizer::rewriteRecursively); + else if (child.isObject()) rewriteRecursively(child); + } + } + + private static String extractModelType(ObjectNode node) { + JsonNode mt = node.get("modelType"); + if (mt == null) return ""; + if (mt.isTextual()) return mt.asText(); + if (mt.isObject()) return mt.path("name").asText(); + return ""; + } + + private static void move(ObjectNode node, String from, String to) { + JsonNode v = node.remove(from); + if (v != null) node.set(to, v); + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java index 6e781ced3..c46a5f530 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java @@ -30,6 +30,7 @@ import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; import co.elastic.clients.elasticsearch.indices.ExistsRequest; +import com.fasterxml.jackson.databind.JsonNode; import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.basyx.core.exceptions.*; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; @@ -43,6 +44,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import java.util.List; public class SearchSubmodelRepository implements SubmodelRepository { @@ -154,7 +156,7 @@ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNot @Override public File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - return getFileByPathSubmodel(submodelId, idShortPath); + return decorated.getFileByPathSubmodel(submodelId, idShortPath); } @Override @@ -182,10 +184,11 @@ public InputStream getFileByFilePath(String submodelId, String filePath) { private void indexSM(Submodel submodel) { try { + JsonNode normalizedSubmodel = IndexNormalizer.toIndexable(submodel); esclient.create( c -> c.index(indexName) .id(submodel.getId()) - .document(submodel) + .document(normalizedSubmodel) ); } catch (IOException e) { throw new RuntimeException(e); @@ -194,11 +197,12 @@ private void indexSM(Submodel submodel) { private void updateSMIndex(Submodel submodel) { try { + JsonNode normalizedSubmodel = IndexNormalizer.toIndexable(submodel); esclient.update( u -> u.index(indexName) .id(submodel.getId()) - .doc(submodel), - Submodel.class + .doc(normalizedSubmodel), + JsonNode.class ); } catch (IOException e) { throw new RuntimeException(e); @@ -223,56 +227,34 @@ private void reindexSM(String smId) { } private void ensureIndexExists() { + String mappingJson = "{ \"mappings\": {" + + " \"dynamic_templates\": [" + + " { \"aas_value_string\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"string\", \"mapping\": { \"type\": \"keyword\" } } }," + + " { \"aas_value_long\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"long\", \"mapping\": { \"type\": \"long\" } } }," + + " { \"aas_value_double\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"double\", \"mapping\": { \"type\": \"double\" } } }," + + " { \"aas_value_boolean\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"boolean\", \"mapping\": { \"type\": \"boolean\" } } }," + + " { \"aas_children\": { \"path_match\": \"*.children\", \"match_mapping_type\": \"object\", \"mapping\": { \"type\": \"nested\" } } }" + + " ]," + + " \"properties\": {" + + " \"id\": { \"type\": \"keyword\" }," + + " \"idShort\": { \"type\": \"keyword\" }," + + " \"description\": { \"type\": \"object\", \"enabled\": true }," + + " \"displayName\": { \"type\": \"object\", \"enabled\": true }," + + " \"semanticId\": { \"type\": \"object\", \"enabled\": true }," + + " \"submodelElements\": { \"type\": \"nested\" }," + + " \"category\": { \"type\": \"keyword\" }," + + " \"kind\": { \"type\": \"keyword\" }" + + " }" + + "} }"; + try { - // Check if index exists boolean indexExists = esclient.indices().exists(ExistsRequest.of(e -> e.index(indexName))).value(); - if (!indexExists) { - // Create index with proper mapping - CreateIndexRequest createIndexRequest = CreateIndexRequest.of(c -> c - .index(indexName) - .mappings(TypeMapping.of(m -> m - .properties("id", Property.of(p -> p.keyword(k -> k))) - .properties("idShort", Property.of(p -> p.text(t -> t))) - .properties("description", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex description objects - ))) - .properties("displayName", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex displayName objects - ))) - .properties("semanticId", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex semanticId objects - ))) - .properties("submodelElements", Property.of(p -> p.nested(n -> n - .properties("idShort", Property.of(p2 -> p2.text(t2 -> t2))) - .properties("modelType", Property.of(p2 -> p2.text(t2 -> t2))) - .properties("description", Property.of(p2 -> p2.object(o2 -> o2))) - .properties("displayName", Property.of(p2 -> p2.object(o2 -> o2))) - .properties("semanticId", Property.of(p2 -> p2.object(o2 -> o2))) - .properties("category", Property.of(p2 -> p2.text(t2 -> t2))) - ))) - .properties("qualifiers", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex qualifiers - ))) - .properties("extensions", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex extensions - ))) - .properties("supplementalSemanticIds", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex supplementalSemanticIds - ))) - .properties("embeddedDataSpecifications", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex embeddedDataSpecifications - ))) - .properties("administration", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex administration objects - ))) - .properties("category", Property.of(p -> p.text(t -> t))) - .properties("kind", Property.of(p -> p.keyword(k -> k))) - )) - ); - - esclient.indices().create(createIndexRequest); - logger.info("Created Elasticsearch index: {} with proper mappings", indexName); + esclient.indices().create(CreateIndexRequest.of(c -> c + .index(indexName) + .withJson(new StringReader(mappingJson)) + )); + logger.info("Created Elasticsearch index: {} with dynamic template mapping", indexName); } } catch (Exception e) { logger.error("Failed to ensure index exists: {}", indexName, e); diff --git a/examples/BaSyxQueryLanguage/basyx/aas-env.properties b/examples/BaSyxQueryLanguage/basyx/aas-env.properties index 2ed0983e2..1ac3a6855 100644 --- a/examples/BaSyxQueryLanguage/basyx/aas-env.properties +++ b/examples/BaSyxQueryLanguage/basyx/aas-env.properties @@ -13,3 +13,13 @@ spring.data.mongodb.database=aas-env spring.data.mongodb.authentication-database=admin spring.data.mongodb.username=mongoAdmin spring.data.mongodb.password=mongoPassword +# Elasticsearch configuration +spring.elasticsearch.uris=http://elasticsearch:9200 +spring.elasticsearch.username=elastic +spring.elasticsearch.password=vtzJFt1b +basyx.aasrepository.feature.search.enabled=true +basyx.aasrepository.feature.search.indexname=aas-index +basyx.submodelrepository.feature.search.enabled=true +basyx.submodelrepository.feature.search.indexname=sm-index +basyx.conceptdescriptionrepository.feature.search.enabled=true +basyx.conceptdescriptionrepository.feature.search.indexname=cd-index diff --git a/examples/BaSyxQueryLanguage/basyx/aas-registry.yml b/examples/BaSyxQueryLanguage/basyx/aas-registry.yml index fb071597c..44d822c9a 100644 --- a/examples/BaSyxQueryLanguage/basyx/aas-registry.yml +++ b/examples/BaSyxQueryLanguage/basyx/aas-registry.yml @@ -2,7 +2,16 @@ basyx: cors: allowed-origins: '*' allowed-methods: GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD + aasregistry: + feature: + search: + enabled: true + indexname: aasdesc-index spring: data: mongodb: uri: mongodb://mongoAdmin:mongoPassword@mongo:27017 + elasticsearch: + uris: http://elasticsearch:9200 + username: elastic + password: vtzJFt1b diff --git a/examples/BaSyxQueryLanguage/basyx/sm-registry.yml b/examples/BaSyxQueryLanguage/basyx/sm-registry.yml index fb071597c..58d8c0e21 100644 --- a/examples/BaSyxQueryLanguage/basyx/sm-registry.yml +++ b/examples/BaSyxQueryLanguage/basyx/sm-registry.yml @@ -2,7 +2,16 @@ basyx: cors: allowed-origins: '*' allowed-methods: GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD + submodelregistry: + feature: + search: + enabled: true + indexname: smdesc-index spring: data: mongodb: uri: mongodb://mongoAdmin:mongoPassword@mongo:27017 + elasticsearch: + uris: http://elasticsearch:9200 + username: elastic + password: vtzJFt1b diff --git a/examples/BaSyxQueryLanguage/docker-compose.yml b/examples/BaSyxQueryLanguage/docker-compose.yml index bdf488671..16afb96cb 100644 --- a/examples/BaSyxQueryLanguage/docker-compose.yml +++ b/examples/BaSyxQueryLanguage/docker-compose.yml @@ -1,24 +1,24 @@ include: - docker-compose-elastic.yml services: -# aas-env: -# image: eclipsebasyx/aas-environment:2.0.0-SNAPSHOT -# container_name: aas-env -# environment: - # - SERVER_PORT=8081 - # volumes: - # - ./aas:/application/aas - # - ./basyx/aas-env.properties:/application/application.properties - # ports: - # - '8081:8081' - # restart: always - # depends_on: - # aas-registry: - # condition: service_healthy - # sm-registry: - # condition: service_healthy - # mongo: - # condition: service_healthy + aas-env: + image: eclipsebasyx/aas-environment:2.0.0-SNAPSHOT + container_name: aas-env + environment: + - SERVER_PORT=8081 + volumes: + - ./aas:/application/aas + - ./basyx/aas-env.properties:/application/application.properties + ports: + - '8081:8081' + restart: always + depends_on: + aas-registry: + condition: service_healthy + sm-registry: + condition: service_healthy + mongo: + condition: service_healthy aas-registry: image: eclipsebasyx/aas-registry-log-mongodb:2.0.0-SNAPSHOT container_name: aas-registry From c2de6da429b6ea3353ee01118eac9effdff725a9 Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 11 Aug 2025 11:00:04 +0200 Subject: [PATCH 19/52] WIP Adds QL for other modules --- .../feature/search/IndexNormalizer.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 7544500fb..775fc48f4 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -27,19 +27,24 @@ private static void rewriteRecursively(JsonNode node) { switch (modelType) { case "SubmodelElementCollection": + move(obj, "value", "smcChildren"); + break; case "SubmodelElementList": - move(obj, "value", "children"); + move(obj, "value", "smlChildren"); break; - case "Reference": + move(obj, "value", "referenceChildren"); + break; case "MultiLanguageProperty": move(obj, "value", "content"); break; - - case "Blob": - obj.remove("value"); - break; - +// TODO: Remove if unnecessary +// case "Blob": +// move(obj, "value", "blobValue"); +// break; +// case "File": +// move(obj, "value", "fileValue"); +// break; default: // nothing special } From 40bd0e74f88e0e026b44a2f26b19a7991adb27fe Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 11 Aug 2025 13:24:29 +0200 Subject: [PATCH 20/52] Adds Field Renaming --- .../query/executor/ESQueryExecutor.java | 54 +++++++ .../feature/search/IndexNormalizer.java | 147 +++++++++++++----- .../search/SearchSubmodelRepository.java | 51 ++---- 3 files changed, 176 insertions(+), 76 deletions(-) diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index dce9cc6ac..0eec656e7 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -6,12 +6,18 @@ import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryPaging; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResult; import org.eclipse.digitaltwin.basyx.querycore.query.converter.ElasticSearchRequestBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -24,6 +30,7 @@ public class ESQueryExecutor { private final String modelName; public static final int DEFAULT_PAGE_SIZE = 100; private final ElasticsearchClient client; + private static final Logger logger = LoggerFactory.getLogger(ESQueryExecutor.class); public ESQueryExecutor(ElasticsearchClient client, String indexName, String modelName){ this.indexName = indexName; @@ -50,13 +57,27 @@ public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, B boolean hasMore = topHits.size() > pageSize; List> pageHits = hasMore ? topHits.subList(0, pageSize) : topHits; + ObjectMapper mapper = new ObjectMapper(); + List objectHits = pageHits.stream() .map(Hit::source) .map(this::filterEmptyArrays) + .map(mapper::valueToTree) .toList(); + + String nextCursor = getNextCursor(hasMore, pageHits); + for(Object hit : objectHits){ + if (hit instanceof ObjectNode) { + ObjectNode node = (ObjectNode) hit; + rewriteRecursively(node); + } else { + logger.warn("Hit is not an ObjectNode: {}", hit.getClass().getName()); + } + } + QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); return queryResponse; } @@ -150,4 +171,37 @@ private SearchRequest.Builder buildSearchRequestWithPagination(SearchRequest bas private static int getPageSize(Integer limit) { return (limit != null && limit > 0) ? limit : DEFAULT_PAGE_SIZE; } + + private static void move(ObjectNode node, String from, String to) { + JsonNode v = node.remove(from); + if (v != null) node.set(to, v); + } + + public void rewriteRecursively(JsonNode node){ + if (!node.isObject()) return; + ObjectNode obj = (ObjectNode) node; + + move(obj, "smcChildren", "value"); + move(obj, "smlChildren", "value"); + move(obj, "referenceChildren", "value"); + move(obj, "langContent", "value"); + + // Recursively process child nodes + List names = new ArrayList<>(); + obj.fieldNames().forEachRemaining(names::add); + + for (String name : names) { + JsonNode child = obj.get(name); + + if (child.isArray()) { + for (int i = 0; i < child.size(); i++) { + JsonNode arrayChild = child.get(i); + rewriteRecursively(arrayChild); + } + } else if (child.isObject()) { + rewriteRecursively(child); + } + } + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 775fc48f4..303291d3c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -3,50 +3,41 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; +import org.eclipse.digitaltwin.aas4j.v3.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; public final class IndexNormalizer { + private static final Logger logger = LoggerFactory.getLogger(IndexNormalizer.class); private static final ObjectMapper MAPPER = new ObjectMapper(); - public static JsonNode toIndexable(Submodel sm) { + public static JsonNode toIndexable(Submodel sm) throws SerializationException { JsonNode root = MAPPER.valueToTree(sm); - rewriteRecursively(root); + rewriteRecursively(root, sm); return root; } - - private static void rewriteRecursively(JsonNode node) { - if (!node.isObject()) return; + private static void rewriteRecursively(JsonNode node, Object sourceObject) { + if (!node.isObject() || sourceObject == null) return; ObjectNode obj = (ObjectNode) node; - String modelType = extractModelType(obj); - - switch (modelType) { - case "SubmodelElementCollection": + + JsonNode valueNode = obj.get("value"); + if (valueNode != null && (valueNode.isObject() || valueNode.isArray())) { + if (sourceObject instanceof SubmodelElementCollection) { move(obj, "value", "smcChildren"); - break; - case "SubmodelElementList": + } else if (sourceObject instanceof SubmodelElementList) { move(obj, "value", "smlChildren"); - break; - case "Reference": + } else if (sourceObject instanceof Reference) { move(obj, "value", "referenceChildren"); - break; - case "MultiLanguageProperty": - move(obj, "value", "content"); - break; -// TODO: Remove if unnecessary -// case "Blob": -// move(obj, "value", "blobValue"); -// break; -// case "File": -// move(obj, "value", "fileValue"); -// break; - default: - // nothing special + } else if (sourceObject instanceof MultiLanguageProperty) { + move(obj, "value", "langContent"); + } } List names = new ArrayList<>(); @@ -54,17 +45,103 @@ private static void rewriteRecursively(JsonNode node) { for (String name : names) { JsonNode child = obj.get(name); - if (child.isArray()) child.forEach(IndexNormalizer::rewriteRecursively); - else if (child.isObject()) rewriteRecursively(child); + Object childSourceObject = getChildSourceObject(sourceObject, name); + + if (child.isArray()) { + for (int i = 0; i < child.size(); i++) { + JsonNode arrayChild = child.get(i); + Object arrayChildSource = getArrayChildSourceObject(childSourceObject, i); + rewriteRecursively(arrayChild, arrayChildSource); + } + } else if (child.isObject()) { + rewriteRecursively(child, childSourceObject); + } + } + } + + /** + * Gets the child source object corresponding to a field name + */ + private static Object getChildSourceObject(Object parent, String fieldName) { + if (parent == null) return null; + + try { + switch (fieldName) { + case "submodelElements": + if (parent instanceof Submodel) { + return ((Submodel) parent).getSubmodelElements(); + } + break; + case "value": + if (parent instanceof SubmodelElementCollection) { + return ((SubmodelElementCollection) parent).getValue(); + } else if (parent instanceof SubmodelElementList) { + return ((SubmodelElementList) parent).getValue(); + } else if (parent instanceof Reference) { + return ((Reference) parent).getKeys(); + } else if (parent instanceof MultiLanguageProperty) { + return ((MultiLanguageProperty) parent).getValue(); + } + break; + case "smcChildren": // Handle renamed field + if (parent instanceof SubmodelElementCollection) { + return ((SubmodelElementCollection) parent).getValue(); + } + break; + case "smlChildren": // Handle renamed field + if (parent instanceof SubmodelElementList) { + return ((SubmodelElementList) parent).getValue(); + } + break; + case "referenceChildren": // Handle renamed field + if (parent instanceof Reference) { + return ((Reference) parent).getKeys(); + } + break; + case "langContent": // Handle renamed field + if (parent instanceof MultiLanguageProperty) { + return ((MultiLanguageProperty) parent).getValue(); + } + break; + case "semanticId": + if (parent instanceof HasSemantics) { + return ((HasSemantics) parent).getSemanticId(); + } + break; + case "keys": + if (parent instanceof Reference) { + return ((Reference) parent).getKeys(); + } + break; + // Add more field mappings as needed + } + } catch (Exception e) { + logger.debug("Could not get child source object for field '{}' from parent type '{}'", + fieldName, parent.getClass().getSimpleName()); } + + return null; } - private static String extractModelType(ObjectNode node) { - JsonNode mt = node.get("modelType"); - if (mt == null) return ""; - if (mt.isTextual()) return mt.asText(); - if (mt.isObject()) return mt.path("name").asText(); - return ""; + /** + * Gets the array child source object at a specific index + */ + private static Object getArrayChildSourceObject(Object arraySource, int index) { + if (arraySource == null) return null; + + try { + if (arraySource instanceof List) { + List list = (List) arraySource; + if (index >= 0 && index < list.size()) { + return list.get(index); + } + } + } catch (Exception e) { + logger.debug("Could not get array child source object at index {} from array type '{}'", + index, arraySource.getClass().getSimpleName()); + } + + return null; } private static void move(ObjectNode node, String from, String to) { diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java index c46a5f530..9602ed2c1 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java @@ -31,6 +31,7 @@ import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; import co.elastic.clients.elasticsearch.indices.ExistsRequest; import com.fasterxml.jackson.databind.JsonNode; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.basyx.core.exceptions.*; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; @@ -58,7 +59,7 @@ public SearchSubmodelRepository(SubmodelRepository decorated, ElasticsearchClien this.decorated = decorated; this.esclient = esclient; this.indexName = indexName; - ensureIndexExists(); + //ensureIndexExists(); } @Override @@ -192,8 +193,10 @@ private void indexSM(Submodel submodel) { ); } catch (IOException e) { throw new RuntimeException(e); - } - } + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } private void updateSMIndex(Submodel submodel) { try { @@ -206,8 +209,10 @@ private void updateSMIndex(Submodel submodel) { ); } catch (IOException e) { throw new RuntimeException(e); - } - } + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } private void deindexSM(String smID) { try { @@ -226,40 +231,4 @@ private void reindexSM(String smId) { indexSM(submodel); } - private void ensureIndexExists() { - String mappingJson = "{ \"mappings\": {" - + " \"dynamic_templates\": [" - + " { \"aas_value_string\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"string\", \"mapping\": { \"type\": \"keyword\" } } }," - + " { \"aas_value_long\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"long\", \"mapping\": { \"type\": \"long\" } } }," - + " { \"aas_value_double\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"double\", \"mapping\": { \"type\": \"double\" } } }," - + " { \"aas_value_boolean\": { \"path_match\": \"*.value\", \"match_mapping_type\": \"boolean\", \"mapping\": { \"type\": \"boolean\" } } }," - + " { \"aas_children\": { \"path_match\": \"*.children\", \"match_mapping_type\": \"object\", \"mapping\": { \"type\": \"nested\" } } }" - + " ]," - + " \"properties\": {" - + " \"id\": { \"type\": \"keyword\" }," - + " \"idShort\": { \"type\": \"keyword\" }," - + " \"description\": { \"type\": \"object\", \"enabled\": true }," - + " \"displayName\": { \"type\": \"object\", \"enabled\": true }," - + " \"semanticId\": { \"type\": \"object\", \"enabled\": true }," - + " \"submodelElements\": { \"type\": \"nested\" }," - + " \"category\": { \"type\": \"keyword\" }," - + " \"kind\": { \"type\": \"keyword\" }" - + " }" - + "} }"; - - try { - boolean indexExists = esclient.indices().exists(ExistsRequest.of(e -> e.index(indexName))).value(); - if (!indexExists) { - esclient.indices().create(CreateIndexRequest.of(c -> c - .index(indexName) - .withJson(new StringReader(mappingJson)) - )); - logger.info("Created Elasticsearch index: {} with dynamic template mapping", indexName); - } - } catch (Exception e) { - logger.error("Failed to ensure index exists: {}", indexName, e); - throw new RuntimeException("Failed to initialize Elasticsearch index", e); - } - } - } From d43d3973afa5e838f0f2f3ab9ca55b8e79c59332 Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 11 Aug 2025 13:33:21 +0200 Subject: [PATCH 21/52] Changes Recursion to Iterative Method --- .../query/executor/ESQueryExecutor.java | 56 ++++++------ .../feature/search/IndexNormalizer.java | 85 ++++++++++++------- 2 files changed, 88 insertions(+), 53 deletions(-) diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index 0eec656e7..674a6803b 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -110,10 +110,9 @@ private Object filterEmptyArrays(Object obj) { if (obj instanceof List) { List list = (List) obj; if (list.isEmpty()) { - return list; // Return empty list as-is, will be filtered out by parent + return list; } - // Filter each element in the list return list.stream() .map(this::filterEmptyArrays) .collect(Collectors.toList()); @@ -160,7 +159,6 @@ private SearchRequest.Builder buildSearchRequestWithPagination(SearchRequest bas .size(pageSize + 1) .sort(SortOptions.of(s -> s.field(f -> f.field("id.keyword").order(SortOrder.Asc)))); - // Preserve source filtering from base request to ensure $select field works if (baseSearchRequest.source() != null) { searchRequestBuilder.source(baseSearchRequest.source()); } @@ -178,28 +176,38 @@ private static void move(ObjectNode node, String from, String to) { } public void rewriteRecursively(JsonNode node){ - if (!node.isObject()) return; - ObjectNode obj = (ObjectNode) node; - - move(obj, "smcChildren", "value"); - move(obj, "smlChildren", "value"); - move(obj, "referenceChildren", "value"); - move(obj, "langContent", "value"); - - // Recursively process child nodes - List names = new ArrayList<>(); - obj.fieldNames().forEachRemaining(names::add); - - for (String name : names) { - JsonNode child = obj.get(name); - - if (child.isArray()) { - for (int i = 0; i < child.size(); i++) { - JsonNode arrayChild = child.get(i); - rewriteRecursively(arrayChild); + if (node == null || !node.isObject()) return; + + Stack nodeStack = new Stack<>(); + nodeStack.push(node); + + while (!nodeStack.isEmpty()) { + JsonNode currentNode = nodeStack.pop(); + + if (!currentNode.isObject()) continue; + ObjectNode obj = (ObjectNode) currentNode; + + move(obj, "smcChildren", "value"); + move(obj, "smlChildren", "value"); + move(obj, "referenceChildren", "value"); + move(obj, "langContent", "value"); + + List names = new ArrayList<>(); + obj.fieldNames().forEachRemaining(names::add); + + for (String name : names) { + JsonNode child = obj.get(name); + + if (child.isArray()) { + for (int i = 0; i < child.size(); i++) { + JsonNode arrayChild = child.get(i); + if (arrayChild.isObject()) { + nodeStack.push(arrayChild); + } + } + } else if (child.isObject()) { + nodeStack.push(child); } - } else if (child.isObject()) { - rewriteRecursively(child); } } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 303291d3c..90108380e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -8,7 +8,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; public final class IndexNormalizer { @@ -16,45 +18,70 @@ public final class IndexNormalizer { private static final ObjectMapper MAPPER = new ObjectMapper(); + /** + * Represents a node and its corresponding source object for iterative processing + */ + private static class ProcessingNode { + final JsonNode jsonNode; + final Object sourceObject; + + ProcessingNode(JsonNode jsonNode, Object sourceObject) { + this.jsonNode = jsonNode; + this.sourceObject = sourceObject; + } + } + public static JsonNode toIndexable(Submodel sm) throws SerializationException { JsonNode root = MAPPER.valueToTree(sm); - rewriteRecursively(root, sm); + rewriteIteratively(root, sm); return root; } - private static void rewriteRecursively(JsonNode node, Object sourceObject) { - if (!node.isObject() || sourceObject == null) return; + private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObject) { + Deque stack = new ArrayDeque<>(); + stack.push(new ProcessingNode(rootNode, rootSourceObject)); - ObjectNode obj = (ObjectNode) node; - - JsonNode valueNode = obj.get("value"); - if (valueNode != null && (valueNode.isObject() || valueNode.isArray())) { - if (sourceObject instanceof SubmodelElementCollection) { - move(obj, "value", "smcChildren"); - } else if (sourceObject instanceof SubmodelElementList) { - move(obj, "value", "smlChildren"); - } else if (sourceObject instanceof Reference) { - move(obj, "value", "referenceChildren"); - } else if (sourceObject instanceof MultiLanguageProperty) { - move(obj, "value", "langContent"); - } - } + while (!stack.isEmpty()) { + ProcessingNode current = stack.pop(); + JsonNode node = current.jsonNode; + Object sourceObject = current.sourceObject; - List names = new ArrayList<>(); - obj.fieldNames().forEachRemaining(names::add); + if (!node.isObject() || sourceObject == null) { + continue; + } - for (String name : names) { - JsonNode child = obj.get(name); - Object childSourceObject = getChildSourceObject(sourceObject, name); + ObjectNode obj = (ObjectNode) node; - if (child.isArray()) { - for (int i = 0; i < child.size(); i++) { - JsonNode arrayChild = child.get(i); - Object arrayChildSource = getArrayChildSourceObject(childSourceObject, i); - rewriteRecursively(arrayChild, arrayChildSource); + JsonNode valueNode = obj.get("value"); + if (valueNode != null && (valueNode.isObject() || valueNode.isArray())) { + if (sourceObject instanceof SubmodelElementCollection) { + move(obj, "value", "smcChildren"); + } else if (sourceObject instanceof SubmodelElementList) { + move(obj, "value", "smlChildren"); + } else if (sourceObject instanceof Reference) { + move(obj, "value", "referenceChildren"); + } else if (sourceObject instanceof MultiLanguageProperty) { + move(obj, "value", "langContent"); + } + } + + List names = new ArrayList<>(); + obj.fieldNames().forEachRemaining(names::add); + + for (int i = names.size() - 1; i >= 0; i--) { + String name = names.get(i); + JsonNode child = obj.get(name); + Object childSourceObject = getChildSourceObject(sourceObject, name); + + if (child.isArray()) { + for (int j = child.size() - 1; j >= 0; j--) { + JsonNode arrayChild = child.get(j); + Object arrayChildSource = getArrayChildSourceObject(childSourceObject, j); + stack.push(new ProcessingNode(arrayChild, arrayChildSource)); + } + } else if (child.isObject()) { + stack.push(new ProcessingNode(child, childSourceObject)); } - } else if (child.isObject()) { - rewriteRecursively(child, childSourceObject); } } } From d542f234ffaa9aac6b5d4abc5586b12f73b4637a Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 11 Aug 2025 13:40:01 +0200 Subject: [PATCH 22/52] Cahnges CRLF to LF --- .../basyx.aasenvironment.component/pom.xml | 534 +++++++-------- .../basyx.aasregistry-service/pom.xml | 430 ++++++------ .../pom.xml | 146 ++-- basyx.submodelregistry/pom.xml | 648 +++++++++--------- .../pom.xml | 478 ++++++------- 5 files changed, 1118 insertions(+), 1118 deletions(-) diff --git a/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml b/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml index b46a52306..19faa8ec9 100644 --- a/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml +++ b/basyx.aasenvironment/basyx.aasenvironment.component/pom.xml @@ -1,267 +1,267 @@ - - 4.0.0 - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment - ${revision} - - - basyx.aasenvironment.component - BaSyx AAS Environment Component - BaSyx AAS Environment Component - - - aas-environment - http://localhost:${docker.host.port}/shells - - - - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository.component - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-backend - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-backend - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository.component - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.conceptdescriptionrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.mongodbcore - test - - - org.eclipse.digitaltwin.basyx - basyx.conceptdescriptionrepository.component - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment-core - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment-http - - - org.eclipse.digitaltwin.basyx - basyx.aasenvironment-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-feature-kafka - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-feature-search - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-search - - - org.eclipse.digitaltwin.basyx - basyx.conceptdescriptionrepository-feature-search - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - - - org.eclipse.digitaltwin.basyx - basyx.aasrepository-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.http - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-core - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.http - tests - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - test - - - org.springframework.boot - spring-boot-starter-actuator - - - org.eclipse.digitaltwin.aas4j - aas4j-dataformat-xml - - - org.eclipse.digitaltwin.aas4j - aas4j-dataformat-aasx - - - org.xmlunit - xmlunit-core - - - org.xmlunit - xmlunit-matchers - - - org.apache.httpcomponents.client5 - httpclient5 - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.eclipse.digitaltwin.basyx - basyx.mongodbcore - - - org.eclipse.digitaltwin.basyx - basyx.querycore - - - co.elastic.clients - elasticsearch-java - 9.0.1 - - - - - - org.springframework.boot - spring-boot-maven-plugin - - exec - - - - - - - - docker - - - docker.namespace - - - - - - io.fabric8 - docker-maven-plugin - - - - - - - ${docker.host.port}:${docker.container.port} - - - - ${docker.container.waitForEndpoint} - - - - - - - - - build-docker - - - push-docker - - - docker-compose-up - pre-integration-test - - start - - - - docker-compose-down - post-integration-test - - stop - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - - - + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment + ${revision} + + + basyx.aasenvironment.component + BaSyx AAS Environment Component + BaSyx AAS Environment Component + + + aas-environment + http://localhost:${docker.host.port}/shells + + + + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository.component + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-backend + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-backend + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository.component + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.mongodbcore + test + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository.component + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment-core + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment-http + + + org.eclipse.digitaltwin.basyx + basyx.aasenvironment-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-kafka + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.http + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-core + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.http + tests + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.boot + spring-boot-starter-actuator + + + org.eclipse.digitaltwin.aas4j + aas4j-dataformat-xml + + + org.eclipse.digitaltwin.aas4j + aas4j-dataformat-aasx + + + org.xmlunit + xmlunit-core + + + org.xmlunit + xmlunit-matchers + + + org.apache.httpcomponents.client5 + httpclient5 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.eclipse.digitaltwin.basyx + basyx.mongodbcore + + + org.eclipse.digitaltwin.basyx + basyx.querycore + + + co.elastic.clients + elasticsearch-java + 9.0.1 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + + + + docker + + + docker.namespace + + + + + + io.fabric8 + docker-maven-plugin + + + + + + + ${docker.host.port}:${docker.container.port} + + + + ${docker.container.waitForEndpoint} + + + + + + + + + build-docker + + + push-docker + + + docker-compose-up + pre-integration-test + + start + + + + docker-compose-down + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + diff --git a/basyx.aasregistry/basyx.aasregistry-service/pom.xml b/basyx.aasregistry/basyx.aasregistry-service/pom.xml index 2cc52ca75..da626473a 100644 --- a/basyx.aasregistry/basyx.aasregistry-service/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service/pom.xml @@ -1,215 +1,215 @@ - - - 4.0.0 - - org.eclipse.digitaltwin.basyx - basyx.aasregistry - ${revision} - - - basyx.aasregistry-service - BaSyx AAS Registry Service - BaSyx AAS Registry Service - - jar - - - 2024.0.1 - ${project.basedir}/${openapi.folder.name} - ${openapi.folder}/${openapi.name} - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - generate-sources - - add-source - - - - ${generated.folder}/java - - - - - - - maven-clean-plugin - - - - ${project.basedir}/${generated.folder} - - **/.gitkeep - - false - - - ${openapi.folder} - - .gitkeep - - false - - - - - - de.dfki.cos.basys.common - jsonpatch-maven-plugin - - - generate-sources - - jsonpatch-maven-plugin - - - ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} - ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} - ${openapi.result.file} - - - - - - org.openapitools - openapi-generator-maven-plugin - - - - generate - - - false - true - - ${openapi.result.file} - spring - spring-boot - ${project.basedir}/${generated.folder} - - ${project.basedir}/templates - - - true - java8 - java - true - true - true - org.eclipse.digitaltwin.basyx.aasregistry.service.configuration - org.eclipse.digitaltwin.basyx.aasregistry.service.api - org.eclipse.digitaltwin.basyx.aasregistry.service - org.eclipse.digitaltwin.basyx.aasregistry.model - true - springdoc - true - - - - - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - org.projectlombok - lombok - true - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - jakarta.validation - jakarta.validation-api - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-actuator - - - com.google.guava - guava - - - org.eclipse.digitaltwin.basyx - basyx.aasregistry-service-basemodel - provided - - - org.eclipse.digitaltwin.basyx - basyx.aasregistry-paths - - - org.openapitools - jackson-databind-nullable - - - org.eclipse.digitaltwin.basyx - basyx.core - - - org.eclipse.digitaltwin.basyx - basyx.http - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - org.hamcrest - hamcrest-core - - - - - org.apache.commons - commons-lang3 - test - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - - - + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.aasregistry + ${revision} + + + basyx.aasregistry-service + BaSyx AAS Registry Service + BaSyx AAS Registry Service + + jar + + + 2024.0.1 + ${project.basedir}/${openapi.folder.name} + ${openapi.folder}/${openapi.name} + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${generated.folder}/java + + + + + + + maven-clean-plugin + + + + ${project.basedir}/${generated.folder} + + **/.gitkeep + + false + + + ${openapi.folder} + + .gitkeep + + false + + + + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + + + generate-sources + + jsonpatch-maven-plugin + + + ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} + ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} + ${openapi.result.file} + + + + + + org.openapitools + openapi-generator-maven-plugin + + + + generate + + + false + true + + ${openapi.result.file} + spring + spring-boot + ${project.basedir}/${generated.folder} + + ${project.basedir}/templates + + + true + java8 + java + true + true + true + org.eclipse.digitaltwin.basyx.aasregistry.service.configuration + org.eclipse.digitaltwin.basyx.aasregistry.service.api + org.eclipse.digitaltwin.basyx.aasregistry.service + org.eclipse.digitaltwin.basyx.aasregistry.model + true + springdoc + true + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.projectlombok + lombok + true + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + com.google.guava + guava + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service-basemodel + provided + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-paths + + + org.openapitools + jackson-databind-nullable + + + org.eclipse.digitaltwin.basyx + basyx.core + + + org.eclipse.digitaltwin.basyx + basyx.http + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + org.hamcrest + hamcrest-core + + + + + org.apache.commons + commons-lang3 + test + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml index c4f9be227..ce7017dbb 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml @@ -1,73 +1,73 @@ - - - 4.0.0 - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry - ${revision} - - - basyx.submodelregistry-service-release-kafka-mongodb - BaSyx Submodel Registry Service Release Kafka MongoDB - BaSyx Submodel Registry Service Release Kafka MongoDB - - - 2020.0.4 - org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication - submodel-registry-kafka-mongodb - - - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-hierarchy - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-search - - - org.eclipse.digitaltwin.basyx - basyx.authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-mongodb-storage - - - org.apache.commons - commons-lang3 - - - org.junit.vintage - junit-vintage-engine - test - - - org.hamcrest - hamcrest-core - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-kafka-events - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basetests - test - - - + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + + + basyx.submodelregistry-service-release-kafka-mongodb + BaSyx Submodel Registry Service Release Kafka MongoDB + BaSyx Submodel Registry Service Release Kafka MongoDB + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-kafka-mongodb + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + + + org.apache.commons + commons-lang3 + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-kafka-events + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + + + diff --git a/basyx.submodelregistry/pom.xml b/basyx.submodelregistry/pom.xml index 53db67cb7..14a8f4926 100644 --- a/basyx.submodelregistry/pom.xml +++ b/basyx.submodelregistry/pom.xml @@ -1,324 +1,324 @@ - - - 4.0.0 - basyx.submodelregistry - BaSyx submodelregistry - BaSyx submodelregistry - pom - - - org.eclipse.digitaltwin.basyx - basyx.parent - ${revision} - - - - 1.18.20.0 - 1.18.38 - ${java.version} - ${java.version} - src/generated - 3.0.68 - 3.0.0 - open-api - - Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml - Plattform_i40-SubmodelRegistry-and-Discovery.yaml - 33.4.8-jre - 3.6.0 - 3.0-alpha-2 - 0.9.14 - ${project.artifactId} - 6.6.0 - 0.2.6 - 3.0.2 - 0.5.1 - patch-base-extensions.yaml - - - - - basyx.submodelregistry-service-basemodel - basyx.submodelregistry-service - basyx.submodelregistry-service-inmemory-storage - basyx.submodelregistry-service-release-log-mem - basyx.submodelregistry-client-native - basyx.submodelregistry-service-mongodb-storage - basyx.submodelregistry-service-kafka-events - basyx.submodelregistry-service-basetests - basyx.submodelregistry-service-release-kafka-mem - basyx.submodelregistry-service-release-log-mongodb - basyx.submodelregistry-service-release-kafka-mongodb - basyx.submodelregistry-feature-authorization - basyx.submodelregistry-feature-hierarchy - basyx.submodelregistry-feature-hierarchy-example - basyx.submodelregistry-feature-search - - - - - Gerhard Sonnenberg - gerhard.sonnenberg@dfki.de - DFKI GmbH - https://www.dfki.de/en/web - - developer - - - - - - - - org.projectlombok - lombok-maven-plugin - ${lombok.maven-plugin.version} - - - org.projectlombok - lombok - ${lombok.maven-plugin.lombok.version} - - - - - io.swagger.codegen.v3 - swagger-codegen-maven-plugin - ${swagger.codegen.version} - - - - org.openapitools - openapi-generator-maven-plugin - ${openapitools.version} - - - de.dfki.cos.basys.common - jsonpatch-maven-plugin - ${jsonpatch.plugin.version} - - - - - - - - com.google.code.findbugs - jsr305 - ${jsr305.version} - - - org.openapitools - openapi-generator - ${openapitools.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-kafka-events - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-mongodb-storage - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basemodel - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-client-native - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-inmemory-storage - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basetests - test - ${project.version} - - - io.springfox - springfox-oas - ${spring.fox.version} - - - io.springfox - springfox-swagger-ui - ${spring.fox.version} - - - org.openapitools - jackson-databind-nullable - ${openapitools.jacksonnullable.version} - - - com.google.guava - guava - ${guava.version} - - - org.apache.maven - maven-plugin-api - ${maven-plugin.version} - - - org.apache.maven.plugin-tools - maven-plugin-annotations - ${maven-plugin.version} - - - org.apache.maven - maven-project - ${maven-project.version} - - - org.apache.maven.plugins - maven-plugin-plugin - ${maven-plugin.version} - - - com.github.spullara.mustache.java - compiler - ${mustache.compiler.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-authorization - ${project.version} - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-hierarchy - ${project.version} - - - - - - delombok - - - src/main/lombok - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - generate-sources - - add-source - - - - src/main/generated - - - - - - - org.projectlombok - lombok-maven-plugin - - - org.projectlombok - lombok - ${lombok.maven-plugin.lombok.version} - - - - - generate-sources - - delombok - - - - - - - - - dockerbuild - - - - src/main/docker/Dockerfile - - - docker.namespace - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - true - true - - - - - - repackage - - - - - - io.fabric8 - docker-maven-plugin - - - - - - artifact - - Dockerfile - - ${project.basedir}/src/main/docker - - ${docker.target.platforms} - - - - - - - - build-docker - - - push-docker - - - - - - - - + + + 4.0.0 + basyx.submodelregistry + BaSyx submodelregistry + BaSyx submodelregistry + pom + + + org.eclipse.digitaltwin.basyx + basyx.parent + ${revision} + + + + 1.18.20.0 + 1.18.38 + ${java.version} + ${java.version} + src/generated + 3.0.68 + 3.0.0 + open-api + + Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml + Plattform_i40-SubmodelRegistry-and-Discovery.yaml + 33.4.8-jre + 3.6.0 + 3.0-alpha-2 + 0.9.14 + ${project.artifactId} + 6.6.0 + 0.2.6 + 3.0.2 + 0.5.1 + patch-base-extensions.yaml + + + + + basyx.submodelregistry-service-basemodel + basyx.submodelregistry-service + basyx.submodelregistry-service-inmemory-storage + basyx.submodelregistry-service-release-log-mem + basyx.submodelregistry-client-native + basyx.submodelregistry-service-mongodb-storage + basyx.submodelregistry-service-kafka-events + basyx.submodelregistry-service-basetests + basyx.submodelregistry-service-release-kafka-mem + basyx.submodelregistry-service-release-log-mongodb + basyx.submodelregistry-service-release-kafka-mongodb + basyx.submodelregistry-feature-authorization + basyx.submodelregistry-feature-hierarchy + basyx.submodelregistry-feature-hierarchy-example + basyx.submodelregistry-feature-search + + + + + Gerhard Sonnenberg + gerhard.sonnenberg@dfki.de + DFKI GmbH + https://www.dfki.de/en/web + + developer + + + + + + + + org.projectlombok + lombok-maven-plugin + ${lombok.maven-plugin.version} + + + org.projectlombok + lombok + ${lombok.maven-plugin.lombok.version} + + + + + io.swagger.codegen.v3 + swagger-codegen-maven-plugin + ${swagger.codegen.version} + + + + org.openapitools + openapi-generator-maven-plugin + ${openapitools.version} + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + ${jsonpatch.plugin.version} + + + + + + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + + + org.openapitools + openapi-generator + ${openapitools.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-kafka-events + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-client-native + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-inmemory-storage + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + ${project.version} + + + io.springfox + springfox-oas + ${spring.fox.version} + + + io.springfox + springfox-swagger-ui + ${spring.fox.version} + + + org.openapitools + jackson-databind-nullable + ${openapitools.jacksonnullable.version} + + + com.google.guava + guava + ${guava.version} + + + org.apache.maven + maven-plugin-api + ${maven-plugin.version} + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin.version} + + + org.apache.maven + maven-project + ${maven-project.version} + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin.version} + + + com.github.spullara.mustache.java + compiler + ${mustache.compiler.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-authorization + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + ${project.version} + + + + + + delombok + + + src/main/lombok + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + src/main/generated + + + + + + + org.projectlombok + lombok-maven-plugin + + + org.projectlombok + lombok + ${lombok.maven-plugin.lombok.version} + + + + + generate-sources + + delombok + + + + + + + + + dockerbuild + + + + src/main/docker/Dockerfile + + + docker.namespace + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + true + true + + + + + + repackage + + + + + + io.fabric8 + docker-maven-plugin + + + + + + artifact + + Dockerfile + + ${project.basedir}/src/main/docker + + ${docker.target.platforms} + + + + + + + + build-docker + + + push-docker + + + + + + + + diff --git a/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml b/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml index 24c6f9cc5..6966ced69 100644 --- a/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository.component/pom.xml @@ -1,239 +1,239 @@ - - 4.0.0 - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository - ${revision} - - - basyx.submodelrepository.component - BaSyx submodelrepository.component - BaSyx submodelrepository.component - - - submodel-repository - - http://localhost:${docker.host.port}/submodels - - - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-core - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-backend-inmemory - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-backend-inmemory - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-backend-mongodb - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-mqtt - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-registry-integration - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-registry-integration - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-search - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-authorization - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-operation-delegation - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-operation-delegation - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-feature-kafka - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-http - - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - test - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.eclipse.digitaltwin.basyx - basyx.submodelservice-core - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-http - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.submodelrepository-tck - tests - test - - - org.eclipse.digitaltwin.basyx - basyx.http - tests - test - - - org.apache.httpcomponents.client5 - httpclient5 - test - - - commons-io - commons-io - - - com.google.code.gson - gson - test - - - org.eclipse.digitaltwin.basyx - basyx.authorization - tests - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - exec - - - - - - - - docker - - - docker.namespace - - - - - - io.fabric8 - docker-maven-plugin - - - - - - - ${docker.host.port}:${docker.container.port} - - - - ${docker.container.waitForEndpoint} - - - - - - - - - build-docker - - - push-docker - - - docker-compose-up - pre-integration-test - - start - - - - docker-compose-down - post-integration-test - - stop - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - - - + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository + ${revision} + + + basyx.submodelrepository.component + BaSyx submodelrepository.component + BaSyx submodelrepository.component + + + submodel-repository + + http://localhost:${docker.host.port}/submodels + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-core + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-backend-inmemory + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-backend-inmemory + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-backend-mongodb + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-mqtt + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-registry-integration + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-registry-integration + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-authorization + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-operation-delegation + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-operation-delegation + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-feature-kafka + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-http + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-core + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-http + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-tck + tests + test + + + org.eclipse.digitaltwin.basyx + basyx.http + tests + test + + + org.apache.httpcomponents.client5 + httpclient5 + test + + + commons-io + commons-io + + + com.google.code.gson + gson + test + + + org.eclipse.digitaltwin.basyx + basyx.authorization + tests + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + + + + docker + + + docker.namespace + + + + + + io.fabric8 + docker-maven-plugin + + + + + + + ${docker.host.port}:${docker.container.port} + + + + ${docker.container.waitForEndpoint} + + + + + + + + + build-docker + + + push-docker + + + docker-compose-up + pre-integration-test + + start + + + + docker-compose-down + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + From 4a282e4aaa5e12a50c871e505842d743ee3ca1d2 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Tue, 12 Aug 2025 10:12:34 +0200 Subject: [PATCH 23/52] Adds Querying for nested SME's --- .../query/converter/ValueConverter.java | 155 ++++++++++++++++++ .../query/executor/ESQueryExecutor.java | 5 +- .../feature/search/IndexNormalizer.java | 6 +- 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 5922ae0f1..ca30d3a52 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -34,6 +34,11 @@ public Query convertEqualityComparison(Value leftValue, Value rightValue) { Object value = leftField != null ? extractValue(rightValue) : extractValue(leftValue); if (fieldName != null && value != null) { + // Check if this is an SME wildcard field + if (isSmeWildcardField(fieldName)) { + return createSmeWildcardQuery(fieldName, value.toString()); + } + return QueryBuilders.term() .field(fieldName) .value(convertToFieldValue(value)) @@ -60,6 +65,13 @@ public Query convertInequalityComparison(Value leftValue, Value rightValue) { Object value = leftField != null ? extractValue(rightValue) : extractValue(leftValue); if (fieldName != null && value != null) { + // Check if this is an SME wildcard field + if (isSmeWildcardField(fieldName)) { + return QueryBuilders.bool() + .mustNot(createSmeWildcardQuery(fieldName, value.toString())) + .build()._toQuery(); + } + return QueryBuilders.bool() .mustNot(QueryBuilders.term() .field(fieldName) @@ -98,6 +110,11 @@ public Query convertRangeComparison(Value leftValue, Object rawValue = leftField != null ? extractValue(rightValue) : extractValue(leftValue); if (fieldName != null && rawValue != null) { + // Check if this is an SME wildcard field + if (isSmeWildcardField(fieldName)) { + return createSmeWildcardRangeQuery(fieldName, rawValue, operator); + } + JsonData value = JsonData.of(rawValue); // works for all scalar types return QueryBuilders.range(r -> r @@ -137,6 +154,11 @@ public Query convertStringComparison(StringValue leftValue, StringValue rightVal String value = leftField != null ? extractStringValue(rightValue) : extractStringValue(leftValue); if (fieldName != null && value != null) { + // Check if this is an SME wildcard field + if (isSmeWildcardField(fieldName)) { + return createSmeWildcardStringQuery(fieldName, value, operation); + } + switch (operation) { case "contains": return QueryBuilders.wildcard() @@ -375,6 +397,8 @@ private String convertModelFieldToElasticField(String modelField) { result = modelField.replace("$sm#", ""); } else if (modelField.startsWith("$sme")) { result = modelField.replaceFirst("\\$sme(?:\\.[^#]*)?#", ""); + // Mark as SME field for wildcard handling + result = "SME_WILDCARD:" + result; } else if (modelField.startsWith("$cd#")) { result = modelField.replace("$cd#", ""); } else if (modelField.startsWith("$aasdesc#")) { @@ -615,4 +639,135 @@ private String generateCastScript(String fieldExpression, String castType) { return fieldExpression; // No casting applied } } + + /** + * Checks if a field is an SME wildcard field that needs special handling + */ + private boolean isSmeWildcardField(String fieldName) { + return fieldName != null && fieldName.startsWith("SME_WILDCARD:"); + } + + /** + * Extracts the actual field name from an SME wildcard field + */ + private String extractSmeFieldName(String wildcardField) { + if (wildcardField.startsWith("SME_WILDCARD:")) { + return wildcardField.substring("SME_WILDCARD:".length()); + } + return wildcardField; + } + + /** + * Creates a wildcard query using QueryBuilders.queryString for SME fields at any nesting level + */ + private Query createSmeWildcardQuery(String wildcardField, String value) { + String fieldName = extractSmeFieldName(wildcardField); + + // Add .keyword suffix for string fields that need exact matching + String searchField = fieldName; + + // Create a wildcard pattern that matches the field at any nesting level + // Pattern: submodelElements.*{fieldName}:{value} OR submodelElements.*.smcChildren.*{fieldName}:{value} + String queryPattern = "submodelElements.*."+searchField; + + return QueryBuilders.queryString(q -> q + .query(value) + .fields(queryPattern) + ); + } + + /** + * Creates a wildcard string query using QueryBuilders.queryString for SME fields at any nesting level + */ + private Query createSmeWildcardStringQuery(String wildcardField, String value, String operation) { + String fieldName = extractSmeFieldName(wildcardField); + + // Add .keyword suffix for string fields that need exact matching + String searchField = fieldName; + if (isStringField(fieldName)) { + searchField = fieldName + ".keyword"; + } + + String searchValue; + switch (operation) { + case "contains": + searchValue = "*" + escapeQueryString(value) + "*"; + break; + case "starts-with": + searchValue = escapeQueryString(value) + "*"; + break; + case "ends-with": + searchValue = "*" + escapeQueryString(value); + break; + case "regex": + // For regex, use the value as-is (queryString supports regex) + searchValue = value; + break; + default: + searchValue = escapeQueryString(value); + break; + } + + // Create a wildcard pattern that matches the field at any nesting level + String queryPattern = "submodelElements.*."+searchField; + + return QueryBuilders.queryString(q -> q + .query(value) + .fields(queryPattern) + ); + } + + /** + * Creates a wildcard range query using QueryBuilders.queryString for SME fields at any nesting level + */ + private Query createSmeWildcardRangeQuery(String wildcardField, Object value, String operator) { + String fieldName = extractSmeFieldName(wildcardField); + + // Add .keyword suffix for string fields that need exact matching + String searchField = fieldName; + if (isStringField(fieldName)) { + searchField = fieldName + ".keyword"; + } + + String rangeOperator; + switch (operator) { + case "gt": rangeOperator = ">"; break; + case "gte": rangeOperator = ">="; break; + case "lt": rangeOperator = "<"; break; + case "lte": rangeOperator = "<="; break; + default: throw new IllegalArgumentException("Unsupported range operator: " + operator); + } + + // Create a wildcard pattern that matches the field at any nesting level with range comparison + String queryPattern = "submodelElements.*."+searchField; + + return QueryBuilders.queryString(q -> q + .query(value.toString()) + .fields(queryPattern) + ); + } + + /** + * Escapes special characters for Elasticsearch query string queries + */ + private String escapeQueryString(String value) { + // Escape special query string characters + return value.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("+", "\\+") + .replace("-", "\\-") + .replace("=", "\\=") + .replace("&&", "\\&&") + .replace("||", "\\||") + .replace("!", "\\!") + .replace("(", "\\(") + .replace(")", "\\)") + .replace("{", "\\{") + .replace("}", "\\}") + .replace("[", "\\[") + .replace("]", "\\]") + .replace("^", "\\^") + .replace("~", "\\~") + .replace(":", "\\:"); + } } \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index 674a6803b..c9fced267 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -9,7 +9,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryPaging; @@ -72,7 +71,7 @@ public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, B for(Object hit : objectHits){ if (hit instanceof ObjectNode) { ObjectNode node = (ObjectNode) hit; - rewriteRecursively(node); + rewriteIterative(node); } else { logger.warn("Hit is not an ObjectNode: {}", hit.getClass().getName()); } @@ -175,7 +174,7 @@ private static void move(ObjectNode node, String from, String to) { if (v != null) node.set(to, v); } - public void rewriteRecursively(JsonNode node){ + public void rewriteIterative(JsonNode node){ if (node == null || !node.isObject()) return; Stack nodeStack = new Stack<>(); diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 90108380e..6af9089fb 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -58,10 +58,12 @@ private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObjec move(obj, "value", "smcChildren"); } else if (sourceObject instanceof SubmodelElementList) { move(obj, "value", "smlChildren"); - } else if (sourceObject instanceof Reference) { - move(obj, "value", "referenceChildren"); + } else if (sourceObject instanceof ReferenceElement) { + move(obj, "value", "referenceElementChildren"); } else if (sourceObject instanceof MultiLanguageProperty) { move(obj, "value", "langContent"); + } else { + move(obj, "value", "peps"); } } From 7c544f8079aa8702ac8a9c229090e5207e8fcbdc Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Tue, 12 Aug 2025 15:42:52 +0200 Subject: [PATCH 24/52] Adapts SM Repo QL --- .../README.md | 10 +- .../ElasticSearchRequestBuilder.java | 67 +---------- .../query/converter/ValueConverter.java | 45 ++++---- .../query/executor/ESQueryExecutor.java | 3 +- .../querycore/query/model/QueryResponse.java | 6 +- .../src/test/java/AASQueryConverterTest.java | 2 + .../input/ends-with_field_strval.json | 8 ++ .../input/starts-with_field_strval.json | 8 ++ .../expected_ends-with_field_strval.json | 1 + .../expected_starts-with_field_strval.json | 1 + .../feature/search/IndexNormalizer.java | 107 ++++++++++++------ ...chSubmodelRepositoryApiHTTPController.java | 42 ++++++- 12 files changed, 171 insertions(+), 129 deletions(-) create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/input/starts-with_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_ends-with_field_strval.json create mode 100644 basyx.common/basyx.querycore/src/test/resources/output/expected_starts-with_field_strval.json diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index e3f3eb10e..c7ebd3137 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -47,6 +47,7 @@ ## Risks * Errors can be catched in the decorated functions (transactional) +* Security needs to be considered -> Just query id of elements and then go to the MongoDB ## TODOS @@ -61,7 +62,10 @@ * [x] Configurable Index Names * [x] Implement Casting Operators * [x] Unit Test -* [x] !Priority! Duplicate search module for the missing components -* [ ] In SearchSubmodelRepository, ensure that the index does not disable certain fields in ensureIndexExists +* [x] !Priority! Duplicate search module for the missing components * [ ] Integration Tests -* [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL \ No newline at end of file +* [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL +* [ ] Make the following SME Props queryable: value, valueType, semanticId, idShort +* [ ] Make other Components (AAS,CD) also fetch Data from MongoDB not ES +* [ ] Note down that comparison operators don't work for SME filtering in SM Repo without idShortPath +* [ ] Handle Search Queries with SML (Indices) \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java index 8244a50d2..e6bb5351a 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java @@ -69,68 +69,5 @@ public SearchRequest buildSearchRequest(AASQuery customQuery, String indexName, return searchBuilder.build(); } - - /** - * Builds ElasticSearch SearchRequest with custom source includes - * - * @param customQuery The custom query to convert - * @param indexName The ElasticSearch index name to search - * @param sourceIncludes List of fields to include in the response - * @return ElasticSearch SearchRequest - */ - public SearchRequest buildSearchRequestWithSources(AASQuery customQuery, String indexName, List sourceIncludes) { - co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = queryConverter.convert(customQuery); - - SearchRequest.Builder searchBuilder = new SearchRequest.Builder() - .index(indexName) - .query(esQuery); - - if (sourceIncludes != null && !sourceIncludes.isEmpty()) { - searchBuilder.source(SourceConfig.of(s -> s - .filter(SourceFilter.of(f -> f - .includes(sourceIncludes) - )) - )); - } - - return searchBuilder.build(); - } - - /** - * Builds ElasticSearch SearchRequest with custom source includes and excludes - * - * @param customQuery The custom query to convert - * @param indexName The ElasticSearch index name to search - * @param sourceIncludes List of fields to include in the response - * @param sourceExcludes List of fields to exclude from the response - * @return ElasticSearch SearchRequest - */ - public SearchRequest buildSearchRequestWithSourceFilters(AASQuery customQuery, String indexName, - List sourceIncludes, List sourceExcludes) { - co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = queryConverter.convert(customQuery); - - SearchRequest.Builder searchBuilder = new SearchRequest.Builder() - .index(indexName) - .query(esQuery); - - if ((sourceIncludes != null && !sourceIncludes.isEmpty()) || - (sourceExcludes != null && !sourceExcludes.isEmpty())) { - - SourceFilter.Builder filterBuilder = new SourceFilter.Builder(); - - if (sourceIncludes != null && !sourceIncludes.isEmpty()) { - filterBuilder.includes(sourceIncludes); - } - - if (sourceExcludes != null && !sourceExcludes.isEmpty()) { - filterBuilder.excludes(sourceExcludes); - } - - searchBuilder.source(SourceConfig.of(s -> s - .filter(filterBuilder.build()) - )); - } - - return searchBuilder.build(); - } -} \ No newline at end of file + +} diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index ca30d3a52..c42b82b66 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -396,9 +396,19 @@ private String convertModelFieldToElasticField(String modelField) { } else if (modelField.startsWith("$sm#")) { result = modelField.replace("$sm#", ""); } else if (modelField.startsWith("$sme")) { + boolean hasIdShortPath = !result.startsWith("$sme#"); + String idShortPath = ""; + if (hasIdShortPath) { + int indexOfRaute = result.indexOf("#"); + idShortPath = result.substring(5, indexOfRaute); + } result = modelField.replaceFirst("\\$sme(?:\\.[^#]*)?#", ""); // Mark as SME field for wildcard handling - result = "SME_WILDCARD:" + result; + if (!hasIdShortPath) { + result = "SME_WILDCARD:" + result; + } else { + result = "submodelElements." + idShortPath+ "." + result; + } } else if (modelField.startsWith("$cd#")) { result = modelField.replace("$cd#", ""); } else if (modelField.startsWith("$aasdesc#")) { @@ -668,7 +678,7 @@ private Query createSmeWildcardQuery(String wildcardField, String value) { // Create a wildcard pattern that matches the field at any nesting level // Pattern: submodelElements.*{fieldName}:{value} OR submodelElements.*.smcChildren.*{fieldName}:{value} - String queryPattern = "submodelElements.*."+searchField; + String queryPattern = "*"+searchField; return QueryBuilders.queryString(q -> q .query(value) @@ -682,25 +692,20 @@ private Query createSmeWildcardQuery(String wildcardField, String value) { private Query createSmeWildcardStringQuery(String wildcardField, String value, String operation) { String fieldName = extractSmeFieldName(wildcardField); - // Add .keyword suffix for string fields that need exact matching String searchField = fieldName; - if (isStringField(fieldName)) { - searchField = fieldName + ".keyword"; - } - + String searchValue; switch (operation) { case "contains": - searchValue = "*" + escapeQueryString(value) + "*"; + searchValue = "*" + value + "*"; break; case "starts-with": - searchValue = escapeQueryString(value) + "*"; + searchValue = value + "*"; break; case "ends-with": - searchValue = "*" + escapeQueryString(value); + searchValue = "*" + value; break; case "regex": - // For regex, use the value as-is (queryString supports regex) searchValue = value; break; default: @@ -709,10 +714,10 @@ private Query createSmeWildcardStringQuery(String wildcardField, String value, S } // Create a wildcard pattern that matches the field at any nesting level - String queryPattern = "submodelElements.*."+searchField; + String queryPattern = "*"+searchField; return QueryBuilders.queryString(q -> q - .query(value) + .query(searchValue) .fields(queryPattern) ); } @@ -723,12 +728,8 @@ private Query createSmeWildcardStringQuery(String wildcardField, String value, S private Query createSmeWildcardRangeQuery(String wildcardField, Object value, String operator) { String fieldName = extractSmeFieldName(wildcardField); - // Add .keyword suffix for string fields that need exact matching String searchField = fieldName; - if (isStringField(fieldName)) { - searchField = fieldName + ".keyword"; - } - + String rangeOperator; switch (operator) { case "gt": rangeOperator = ">"; break; @@ -739,11 +740,13 @@ private Query createSmeWildcardRangeQuery(String wildcardField, Object value, St } // Create a wildcard pattern that matches the field at any nesting level with range comparison - String queryPattern = "submodelElements.*."+searchField; + String queryPattern = "*"+searchField; + + // Construct the range query string with proper syntax: fieldPattern:(>=value) + String rangeQuery = queryPattern + ":(" + rangeOperator + value.toString() + ")"; return QueryBuilders.queryString(q -> q - .query(value.toString()) - .fields(queryPattern) + .query(rangeQuery) ); } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index c9fced267..3e2e8e11f 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -121,9 +121,8 @@ private Object filterEmptyArrays(Object obj) { } private QueryResponse getQueryResponse(AASQuery query, List objectHits, String nextCursor) { - QueryResult queryResult = new QueryResult(objectHits); QueryPaging queryPaging = new QueryPaging(nextCursor, getResultType(query)); - QueryResponse queryResponse = new QueryResponse(queryPaging, queryResult); + QueryResponse queryResponse = new QueryResponse(queryPaging, objectHits); return queryResponse; } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java index f96a0209f..bb1f63da4 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java @@ -1,11 +1,13 @@ package org.eclipse.digitaltwin.basyx.querycore.query.model; +import java.util.List; + public class QueryResponse { public QueryPaging paging_metadata; - public QueryResult result; + public List result; - public QueryResponse(QueryPaging paging_metadata, QueryResult result) { + public QueryResponse(QueryPaging paging_metadata, List result) { this.paging_metadata = paging_metadata; this.result = result; } diff --git a/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java b/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java index 6d12af233..d3412979c 100644 --- a/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java +++ b/basyx.common/basyx.querycore/src/test/java/AASQueryConverterTest.java @@ -52,6 +52,8 @@ public class AASQueryConverterTest { put("match_eq_eq_field_strval.json", "expected_match_eq_eq_field_strval.json"); put("match_specific_asset_ids.json", "expected_match_specific_asset_ids.json"); put("regex_field_strval.json", "expected_regex_field_strval.json"); + put("starts-with_field_strval.json", "expected_starts-with_field_strval.json"); + put("ends-with_field_strval.json", "expected_ends-with_field_strval.json"); }}; @Test diff --git a/basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json new file mode 100644 index 000000000..69aa92479 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json @@ -0,0 +1,8 @@ +{ + "$condition": { + "$starts-with": [ + { "$field": "$aas#assetInformation.assetKind" }, + { "$strVal": "NST" } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/input/starts-with_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/starts-with_field_strval.json new file mode 100644 index 000000000..69aa92479 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/input/starts-with_field_strval.json @@ -0,0 +1,8 @@ +{ + "$condition": { + "$starts-with": [ + { "$field": "$aas#assetInformation.assetKind" }, + { "$strVal": "NST" } + ] + } +} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_ends-with_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_ends-with_field_strval.json new file mode 100644 index 000000000..1d3c321de --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_ends-with_field_strval.json @@ -0,0 +1 @@ +{"wildcard":{"assetInformation.assetKind.keyword":{"value":"*NST"}}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_starts-with_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_starts-with_field_strval.json new file mode 100644 index 000000000..f34e084d7 --- /dev/null +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_starts-with_field_strval.json @@ -0,0 +1 @@ +{"wildcard":{"assetInformation.assetKind.keyword":{"value":"NST*"}}} \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 6af9089fb..90c1283d2 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -4,10 +4,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonSerializer; import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -24,22 +27,26 @@ public final class IndexNormalizer { private static class ProcessingNode { final JsonNode jsonNode; final Object sourceObject; + final String pathPrefix; // e.g. "submodelElements.1234." - ProcessingNode(JsonNode jsonNode, Object sourceObject) { + ProcessingNode(JsonNode jsonNode, Object sourceObject, String pathPrefix) { this.jsonNode = jsonNode; this.sourceObject = sourceObject; + this.pathPrefix = pathPrefix; } } - public static JsonNode toIndexable(Submodel sm) throws SerializationException { - JsonNode root = MAPPER.valueToTree(sm); + public static JsonNode toIndexable(Submodel sm) throws SerializationException, IOException { + JsonSerializer serializer = new JsonSerializer(); + String submodelAsString = serializer.write(sm); + JsonNode root = MAPPER.readTree(new StringReader(submodelAsString)); rewriteIteratively(root, sm); return root; } private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObject) { Deque stack = new ArrayDeque<>(); - stack.push(new ProcessingNode(rootNode, rootSourceObject)); + stack.push(new ProcessingNode(rootNode, rootSourceObject, "submodelElements.")); while (!stack.isEmpty()) { ProcessingNode current = stack.pop(); @@ -51,40 +58,41 @@ private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObjec } ObjectNode obj = (ObjectNode) node; - + + // Get the idShort to build path + // TODO: Handle SML Indizes + String idShort = obj.has("idShort") && !(sourceObject instanceof Submodel) ? obj.get("idShort").asText() : null; + String currentPath = idShort != null ? current.pathPrefix + idShort + "." : current.pathPrefix; + + // If this element has a "value" that is primitive, we can directly move it to the flattened key JsonNode valueNode = obj.get("value"); - if (valueNode != null && (valueNode.isObject() || valueNode.isArray())) { - if (sourceObject instanceof SubmodelElementCollection) { - move(obj, "value", "smcChildren"); - } else if (sourceObject instanceof SubmodelElementList) { - move(obj, "value", "smlChildren"); - } else if (sourceObject instanceof ReferenceElement) { - move(obj, "value", "referenceElementChildren"); - } else if (sourceObject instanceof MultiLanguageProperty) { - move(obj, "value", "langContent"); - } else { - move(obj, "value", "peps"); - } + if (valueNode != null && !valueNode.isObject() && !valueNode.isArray()) { + // Set it at the flattened path + ((ObjectNode) rootNode).set(currentPath + "value", valueNode); + } + + // Traverse children if it's a collection/list/etc. + List children = null; + if (sourceObject instanceof Submodel) { + children = ((Submodel) sourceObject).getSubmodelElements(); + } else if (sourceObject instanceof SubmodelElementCollection) { + children = ((SubmodelElementCollection) sourceObject).getValue(); + } else if (sourceObject instanceof SubmodelElementList) { + children = ((SubmodelElementList) sourceObject).getValue(); } - List names = new ArrayList<>(); - obj.fieldNames().forEachRemaining(names::add); - - for (int i = names.size() - 1; i >= 0; i--) { - String name = names.get(i); - JsonNode child = obj.get(name); - Object childSourceObject = getChildSourceObject(sourceObject, name); - - if (child.isArray()) { - for (int j = child.size() - 1; j >= 0; j--) { - JsonNode arrayChild = child.get(j); - Object arrayChildSource = getArrayChildSourceObject(childSourceObject, j); - stack.push(new ProcessingNode(arrayChild, arrayChildSource)); + if (children != null) { + for (int i = children.size() - 1; i >= 0; i--) { + JsonNode childNode = obj.get("value") != null ? obj.get("value").get(i) : (obj.get("submodelElements") != null ? obj.get("submodelElements").get(i) : null); + if (childNode != null) { + stack.push(new ProcessingNode(childNode, children.get(i), currentPath)); } - } else if (child.isObject()) { - stack.push(new ProcessingNode(child, childSourceObject)); } } + if(valueNode != null && (valueNode.isObject() || valueNode.isArray())){ + // Remove Value if is non-primitive + move(obj,"value", "_value"); + } } } @@ -132,6 +140,41 @@ private static Object getChildSourceObject(Object parent, String fieldName) { return ((MultiLanguageProperty) parent).getValue(); } break; + default: + // Handle dynamically created fields based on SubmodelElement idShort + if (parent instanceof SubmodelElement parentElement) { + String parentIdShort = parentElement.getIdShort(); + // Check if this field name matches the idShort or is a nested field + if (fieldName.equals(parentIdShort)) { + if (parent instanceof SubmodelElementCollection) { + return ((SubmodelElementCollection) parent).getValue(); + } else if (parent instanceof SubmodelElementList) { + return ((SubmodelElementList) parent).getValue(); + } else if (parent instanceof ReferenceElement) { + return ((ReferenceElement) parent).getValue(); + } else if (parent instanceof MultiLanguageProperty) { + return ((MultiLanguageProperty) parent).getValue(); + } else if (parent instanceof Property) { + return parent; // For Property.value nested structure + } + } + // Handle nested .value fields (e.g., "porpa" field containing {value: "123"}) + if (fieldName.equals("value") && parent instanceof Property) { + return parent; + } + } + + // Handle cases where we're processing arrays of SubmodelElements + if (parent instanceof List parentList && !parentList.isEmpty()) { + // Try to find a SubmodelElement in the list that matches the field name + for (Object item : parentList) { + if (item instanceof SubmodelElement element && + fieldName.equals(element.getIdShort())) { + return element; + } + } + } + break; case "semanticId": if (parent instanceof HasSemantics) { return ((HasSemantics) parent).getSemanticId(); diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java index 9efc015f2..592fdc2b4 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java @@ -26,10 +26,14 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResult; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -38,6 +42,10 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @RestController @@ -45,26 +53,52 @@ public class SearchSubmodelRepositoryApiHTTPController implements SearchSubmodelRepositoryHTTPApi { private final ElasticsearchClient client; + private final SubmodelRepository backend; @Value("${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".indexname:" + SearchSubmodelRepositoryFeature.DEFAULT_INDEX + "}") private String indexName; @Autowired - public SearchSubmodelRepositoryApiHTTPController(ElasticsearchClient client) { + public SearchSubmodelRepositoryApiHTTPController(ElasticsearchClient client, SubmodelRepository backend) { this.client = client; + this.backend = backend; } @Override public ResponseEntity querySubmodels(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { QueryResponse queryResponse = null; try { - ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "Submodel"); - queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); - } catch (IOException e) { + if (query.get$select() != null && query.get$select().equals("id")) { + queryResponse = getQueryResponse(query, limit, cursor); + } else { + // Hard Code to only retrieve ids -> Fetching the actual Submodels from MongoDB + query.set$select("id"); + queryResponse = getQueryResponse(query, limit, cursor); + queryResponse.paging_metadata.resulType = "Submodel"; + List submodels = new ArrayList<>(); + for (Object id : queryResponse.result) { + String identifier = ((ObjectNode) id).get("id").asText(); + Submodel submodel = backend.getSubmodel(identifier); + submodels.add(submodel); + + } + //Stream the submodel list to a generic List + queryResponse.result = submodels.stream() + .map(sm -> (Object) sm) + .toList(); + } + } catch (IOException e) { throw new RuntimeException(e); } return new ResponseEntity(queryResponse, HttpStatus.OK); } + private QueryResponse getQueryResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + QueryResponse queryResponse; + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "Submodel"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + return queryResponse; + } + } From 0614143ddc183514056cb624e7dcc0e4f34ba309 Mon Sep 17 00:00:00 2001 From: fried Date: Thu, 14 Aug 2025 09:24:21 +0200 Subject: [PATCH 25/52] Other Search Components Utilize MongoDB now --- .../SearchAasRegistryApiHTTPController.java | 40 ++++++++++++++++--- .../README.md | 2 +- .../SearchAasRepositoryApiHTTPController.java | 37 +++++++++++++++-- .../query/converter/ValueConverter.java | 3 +- .../SearchCdRepositoryApiHTTPController.java | 40 ++++++++++++++++--- ...archSubmodelRegistryApiHTTPController.java | 40 ++++++++++++++++--- 6 files changed, 141 insertions(+), 21 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index 2187df93d..07edeac08 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -25,6 +25,9 @@ package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorage; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; @@ -38,6 +41,8 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @RestController @@ -45,21 +50,39 @@ public class SearchAasRegistryApiHTTPController implements SearchAasRegistryHTTP private static final Logger log = LoggerFactory.getLogger(SearchAasRegistryApiHTTPController.class); - private final ElasticsearchClient esClient; + private final ElasticsearchClient client; + private final AasRegistryStorage backend; @Value("${" + SearchAasRegistryFeature.FEATURENAME + ".indexname:" + SearchAasRegistryFeature.DEFAULT_INDEX + "}") private String indexName; @Autowired - public SearchAasRegistryApiHTTPController(ElasticsearchClient esClient) { - this.esClient = esClient; + public SearchAasRegistryApiHTTPController(ElasticsearchClient client, AasRegistryStorage backend) { + this.client = client; + this.backend = backend; } public ResponseEntity queryAssetAdministrationShellDescriptors(Integer limit, Base64UrlEncodedCursor cursor, AASQuery query) { QueryResponse queryResponse; try { - ESQueryExecutor executor = new ESQueryExecutor(esClient, indexName, "AssetAdministrationShellDescriptor"); - queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + if (query.get$select() != null && query.get$select().equals("id")) { + queryResponse = getQueryResponse(query, limit, cursor); + } else { + // Hard Code to only retrieve ids -> Fetching the actual AAS Descs from MongoDB + query.set$select("id"); + queryResponse = getQueryResponse(query, limit, cursor); + queryResponse.paging_metadata.resulType = "AssetAdministrationShellDescriptor"; + List aasDescs = new ArrayList<>(); + for (Object id : queryResponse.result) { + String identifier = ((ObjectNode) id).get("id").asText(); + AssetAdministrationShellDescriptor aasDesc = backend.getAasDescriptor(identifier); + aasDescs.add(aasDesc); + + } + queryResponse.result = aasDescs.stream() + .map(aasDesc -> (Object) aasDesc) + .toList(); + } } catch (IOException e) { throw new RuntimeException(e); } @@ -67,4 +90,11 @@ public ResponseEntity queryAssetAdministrationShellDescriptors(In return new ResponseEntity<>(queryResponse, HttpStatus.OK); } + private QueryResponse getQueryResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + QueryResponse queryResponse; + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "AssetAdministrationShellDescriptor"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + return queryResponse; + } + } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index c7ebd3137..04e5e51dd 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -66,6 +66,6 @@ * [ ] Integration Tests * [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL * [ ] Make the following SME Props queryable: value, valueType, semanticId, idShort -* [ ] Make other Components (AAS,CD) also fetch Data from MongoDB not ES +* [x] Make other Components (AAS,CD) also fetch Data from MongoDB not ES * [ ] Note down that comparison operators don't work for SME filtering in SM Repo without idShortPath * [ ] Handle Search Queries with SML (Indices) \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index ff0da5182..32f81a09d 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -26,6 +26,9 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; @@ -38,6 +41,8 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @RestController @@ -45,26 +50,50 @@ public class SearchAasRepositoryApiHTTPController implements SearchAasRepositoryHTTPApi { private final ElasticsearchClient client; + private final AasRepository backend; @Value("${" + SearchAasRepositoryFeature.FEATURENAME + ".indexname:" + SearchAasRepositoryFeature.DEFAULT_INDEX + "}") private String indexName; @Autowired - public SearchAasRepositoryApiHTTPController(ElasticsearchClient client) { + public SearchAasRepositoryApiHTTPController(ElasticsearchClient client, AasRepository backend) { this.client = client; + this.backend = backend; } @Override public ResponseEntity queryAssetAdministrationShells(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) { QueryResponse queryResponse = null; try { - ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "AssetAdministrationShell"); - queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + if (query.get$select() != null && query.get$select().equals("id")) { + queryResponse = getQueryResponse(query, limit, cursor); + } else { + // Hard Code to only retrieve ids -> Fetching the actual AAS from MongoDB + query.set$select("id"); + queryResponse = getQueryResponse(query, limit, cursor); + queryResponse.paging_metadata.resulType = "AssetAdministrationShell"; + List shells = new ArrayList<>(); + for (Object id : queryResponse.result) { + String identifier = ((ObjectNode) id).get("id").asText(); + AssetAdministrationShell shell = backend.getAas(identifier); + shells.add(shell); + + } + queryResponse.result = shells.stream() + .map(aas -> (Object) aas) + .toList(); + } } catch (IOException e) { throw new RuntimeException(e); } - return new ResponseEntity(queryResponse, HttpStatus.OK); + return new ResponseEntity<>(queryResponse, HttpStatus.OK); } + private QueryResponse getQueryResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + QueryResponse queryResponse; + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "AssetAdministrationShell"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + return queryResponse; + } } diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index c42b82b66..92e0ab40c 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -681,7 +681,7 @@ private Query createSmeWildcardQuery(String wildcardField, String value) { String queryPattern = "*"+searchField; return QueryBuilders.queryString(q -> q - .query(value) + .query(escapeQueryString(value)) .fields(queryPattern) ); } @@ -771,6 +771,7 @@ private String escapeQueryString(String value) { .replace("]", "\\]") .replace("^", "\\^") .replace("~", "\\~") + .replace("*", "\\*") .replace(":", "\\:"); } } \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java index c903d2989..f1d849694 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java @@ -26,6 +26,9 @@ package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; @@ -38,6 +41,8 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @RestController @@ -45,26 +50,51 @@ public class SearchCdRepositoryApiHTTPController implements SearchCdRepositoryHTTPApi { private final ElasticsearchClient client; + private final ConceptDescriptionRepository backend; @Value("${" + SearchCdRepositoryFeature.FEATURENAME + ".indexname:" + SearchCdRepositoryFeature.DEFAULT_INDEX + "}") private String indexName; @Autowired - public SearchCdRepositoryApiHTTPController(ElasticsearchClient client) { + public SearchCdRepositoryApiHTTPController(ElasticsearchClient client, ConceptDescriptionRepository backend) { this.client = client; + this.backend = backend; } @Override - public ResponseEntity queryConceptDescriptions(Integer limit, Base64UrlEncodedCursor cursor,AASQuery query) { + public ResponseEntity queryConceptDescriptions(Integer limit, Base64UrlEncodedCursor cursor, AASQuery query) { QueryResponse queryResponse = null; try { - ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "ConceptDescription"); - queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + if (query.get$select() != null && query.get$select().equals("id")) { + queryResponse = getQueryResponse(query, limit, cursor); + } else { + // Hard Code to only retrieve ids -> Fetching the actual CD from MongoDB + query.set$select("id"); + queryResponse = getQueryResponse(query, limit, cursor); + queryResponse.paging_metadata.resulType = "ConceptDescription"; + List cds = new ArrayList<>(); + for (Object id : queryResponse.result) { + String identifier = ((ObjectNode) id).get("id").asText(); + ConceptDescription cd = backend.getConceptDescription(identifier); + cds.add(cd); + + } + queryResponse.result = cds.stream() + .map(cd -> (Object) cd) + .toList(); + } } catch (IOException e) { throw new RuntimeException(e); } - return new ResponseEntity(queryResponse, HttpStatus.OK); + return new ResponseEntity<>(queryResponse, HttpStatus.OK); + } + + private QueryResponse getQueryResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + QueryResponse queryResponse; + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "ConceptDescription"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + return queryResponse; } } diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java index 95fed4656..1d591dd54 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java @@ -26,10 +26,13 @@ package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.executor.ESQueryExecutor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +42,8 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @RestController @@ -46,21 +51,39 @@ public class SearchSubmodelRegistryApiHTTPController implements SearchSubmodelRe private static final Logger log = LoggerFactory.getLogger(SearchSubmodelRegistryApiHTTPController.class); - private final ElasticsearchClient esClient; + private final ElasticsearchClient client; + private final SubmodelRegistryStorage backend; @Value("${" + SearchSubmodelRegistryFeature.FEATURENAME + ".indexname:" + SearchSubmodelRegistryFeature.DEFAULT_INDEX + "}") private String indexName; @Autowired - public SearchSubmodelRegistryApiHTTPController(ElasticsearchClient esClient) { - this.esClient = esClient; + public SearchSubmodelRegistryApiHTTPController(ElasticsearchClient client, SubmodelRegistryStorage backend) { + this.client = client; + this.backend = backend; } public ResponseEntity querySubmodelDescriptors(Integer limit, Base64UrlEncodedCursor cursor, AASQuery query) { QueryResponse queryResponse; try { - ESQueryExecutor executor = new ESQueryExecutor(esClient, indexName, "SubmodelDescriptor"); - queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + if (query.get$select() != null && query.get$select().equals("id")) { + queryResponse = getQueryResponse(query, limit, cursor); + } else { + // Hard Code to only retrieve ids -> Fetching the actual SM Descs from MongoDB + query.set$select("id"); + queryResponse = getQueryResponse(query, limit, cursor); + queryResponse.paging_metadata.resulType = "SubmodelDescriptor"; + List smDescs = new ArrayList<>(); + for (Object id : queryResponse.result) { + String identifier = ((ObjectNode) id).get("id").asText(); + SubmodelDescriptor smDesc = backend.getSubmodelDescriptor(identifier); + smDescs.add(smDesc); + + } + queryResponse.result = smDescs.stream() + .map(smDesc -> (Object) smDesc) + .toList(); + } } catch (IOException e) { throw new RuntimeException(e); } @@ -68,4 +91,11 @@ public ResponseEntity querySubmodelDescriptors(Integer limit, Bas return new ResponseEntity<>(queryResponse, HttpStatus.OK); } + private QueryResponse getQueryResponse(AASQuery query, Integer limit, Base64UrlEncodedCursor cursor) throws IOException { + QueryResponse queryResponse; + ESQueryExecutor executor = new ESQueryExecutor(client, indexName, "SubmodelDescriptor"); + queryResponse = executor.executeQueryAndGetResponse(query, limit, cursor); + return queryResponse; + } + } From 080aaac8bece482524e02526497ef16d0f90f51d Mon Sep 17 00:00:00 2001 From: fried Date: Thu, 14 Aug 2025 11:01:27 +0200 Subject: [PATCH 26/52] Adapts Path Parsing --- .../README.md | 3 +- .../query/executor/ESQueryExecutor.java | 48 ++++++++++--------- .../feature/search/IndexNormalizer.java | 39 +++++++++++---- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index 04e5e51dd..0c8649e5b 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -68,4 +68,5 @@ * [ ] Make the following SME Props queryable: value, valueType, semanticId, idShort * [x] Make other Components (AAS,CD) also fetch Data from MongoDB not ES * [ ] Note down that comparison operators don't work for SME filtering in SM Repo without idShortPath -* [ ] Handle Search Queries with SML (Indices) \ No newline at end of file +* [ ] Handle Search Queries with SML (Indices) +* [ ] Handle error case when Query is not valid (e.g. "$field: $sme", "$field: $sme.smc.smc.propa") \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index 3e2e8e11f..4a9c5ba9b 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -1,6 +1,7 @@ package org.eclipse.digitaltwin.basyx.querycore.query.executor; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.ElasticsearchException; import co.elastic.clients.elasticsearch._types.SortOptions; import co.elastic.clients.elasticsearch._types.SortOrder; import co.elastic.clients.elasticsearch.core.SearchRequest; @@ -48,37 +49,40 @@ public QueryResponse executeQueryAndGetResponse(AASQuery query, Integer limit, B applyCursor(cursor, searchRequestBuilder); SearchRequest paginatedSearchRequest = searchRequestBuilder.build(); + try { + SearchResponse response = client.search(paginatedSearchRequest, Object.class); - SearchResponse response = client.search(paginatedSearchRequest, Object.class); + List> topHits = response.hits().hits(); - List> topHits = response.hits().hits(); + boolean hasMore = topHits.size() > pageSize; + List> pageHits = hasMore ? topHits.subList(0, pageSize) : topHits; - boolean hasMore = topHits.size() > pageSize; - List> pageHits = hasMore ? topHits.subList(0, pageSize) : topHits; - - ObjectMapper mapper = new ObjectMapper(); - - List objectHits = pageHits.stream() - .map(Hit::source) - .map(this::filterEmptyArrays) - .map(mapper::valueToTree) - .toList(); + ObjectMapper mapper = new ObjectMapper(); + List objectHits = pageHits.stream() + .map(Hit::source) + .map(this::filterEmptyArrays) + .map(mapper::valueToTree) + .toList(); - String nextCursor = getNextCursor(hasMore, pageHits); + String nextCursor = getNextCursor(hasMore, pageHits); - for(Object hit : objectHits){ - if (hit instanceof ObjectNode) { - ObjectNode node = (ObjectNode) hit; - rewriteIterative(node); - } else { - logger.warn("Hit is not an ObjectNode: {}", hit.getClass().getName()); + for (Object hit : objectHits) { + if (hit instanceof ObjectNode) { + ObjectNode node = (ObjectNode) hit; + rewriteIterative(node); + } else { + logger.warn("Hit is not an ObjectNode: {}", hit.getClass().getName()); + } } - } - QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); - return queryResponse; + QueryResponse queryResponse = getQueryResponse(query, objectHits, nextCursor); + return queryResponse; + } catch(ElasticsearchException exception) { + logger.error("Elasticsearch query execution failed: {}", exception.getMessage(), exception); + return new QueryResponse(new QueryPaging(null, getResultType(query)), Collections.emptyList()); + } } /** diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 90c1283d2..82c08e3b4 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -28,11 +28,15 @@ private static class ProcessingNode { final JsonNode jsonNode; final Object sourceObject; final String pathPrefix; // e.g. "submodelElements.1234." + final int childIndex; // Used for array elements to track the index + private final Object parentObject; - ProcessingNode(JsonNode jsonNode, Object sourceObject, String pathPrefix) { + ProcessingNode(JsonNode jsonNode, Object sourceObject, Object parentObject, String pathPrefix, int childIndex) { this.jsonNode = jsonNode; this.sourceObject = sourceObject; this.pathPrefix = pathPrefix; + this.childIndex = childIndex; + this.parentObject = parentObject; } } @@ -46,7 +50,7 @@ public static JsonNode toIndexable(Submodel sm) throws SerializationException, I private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObject) { Deque stack = new ArrayDeque<>(); - stack.push(new ProcessingNode(rootNode, rootSourceObject, "submodelElements.")); + stack.push(new ProcessingNode(rootNode, rootSourceObject,null, "submodelElements.",0)); while (!stack.isEmpty()) { ProcessingNode current = stack.pop(); @@ -62,14 +66,22 @@ private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObjec // Get the idShort to build path // TODO: Handle SML Indizes String idShort = obj.has("idShort") && !(sourceObject instanceof Submodel) ? obj.get("idShort").asText() : null; - String currentPath = idShort != null ? current.pathPrefix + idShort + "." : current.pathPrefix; + String currentPath = ""; + if (current.parentObject instanceof SubmodelElementList) { + currentPath = current.pathPrefix.endsWith(".") ? current.pathPrefix.substring(0, current.pathPrefix.length() - 1) : current.pathPrefix; + currentPath = currentPath + "["+current.childIndex+"]" + (current.sourceObject instanceof SubmodelElementList ? "" : "."); + } else { + currentPath = idShort != null ? current.pathPrefix + idShort + "." : current.pathPrefix; + } // If this element has a "value" that is primitive, we can directly move it to the flattened key - JsonNode valueNode = obj.get("value"); - if (valueNode != null && !valueNode.isObject() && !valueNode.isArray()) { - // Set it at the flattened path - ((ObjectNode) rootNode).set(currentPath + "value", valueNode); - } + JsonNode valueNode = indexField(obj, "value", (ObjectNode) rootNode, currentPath); + + indexField(obj, "valueType", (ObjectNode) rootNode, currentPath); + + indexField(obj, "idShort", (ObjectNode) rootNode, currentPath); + + indexField(obj, "language", (ObjectNode) rootNode, currentPath); // Traverse children if it's a collection/list/etc. List children = null; @@ -85,7 +97,7 @@ private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObjec for (int i = children.size() - 1; i >= 0; i--) { JsonNode childNode = obj.get("value") != null ? obj.get("value").get(i) : (obj.get("submodelElements") != null ? obj.get("submodelElements").get(i) : null); if (childNode != null) { - stack.push(new ProcessingNode(childNode, children.get(i), currentPath)); + stack.push(new ProcessingNode(childNode,children.get(i), current.sourceObject, currentPath, i)); } } } @@ -96,6 +108,15 @@ private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObjec } } + private static JsonNode indexField(ObjectNode obj, String fieldName, ObjectNode rootNode, String currentPath) { + JsonNode node = obj.get(fieldName); + if (node != null && !node.isObject() && !node.isArray()) { + // Set it at the flattened path + rootNode.set(currentPath + (currentPath.endsWith("]") ? "." : "") + fieldName, node); + } + return node; + } + /** * Gets the child source object corresponding to a field name */ From 2af05689a5265b21bc145ed8405a3adbe5aeabf0 Mon Sep 17 00:00:00 2001 From: fried Date: Thu, 14 Aug 2025 11:21:52 +0200 Subject: [PATCH 27/52] Adapts Path Parsing --- .../basyx.aasrepository-feature-search/README.md | 4 ++-- .../basyx/querycore/query/converter/ValueConverter.java | 7 ++----- .../submodelrepository/feature/search/IndexNormalizer.java | 1 - 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md index 0c8649e5b..53c968c41 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/README.md @@ -65,8 +65,8 @@ * [x] !Priority! Duplicate search module for the missing components * [ ] Integration Tests * [ ] Validate Expected Queries (unformatted ones) -> Depends on other components to utilize the AASQL -* [ ] Make the following SME Props queryable: value, valueType, semanticId, idShort +* [x] Make the following SME Props queryable: value, valueType, semanticId, idShort * [x] Make other Components (AAS,CD) also fetch Data from MongoDB not ES * [ ] Note down that comparison operators don't work for SME filtering in SM Repo without idShortPath -* [ ] Handle Search Queries with SML (Indices) +* [x] Handle Search Queries with SML (Indices) * [ ] Handle error case when Query is not valid (e.g. "$field: $sme", "$field: $sme.smc.smc.propa") \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 92e0ab40c..28eac2235 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -417,9 +417,6 @@ private String convertModelFieldToElasticField(String modelField) { result = modelField.replace("$smdesc#", ""); } - // Remove array brackets [] from field names - result = result.replaceAll("\\[\\]", ""); - // Add .keyword suffix for string fields that need exact matching // This is typically needed for fields that contain text values if (isStringField(result)) { @@ -654,7 +651,7 @@ private String generateCastScript(String fieldExpression, String castType) { * Checks if a field is an SME wildcard field that needs special handling */ private boolean isSmeWildcardField(String fieldName) { - return fieldName != null && fieldName.startsWith("SME_WILDCARD:"); + return fieldName != null && (fieldName.startsWith("SME_WILDCARD:") || fieldName.contains("[]")); } /** @@ -682,7 +679,7 @@ private Query createSmeWildcardQuery(String wildcardField, String value) { return QueryBuilders.queryString(q -> q .query(escapeQueryString(value)) - .fields(queryPattern) + .fields(queryPattern.replace("[]","[*]")) ); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 82c08e3b4..7aa175439 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -64,7 +64,6 @@ private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObjec ObjectNode obj = (ObjectNode) node; // Get the idShort to build path - // TODO: Handle SML Indizes String idShort = obj.has("idShort") && !(sourceObject instanceof Submodel) ? obj.get("idShort").asText() : null; String currentPath = ""; if (current.parentObject instanceof SubmodelElementList) { From 7cbf097d457f91c2e8c9ea62b292c3e95910ae4f Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 18 Aug 2025 11:47:43 +0200 Subject: [PATCH 28/52] Adapts QL --- .../search/SearchAasRegistryHTTPApi.java | 4 +- .../SearchAasRepositoryApiHTTPController.java | 2 +- .../SearchAasRepositoryConfiguration.java | 1 - ...SearchAasRepositoryConfigurationGuard.java | 25 + .../search/SearchAasRepositoryFactory.java | 1 - .../search/SearchAasRepositoryFeature.java | 1 - .../search/SearchAasRepositoryHTTPApi.java | 2 +- .../basyx.querycore}/README.md | 5 +- .../AASQueryToElasticSearchConverter.java | 25 + .../ElasticSearchRequestBuilder.java | 25 + .../converter/LogicalExpressionConverter.java | 25 + .../converter/MatchExpressionConverter.java | 25 + .../query/converter/ValueConverter.java | 23 +- .../query/executor/ESQueryExecutor.java | 25 + .../basyx/querycore/query/model/AASQuery.java | 30 + .../query/model/AccessPermissionRule.java | 30 + .../basyx/querycore/query/model/Acl.java | 30 + .../query/model/AllAccessPermissionRules.java | 30 + .../querycore/query/model/AttributeItem.java | 30 + .../basyx/querycore/query/model/Defacl.java | 30 + .../querycore/query/model/Defattribute.java | 30 + .../querycore/query/model/Defformula.java | 30 + .../querycore/query/model/Defobject.java | 30 + .../query/model/LogicalExpression.java | 30 + .../query/model/MatchExpression.java | 30 + .../querycore/query/model/ObjectItem.java | 30 + .../basyx/querycore/query/model/QlSchema.java | 30 + .../querycore/query/model/QueryPaging.java | 25 + .../querycore/query/model/QueryResponse.java | 25 + .../querycore/query/model/QueryResult.java | 25 + .../querycore/query/model/RightsEnum.java | 30 + .../querycore/query/model/StringValue.java | 30 + .../basyx/querycore/query/model/Value.java | 36 +- .../feature/search/SearchCdRepository.java | 1 + .../SearchCdRepositoryApiHTTPController.java | 2 +- .../SearchCdRepositoryConfiguration.java | 1 - .../SearchCdRepositoryConfigurationGuard.java | 25 + .../search/SearchCdRepositoryFactory.java | 1 - .../search/SearchCdRepositoryFeature.java | 1 - .../search/SearchCdRepositoryHTTPApi.java | 26 + .../search/SearchSubmodelRegistryHTTPApi.java | 1 + .../pom.xml | 39 + .../feature/search/IndexNormalizer.java | 30 +- .../search/SearchSubmodelRepository.java | 2 +- ...chSubmodelRepositoryApiHTTPController.java | 2 +- ...SearchSubmodelRepositoryConfiguration.java | 1 - ...hSubmodelRepositoryConfigurationGuard.java | 25 + .../SearchSubmodelRepositoryFactory.java | 1 - .../SearchSubmodelRepositoryFeature.java | 1 - .../SearchSubmodelRepositoryHTTPApi.java | 2 +- ...ummySearchSubmodelRepositoryComponent.java | 42 + .../search/DummySubmodelRepositoryConfig.java | 52 + .../search/TestSearchSubmodelRepository.java | 108 + .../src/test/resources/Example-Full.json | 2597 +++++++++++++++++ .../src/test/resources/application.properties | 57 + .../src/test/resources/contains.json | 48 + .../basyx.submodelrepository-http/pom.xml | 1 - ci/docker-compose.yml | 2 + pom.xml | 6 + 59 files changed, 3786 insertions(+), 38 deletions(-) rename {basyx.aasrepository/basyx.aasrepository-feature-search => basyx.common/basyx.querycore}/README.md (97%) create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySearchSubmodelRepositoryComponent.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySubmodelRepositoryConfig.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/Example-Full.json create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/application.properties create mode 100644 basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/contains.json diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java index 52a0d67f7..a986c3456 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryHTTPApi.java @@ -28,8 +28,8 @@ * https://github.com/swagger-api/swagger-codegen * Do not edit the class manually. */ -package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -51,8 +51,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.eclipse.digitaltwin.aas4j.v3.model.Result; - - @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @Validated public interface SearchAasRegistryHTTPApi { diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java index 32f81a09d..6e1d8d74a 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryApiHTTPController.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java index b5f83dc74..f6f31d23e 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java index ac912619c..c69231798 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfigurationGuard.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import org.slf4j.Logger; diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java index 7cb411e83..af807ae58 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFactory.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java index 39fc7a5ba..479ecc0c1 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java index c557f7c06..13ab52b74 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryHTTPApi.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md b/basyx.common/basyx.querycore/README.md similarity index 97% rename from basyx.aasrepository/basyx.aasrepository-feature-search/README.md rename to basyx.common/basyx.querycore/README.md index 53c968c41..2798afc6a 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/README.md +++ b/basyx.common/basyx.querycore/README.md @@ -51,6 +51,7 @@ ## TODOS + * [x] Extract Query Package from basyx.core to own module (e.g. basyx.querycore) * [x] Comparison of two fields is not possible yet * [x] Fix Error: java.lang.ClassNotFoundException: co.elastic.clients.elasticsearch._types.ScriptSource$Builder in ValueConverter during Runtime @@ -69,4 +70,6 @@ * [x] Make other Components (AAS,CD) also fetch Data from MongoDB not ES * [ ] Note down that comparison operators don't work for SME filtering in SM Repo without idShortPath * [x] Handle Search Queries with SML (Indices) -* [ ] Handle error case when Query is not valid (e.g. "$field: $sme", "$field: $sme.smc.smc.propa") \ No newline at end of file +* [ ] Handle error case when Query is not valid (e.g. "$field: $sme", "$field: $sme.smc.smc.propa") +* [x] License Header hinzufügen +* When querying for (supplemental-)semanticId Keys append with .value \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/AASQueryToElasticSearchConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/AASQueryToElasticSearchConverter.java index 6358c1680..cf15453d5 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/AASQueryToElasticSearchConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/AASQueryToElasticSearchConverter.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java index e6bb5351a..b1cb57a11 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ElasticSearchRequestBuilder.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch.core.SearchRequest; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java index 6ad8ba870..02b8d0f7e 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/LogicalExpressionConverter.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.Query; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java index d69636830..07c246974 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/MatchExpressionConverter.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.converter; import co.elastic.clients.elasticsearch._types.query_dsl.Query; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 28eac2235..4cd3da7f9 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -23,7 +23,11 @@ public class ValueConverter { public Query convertEqualityComparison(Value leftValue, Value rightValue) { String leftField = extractFieldName(leftValue); String rightField = extractFieldName(rightValue); - + + if(!leftField.endsWith(".keyword")) { + leftField = leftField + ".keyword"; + } + // Field-to-field comparison if (leftField != null && rightField != null) { return createFieldToFieldComparison(leftField, rightField, "eq"); @@ -449,7 +453,7 @@ private FieldValue convertToFieldValue(Object value) { if (value instanceof String) { return FieldValue.of((String) value); } else if (value instanceof Number) { - return FieldValue.of(((Number) value).doubleValue()); + return FieldValue.of(value); } else if (value instanceof Boolean) { return FieldValue.of((Boolean) value); } else { @@ -490,17 +494,12 @@ private String convertToString(Object value) { return value != null ? value.toString() : null; } - private Double convertToNumber(Object value) { - if (value instanceof Number) { - return ((Number) value).doubleValue(); - } else if (value instanceof String) { - try { - return Double.parseDouble((String) value); - } catch (NumberFormatException e) { - return null; - } + private Number convertToNumber(Object value) { + try{ + return (Number) value; + }catch (ClassCastException e){ + return null; } - return null; } private Boolean convertToBoolean(Object value) { diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java index 4a9c5ba9b..64e844859 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/executor/ESQueryExecutor.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.executor; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AASQuery.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AASQuery.java index a3776cfff..674a6bc94 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AASQuery.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AASQuery.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AccessPermissionRule.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AccessPermissionRule.java index ee91fa0e3..a75418545 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AccessPermissionRule.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AccessPermissionRule.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Acl.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Acl.java index 80b2f7b63..2a023fbdc 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Acl.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Acl.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AllAccessPermissionRules.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AllAccessPermissionRules.java index 478962fa4..be8d99d91 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AllAccessPermissionRules.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AllAccessPermissionRules.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AttributeItem.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AttributeItem.java index 177f4ebaa..b272e9960 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AttributeItem.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/AttributeItem.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defacl.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defacl.java index 50267c3de..abe82cb71 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defacl.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defacl.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defattribute.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defattribute.java index 2b9aa5b2c..f92b05189 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defattribute.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defattribute.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defformula.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defformula.java index 85f473269..ba01c43b2 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defformula.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defformula.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defobject.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defobject.java index 8cf910169..b02e7afde 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defobject.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Defobject.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/LogicalExpression.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/LogicalExpression.java index c333c0529..d3d85d156 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/LogicalExpression.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/LogicalExpression.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/MatchExpression.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/MatchExpression.java index c44121b89..520cc74e1 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/MatchExpression.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/MatchExpression.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/ObjectItem.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/ObjectItem.java index bd53bb877..72348471b 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/ObjectItem.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/ObjectItem.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QlSchema.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QlSchema.java index 0d3409afb..ea0998305 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QlSchema.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QlSchema.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryPaging.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryPaging.java index d272aa218..872b01a0d 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryPaging.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryPaging.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.model; public class QueryPaging { diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java index bb1f63da4..598bacb0e 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResponse.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResult.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResult.java index 37ccf1d55..2761a6bc4 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResult.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/QueryResult.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.querycore.query.model; import java.util.List; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/RightsEnum.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/RightsEnum.java index b9afba87a..32d704c00 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/RightsEnum.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/RightsEnum.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/StringValue.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/StringValue.java index b28c44e18..13e938672 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/StringValue.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/StringValue.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Value.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Value.java index b1e7109fa..543928559 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Value.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/model/Value.java @@ -1,3 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +/** + * NOTE: This class is auto generated by the jsonschema2pojo program. + * https://www.jsonschema2pojo.org/ + * Do not edit the class manually. + */ package org.eclipse.digitaltwin.basyx.querycore.query.model; @@ -36,7 +66,7 @@ public class Value { @JsonProperty("$attribute") private AttributeItem $attribute; @JsonProperty("$numVal") - private Double $numVal; + private Number $numVal; @JsonProperty("$hexVal") private String $hexVal; @JsonProperty("$dateTimeVal") @@ -97,12 +127,12 @@ public class Value { } @JsonProperty("$numVal") - public Double get$numVal() { + public Number get$numVal() { return $numVal; } @JsonProperty("$numVal") - public void set$numVal(Double $numVal) { + public void set$numVal(Number $numVal) { this.$numVal = $numVal; } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java index c054c35ee..60f16e8b1 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java @@ -22,6 +22,7 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java index f1d849694..4a444617d 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryApiHTTPController.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java index e1917925a..fbf17b85b 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfiguration.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java index 1b8a3ca10..3bb742319 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryConfigurationGuard.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import org.slf4j.Logger; diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java index 9ea6109e0..509beb906 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFactory.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java index 9e3a48143..b9440d451 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryFeature.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java index f91f18700..6bbb8ca8e 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepositoryHTTPApi.java @@ -1,8 +1,34 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + /** * NOTE: This class is auto generated by the swagger code generator program (3.0.69). * https://github.com/swagger-api/swagger-codegen * Do not edit the class manually. */ + package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import io.swagger.v3.oas.annotations.Operation; diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java index ad9af820e..bd4fb4d07 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryHTTPApi.java @@ -28,6 +28,7 @@ * https://github.com/swagger-api/swagger-codegen * Do not edit the class manually. */ + package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml index 80a225622..fa87ef2f8 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml @@ -18,6 +18,16 @@ org.eclipse.digitaltwin.basyx basyx.submodelrepository-core + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-backend + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-backend-mongodb + test + org.eclipse.digitaltwin.basyx basyx.querycore @@ -27,6 +37,16 @@ elasticsearch-java 9.0.1 + + com.fasterxml.jackson.core + jackson-core + 2.19.0 + + + com.fasterxml.jackson.core + jackson-annotations + 2.19.0 + com.fasterxml.jackson.core jackson-databind @@ -48,12 +68,31 @@ org.eclipse.digitaltwin.basyx basyx.http + + org.eclipse.digitaltwin.basyx + basyx.http + test + tests + javax.annotation javax.annotation-api 1.3.2 compile + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-core + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-core + + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-core + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java index 7aa175439..e176d4306 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/IndexNormalizer.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import com.fasterxml.jackson.databind.JsonNode; @@ -12,7 +37,6 @@ import java.io.IOException; import java.io.StringReader; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Deque; import java.util.List; @@ -77,10 +101,10 @@ private static void rewriteIteratively(JsonNode rootNode, Object rootSourceObjec JsonNode valueNode = indexField(obj, "value", (ObjectNode) rootNode, currentPath); indexField(obj, "valueType", (ObjectNode) rootNode, currentPath); - indexField(obj, "idShort", (ObjectNode) rootNode, currentPath); - indexField(obj, "language", (ObjectNode) rootNode, currentPath); + indexField(obj, "semanticId", (ObjectNode) rootNode, currentPath); + indexField(obj, "supplementalSemanticIds", (ObjectNode) rootNode, currentPath); // Traverse children if it's a collection/list/etc. List children = null; diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java index 9602ed2c1..d1ef29adf 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java @@ -22,6 +22,7 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; @@ -59,7 +60,6 @@ public SearchSubmodelRepository(SubmodelRepository decorated, ElasticsearchClien this.decorated = decorated; this.esclient = esclient; this.indexName = indexName; - //ensureIndexExists(); } @Override diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java index 592fdc2b4..1c2c3b943 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryApiHTTPController.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java index 231f1c7f4..db9806527 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfiguration.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java index e1e115b9b..d38a05a2f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryConfigurationGuard.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import org.slf4j.Logger; diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java index 4a49afefd..28712c4e0 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFactory.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java index a5e41cb33..920386f8d 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryFeature.java @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import co.elastic.clients.elasticsearch.ElasticsearchClient; diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java index 3fbf74092..d1c6f9188 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepositoryHTTPApi.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySearchSubmodelRepositoryComponent.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySearchSubmodelRepositoryComponent.java new file mode 100644 index 000000000..0e542f21a --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySearchSubmodelRepositoryComponent.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Spring application configured for tests. + * + * @author aaronzi, fried + * + */ + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class DummySearchSubmodelRepositoryComponent { + public static void main(String[] args) { + SpringApplication.run(DummySearchSubmodelRepositoryComponent.class, args); + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySubmodelRepositoryConfig.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySubmodelRepositoryConfig.java new file mode 100644 index 000000000..f50740724 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DummySubmodelRepositoryConfig.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.feature.DecoratedSubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.feature.SubmodelRepositoryFeature; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * Configuration for tests + * + * @author mateusmolina, danish + * + */ +@Configuration +public class DummySubmodelRepositoryConfig { + + @Bean + @ConditionalOnMissingBean + public static SubmodelRepository getSubmodelRepository(SubmodelRepositoryFactory submodelRepositoryFactory, List features) { + return new DecoratedSubmodelRepositoryFactory(submodelRepositoryFactory, features).create(); + } +} \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java new file mode 100644 index 000000000..2605da19f --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.ResponseEntity; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.List; + +public class TestSearchSubmodelRepository { + private static ConfigurableApplicationContext appContext; + private static SubmodelRepository searchBackend; + private static SearchSubmodelRepositoryApiHTTPController searchAPI; + + @BeforeClass + public static void startSmRepo() throws Exception { + appContext = new SpringApplicationBuilder(DummySearchSubmodelRepositoryComponent.class).run(new String[] {}); + searchBackend = appContext.getBean(SubmodelRepository.class); + searchAPI = appContext.getBean(SearchSubmodelRepositoryApiHTTPController.class); + preloadSubmodels(); + } + + @Test + public void testRepo() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchSubmodelRepository.class.getResource("/contains.json").getFile()); + AASQuery query = queryFromFile(file); + ResponseEntity result = searchAPI.querySubmodels(query, -1, new Base64UrlEncodedCursor("")); + QueryResponse response = result.getBody(); + assert response != null; + List submodels = response.result.stream().map(o->(Submodel)o).toList(); + Assert.assertEquals(3,submodels.size()); + } + + private static AASQuery queryFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), AASQuery.class); + } + + private static Environment envFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), Environment.class); + } + + private static void preloadSubmodels() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchSubmodelRepository.class.getResource("/Example-Full.json").getFile()); + Environment env = envFromFile(file); + for(Submodel submodel : env.getSubmodels()) { + searchBackend.createSubmodel(submodel); + } + } + + @AfterClass + public static void shutdownAasRepo() { + searchBackend.getAllSubmodels(new PaginationInfo(0, "")).getResult().forEach(submodel -> { + try { + searchBackend.deleteSubmodel(submodel.getId()); + } catch (Exception e) { + // Ignore exceptions during cleanup + } + }); + appContext.close(); + } + + private String getURL() { + return "http://localhost:8081/submodels"; + } + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/Example-Full.json b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/Example-Full.json new file mode 100644 index 000000000..0dcceaa3e --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/Example-Full.json @@ -0,0 +1,2597 @@ +{ + "assetAdministrationShells": [ + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset" + }, + "derivedFrom": { + "keys": [ + { + "type": "AssetAdministrationShell", + "value": "https://acplt.org/TestAssetAdministrationShell2" + } + ], + "type": "ExternalReference" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/Identification" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel2_Mandatory" + } + ], + "type": "ExternalReference" + } + ], + "id": "https://acplt.org/Test_AssetAdministrationShell_Mandatory", + "idShort": "Test_AssetAdministrationShell_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory", + "idShort": "Test_AssetAdministrationShell2_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Missing" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell_Missing", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + } + ], + "conceptDescriptions": [ + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription", + "idShort": "TestConceptDescription", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/DataSpecifications/Conceptdescription/TestConceptDescription" + } + ], + "type": "ExternalReference" + } + ], + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "id": "https://acplt.org/Test_ConceptDescription_Mandatory", + "idShort": "Test_ConceptDescription_Mandatory" + }, + { + "modelType": "ConceptDescription", + "category": "PROPERTY", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription_Missing", + "idShort": "TestConceptDescription1", + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/DataSpecifciations/Example/Identification", + "idShort": "TestSpec_01", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ConceptDescriptionX" + } + ], + "type": "ExternalReference" + } + ], + "embeddedDataSpecifications": [ + { + "dataSpecification": { + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/aas/3/0/RC02/DataSpecificationIec61360" + } + ], + "type": "ExternalReference" + }, + "dataSpecificationContent": { + "modelType": "DataSpecificationIec61360", + "dataType": "REAL_MEASURE", + "sourceOfDefinition": "http://acplt.org/DataSpec/ExampleDef", + "symbol": "SU", + "unit": "SpaceUnit", + "unitId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Units/SpaceUnit" + } + ], + "type": "ExternalReference" + }, + "levelType": { + "max": true, + "min": false, + "nom": false, + "typ": false + }, + "value": "TEST", + "valueFormat": "string", + "valueList": { + "valueReferencePairs": [ + { + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + } + }, + { + "value": "http://acplt.org/ValueId/ExampleValueId2", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId2" + } + ], + "type": "ExternalReference" + } + } + ] + }, + "definition": [ + { + "language": "de", + "text": "Dies ist eine Data Specification für Testzwecke" + }, + { + "language": "en-us", + "text": "This is a DataSpecification for testing purposes" + } + ], + "preferredName": [ + { + "language": "de", + "text": "Test Specification" + }, + { + "language": "en-us", + "text": "TestSpecification" + } + ], + "shortName": [ + { + "language": "de", + "text": "Test Spec" + }, + { + "language": "en-us", + "text": "TestSpec" + } + ] + } + } + ] + } + ], + "submodels": [ + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/AssetIdentification" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/Identification", + "idShort": "Identification", + "submodelElements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO677#002" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ACPLT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ACPLT" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "value": "100", + "valueType": "xs:int", + "kind": "ConceptQualifier" + }, + { + "type": "http://acplt.org/Qualifier/ExampleQualifier2", + "value": "50", + "valueType": "xs:int", + "kind": "ConceptQualifier" + } + ], + "idShort": "ManufacturerName", + "displayName": [ + { + "language": "en-us", + "text": "Manufacturer Name" + } + ], + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Property", + "category": "VARIABLE", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "supplementalSemanticIds": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "something_random_e14ad770" + } + ], + "type": "ExternalReference" + }, + { + "keys": [{ + "type": "GlobalReference", + "value": "something_random_bd061acd" + }], + "type": "ExternalReference" + } + + ], + "value": "978-8234-234-342", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "978-8234-234-342" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "InstanceId", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example asset identification submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + "administration": { + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial", + "idShort": "BillOfMaterial", + "submodelElements": [ + { + "modelType": "Entity", + "entityType": "CoManagedEntity", + "statements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValue2", + "category": "CONSTANT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValue2" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty", + "category": "CONSTANT", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + ], + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Entity", + "entityType": "SelfManagedEntity", + "globalAssetId": "https://acplt.org/Test_Asset2", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity2", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example bill of material submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel", + "idShort": "TestSubmodel", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "value": "some example annotation", + "valueType": "xs:string", + "category": "PARAMETER", + "idShort": "ExampleProperty3" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + },{ + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "direction": "input", + "state": "on", + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example ExampleSubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel ExampleSubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleMultiLanguageValueId" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "id": "https://acplt.org/Test_Submodel_Mandatory", + "idShort": "Test_Submodel_Mandatory", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "AnnotatedRelationshipElement", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "Blob", + "value": "ExampleBlob" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "Operation", + "idShort": "ExampleOperation" + }, + { + "modelType": "Capability", + "idShort": "ExampleCapability" + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off" + }, + { + "modelType": "SubmodelElementList", + "idShort": "ExampleSubmodelElementListUnordered", + "orderRelevant": false, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "valueType": "xs:string", + "idShort": "ExampleProperty" + }, + { + "modelType": "MultiLanguageProperty", + "idShort": "ExampleMultiLanguageProperty" + }, + { + "modelType": "Range", + "valueType": "xs:int", + "idShort": "ExampleRange" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection", + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "idShort": "ExampleBlob" + }, + { + "modelType": "File", + "contentType": "application/pdf", + "idShort": "ExampleFile" + }, + { + "modelType": "ReferenceElement", + "idShort": "ExampleReferenceElement" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection2" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "id": "https://acplt.org/Test_Submodel2_Mandatory", + "idShort": "Test_Submodel2_Mandatory" + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Missing", + "idShort": "TestSubmodelMissing", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "category": "PARAMETER", + "value": "some example annotation", + "valueType": "xs:string", + "idShort": "ExampleProperty" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "input", + "state": "on", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "File", + "value": "ExampleFile" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Template", + "idShort": "TestSubmodelTemplate", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange2", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection2", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + } + ] +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/application.properties b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/application.properties new file mode 100644 index 000000000..157f29b8a --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/application.properties @@ -0,0 +1,57 @@ +server.port=8081 + +spring.application.name=Submodel Repository +basyx.smrepo.name=sm-repo +basyx.backend=MongoDB +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=sm-repo-ci-search-test +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword +basyx.submodelrepository.feature.search.enabled=true +basyx.submodelrepository.feature.search.indexname=sm-index-ci +spring.elasticsearch.uris=http://localhost:9200 +spring.elasticsearch.username=elastic +spring.elasticsearch.password=vtzJFt1b +# basyx.submodelrepository.feature.registryintegration=http://localhost:8060 +# basyx.externalurl=http://localhost:8081 + +#basyx.backend = MongoDB +#spring.data.mongodb.host=mongo +# or spring.data.mongodb.host=127.0.0.1 +#spring.data.mongodb.port=27017 +#spring.data.mongodb.database=submodels +#spring.data.mongodb.authentication-database=admin +#spring.data.mongodb.username=mongoAdmin +#spring.data.mongodb.password=mongoPassword + +# basyx.submodelrepository.feature.mqtt.enabled = true +# mqtt.clientId=TestClient +# mqtt.hostname = localhost +# mqtt.port = 1883 + +# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 +# basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD + +#################################################################################### +# Authorization +#################################################################################### +#basyx.feature.authorization.enabled = true +#basyx.feature.authorization.type = rbac +#basyx.feature.authorization.jwtBearerTokenProvider = keycloak +#basyx.feature.authorization.rbac.file = classpath:rbac_rules.json +#spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9096/realms/BaSyx + +#################################################################################### +# Operation Delegation +#################################################################################### +# This feature is enabled by default + +#basyx.submodelrepository.feature.operation.delegation.enabled = false + +#################################################################################### +# Disable the Swagger UI +#################################################################################### +#springdoc.swagger-ui.enabled=false +#springdoc.api-docs.enabled=false diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/contains.json b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/contains.json new file mode 100644 index 000000000..8d20d43e8 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/contains.json @@ -0,0 +1,48 @@ +{ + "$condition": { + "$and": [ + { + "$eq": [ + { + "$field": "$sm#kind" + }, + { + "$strVal": "Instance" + } + ] + }, + { + "$eq": [ + { + "$numCast": { + "$field": "$sm#administration.revision" + } + }, + { + "$numVal": 9 + } + ] + }, + { + "$ne": [ + { + "$field": "$sm#administration.version" + }, + { + "$field": "$sm#administration.revision" + } + ] + }, + { + "$contains": [ + { + "$field": "$sm#semanticId.keys[]" + }, + { + "$strVal": "SubmodelTemplates" + } + ] + } + ] + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml index 9f58e1ae5..6c4e3de25 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-http/pom.xml @@ -60,7 +60,6 @@ basyx.submodelservice-backend-inmemory test - org.springframework spring-context diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index b44dcedcf..529d2ab03 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -1,3 +1,5 @@ +include: + docker-compose-elastic.yml services: nginx-proxy: diff --git a/pom.xml b/pom.xml index cb1539982..2eb8023f9 100644 --- a/pom.xml +++ b/pom.xml @@ -1031,6 +1031,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-feature-search + ${revision} + tests + org.eclipse.digitaltwin.basyx From 624378f552884aa8e96a5dd3b9e555a4f22b5297 Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 18 Aug 2025 11:48:06 +0200 Subject: [PATCH 29/52] Adapts QL --- ci/docker-compose-elastic.yml | 85 +++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 ci/docker-compose-elastic.yml diff --git a/ci/docker-compose-elastic.yml b/ci/docker-compose-elastic.yml new file mode 100644 index 000000000..719f53cb7 --- /dev/null +++ b/ci/docker-compose-elastic.yml @@ -0,0 +1,85 @@ +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:${ES_LOCAL_VERSION} + container_name: ${ES_LOCAL_CONTAINER_NAME} + volumes: + - dev-elasticsearch:/usr/share/elasticsearch/data + ports: + - 127.0.0.1:${ES_LOCAL_PORT}:9200 + environment: + - discovery.type=single-node + - ELASTIC_PASSWORD=${ES_LOCAL_PASSWORD} + - xpack.security.enabled=true + - xpack.security.http.ssl.enabled=false + - xpack.license.self_generated.type=trial + - xpack.ml.use_auto_machine_memory_percent=true + - ES_JAVA_OPTS=-Xms${ES_LOCAL_HEAP_INIT} -Xmx${ES_LOCAL_HEAP_MAX} + - cluster.routing.allocation.disk.watermark.low=${ES_LOCAL_DISK_SPACE_REQUIRED} + - cluster.routing.allocation.disk.watermark.high=${ES_LOCAL_DISK_SPACE_REQUIRED} + - cluster.routing.allocation.disk.watermark.flood_stage=${ES_LOCAL_DISK_SPACE_REQUIRED} + ulimits: + memlock: + soft: -1 + hard: -1 + healthcheck: + test: + [ + "CMD-SHELL", + "curl --output /dev/null --silent --head --fail -u elastic:${ES_LOCAL_PASSWORD} http://elasticsearch:9200", + ] + interval: 10s + timeout: 10s + retries: 30 + + kibana_settings: + depends_on: + elasticsearch: + condition: service_healthy + image: docker.elastic.co/elasticsearch/elasticsearch:${ES_LOCAL_VERSION} + container_name: kibana_settings + restart: 'no' + command: > + bash -c ' + echo "Setup the kibana_system password"; + start_time=$$(date +%s); + timeout=60; + until curl -s -u "elastic:${ES_LOCAL_PASSWORD}" -X POST http://elasticsearch:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_LOCAL_PASSWORD}\"}" -H "Content-Type: application/json" | grep -q "^{}"; do + if [ $$(($$(date +%s) - $$start_time)) -ge $$timeout ]; then + echo "Error: Elasticsearch timeout"; + exit 1; + fi; + sleep 2; + done; + ' + + kibana: + depends_on: + kibana_settings: + condition: service_completed_successfully + image: docker.elastic.co/kibana/kibana:${ES_LOCAL_VERSION} + container_name: ${KIBANA_LOCAL_CONTAINER_NAME} + volumes: + - dev-kibana:/usr/share/kibana/data + - ./config/telemetry.yml:/usr/share/kibana/config/telemetry.yml + ports: + - 127.0.0.1:${KIBANA_LOCAL_PORT}:5601 + environment: + - SERVER_NAME=kibana + - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 + - ELASTICSEARCH_USERNAME=kibana_system + - ELASTICSEARCH_PASSWORD=${KIBANA_LOCAL_PASSWORD} + - XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY} + - ELASTICSEARCH_PUBLICBASEURL=http://localhost:${ES_LOCAL_PORT} + healthcheck: + test: + [ + "CMD-SHELL", + "curl -s -I http://kibana:5601 | grep -q 'HTTP/1.1 302 Found'", + ] + interval: 10s + timeout: 10s + retries: 30 + +volumes: + dev-elasticsearch: + dev-kibana: \ No newline at end of file From f5950075753979b839e9776c2b91622daf547b41 Mon Sep 17 00:00:00 2001 From: fried Date: Mon, 18 Aug 2025 14:30:43 +0200 Subject: [PATCH 30/52] Intermediate Commit --- .../pom.xml | 60 +++++++--- .../search/DummyAasRepositoryConfig.java | 52 +++++++++ .../DummySearchAasRepositoryComponent.java | 44 ++++++++ .../search/TestSearchAasRepository.java | 103 ++++++++++++++++++ .../src/test/resources/query.json | 48 ++++++++ .../query/converter/ValueConverter.java | 13 ++- .../src/test/resources/query.json | 38 +++++++ .../search/TestSearchSubmodelRepository.java | 2 +- .../resources/{contains.json => query.json} | 0 9 files changed, 340 insertions(+), 20 deletions(-) create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummyAasRepositoryConfig.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummySearchAasRepositoryComponent.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json rename basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/{contains.json => query.json} (100%) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml index 6047544d6..3ea026355 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml @@ -20,33 +20,65 @@ org.eclipse.digitaltwin.basyx - basyx.querycore + basyx.aasservice-core - co.elastic.clients - elasticsearch-java - 9.0.1 + org.eclipse.digitaltwin.basyx + basyx.http + test + tests - com.fasterxml.jackson.core - jackson-databind - 2.19.0 + org.eclipse.digitaltwin.basyx + basyx.http - org.springframework - spring-context + org.eclipse.digitaltwin.basyx + basyx.authorization + test + tests - org.springframework.boot - spring-boot-starter-web + org.eclipse.digitaltwin.basyx + basyx.aasrepository-http + test - org.springdoc - springdoc-openapi-starter-webmvc-ui + org.eclipse.digitaltwin.basyx + basyx.aasrepository-http + tests + test + + + org.apache.httpcomponents.client5 + httpclient5 + test org.eclipse.digitaltwin.basyx - basyx.http + basyx.aasrepository-backend-mongodb + test + + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-backend + + + org.eclipse.digitaltwin.basyx + basyx.aasservice-backend-mongodb + test + + + org.eclipse.digitaltwin.basyx + basyx.aasservice-backend-mongodb + + + org.eclipse.digitaltwin.basyx + basyx.querycore + + + commons-io + commons-io diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummyAasRepositoryConfig.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummyAasRepositoryConfig.java new file mode 100644 index 000000000..30c831c8b --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummyAasRepositoryConfig.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; +import org.eclipse.digitaltwin.basyx.aasrepository.feature.AasRepositoryFeature; +import org.eclipse.digitaltwin.basyx.aasrepository.feature.DecoratedAasRepositoryFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * Configuration for tests + * + * @author mateusmolina, danish + * + */ +@Configuration +public class DummyAasRepositoryConfig { + + @Bean + @ConditionalOnMissingBean + public static AasRepository getAasRepository(AasRepositoryFactory aasRepositoryFactory, List features) { + return new DecoratedAasRepositoryFactory(aasRepositoryFactory, features).create(); + } +} \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummySearchAasRepositoryComponent.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummySearchAasRepositoryComponent.java new file mode 100644 index 000000000..9f46360b8 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DummySearchAasRepositoryComponent.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import org.eclipse.digitaltwin.basyx.aasrepository.backend.mongodb.MongoDBAasBackendConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * Spring application configured for tests. + * + * @author aaronzi, fried + * + */ + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class DummySearchAasRepositoryComponent { + public static void main(String[] args) { + SpringApplication.run(DummySearchAasRepositoryComponent.class, args); + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java new file mode 100644 index 000000000..8fdcbee39 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.ResponseEntity; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.List; + +public class TestSearchAasRepository { + private static ConfigurableApplicationContext appContext; + private static AasRepository searchBackend; + private static SearchAasRepositoryApiHTTPController searchAPI; + + @BeforeClass + public static void startSmRepo() throws Exception { + appContext = new SpringApplicationBuilder(DummySearchAasRepositoryComponent.class).run(new String[] {}); + searchBackend = appContext.getBean(AasRepository.class); + searchAPI = appContext.getBean(SearchAasRepositoryApiHTTPController.class); + preloadShells(); + } + + @Test + public void testRepo() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchAasRepository.class.getResource("/query.json").getFile()); + AASQuery query = queryFromFile(file); + ResponseEntity result = searchAPI.queryAssetAdministrationShells(query, -1, new Base64UrlEncodedCursor("")); + QueryResponse response = result.getBody(); + assert response != null; + List shells = response.result.stream().map(o->(AssetAdministrationShell)o).toList(); + Assert.assertEquals(3,shells.size()); + } + + private static AASQuery queryFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), AASQuery.class); + } + + private static Environment envFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), Environment.class); + } + + private static void preloadShells() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchAasRepository.class.getResource("/Example-Full.json").getFile()); + Environment env = envFromFile(file); + for(AssetAdministrationShell aas : env.getAssetAdministrationShells()) { + searchBackend.createAas(aas); + } + } + + @AfterClass + public static void shutdownAasRepo() { + searchBackend.getAllAas(null, null, new PaginationInfo(0, "")).getResult().forEach(aas -> { + try { + searchBackend.deleteAas(aas.getId()); + } catch (Exception e) { + // Ignore exceptions during cleanup + } + }); + appContext.close(); + } + +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json new file mode 100644 index 000000000..15a3ae6fc --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json @@ -0,0 +1,48 @@ +{ + "$condition": { + "or": [ + { + "$contains": [ + { + "$field": "$aas#idShort" + }, + { + "$strVal": "Test" + } + ] + }, + { + "$lt": [ + { + "$numCast": { + "$field": "$aas#administration.revision" + } + }, + { + "$numVal": 11 + } + ] + }, + { + "$ne": [ + { + "$field": "$aas#assetInformation.globalAssetId" + }, + { + "$field": "$aas#assetInformation.assetKind" + } + ] + }, + { + "$contains": [ + { + "$field": "$aas#submodels.keys[]" + }, + { + "$strVal": "BillOfMaterial" + } + ] + } + ] + } +} diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index 4cd3da7f9..ae5e5d9fe 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -162,7 +162,7 @@ public Query convertStringComparison(StringValue leftValue, StringValue rightVal if (isSmeWildcardField(fieldName)) { return createSmeWildcardStringQuery(fieldName, value, operation); } - + switch (operation) { case "contains": return QueryBuilders.wildcard() @@ -393,7 +393,7 @@ private String convertModelFieldToElasticField(String modelField) { } String result = modelField; - + // Remove model prefixes and convert to actual field names if (modelField.startsWith("$aas#")) { result = modelField.replace("$aas#", ""); @@ -426,7 +426,8 @@ private String convertModelFieldToElasticField(String modelField) { if (isStringField(result)) { result = result + ".keyword"; } - +// result = result.replace("semanticId.keys[]", "semanticId.keys.value"); +// result = result.replace("supplementalSemanticIds[].keys[]", "supplementalSemanticIds.keys.value"); return result; } @@ -674,8 +675,8 @@ private Query createSmeWildcardQuery(String wildcardField, String value) { // Create a wildcard pattern that matches the field at any nesting level // Pattern: submodelElements.*{fieldName}:{value} OR submodelElements.*.smcChildren.*{fieldName}:{value} - String queryPattern = "*"+searchField; + String queryPattern = "*"+searchField; return QueryBuilders.queryString(q -> q .query(escapeQueryString(value)) .fields(queryPattern.replace("[]","[*]")) @@ -687,7 +688,9 @@ private Query createSmeWildcardQuery(String wildcardField, String value) { */ private Query createSmeWildcardStringQuery(String wildcardField, String value, String operation) { String fieldName = extractSmeFieldName(wildcardField); - + fieldName = fieldName.replace("semanticId.keys[]", "semanticId.keys.value") + .replace("supplementalSemanticIds[].keys[]", "supplementalSemanticIds.keys.value") + .replace("submodels.keys[]", "submodels.keys.value"); String searchField = fieldName; String searchValue; diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json new file mode 100644 index 000000000..eb428781d --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json @@ -0,0 +1,38 @@ +{ + "$condition": { + "match": [ + { + "$eq": [ + { + "$field": "$cd#description[].language" + }, + { + "$strVal": "en-us" + } + ] + }, + { + "$lt": [ + { + "$numCast": { + "$field": "cd#administration.revision" + } + }, + { + "$numVal": 11 + } + ] + }, + { + "$contains": [ + { + "$field": "$cd#isCaseOf.keys[]" + }, + { + "$strVal": "acplt" + } + ] + } + ] + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java index 2605da19f..a675e7072 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java @@ -62,7 +62,7 @@ public static void startSmRepo() throws Exception { @Test public void testRepo() throws FileNotFoundException, DeserializationException { - File file = new File(TestSearchSubmodelRepository.class.getResource("/contains.json").getFile()); + File file = new File(TestSearchSubmodelRepository.class.getResource("/query.json").getFile()); AASQuery query = queryFromFile(file); ResponseEntity result = searchAPI.querySubmodels(query, -1, new Base64UrlEncodedCursor("")); QueryResponse response = result.getBody(); diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/contains.json b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/query.json similarity index 100% rename from basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/contains.json rename to basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/resources/query.json From 376cc62dec3391bcf0c2162facab01ed1a4d18bc Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 10:59:01 +0200 Subject: [PATCH 31/52] IC --- aas.schema.json | 1532 ++++++++++ .../src/test/resources/query.json | 36 + .../pom.xml | 5 + .../search/TestSearchAasRepository.java | 2 +- .../src/test/resources/Example-Full.json | 2597 +++++++++++++++++ .../src/test/resources/application.properties | 19 + .../src/test/resources/query.json | 2 +- .../query/converter/ValueConverter.java | 37 +- .../pom.xml | 18 + .../feature/search/SearchCdRepository.java | 44 - .../search/DummyCdRepositoryConfig.java | 52 + .../DummySearchCdRepositoryComponent.java | 42 + .../search/TestSearchCdRepository.java | 113 + .../src/test/resources/Example-Full.json | 2597 +++++++++++++++++ .../src/test/resources/application.properties | 17 + .../src/test/resources/query.json | 8 +- .../pom.xml | 24 + .../DummySearchSubmodelRegistryComponent.java | 42 + .../search/TestSearchSubmodelRegistry.java | 174 ++ .../src/test/resources/Example-Full.json | 2597 +++++++++++++++++ .../src/test/resources/application.yml | 55 + .../src/test/resources/query.json | 36 + pom.xml | 18 + 23 files changed, 10004 insertions(+), 63 deletions(-) create mode 100644 aas.schema.json create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/Example-Full.json create mode 100644 basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/application.properties create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummyCdRepositoryConfig.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummySearchCdRepositoryComponent.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/Example-Full.json create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/application.properties create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DummySearchSubmodelRegistryComponent.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/Example-Full.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/application.yml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/query.json diff --git a/aas.schema.json b/aas.schema.json new file mode 100644 index 000000000..845129371 --- /dev/null +++ b/aas.schema.json @@ -0,0 +1,1532 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "IDTA-01001-3-1 AAS JSON Schema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/Environment" + } + ], + "$id": "https://admin-shell.io/aas/3/1", + "definitions": { + "AasSubmodelElements": { + "type": "string", + "enum": [ + "AnnotatedRelationshipElement", + "BasicEventElement", + "Blob", + "Capability", + "DataElement", + "Entity", + "EventElement", + "File", + "MultiLanguageProperty", + "Operation", + "Property", + "Range", + "ReferenceElement", + "RelationshipElement", + "SubmodelElement", + "SubmodelElementCollection", + "SubmodelElementList" + ] + }, + "AbstractLangString": { + "type": "object", + "properties": { + "language": { + "type": "string", + "pattern": "^(([a-zA-Z]{2,3}(-[a-zA-Z]{3}(-[a-zA-Z]{3}){0,2})?|[a-zA-Z]{4}|[a-zA-Z]{5,8})(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-(([a-zA-Z0-9]){5,8}|[0-9]([a-zA-Z0-9]){3}))*(-[0-9A-WY-Za-wy-z](-([a-zA-Z0-9]){2,8})+)*(-[xX](-([a-zA-Z0-9]){1,8})+)?|[xX](-([a-zA-Z0-9]){1,8})+|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$" + }, + "text": { + "type": "string", + "minLength": 1, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + } + }, + "required": [ + "language", + "text" + ] + }, + "AdministrativeInformation": { + "allOf": [ + { + "$ref": "#/definitions/HasDataSpecification" + }, + { + "properties": { + "version": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 4 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^(0|[1-9][0-9]*)$" + } + ] + }, + "revision": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 4 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^(0|[1-9][0-9]*)$" + } + ] + }, + "creator": { + "$ref": "#/definitions/Reference" + }, + "templateId": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + } + } + } + ] + }, + "AnnotatedRelationshipElement": { + "allOf": [ + { + "$ref": "#/definitions/RelationshipElement_abstract" + }, + { + "properties": { + "annotations": { + "type": "array", + "items": { + "$ref": "#/definitions/DataElement_choice" + }, + "minItems": 1 + }, + "modelType": { + "const": "AnnotatedRelationshipElement" + } + } + } + ] + }, + "AssetAdministrationShell": { + "allOf": [ + { + "$ref": "#/definitions/Identifiable" + }, + { + "$ref": "#/definitions/HasDataSpecification" + }, + { + "properties": { + "derivedFrom": { + "$ref": "#/definitions/Reference" + }, + "assetInformation": { + "$ref": "#/definitions/AssetInformation" + }, + "submodels": { + "type": "array", + "items": { + "$ref": "#/definitions/Reference" + }, + "minItems": 1 + }, + "modelType": { + "const": "AssetAdministrationShell" + } + }, + "required": [ + "assetInformation" + ] + } + ] + }, + "AssetInformation": { + "type": "object", + "properties": { + "assetKind": { + "$ref": "#/definitions/AssetKind" + }, + "globalAssetId": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "specificAssetIds": { + "type": "array", + "items": { + "$ref": "#/definitions/SpecificAssetId" + }, + "minItems": 1 + }, + "assetType": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "defaultThumbnail": { + "$ref": "#/definitions/Resource" + } + }, + "required": [ + "assetKind" + ] + }, + "AssetKind": { + "type": "string", + "enum": [ + "Instance", + "NotApplicable", + "Role", + "Type" + ] + }, + "BasicEventElement": { + "allOf": [ + { + "$ref": "#/definitions/EventElement" + }, + { + "properties": { + "observed": { + "$ref": "#/definitions/Reference" + }, + "direction": { + "$ref": "#/definitions/Direction" + }, + "state": { + "$ref": "#/definitions/StateOfEvent" + }, + "messageTopic": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "messageBroker": { + "$ref": "#/definitions/Reference" + }, + "lastUpdate": { + "type": "string", + "pattern": "^-?(([1-9][0-9][0-9][0-9]+)|(0[0-9][0-9][0-9]))-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))T(((([01][0-9])|(2[0-3])):[0-5][0-9]:([0-5][0-9])(\\.[0-9]+)?)|24:00:00(\\.0+)?)(Z|\\+00:00|-00:00)$" + }, + "minInterval": { + "type": "string", + "pattern": "^-?P((([0-9]+Y([0-9]+M)?([0-9]+D)?|([0-9]+M)([0-9]+D)?|([0-9]+D))(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S)))?)|(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S))))$" + }, + "maxInterval": { + "type": "string", + "pattern": "^-?P((([0-9]+Y([0-9]+M)?([0-9]+D)?|([0-9]+M)([0-9]+D)?|([0-9]+D))(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S)))?)|(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S))))$" + }, + "modelType": { + "const": "BasicEventElement" + } + }, + "required": [ + "observed", + "direction", + "state" + ] + } + ] + }, + "Blob": { + "allOf": [ + { + "$ref": "#/definitions/DataElement" + }, + { + "properties": { + "value": { + "type": "string", + "contentEncoding": "base64" + }, + "contentType": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 128 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\\x80-\\xff])|\\\\([\\t !-~]|[\\x80-\\xff]))*\"))*$" + } + ] + }, + "modelType": { + "const": "Blob" + } + } + } + ] + }, + "Capability": { + "allOf": [ + { + "$ref": "#/definitions/SubmodelElement" + }, + { + "properties": { + "modelType": { + "const": "Capability" + } + } + } + ] + }, + "ConceptDescription": { + "allOf": [ + { + "$ref": "#/definitions/Identifiable" + }, + { + "$ref": "#/definitions/HasDataSpecification" + }, + { + "properties": { + "isCaseOf": { + "type": "array", + "items": { + "$ref": "#/definitions/Reference" + }, + "minItems": 1 + }, + "modelType": { + "const": "ConceptDescription" + } + } + } + ] + }, + "DataElement": { + "$ref": "#/definitions/SubmodelElement" + }, + "DataElement_choice": { + "oneOf": [ + { + "$ref": "#/definitions/Blob" + }, + { + "$ref": "#/definitions/File" + }, + { + "$ref": "#/definitions/MultiLanguageProperty" + }, + { + "$ref": "#/definitions/Property" + }, + { + "$ref": "#/definitions/Range" + }, + { + "$ref": "#/definitions/ReferenceElement" + } + ] + }, + "DataSpecificationContent": { + "type": "object", + "properties": { + "modelType": { + "$ref": "#/definitions/ModelType" + } + }, + "required": [ + "modelType" + ] + }, + "DataSpecificationContent_choice": { + "oneOf": [ + { + "$ref": "#/definitions/DataSpecificationIec61360" + } + ] + }, + "DataSpecificationIec61360": { + "allOf": [ + { + "$ref": "#/definitions/DataSpecificationContent" + }, + { + "properties": { + "preferredName": { + "type": "array", + "items": { + "$ref": "#/definitions/LangStringPreferredNameTypeIec61360" + }, + "minItems": 1 + }, + "shortName": { + "type": "array", + "items": { + "$ref": "#/definitions/LangStringShortNameTypeIec61360" + }, + "minItems": 1 + }, + "unit": { + "type": "string", + "minLength": 1, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "unitId": { + "$ref": "#/definitions/Reference" + }, + "sourceOfDefinition": { + "type": "string", + "minLength": 1, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "symbol": { + "type": "string", + "minLength": 1, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "dataType": { + "$ref": "#/definitions/DataTypeIec61360" + }, + "definition": { + "type": "array", + "items": { + "$ref": "#/definitions/LangStringDefinitionTypeIec61360" + }, + "minItems": 1 + }, + "valueFormat": { + "type": "string", + "minLength": 1, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "valueList": { + "$ref": "#/definitions/ValueList" + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "levelType": { + "$ref": "#/definitions/LevelType" + }, + "modelType": { + "const": "DataSpecificationIec61360" + } + }, + "required": [ + "preferredName" + ] + } + ] + }, + "DataTypeDefXsd": { + "type": "string", + "enum": [ + "xs:anyURI", + "xs:base64Binary", + "xs:boolean", + "xs:byte", + "xs:date", + "xs:dateTime", + "xs:decimal", + "xs:double", + "xs:duration", + "xs:float", + "xs:gDay", + "xs:gMonth", + "xs:gMonthDay", + "xs:gYear", + "xs:gYearMonth", + "xs:hexBinary", + "xs:int", + "xs:integer", + "xs:long", + "xs:negativeInteger", + "xs:nonNegativeInteger", + "xs:nonPositiveInteger", + "xs:positiveInteger", + "xs:short", + "xs:string", + "xs:time", + "xs:unsignedByte", + "xs:unsignedInt", + "xs:unsignedLong", + "xs:unsignedShort" + ] + }, + "DataTypeIec61360": { + "type": "string", + "enum": [ + "BLOB", + "BOOLEAN", + "DATE", + "FILE", + "HTML", + "INTEGER_COUNT", + "INTEGER_CURRENCY", + "INTEGER_MEASURE", + "IRDI", + "IRI", + "RATIONAL", + "RATIONAL_MEASURE", + "REAL_COUNT", + "REAL_CURRENCY", + "REAL_MEASURE", + "STRING", + "STRING_TRANSLATABLE", + "TIME", + "TIMESTAMP" + ] + }, + "Direction": { + "type": "string", + "enum": [ + "input", + "output" + ] + }, + "EmbeddedDataSpecification": { + "type": "object", + "properties": { + "dataSpecification": { + "$ref": "#/definitions/Reference" + }, + "dataSpecificationContent": { + "$ref": "#/definitions/DataSpecificationContent_choice" + } + }, + "required": [ + "dataSpecification", + "dataSpecificationContent" + ] + }, + "Entity": { + "allOf": [ + { + "$ref": "#/definitions/SubmodelElement" + }, + { + "properties": { + "statements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmodelElement_choice" + }, + "minItems": 1 + }, + "entityType": { + "$ref": "#/definitions/EntityType" + }, + "globalAssetId": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "specificAssetIds": { + "type": "array", + "items": { + "$ref": "#/definitions/SpecificAssetId" + }, + "minItems": 1 + }, + "modelType": { + "const": "Entity" + } + } + } + ] + }, + "EntityType": { + "type": "string", + "enum": [ + "CoManagedEntity", + "SelfManagedEntity" + ] + }, + "Environment": { + "type": "object", + "properties": { + "assetAdministrationShells": { + "type": "array", + "items": { + "$ref": "#/definitions/AssetAdministrationShell" + }, + "minItems": 1 + }, + "submodels": { + "type": "array", + "items": { + "$ref": "#/definitions/Submodel" + }, + "minItems": 1 + }, + "conceptDescriptions": { + "type": "array", + "items": { + "$ref": "#/definitions/ConceptDescription" + }, + "minItems": 1 + } + } + }, + "EventElement": { + "$ref": "#/definitions/SubmodelElement" + }, + "EventPayload": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/Reference" + }, + "sourceSemanticId": { + "$ref": "#/definitions/Reference" + }, + "observableReference": { + "$ref": "#/definitions/Reference" + }, + "observableSemanticId": { + "$ref": "#/definitions/Reference" + }, + "topic": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "subjectId": { + "$ref": "#/definitions/Reference" + }, + "timeStamp": { + "type": "string", + "pattern": "^-?(([1-9][0-9][0-9][0-9]+)|(0[0-9][0-9][0-9]))-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))T(((([01][0-9])|(2[0-3])):[0-5][0-9]:([0-5][0-9])(\\.[0-9]+)?)|24:00:00(\\.0+)?)(Z|\\+00:00|-00:00)$" + }, + "payload": { + "type": "string", + "contentEncoding": "base64" + } + }, + "required": [ + "source", + "observableReference", + "timeStamp" + ] + }, + "Extension": { + "allOf": [ + { + "$ref": "#/definitions/HasSemantics" + }, + { + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "valueType": { + "$ref": "#/definitions/DataTypeDefXsd" + }, + "value": { + "type": "string", + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "refersTo": { + "type": "array", + "items": { + "$ref": "#/definitions/Reference" + }, + "minItems": 1 + } + }, + "required": [ + "name" + ] + } + ] + }, + "File": { + "allOf": [ + { + "$ref": "#/definitions/DataElement" + }, + { + "properties": { + "value": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 2048 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^([a-zA-Z][a-zA-Z0-9+\\-.]*:((//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;?:@&=+$,])(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)|(//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;@&=+$,])+(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?)?(#(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?$" + } + ] + }, + "contentType": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 128 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\\x80-\\xff])|\\\\([\\t !-~]|[\\x80-\\xff]))*\"))*$" + } + ] + }, + "modelType": { + "const": "File" + } + } + } + ] + }, + "HasDataSpecification": { + "type": "object", + "properties": { + "embeddedDataSpecifications": { + "type": "array", + "items": { + "$ref": "#/definitions/EmbeddedDataSpecification" + }, + "minItems": 1 + } + } + }, + "HasExtensions": { + "type": "object", + "properties": { + "extensions": { + "type": "array", + "items": { + "$ref": "#/definitions/Extension" + }, + "minItems": 1 + } + } + }, + "HasKind": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/definitions/ModellingKind" + } + } + }, + "HasSemantics": { + "type": "object", + "properties": { + "semanticId": { + "$ref": "#/definitions/Reference" + }, + "supplementalSemanticIds": { + "type": "array", + "items": { + "$ref": "#/definitions/Reference" + }, + "minItems": 1 + } + } + }, + "Identifiable": { + "allOf": [ + { + "$ref": "#/definitions/Referable" + }, + { + "properties": { + "administration": { + "$ref": "#/definitions/AdministrativeInformation" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + } + }, + "required": [ + "id" + ] + } + ] + }, + "Key": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/KeyTypes" + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + } + }, + "required": [ + "type", + "value" + ] + }, + "KeyTypes": { + "type": "string", + "enum": [ + "AnnotatedRelationshipElement", + "AssetAdministrationShell", + "BasicEventElement", + "Blob", + "Capability", + "ConceptDescription", + "DataElement", + "Entity", + "EventElement", + "File", + "FragmentReference", + "GlobalReference", + "Identifiable", + "MultiLanguageProperty", + "Operation", + "Property", + "Range", + "Referable", + "ReferenceElement", + "RelationshipElement", + "Submodel", + "SubmodelElement", + "SubmodelElementCollection", + "SubmodelElementList" + ] + }, + "LangStringDefinitionTypeIec61360": { + "allOf": [ + { + "$ref": "#/definitions/AbstractLangString" + }, + { + "properties": { + "text": { + "maxLength": 1023 + } + } + } + ] + }, + "LangStringNameType": { + "allOf": [ + { + "$ref": "#/definitions/AbstractLangString" + }, + { + "properties": { + "text": { + "maxLength": 128 + } + } + } + ] + }, + "LangStringPreferredNameTypeIec61360": { + "allOf": [ + { + "$ref": "#/definitions/AbstractLangString" + }, + { + "properties": { + "text": { + "maxLength": 255 + } + } + } + ] + }, + "LangStringShortNameTypeIec61360": { + "allOf": [ + { + "$ref": "#/definitions/AbstractLangString" + }, + { + "properties": { + "text": { + "maxLength": 18 + } + } + } + ] + }, + "LangStringTextType": { + "allOf": [ + { + "$ref": "#/definitions/AbstractLangString" + }, + { + "properties": { + "text": { + "maxLength": 1023 + } + } + } + ] + }, + "LevelType": { + "type": "object", + "properties": { + "min": { + "type": "boolean" + }, + "nom": { + "type": "boolean" + }, + "typ": { + "type": "boolean" + }, + "max": { + "type": "boolean" + } + }, + "required": [ + "min", + "nom", + "typ", + "max" + ] + }, + "ModelType": { + "type": "string", + "enum": [ + "AnnotatedRelationshipElement", + "AssetAdministrationShell", + "BasicEventElement", + "Blob", + "Capability", + "ConceptDescription", + "DataSpecificationIec61360", + "Entity", + "File", + "MultiLanguageProperty", + "Operation", + "Property", + "Range", + "ReferenceElement", + "RelationshipElement", + "Submodel", + "SubmodelElementCollection", + "SubmodelElementList" + ] + }, + "ModellingKind": { + "type": "string", + "enum": [ + "Instance", + "Template" + ] + }, + "MultiLanguageProperty": { + "allOf": [ + { + "$ref": "#/definitions/DataElement" + }, + { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/LangStringTextType" + }, + "minItems": 1 + }, + "valueId": { + "$ref": "#/definitions/Reference" + }, + "modelType": { + "const": "MultiLanguageProperty" + } + } + } + ] + }, + "Operation": { + "allOf": [ + { + "$ref": "#/definitions/SubmodelElement" + }, + { + "properties": { + "inputVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/OperationVariable" + }, + "minItems": 1 + }, + "outputVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/OperationVariable" + }, + "minItems": 1 + }, + "inoutputVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/OperationVariable" + }, + "minItems": 1 + }, + "modelType": { + "const": "Operation" + } + } + } + ] + }, + "OperationVariable": { + "type": "object", + "properties": { + "value": { + "$ref": "#/definitions/SubmodelElement_choice" + } + }, + "required": [ + "value" + ] + }, + "Property": { + "allOf": [ + { + "$ref": "#/definitions/DataElement" + }, + { + "properties": { + "valueType": { + "$ref": "#/definitions/DataTypeDefXsd" + }, + "value": { + "type": "string", + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "valueId": { + "$ref": "#/definitions/Reference" + }, + "modelType": { + "const": "Property" + } + }, + "required": [ + "valueType" + ] + } + ] + }, + "Qualifiable": { + "type": "object", + "properties": { + "qualifiers": { + "type": "array", + "items": { + "$ref": "#/definitions/Qualifier" + }, + "minItems": 1 + }, + "modelType": { + "$ref": "#/definitions/ModelType" + } + }, + "required": [ + "modelType" + ] + }, + "Qualifier": { + "allOf": [ + { + "$ref": "#/definitions/HasSemantics" + }, + { + "properties": { + "kind": { + "$ref": "#/definitions/QualifierKind" + }, + "type": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "valueType": { + "$ref": "#/definitions/DataTypeDefXsd" + }, + "value": { + "type": "string", + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "valueId": { + "$ref": "#/definitions/Reference" + } + }, + "required": [ + "type", + "valueType" + ] + } + ] + }, + "QualifierKind": { + "type": "string", + "enum": [ + "ConceptQualifier", + "TemplateQualifier", + "ValueQualifier" + ] + }, + "Range": { + "allOf": [ + { + "$ref": "#/definitions/DataElement" + }, + { + "properties": { + "valueType": { + "$ref": "#/definitions/DataTypeDefXsd" + }, + "min": { + "type": "string", + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "max": { + "type": "string", + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "modelType": { + "const": "Range" + } + }, + "required": [ + "valueType" + ] + } + ] + }, + "Referable": { + "allOf": [ + { + "$ref": "#/definitions/HasExtensions" + }, + { + "properties": { + "category": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "idShort": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 128 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9_]+$" + } + ] + }, + "displayName": { + "type": "array", + "items": { + "$ref": "#/definitions/LangStringNameType" + }, + "minItems": 1 + }, + "description": { + "type": "array", + "items": { + "$ref": "#/definitions/LangStringTextType" + }, + "minItems": 1 + }, + "modelType": { + "$ref": "#/definitions/ModelType" + } + }, + "required": [ + "modelType" + ] + } + ] + }, + "Reference": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/ReferenceTypes" + }, + "referredSemanticId": { + "$ref": "#/definitions/Reference" + }, + "keys": { + "type": "array", + "items": { + "$ref": "#/definitions/Key" + }, + "minItems": 1 + } + }, + "required": [ + "type", + "keys" + ] + }, + "ReferenceElement": { + "allOf": [ + { + "$ref": "#/definitions/DataElement" + }, + { + "properties": { + "value": { + "$ref": "#/definitions/Reference" + }, + "modelType": { + "const": "ReferenceElement" + } + } + } + ] + }, + "ReferenceTypes": { + "type": "string", + "enum": [ + "ExternalReference", + "ModelReference" + ] + }, + "RelationshipElement": { + "allOf": [ + { + "$ref": "#/definitions/RelationshipElement_abstract" + }, + { + "properties": { + "modelType": { + "const": "RelationshipElement" + } + } + } + ] + }, + "RelationshipElement_abstract": { + "allOf": [ + { + "$ref": "#/definitions/SubmodelElement" + }, + { + "properties": { + "first": { + "$ref": "#/definitions/Reference" + }, + "second": { + "$ref": "#/definitions/Reference" + } + } + } + ] + }, + "RelationshipElement_choice": { + "oneOf": [ + { + "$ref": "#/definitions/RelationshipElement" + }, + { + "$ref": "#/definitions/AnnotatedRelationshipElement" + } + ] + }, + "Resource": { + "type": "object", + "properties": { + "path": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 2048 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^([a-zA-Z][a-zA-Z0-9+\\-.]*:((//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;?:@&=+$,])(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)|(//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;@&=+$,])+(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?)?(#(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?$" + } + ] + }, + "contentType": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "maxLength": 128 + }, + { + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + { + "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\\x80-\\xff])|\\\\([\\t !-~]|[\\x80-\\xff]))*\"))*$" + } + ] + } + }, + "required": [ + "path" + ] + }, + "SpecificAssetId": { + "allOf": [ + { + "$ref": "#/definitions/HasSemantics" + }, + { + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "externalSubjectId": { + "$ref": "#/definitions/Reference" + } + }, + "required": [ + "name", + "value" + ] + } + ] + }, + "StateOfEvent": { + "type": "string", + "enum": [ + "off", + "on" + ] + }, + "Submodel": { + "allOf": [ + { + "$ref": "#/definitions/Identifiable" + }, + { + "$ref": "#/definitions/HasKind" + }, + { + "$ref": "#/definitions/HasSemantics" + }, + { + "$ref": "#/definitions/Qualifiable" + }, + { + "$ref": "#/definitions/HasDataSpecification" + }, + { + "properties": { + "submodelElements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmodelElement_choice" + }, + "minItems": 1 + }, + "modelType": { + "const": "Submodel" + } + } + } + ] + }, + "SubmodelElement": { + "allOf": [ + { + "$ref": "#/definitions/Referable" + }, + { + "$ref": "#/definitions/HasSemantics" + }, + { + "$ref": "#/definitions/Qualifiable" + }, + { + "$ref": "#/definitions/HasDataSpecification" + } + ] + }, + "SubmodelElementCollection": { + "allOf": [ + { + "$ref": "#/definitions/SubmodelElement" + }, + { + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmodelElement_choice" + }, + "minItems": 1 + }, + "modelType": { + "const": "SubmodelElementCollection" + } + } + } + ] + }, + "SubmodelElementList": { + "allOf": [ + { + "$ref": "#/definitions/SubmodelElement" + }, + { + "properties": { + "orderRelevant": { + "type": "boolean" + }, + "semanticIdListElement": { + "$ref": "#/definitions/Reference" + }, + "typeValueListElement": { + "$ref": "#/definitions/AasSubmodelElements" + }, + "valueTypeListElement": { + "$ref": "#/definitions/DataTypeDefXsd" + }, + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmodelElement_choice" + }, + "minItems": 1 + }, + "modelType": { + "const": "SubmodelElementList" + } + }, + "required": [ + "typeValueListElement" + ] + } + ] + }, + "SubmodelElement_choice": { + "oneOf": [ + { + "$ref": "#/definitions/RelationshipElement" + }, + { + "$ref": "#/definitions/AnnotatedRelationshipElement" + }, + { + "$ref": "#/definitions/BasicEventElement" + }, + { + "$ref": "#/definitions/Blob" + }, + { + "$ref": "#/definitions/Capability" + }, + { + "$ref": "#/definitions/Entity" + }, + { + "$ref": "#/definitions/File" + }, + { + "$ref": "#/definitions/MultiLanguageProperty" + }, + { + "$ref": "#/definitions/Operation" + }, + { + "$ref": "#/definitions/Property" + }, + { + "$ref": "#/definitions/Range" + }, + { + "$ref": "#/definitions/ReferenceElement" + }, + { + "$ref": "#/definitions/SubmodelElementCollection" + }, + { + "$ref": "#/definitions/SubmodelElementList" + } + ] + }, + "ValueList": { + "type": "object", + "properties": { + "valueReferencePairs": { + "type": "array", + "items": { + "$ref": "#/definitions/ValueReferencePair" + }, + "minItems": 1 + } + }, + "required": [ + "valueReferencePairs" + ] + }, + "ValueReferencePair": { + "type": "object", + "properties": { + "value": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + }, + "valueId": { + "$ref": "#/definitions/Reference" + } + }, + "required": [ + "value" + ] + } + } +} \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json new file mode 100644 index 000000000..452a43f6b --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json @@ -0,0 +1,36 @@ +{ + "$condition": { + "$match": [ + { + "$contains": [ + { + "$field": "$aasdesc#endpoints[].protocolInformation.href" + }, + { + "$strVal": "localhost" + } + ] + }, + { + "$eq": [ + { + "$field": "$aasdesc#assetKind" + }, + { + "$strVal": "Instance" + } + ] + }, + { + "$startsWith": [ + { + "$field": "$aasdesc#idShort" + }, + { + "$strVal": "Test" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml index 3ea026355..6c397aaa5 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml @@ -80,6 +80,11 @@ commons-io commons-io + + co.elastic.clients + elasticsearch-java + 9.0.1 + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java index 8fdcbee39..4a6fe1599 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java @@ -67,7 +67,7 @@ public void testRepo() throws FileNotFoundException, DeserializationException { QueryResponse response = result.getBody(); assert response != null; List shells = response.result.stream().map(o->(AssetAdministrationShell)o).toList(); - Assert.assertEquals(3,shells.size()); + Assert.assertEquals(4,shells.size()); } private static AASQuery queryFromFile(File file) throws FileNotFoundException, DeserializationException { diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/Example-Full.json b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/Example-Full.json new file mode 100644 index 000000000..0dcceaa3e --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/Example-Full.json @@ -0,0 +1,2597 @@ +{ + "assetAdministrationShells": [ + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset" + }, + "derivedFrom": { + "keys": [ + { + "type": "AssetAdministrationShell", + "value": "https://acplt.org/TestAssetAdministrationShell2" + } + ], + "type": "ExternalReference" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/Identification" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel2_Mandatory" + } + ], + "type": "ExternalReference" + } + ], + "id": "https://acplt.org/Test_AssetAdministrationShell_Mandatory", + "idShort": "Test_AssetAdministrationShell_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory", + "idShort": "Test_AssetAdministrationShell2_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Missing" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell_Missing", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + } + ], + "conceptDescriptions": [ + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription", + "idShort": "TestConceptDescription", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/DataSpecifications/Conceptdescription/TestConceptDescription" + } + ], + "type": "ExternalReference" + } + ], + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "id": "https://acplt.org/Test_ConceptDescription_Mandatory", + "idShort": "Test_ConceptDescription_Mandatory" + }, + { + "modelType": "ConceptDescription", + "category": "PROPERTY", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription_Missing", + "idShort": "TestConceptDescription1", + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/DataSpecifciations/Example/Identification", + "idShort": "TestSpec_01", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ConceptDescriptionX" + } + ], + "type": "ExternalReference" + } + ], + "embeddedDataSpecifications": [ + { + "dataSpecification": { + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/aas/3/0/RC02/DataSpecificationIec61360" + } + ], + "type": "ExternalReference" + }, + "dataSpecificationContent": { + "modelType": "DataSpecificationIec61360", + "dataType": "REAL_MEASURE", + "sourceOfDefinition": "http://acplt.org/DataSpec/ExampleDef", + "symbol": "SU", + "unit": "SpaceUnit", + "unitId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Units/SpaceUnit" + } + ], + "type": "ExternalReference" + }, + "levelType": { + "max": true, + "min": false, + "nom": false, + "typ": false + }, + "value": "TEST", + "valueFormat": "string", + "valueList": { + "valueReferencePairs": [ + { + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + } + }, + { + "value": "http://acplt.org/ValueId/ExampleValueId2", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId2" + } + ], + "type": "ExternalReference" + } + } + ] + }, + "definition": [ + { + "language": "de", + "text": "Dies ist eine Data Specification für Testzwecke" + }, + { + "language": "en-us", + "text": "This is a DataSpecification for testing purposes" + } + ], + "preferredName": [ + { + "language": "de", + "text": "Test Specification" + }, + { + "language": "en-us", + "text": "TestSpecification" + } + ], + "shortName": [ + { + "language": "de", + "text": "Test Spec" + }, + { + "language": "en-us", + "text": "TestSpec" + } + ] + } + } + ] + } + ], + "submodels": [ + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/AssetIdentification" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/Identification", + "idShort": "Identification", + "submodelElements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO677#002" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ACPLT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ACPLT" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "value": "100", + "valueType": "xs:int", + "kind": "ConceptQualifier" + }, + { + "type": "http://acplt.org/Qualifier/ExampleQualifier2", + "value": "50", + "valueType": "xs:int", + "kind": "ConceptQualifier" + } + ], + "idShort": "ManufacturerName", + "displayName": [ + { + "language": "en-us", + "text": "Manufacturer Name" + } + ], + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Property", + "category": "VARIABLE", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "supplementalSemanticIds": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "something_random_e14ad770" + } + ], + "type": "ExternalReference" + }, + { + "keys": [{ + "type": "GlobalReference", + "value": "something_random_bd061acd" + }], + "type": "ExternalReference" + } + + ], + "value": "978-8234-234-342", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "978-8234-234-342" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "InstanceId", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example asset identification submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + "administration": { + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial", + "idShort": "BillOfMaterial", + "submodelElements": [ + { + "modelType": "Entity", + "entityType": "CoManagedEntity", + "statements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValue2", + "category": "CONSTANT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValue2" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty", + "category": "CONSTANT", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + ], + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Entity", + "entityType": "SelfManagedEntity", + "globalAssetId": "https://acplt.org/Test_Asset2", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity2", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example bill of material submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel", + "idShort": "TestSubmodel", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "value": "some example annotation", + "valueType": "xs:string", + "category": "PARAMETER", + "idShort": "ExampleProperty3" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + },{ + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "direction": "input", + "state": "on", + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example ExampleSubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel ExampleSubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleMultiLanguageValueId" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "id": "https://acplt.org/Test_Submodel_Mandatory", + "idShort": "Test_Submodel_Mandatory", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "AnnotatedRelationshipElement", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "Blob", + "value": "ExampleBlob" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "Operation", + "idShort": "ExampleOperation" + }, + { + "modelType": "Capability", + "idShort": "ExampleCapability" + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off" + }, + { + "modelType": "SubmodelElementList", + "idShort": "ExampleSubmodelElementListUnordered", + "orderRelevant": false, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "valueType": "xs:string", + "idShort": "ExampleProperty" + }, + { + "modelType": "MultiLanguageProperty", + "idShort": "ExampleMultiLanguageProperty" + }, + { + "modelType": "Range", + "valueType": "xs:int", + "idShort": "ExampleRange" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection", + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "idShort": "ExampleBlob" + }, + { + "modelType": "File", + "contentType": "application/pdf", + "idShort": "ExampleFile" + }, + { + "modelType": "ReferenceElement", + "idShort": "ExampleReferenceElement" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection2" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "id": "https://acplt.org/Test_Submodel2_Mandatory", + "idShort": "Test_Submodel2_Mandatory" + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Missing", + "idShort": "TestSubmodelMissing", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "category": "PARAMETER", + "value": "some example annotation", + "valueType": "xs:string", + "idShort": "ExampleProperty" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "input", + "state": "on", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "File", + "value": "ExampleFile" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Template", + "idShort": "TestSubmodelTemplate", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange2", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection2", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + } + ] +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/application.properties b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/application.properties new file mode 100644 index 000000000..54f4d09e9 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/application.properties @@ -0,0 +1,19 @@ +server.port=8081 +server.error.path=/error + +spring.application.name=AAS Repository CI +basyx.aasrepo.name=aas-repo-ci + +basyx.backend = MongoDB +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas-test-ci +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword + +basyx.aasrepository.feature.search.enabled=true +basyx.aasrepository.feature.search.indexname=aas-index-test-ci +spring.elasticsearch.uris=http://localhost:9200 +spring.elasticsearch.username=elastic +spring.elasticsearch.password=vtzJFt1b diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json index 15a3ae6fc..be7f8848d 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/resources/query.json @@ -1,6 +1,6 @@ { "$condition": { - "or": [ + "$or": [ { "$contains": [ { diff --git a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java index ae5e5d9fe..e9ca11f15 100644 --- a/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java +++ b/basyx.common/basyx.querycore/src/main/java/org/eclipse/digitaltwin/basyx/querycore/query/converter/ValueConverter.java @@ -208,8 +208,8 @@ public Query convertStringComparison(StringValue leftValue, StringValue rightVal private Query createFieldToFieldComparison(String leftField, String rightField, String operator) { String scriptSource; - String scriptLeftField = leftField; - String scriptRightField = rightField; + String scriptLeftField = convertToESFields(leftField); + String scriptRightField = convertToESFields(rightField); if (!leftField.endsWith(".keyword")) { scriptLeftField += ".keyword"; @@ -240,10 +240,12 @@ private Query createFieldToFieldComparison(String leftField, String rightField, default: throw new IllegalArgumentException("Unsupported field-to-field operator: " + operator); } - + + String finalScriptLeftField = scriptLeftField; + String finalScriptRightField = scriptRightField; return QueryBuilders.bool(b -> b - .must(QueryBuilders.exists(e -> e.field(leftField))) - .must(QueryBuilders.exists(e -> e.field(rightField))) + .must(QueryBuilders.exists(e -> e.field(finalScriptLeftField))) + .must(QueryBuilders.exists(e -> e.field(finalScriptRightField))) .must(QueryBuilders.script(s -> s .script(script -> script .source(source -> source.scriptString(scriptSource)) @@ -571,8 +573,8 @@ private String detectCastType(Value value) { private Query createFieldToFieldCastComparison(String leftField, String rightField, String leftCastType, String rightCastType, String operator) { String scriptSource; - String scriptLeftField = leftField; - String scriptRightField = rightField; + String scriptLeftField = convertToESFields(leftField); + String scriptRightField = convertToESFields(rightField); if (!leftField.endsWith(".keyword")) { scriptLeftField += ".keyword"; @@ -669,7 +671,7 @@ private String extractSmeFieldName(String wildcardField) { */ private Query createSmeWildcardQuery(String wildcardField, String value) { String fieldName = extractSmeFieldName(wildcardField); - + fieldName = convertToESFields(fieldName); // Add .keyword suffix for string fields that need exact matching String searchField = fieldName; @@ -682,16 +684,13 @@ private Query createSmeWildcardQuery(String wildcardField, String value) { .fields(queryPattern.replace("[]","[*]")) ); } - + /** * Creates a wildcard string query using QueryBuilders.queryString for SME fields at any nesting level */ private Query createSmeWildcardStringQuery(String wildcardField, String value, String operation) { String fieldName = extractSmeFieldName(wildcardField); - fieldName = fieldName.replace("semanticId.keys[]", "semanticId.keys.value") - .replace("supplementalSemanticIds[].keys[]", "supplementalSemanticIds.keys.value") - .replace("submodels.keys[]", "submodels.keys.value"); - String searchField = fieldName; + String searchField = convertToESFields(fieldName); String searchValue; switch (operation) { @@ -773,4 +772,16 @@ private String escapeQueryString(String value) { .replace("*", "\\*") .replace(":", "\\:"); } + + + private static String convertToESFields(String fieldName) { + fieldName = fieldName.replace("semanticId.keys[]", "semanticId.keys.value") + .replace("supplementalSemanticIds[].keys[]", "supplementalSemanticIds.keys.value") + .replace("submodels.keys[]", "submodels.keys.value") + .replace("description[].language", "description.language") + .replace("description[].text", "description.text") + .replace("isCaseOf.keys[]", "isCaseOf.keys.value") + .replace("endpoints[]", "endpoints"); + return fieldName; + } } \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml index e05330719..1918896b8 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml @@ -16,6 +16,14 @@ org.eclipse.digitaltwin.basyx basyx.conceptdescriptionrepository-core + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-backend + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-backend-mongodb + org.eclipse.digitaltwin.basyx basyx.querycore @@ -25,6 +33,16 @@ elasticsearch-java 9.0.1 + + com.fasterxml.jackson.core + jackson-core + 2.19.0 + + + com.fasterxml.jackson.core + jackson-annotations + 2.19.0 + com.fasterxml.jackson.core jackson-databind diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java index 60f16e8b1..fbca49434 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/SearchCdRepository.java @@ -54,7 +54,6 @@ public SearchCdRepository(ConceptDescriptionRepository decorated, ElasticsearchC this.decorated = decorated; this.esclient = esclient; this.indexName = indexName; - ensureIndexExists(); } @Override @@ -136,47 +135,4 @@ private void deindexCD(String cdId) { } } - private void ensureIndexExists() { - try { - // Check if index exists - boolean indexExists = esclient.indices().exists(ExistsRequest.of(e -> e.index(indexName))).value(); - - if (!indexExists) { - // Create index with proper mapping - CreateIndexRequest createIndexRequest = CreateIndexRequest.of(c -> c - .index(indexName) - .mappings(TypeMapping.of(m -> m - .properties("id", Property.of(p -> p.keyword(k -> k))) - .properties("idShort", Property.of(p -> p.text(t -> t))) - .properties("description", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex description objects - ))) - .properties("displayName", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex displayName objects - ))) - .properties("isCaseOf", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex reference objects - ))) - .properties("extensions", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex extensions - ))) - .properties("embeddedDataSpecifications", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex embeddedDataSpecifications - ))) - .properties("administration", Property.of(p -> p.object(o -> o - .enabled(false) // Store but don't analyze complex administration objects - ))) - .properties("category", Property.of(p -> p.text(t -> t))) - )) - ); - - esclient.indices().create(createIndexRequest); - logger.info("Created Elasticsearch index: {} with proper mappings", indexName); - } - } catch (Exception e) { - logger.error("Failed to ensure index exists: {}", indexName, e); - throw new RuntimeException("Failed to initialize Elasticsearch index", e); - } - } - } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummyCdRepositoryConfig.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummyCdRepositoryConfig.java new file mode 100644 index 000000000..ec482ff83 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummyCdRepositoryConfig.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.feature.search; + +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepositoryFactory; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.feature.ConceptDescriptionRepositoryFeature; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.feature.DecoratedConceptDescriptionRepositoryFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * Configuration for tests + * + * @author mateusmolina, danish + * + */ +@Configuration +public class DummyCdRepositoryConfig { + + @Bean + @ConditionalOnMissingBean + public static ConceptDescriptionRepository getCdRepository(ConceptDescriptionRepositoryFactory cdRepositoryFactory, List features) { + return new DecoratedConceptDescriptionRepositoryFactory(cdRepositoryFactory, features).create(); + } +} \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummySearchCdRepositoryComponent.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummySearchCdRepositoryComponent.java new file mode 100644 index 000000000..4ade20f2f --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/DummySearchCdRepositoryComponent.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.feature.search; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Spring application configured for tests. + * + * @author aaronzi, fried + * + */ + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class DummySearchCdRepositoryComponent { + public static void main(String[] args) { + SpringApplication.run(DummySearchCdRepositoryComponent.class, args); + } +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java new file mode 100644 index 000000000..b784f4557 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.feature.search; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.basyx.conceptdescription.feature.search.SearchCdRepositoryApiHTTPController; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.ResponseEntity; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.List; + +public class TestSearchCdRepository { + private static ConfigurableApplicationContext appContext; + private static ConceptDescriptionRepository searchBackend; + private static SearchCdRepositoryApiHTTPController searchAPI; + + @BeforeClass + public static void startCdRepo() throws Exception { + appContext = new SpringApplicationBuilder(DummySearchCdRepositoryComponent.class).run(new String[] {}); + searchBackend = appContext.getBean(ConceptDescriptionRepository.class); + searchAPI = appContext.getBean(SearchCdRepositoryApiHTTPController.class); + preloadCds(); + waitForData(); + } + + @Test + public void testRepo() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchCdRepository.class.getResource("/query.json").getFile()); + AASQuery query = queryFromFile(file); + ResponseEntity result = searchAPI.queryConceptDescriptions(100, new Base64UrlEncodedCursor(""), query); + QueryResponse response = result.getBody(); + assert response != null; + List topHits = response.result.stream().map(o->(ConceptDescription)o).toList(); + Assert.assertEquals(1,topHits.size()); + } + + private static AASQuery queryFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), AASQuery.class); + } + + private static Environment envFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), Environment.class); + } + + private static void preloadCds() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchCdRepository.class.getResource("/Example-Full.json").getFile()); + Environment env = envFromFile(file); + for(ConceptDescription cd : env.getConceptDescriptions()) { + searchBackend.createConceptDescription(cd); + } + } + + @AfterClass + public static void shutdownCdRepo() { + resetRepo(); + appContext.close(); + } + + private static void resetRepo() { + searchBackend.getAllConceptDescriptions(new PaginationInfo(0, "")).getResult().forEach(cd -> { + try { + searchBackend.deleteConceptDescription(cd.getId()); + } catch (Exception e) { + // Ignore exceptions during cleanup + } + }); + } + + private static void waitForData() throws InterruptedException { + Thread.sleep(500); + } + +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/Example-Full.json b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/Example-Full.json new file mode 100644 index 000000000..0dcceaa3e --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/Example-Full.json @@ -0,0 +1,2597 @@ +{ + "assetAdministrationShells": [ + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset" + }, + "derivedFrom": { + "keys": [ + { + "type": "AssetAdministrationShell", + "value": "https://acplt.org/TestAssetAdministrationShell2" + } + ], + "type": "ExternalReference" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/Identification" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel2_Mandatory" + } + ], + "type": "ExternalReference" + } + ], + "id": "https://acplt.org/Test_AssetAdministrationShell_Mandatory", + "idShort": "Test_AssetAdministrationShell_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory", + "idShort": "Test_AssetAdministrationShell2_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Missing" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell_Missing", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + } + ], + "conceptDescriptions": [ + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription", + "idShort": "TestConceptDescription", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/DataSpecifications/Conceptdescription/TestConceptDescription" + } + ], + "type": "ExternalReference" + } + ], + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "id": "https://acplt.org/Test_ConceptDescription_Mandatory", + "idShort": "Test_ConceptDescription_Mandatory" + }, + { + "modelType": "ConceptDescription", + "category": "PROPERTY", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription_Missing", + "idShort": "TestConceptDescription1", + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/DataSpecifciations/Example/Identification", + "idShort": "TestSpec_01", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ConceptDescriptionX" + } + ], + "type": "ExternalReference" + } + ], + "embeddedDataSpecifications": [ + { + "dataSpecification": { + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/aas/3/0/RC02/DataSpecificationIec61360" + } + ], + "type": "ExternalReference" + }, + "dataSpecificationContent": { + "modelType": "DataSpecificationIec61360", + "dataType": "REAL_MEASURE", + "sourceOfDefinition": "http://acplt.org/DataSpec/ExampleDef", + "symbol": "SU", + "unit": "SpaceUnit", + "unitId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Units/SpaceUnit" + } + ], + "type": "ExternalReference" + }, + "levelType": { + "max": true, + "min": false, + "nom": false, + "typ": false + }, + "value": "TEST", + "valueFormat": "string", + "valueList": { + "valueReferencePairs": [ + { + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + } + }, + { + "value": "http://acplt.org/ValueId/ExampleValueId2", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId2" + } + ], + "type": "ExternalReference" + } + } + ] + }, + "definition": [ + { + "language": "de", + "text": "Dies ist eine Data Specification für Testzwecke" + }, + { + "language": "en-us", + "text": "This is a DataSpecification for testing purposes" + } + ], + "preferredName": [ + { + "language": "de", + "text": "Test Specification" + }, + { + "language": "en-us", + "text": "TestSpecification" + } + ], + "shortName": [ + { + "language": "de", + "text": "Test Spec" + }, + { + "language": "en-us", + "text": "TestSpec" + } + ] + } + } + ] + } + ], + "submodels": [ + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/AssetIdentification" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/Identification", + "idShort": "Identification", + "submodelElements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO677#002" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ACPLT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ACPLT" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "value": "100", + "valueType": "xs:int", + "kind": "ConceptQualifier" + }, + { + "type": "http://acplt.org/Qualifier/ExampleQualifier2", + "value": "50", + "valueType": "xs:int", + "kind": "ConceptQualifier" + } + ], + "idShort": "ManufacturerName", + "displayName": [ + { + "language": "en-us", + "text": "Manufacturer Name" + } + ], + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Property", + "category": "VARIABLE", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "supplementalSemanticIds": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "something_random_e14ad770" + } + ], + "type": "ExternalReference" + }, + { + "keys": [{ + "type": "GlobalReference", + "value": "something_random_bd061acd" + }], + "type": "ExternalReference" + } + + ], + "value": "978-8234-234-342", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "978-8234-234-342" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "InstanceId", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example asset identification submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + "administration": { + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial", + "idShort": "BillOfMaterial", + "submodelElements": [ + { + "modelType": "Entity", + "entityType": "CoManagedEntity", + "statements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValue2", + "category": "CONSTANT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValue2" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty", + "category": "CONSTANT", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + ], + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Entity", + "entityType": "SelfManagedEntity", + "globalAssetId": "https://acplt.org/Test_Asset2", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity2", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example bill of material submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel", + "idShort": "TestSubmodel", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "value": "some example annotation", + "valueType": "xs:string", + "category": "PARAMETER", + "idShort": "ExampleProperty3" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + },{ + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "direction": "input", + "state": "on", + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example ExampleSubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel ExampleSubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleMultiLanguageValueId" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "id": "https://acplt.org/Test_Submodel_Mandatory", + "idShort": "Test_Submodel_Mandatory", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "AnnotatedRelationshipElement", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "Blob", + "value": "ExampleBlob" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "Operation", + "idShort": "ExampleOperation" + }, + { + "modelType": "Capability", + "idShort": "ExampleCapability" + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off" + }, + { + "modelType": "SubmodelElementList", + "idShort": "ExampleSubmodelElementListUnordered", + "orderRelevant": false, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "valueType": "xs:string", + "idShort": "ExampleProperty" + }, + { + "modelType": "MultiLanguageProperty", + "idShort": "ExampleMultiLanguageProperty" + }, + { + "modelType": "Range", + "valueType": "xs:int", + "idShort": "ExampleRange" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection", + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "idShort": "ExampleBlob" + }, + { + "modelType": "File", + "contentType": "application/pdf", + "idShort": "ExampleFile" + }, + { + "modelType": "ReferenceElement", + "idShort": "ExampleReferenceElement" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection2" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "id": "https://acplt.org/Test_Submodel2_Mandatory", + "idShort": "Test_Submodel2_Mandatory" + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Missing", + "idShort": "TestSubmodelMissing", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "category": "PARAMETER", + "value": "some example annotation", + "valueType": "xs:string", + "idShort": "ExampleProperty" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "input", + "state": "on", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "File", + "value": "ExampleFile" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Template", + "idShort": "TestSubmodelTemplate", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange2", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection2", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + } + ] +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/application.properties b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/application.properties new file mode 100644 index 000000000..8a66815b2 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/application.properties @@ -0,0 +1,17 @@ +server.port=8081 +spring.application.name=Concept Description Repository +basyx.cdrepo.name = cd-repo + +basyx.backend = MongoDB +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=cd-test-ci +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword + +basyx.cdrepository.feature.search.enabled=true +basyx.cdrepository.feature.search.indexname=cd-index-test-ci +spring.elasticsearch.uris=http://localhost:9200 +spring.elasticsearch.username=elastic +spring.elasticsearch.password=vtzJFt1b diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json index eb428781d..a8c7e84c5 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/resources/query.json @@ -1,6 +1,6 @@ { "$condition": { - "match": [ + "$match": [ { "$eq": [ { @@ -12,14 +12,14 @@ ] }, { - "$lt": [ + "$eq": [ { "$numCast": { - "$field": "cd#administration.revision" + "$field": "$cd#administration.revision" } }, { - "$numVal": 11 + "$numVal": 9 } ] }, diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml index cc1e7e6e9..cc8957bd5 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml @@ -27,6 +27,16 @@ elasticsearch-java 9.0.1 + + com.fasterxml.jackson.core + jackson-core + 2.19.0 + + + com.fasterxml.jackson.core + jackson-annotations + 2.19.0 + com.fasterxml.jackson.core jackson-databind @@ -36,9 +46,23 @@ org.springframework spring-context + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-client-native + test + org.eclipse.digitaltwin.basyx basyx.submodelregistry-service-basemodel + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DummySearchSubmodelRegistryComponent.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DummySearchSubmodelRegistryComponent.java new file mode 100644 index 000000000..62f200cd7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DummySearchSubmodelRegistryComponent.java @@ -0,0 +1,42 @@ + /******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Creates and starts the {@link SearchSubmodelRegistryStorage} for tests + * + * @author zielstor, fried + * + */ +@SpringBootApplication(scanBasePackages = {"org.eclipse.digitaltwin.basyx.submodelregistry.feature.search","org.eclipse.digitaltwin.basyx.submodelregistry.service.api","org.eclipse.digitaltwin.basyx.submodelregistry.service.events", "org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration","org.eclipse.digitaltwin.basyx.submodelregistry.service.errors"}) +public class DummySearchSubmodelRegistryComponent { + public static void main(String[] args) { + SpringApplication.run(DummySearchSubmodelRegistryComponent.class, args); + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java new file mode 100644 index 000000000..2f42a0e79 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.*; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.junit.*; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.ResponseEntity; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link SearchSubmodelRegistryStorage} feature + * + * @author danish + */ +public class TestSearchSubmodelRegistry { + + private static ConfigurableApplicationContext appContext; + private static final String BASE_URL = "http://localhost:8080"; + public static String submodelRegistryBaseUrl = BASE_URL + "/submodel-descriptors"; + private static SubmodelRegistryStorage storage; + private static SearchSubmodelRegistryApiHTTPController searchAPI; + + + @BeforeClass + public static void setUp() throws FileNotFoundException, IOException, DeserializationException { + appContext = new SpringApplication(DummySearchSubmodelRegistryComponent.class).run(new String[] {}); + storage = appContext.getBean(SearchSubmodelRegistryStorage.class); + searchAPI = appContext.getBean(SearchSubmodelRegistryApiHTTPController.class); + preloadSmds(); + } + + @AfterClass + public static void tearDown() { + List descriptors = storage.getAllSubmodelDescriptors(PaginationInfo.NO_LIMIT).getResult(); + + descriptors.forEach(descriptor -> storage.removeSubmodelDescriptor(descriptor.getId())); + appContext.close(); + } + + @Test + public void testRepo() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchSubmodelRegistry.class.getResource("/query.json").getFile()); + AASQuery query = queryFromFile(file); + ResponseEntity result = searchAPI.querySubmodelDescriptors(100, new Base64UrlEncodedCursor(""), query); + QueryResponse response = result.getBody(); + assert response != null; + List topHits = response.result.stream().map(o->(SubmodelDescriptor)o).toList(); + Assert.assertEquals(4,topHits.size()); + } + + private static AASQuery queryFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), AASQuery.class); + } + + private static Environment envFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), Environment.class); + } + + private static void preloadSmds() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchSubmodelRegistry.class.getResource("/Example-Full.json").getFile()); + Environment env = envFromFile(file); + for(Submodel sm : env.getSubmodels()) { + Endpoint endpoint = new Endpoint("AAS-3.0", createProtocolInformation(sm.getId())); + SubmodelDescriptor descriptor = new SubmodelDescriptor(sm.getId(), Arrays.asList(endpoint)); + if(sm.getAdministration() != null) { + AdministrativeInformation administration = new AdministrativeInformation(); + administration.setVersion(sm.getAdministration().getVersion()); + administration.setRevision(sm.getAdministration().getRevision()); + descriptor.setAdministration(administration); + } + descriptor.setIdShort(sm.getIdShort()); + + List descriptions = new ArrayList<>(); + + for(org.eclipse.digitaltwin.aas4j.v3.model.LangStringTextType el : sm.getDescription()){ + LangStringTextType description = new LangStringTextType(); + description.setLanguage(el.getLanguage()); + description.setText(el.getText()); + descriptions.add(description); + } + + List displayNames = new ArrayList<>(); + for(org.eclipse.digitaltwin.aas4j.v3.model.LangStringNameType el : sm.getDisplayName()) { + LangStringNameType displayName = new LangStringNameType(); + displayName.setLanguage(el.getLanguage()); + displayName.setText(el.getText()); + displayNames.add(displayName); + } + + descriptor.setDescription(descriptions); + descriptor.setDisplayName(displayNames); + + if(sm.getSemanticId() != null) { + List keys = new ArrayList<>(); + for (org.eclipse.digitaltwin.aas4j.v3.model.Key key : sm.getSemanticId().getKeys()) { + Key k = new Key(); + k.setType(KeyTypes.SUBMODEL); + k.setValue(key.getValue()); + keys.add(k); + } + + descriptor.setSemanticId(new Reference(ReferenceTypes.EXTERNALREFERENCE, keys)); + } + + storage.insertSubmodelDescriptor(descriptor); + } + } + + private static ProtocolInformation createProtocolInformation(String submodelId) { + String href = String.format("%s/%s", BASE_URL + "/submodels", Base64UrlEncodedIdentifier.encodeIdentifier(submodelId)); + + ProtocolInformation protocolInformation = new ProtocolInformation(href); + protocolInformation.endpointProtocol(getProtocol(href)); + + return protocolInformation; + } + + private static String getProtocol(String endpoint) { + try { + return new URL(endpoint).getProtocol(); + } catch (MalformedURLException e) { + throw new RuntimeException(); + } + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/Example-Full.json b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/Example-Full.json new file mode 100644 index 000000000..0dcceaa3e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/Example-Full.json @@ -0,0 +1,2597 @@ +{ + "assetAdministrationShells": [ + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset" + }, + "derivedFrom": { + "keys": [ + { + "type": "AssetAdministrationShell", + "value": "https://acplt.org/TestAssetAdministrationShell2" + } + ], + "type": "ExternalReference" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/Identification" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel2_Mandatory" + } + ], + "type": "ExternalReference" + } + ], + "id": "https://acplt.org/Test_AssetAdministrationShell_Mandatory", + "idShort": "Test_AssetAdministrationShell_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory", + "idShort": "Test_AssetAdministrationShell2_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Missing" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell_Missing", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + } + ], + "conceptDescriptions": [ + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription", + "idShort": "TestConceptDescription", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/DataSpecifications/Conceptdescription/TestConceptDescription" + } + ], + "type": "ExternalReference" + } + ], + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "id": "https://acplt.org/Test_ConceptDescription_Mandatory", + "idShort": "Test_ConceptDescription_Mandatory" + }, + { + "modelType": "ConceptDescription", + "category": "PROPERTY", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription_Missing", + "idShort": "TestConceptDescription1", + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/DataSpecifciations/Example/Identification", + "idShort": "TestSpec_01", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ConceptDescriptionX" + } + ], + "type": "ExternalReference" + } + ], + "embeddedDataSpecifications": [ + { + "dataSpecification": { + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/aas/3/0/RC02/DataSpecificationIec61360" + } + ], + "type": "ExternalReference" + }, + "dataSpecificationContent": { + "modelType": "DataSpecificationIec61360", + "dataType": "REAL_MEASURE", + "sourceOfDefinition": "http://acplt.org/DataSpec/ExampleDef", + "symbol": "SU", + "unit": "SpaceUnit", + "unitId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Units/SpaceUnit" + } + ], + "type": "ExternalReference" + }, + "levelType": { + "max": true, + "min": false, + "nom": false, + "typ": false + }, + "value": "TEST", + "valueFormat": "string", + "valueList": { + "valueReferencePairs": [ + { + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + } + }, + { + "value": "http://acplt.org/ValueId/ExampleValueId2", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId2" + } + ], + "type": "ExternalReference" + } + } + ] + }, + "definition": [ + { + "language": "de", + "text": "Dies ist eine Data Specification für Testzwecke" + }, + { + "language": "en-us", + "text": "This is a DataSpecification for testing purposes" + } + ], + "preferredName": [ + { + "language": "de", + "text": "Test Specification" + }, + { + "language": "en-us", + "text": "TestSpecification" + } + ], + "shortName": [ + { + "language": "de", + "text": "Test Spec" + }, + { + "language": "en-us", + "text": "TestSpec" + } + ] + } + } + ] + } + ], + "submodels": [ + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/AssetIdentification" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/Identification", + "idShort": "Identification", + "submodelElements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO677#002" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ACPLT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ACPLT" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "value": "100", + "valueType": "xs:int", + "kind": "ConceptQualifier" + }, + { + "type": "http://acplt.org/Qualifier/ExampleQualifier2", + "value": "50", + "valueType": "xs:int", + "kind": "ConceptQualifier" + } + ], + "idShort": "ManufacturerName", + "displayName": [ + { + "language": "en-us", + "text": "Manufacturer Name" + } + ], + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Property", + "category": "VARIABLE", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "supplementalSemanticIds": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "something_random_e14ad770" + } + ], + "type": "ExternalReference" + }, + { + "keys": [{ + "type": "GlobalReference", + "value": "something_random_bd061acd" + }], + "type": "ExternalReference" + } + + ], + "value": "978-8234-234-342", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "978-8234-234-342" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "InstanceId", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example asset identification submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + "administration": { + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial", + "idShort": "BillOfMaterial", + "submodelElements": [ + { + "modelType": "Entity", + "entityType": "CoManagedEntity", + "statements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValue2", + "category": "CONSTANT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValue2" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty", + "category": "CONSTANT", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + ], + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Entity", + "entityType": "SelfManagedEntity", + "globalAssetId": "https://acplt.org/Test_Asset2", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity2", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example bill of material submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel", + "idShort": "TestSubmodel", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "value": "some example annotation", + "valueType": "xs:string", + "category": "PARAMETER", + "idShort": "ExampleProperty3" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + },{ + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "direction": "input", + "state": "on", + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example ExampleSubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel ExampleSubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleMultiLanguageValueId" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "id": "https://acplt.org/Test_Submodel_Mandatory", + "idShort": "Test_Submodel_Mandatory", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "AnnotatedRelationshipElement", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "Blob", + "value": "ExampleBlob" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "Operation", + "idShort": "ExampleOperation" + }, + { + "modelType": "Capability", + "idShort": "ExampleCapability" + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off" + }, + { + "modelType": "SubmodelElementList", + "idShort": "ExampleSubmodelElementListUnordered", + "orderRelevant": false, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "valueType": "xs:string", + "idShort": "ExampleProperty" + }, + { + "modelType": "MultiLanguageProperty", + "idShort": "ExampleMultiLanguageProperty" + }, + { + "modelType": "Range", + "valueType": "xs:int", + "idShort": "ExampleRange" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection", + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "idShort": "ExampleBlob" + }, + { + "modelType": "File", + "contentType": "application/pdf", + "idShort": "ExampleFile" + }, + { + "modelType": "ReferenceElement", + "idShort": "ExampleReferenceElement" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection2" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "id": "https://acplt.org/Test_Submodel2_Mandatory", + "idShort": "Test_Submodel2_Mandatory" + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Missing", + "idShort": "TestSubmodelMissing", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "category": "PARAMETER", + "value": "some example annotation", + "valueType": "xs:string", + "idShort": "ExampleProperty" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "input", + "state": "on", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "File", + "value": "ExampleFile" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Template", + "idShort": "TestSubmodelTemplate", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange2", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection2", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + } + ] +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/application.yml b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/application.yml new file mode 100644 index 000000000..81145fe92 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/application.yml @@ -0,0 +1,55 @@ +--- +events: + sink: log +description: + profiles: https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001 +springdoc: + api-docs: + path: /api-docs +springfox: + documentation: + enabled: true + # open-api.v3.path: /api-docs +management: + endpoints: + web: + exposure: + include: "health,metrics" +logging: + level: + root: INFO +server: + shutdown: graceful + port: 8080 + error: + whitelabel: + enabled: false +spring: + application: + name: Basyx Submodel Registry + data: + mongodb: + uri: mongodb://mongoAdmin:mongoPassword@localhost:27017 + database: submodelregistry-ci-test + elasticsearch: + uris: http://localhost:9200 + username: elastic + password: vtzJFt1b + jackson: + date-format: org.eclipse.digitaltwin.basyx.submodelregistry.service.RFC3339DateFormat + serialization: + WRITE_DATES_AS_TIMESTAMPS: false + profiles: + active: logEvents,mongoDbStorage + +registry: + type: mongoDb + +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" + feature: + search: + enabled: true + indexname: smdesc-index-ci-test \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/query.json b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/query.json new file mode 100644 index 000000000..8c082446e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/resources/query.json @@ -0,0 +1,36 @@ +{ + "$condition": { + "$match": [ + { + "$contains": [ + { + "$field": "$smdesc#endpoints[].protocolInformation.href" + }, + { + "$strVal": "localhost" + } + ] + }, + { + "$eq": [ + { + "$field": "$smdesc#administration.revision" + }, + { + "$numVal": 9 + } + ] + }, + { + "$ne": [ + { + "$field": "$smdesc#idShort" + }, + { + "$field": "$smdesc#semanticId.keys[]" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2eb8023f9..9e39a3c67 100644 --- a/pom.xml +++ b/pom.xml @@ -1264,12 +1264,24 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-feature-search + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.aasregistry-feature-authorization ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-search + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.aasregistry-feature-hierarchy @@ -1294,6 +1306,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-search + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.submodelregistry-feature-hierarchy From e6f6878f3a447af7d13052e8199dbd3068c5b6a4 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 13:52:10 +0200 Subject: [PATCH 32/52] AAS Registry IT --- .../basyx.aasregistry-feature-search/pom.xml | 14 + .../DummySearchAasRegistryComponent.java | 42 + .../feature/search/TestSearchAasRegistry.java | 162 + .../src/test/resources/Example-Full.json | 2597 +++++++++++++++++ .../src/test/resources/application.yml | 55 + .../src/test/resources/query.json | 4 +- .../search/TestSearchSubmodelRegistry.java | 17 +- 7 files changed, 2879 insertions(+), 12 deletions(-) create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DummySearchAasRegistryComponent.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/Example-Full.json create mode 100644 basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/application.yml diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml b/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml index 53f5f01c3..86de9b94d 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml @@ -27,6 +27,16 @@ elasticsearch-java 9.0.1 + + com.fasterxml.jackson.core + jackson-core + 2.19.0 + + + com.fasterxml.jackson.core + jackson-annotations + 2.19.0 + com.fasterxml.jackson.core jackson-databind @@ -40,6 +50,10 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-service-basemodel + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service-mongodb-storage + \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DummySearchAasRegistryComponent.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DummySearchAasRegistryComponent.java new file mode 100644 index 000000000..54220ab51 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DummySearchAasRegistryComponent.java @@ -0,0 +1,42 @@ + /******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Creates and starts the {@link org.eclipse.digitaltwin.basyx.aasregistry.feature.search.SearchAasRegistryStorage} for tests + * + * @author zielstor, fried + * + */ +@SpringBootApplication(scanBasePackages = {"org.eclipse.digitaltwin.basyx.aasregistry.feature.search","org.eclipse.digitaltwin.basyx.aasregistry.service.api","org.eclipse.digitaltwin.basyx.aasregistry.service.events", "org.eclipse.digitaltwin.basyx.aasregistry.service.configuration","org.eclipse.digitaltwin.basyx.aasregistry.service.errors"}) +public class DummySearchAasRegistryComponent { + public static void main(String[] args) { + SpringApplication.run(DummySearchAasRegistryComponent.class, args); + } +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java new file mode 100644 index 000000000..28d75e3dc --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.basyx.aasregistry.model.*; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.DescriptorFilter; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; +import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; +import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.ResponseEntity; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Tests for {@link org.eclipse.digitaltwin.basyx.aasregistry.feature.search.SearchAasRegistryStorage} feature + * + * @author danish + */ +public class TestSearchAasRegistry { + + private static ConfigurableApplicationContext appContext; + private static final String BASE_URL = "http://localhost:8080"; + private static SearchAasRegistryStorage storage; + private static SearchAasRegistryApiHTTPController searchAPI; + + + @BeforeClass + public static void setUp() throws IOException, DeserializationException, InterruptedException { + appContext = new SpringApplication(DummySearchAasRegistryComponent.class).run(); + storage = appContext.getBean(SearchAasRegistryStorage.class); + searchAPI = appContext.getBean(SearchAasRegistryApiHTTPController.class); + preloadAasdf(); + Thread.sleep(500); + } + + @AfterClass + public static void tearDown() { + List descriptors = storage.getAllAasDescriptors(PaginationInfo.NO_LIMIT, new DescriptorFilter(null, null)).getResult(); + + descriptors.forEach(descriptor -> storage.removeAasDescriptor(descriptor.getId())); + appContext.close(); + } + + @Test + public void testRepo() throws FileNotFoundException, DeserializationException { + File file = new File(Objects.requireNonNull(TestSearchAasRegistry.class.getResource("/query.json")).getFile()); + AASQuery query = queryFromFile(file); + ResponseEntity result = searchAPI.queryAssetAdministrationShellDescriptors(100, new Base64UrlEncodedCursor(""), query); + QueryResponse response = result.getBody(); + assert response != null; + List topHits = response.result.stream().map(o->(AssetAdministrationShellDescriptor)o).toList(); + Assert.assertEquals(2,topHits.size()); + } + + private static AASQuery queryFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), AASQuery.class); + } + + private static Environment envFromFile(File file) throws FileNotFoundException, DeserializationException { + JsonDeserializer deserializer = new JsonDeserializer(); + return deserializer.read(new FileInputStream(file), Environment.class); + } + + private static void preloadAasdf() throws FileNotFoundException, DeserializationException { + File file = new File(TestSearchAasRegistry.class.getResource("/Example-Full.json").getFile()); + Environment env = envFromFile(file); + for(AssetAdministrationShell aas : env.getAssetAdministrationShells()) { + Endpoint endpoint = new Endpoint("AAS-3.0", createProtocolInformation(aas.getId())); + + AssetAdministrationShellDescriptor descriptor = new AssetAdministrationShellDescriptor(aas.getId()); + descriptor.setEndpoints(List.of(endpoint)); + + if(aas.getAdministration() != null) { + AdministrativeInformation administration = new AdministrativeInformation(); + administration.setVersion(aas.getAdministration().getVersion()); + administration.setRevision(aas.getAdministration().getRevision()); + descriptor.setAdministration(administration); + } + descriptor.setIdShort(aas.getIdShort()); + + List descriptions = new ArrayList<>(); + for(org.eclipse.digitaltwin.aas4j.v3.model.LangStringTextType el : aas.getDescription()){ + LangStringTextType description = new LangStringTextType(el.getLanguage(), el.getText()); + descriptions.add(description); + } + + List displayNames = new ArrayList<>(); + for(org.eclipse.digitaltwin.aas4j.v3.model.LangStringNameType el : aas.getDisplayName()) { + LangStringNameType displayName = new LangStringNameType(el.getLanguage(), el.getText()); + displayNames.add(displayName); + } + + descriptor.setDescription(descriptions); + descriptor.setDisplayName(displayNames); + descriptor.setAssetKind(AssetKind.INSTANCE); + + storage.insertAasDescriptor(descriptor); + } + } + + private static ProtocolInformation createProtocolInformation(String shellId) { + String href = String.format("%s/%s", BASE_URL + "/shells", Base64UrlEncodedIdentifier.encodeIdentifier(shellId)); + + ProtocolInformation protocolInformation = new ProtocolInformation(href); + protocolInformation.endpointProtocol(getProtocol(href)); + + return protocolInformation; + } + + private static String getProtocol(String endpoint) { + try { + return new URL(endpoint).getProtocol(); + } catch (MalformedURLException e) { + throw new RuntimeException(); + } + } + +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/Example-Full.json b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/Example-Full.json new file mode 100644 index 000000000..0dcceaa3e --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/Example-Full.json @@ -0,0 +1,2597 @@ +{ + "assetAdministrationShells": [ + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset" + }, + "derivedFrom": { + "keys": [ + { + "type": "AssetAdministrationShell", + "value": "https://acplt.org/TestAssetAdministrationShell2" + } + ], + "type": "ExternalReference" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/Identification" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + } + ], + "type": "ExternalReference" + }, + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel2_Mandatory" + } + ], + "type": "ExternalReference" + } + ], + "id": "https://acplt.org/Test_AssetAdministrationShell_Mandatory", + "idShort": "Test_AssetAdministrationShell_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Mandatory" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory", + "idShort": "Test_AssetAdministrationShell2_Mandatory" + }, + { + "modelType": "AssetAdministrationShell", + "assetInformation": { + "assetKind": "Instance", + "globalAssetId": "https://acplt.org/Test_Asset_Missing" + }, + "submodels": [ + { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + } + ], + "type": "ExternalReference" + } + ], + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_AssetAdministrationShell_Missing", + "idShort": "TestAssetAdministrationShell", + "description": [ + { + "language": "en-us", + "text": "An Example Asset Administration Shell for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung" + } + ] + } + ], + "conceptDescriptions": [ + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription", + "idShort": "TestConceptDescription", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/DataSpecifications/Conceptdescription/TestConceptDescription" + } + ], + "type": "ExternalReference" + } + ], + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "id": "https://acplt.org/Test_ConceptDescription_Mandatory", + "idShort": "Test_ConceptDescription_Mandatory" + }, + { + "modelType": "ConceptDescription", + "category": "PROPERTY", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_ConceptDescription_Missing", + "idShort": "TestConceptDescription1", + "description": [ + { + "language": "en-us", + "text": "An example concept description for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-ConceptDescription für eine Test-Anwendung" + } + ] + }, + { + "modelType": "ConceptDescription", + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/DataSpecifciations/Example/Identification", + "idShort": "TestSpec_01", + "isCaseOf": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ConceptDescriptionX" + } + ], + "type": "ExternalReference" + } + ], + "embeddedDataSpecifications": [ + { + "dataSpecification": { + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/aas/3/0/RC02/DataSpecificationIec61360" + } + ], + "type": "ExternalReference" + }, + "dataSpecificationContent": { + "modelType": "DataSpecificationIec61360", + "dataType": "REAL_MEASURE", + "sourceOfDefinition": "http://acplt.org/DataSpec/ExampleDef", + "symbol": "SU", + "unit": "SpaceUnit", + "unitId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Units/SpaceUnit" + } + ], + "type": "ExternalReference" + }, + "levelType": { + "max": true, + "min": false, + "nom": false, + "typ": false + }, + "value": "TEST", + "valueFormat": "string", + "valueList": { + "valueReferencePairs": [ + { + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + } + }, + { + "value": "http://acplt.org/ValueId/ExampleValueId2", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId2" + } + ], + "type": "ExternalReference" + } + } + ] + }, + "definition": [ + { + "language": "de", + "text": "Dies ist eine Data Specification für Testzwecke" + }, + { + "language": "en-us", + "text": "This is a DataSpecification for testing purposes" + } + ], + "preferredName": [ + { + "language": "de", + "text": "Test Specification" + }, + { + "language": "en-us", + "text": "TestSpecification" + } + ], + "shortName": [ + { + "language": "de", + "text": "Test Spec" + }, + { + "language": "en-us", + "text": "TestSpec" + } + ] + } + } + ] + } + ], + "submodels": [ + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/AssetIdentification" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/Identification", + "idShort": "Identification", + "submodelElements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO677#002" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ACPLT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ACPLT" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "value": "100", + "valueType": "xs:int", + "kind": "ConceptQualifier" + }, + { + "type": "http://acplt.org/Qualifier/ExampleQualifier2", + "value": "50", + "valueType": "xs:int", + "kind": "ConceptQualifier" + } + ], + "idShort": "ManufacturerName", + "displayName": [ + { + "language": "en-us", + "text": "Manufacturer Name" + } + ], + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Property", + "category": "VARIABLE", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "supplementalSemanticIds": [ + { + "keys": [ + { + "type": "GlobalReference", + "value": "something_random_e14ad770" + } + ], + "type": "ExternalReference" + }, + { + "keys": [{ + "type": "GlobalReference", + "value": "something_random_bd061acd" + }], + "type": "ExternalReference" + } + + ], + "value": "978-8234-234-342", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "978-8234-234-342" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "InstanceId", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example asset identification submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/SubmodelTemplates/BillOfMaterial" + } + ], + "type": "ExternalReference" + }, + "administration": { + "version": "0" + }, + "id": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial", + "idShort": "BillOfMaterial", + "submodelElements": [ + { + "modelType": "Entity", + "entityType": "CoManagedEntity", + "statements": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValue2", + "category": "CONSTANT", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValue2" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "idShort": "ExampleProperty", + "category": "CONSTANT", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + ], + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + }, + { + "modelType": "Entity", + "entityType": "SelfManagedEntity", + "globalAssetId": "https://acplt.org/Test_Asset2", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber" + } + ], + "type": "ExternalReference" + }, + "idShort": "ExampleEntity2", + "description": [ + { + "language": "en-us", + "text": "Legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation." + }, + { + "language": "de", + "text": "Bezeichnung für eine natürliche oder juristische Person, die für die Auslegung, Herstellung und Verpackung sowie die Etikettierung eines Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen verantwortlich ist" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example bill of material submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel", + "idShort": "TestSubmodel", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial" + }, + { + "type": "Entity", + "value": "ExampleEntity" + }, + { + "type": "Property", + "value": "ExampleProperty2" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "value": "some example annotation", + "valueType": "xs:string", + "category": "PARAMETER", + "idShort": "ExampleProperty3" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + },{ + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "direction": "input", + "state": "on", + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example ExampleSubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel ExampleSubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "http://acplt.org/ValueId/ExampleValueId", + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleValueId" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "valueId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ValueId/ExampleMultiLanguageValueId" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "id": "https://acplt.org/Test_Submodel_Mandatory", + "idShort": "Test_Submodel_Mandatory", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "AnnotatedRelationshipElement", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "Blob", + "value": "ExampleBlob" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + } + }, + { + "modelType": "Operation", + "idShort": "ExampleOperation" + }, + { + "modelType": "Capability", + "idShort": "ExampleCapability" + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Mandatory" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListUnordered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off" + }, + { + "modelType": "SubmodelElementList", + "idShort": "ExampleSubmodelElementListUnordered", + "orderRelevant": false, + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "valueType": "xs:string", + "idShort": "ExampleProperty" + }, + { + "modelType": "MultiLanguageProperty", + "idShort": "ExampleMultiLanguageProperty" + }, + { + "modelType": "Range", + "valueType": "xs:int", + "idShort": "ExampleRange" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection", + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "idShort": "ExampleBlob" + }, + { + "modelType": "File", + "contentType": "application/pdf", + "idShort": "ExampleFile" + }, + { + "modelType": "ReferenceElement", + "idShort": "ExampleReferenceElement" + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "idShort": "ExampleSubmodelElementCollection2" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Instance", + "id": "https://acplt.org/Test_Submodel2_Mandatory", + "idShort": "Test_Submodel2_Mandatory" + }, + { + "modelType": "Submodel", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Missing", + "idShort": "TestSubmodelMissing", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "MultiLanguageProperty", + "value": "ExampleMultiLanguageProperty" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "modelType": "Property", + "category": "PARAMETER", + "value": "some example annotation", + "valueType": "xs:string", + "idShort": "ExampleProperty" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty3", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty1", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty2", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementList", + "value": "ExampleSubmodelElementListOrdered" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "input", + "state": "on", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "value": "exampleValue", + "valueType": "xs:string", + "qualifiers": [ + { + "type": "http://acplt.org/Qualifier/ExampleQualifier", + "valueType": "xs:string", + "kind": "ConceptQualifier" + } + ], + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "value": [ + { + "language": "en-us", + "text": "Example value of a MultiLanguageProperty element" + }, + { + "language": "de", + "text": "Beispielswert für ein MultiLanguageProperty-Element" + } + ], + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "value": "AQIDBAU=", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "value": "file:///TestFile.pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "value": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Missing" + }, + { + "type": "SubmodelElementCollection", + "value": "ExampleSubmodelElementCollection" + }, + { + "type": "File", + "value": "ExampleFile" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + }, + { + "modelType": "Submodel", + "kind": "Template", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelTemplates/ExampleSubmodel" + } + ], + "type": "ExternalReference" + }, + "administration": { + "revision": "9", + "version": "0" + }, + "id": "https://acplt.org/Test_Submodel_Template", + "idShort": "TestSubmodelTemplate", + "submodelElements": [ + { + "modelType": "RelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example RelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel RelationshipElement Element" + } + ] + }, + { + "modelType": "AnnotatedRelationshipElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/RelationshipElements/ExampleAnnotatedRelationshipElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleAnnotatedRelationshipElement", + "first": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "second": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "description": [ + { + "language": "en-us", + "text": "Example AnnotatedRelationshipElement object" + }, + { + "language": "de", + "text": "Beispiel AnnotatedRelationshipElement Element" + } + ] + }, + { + "modelType": "Operation", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Operations/ExampleOperation" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleOperation", + "inoutputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "inputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + } + } + ], + "description": [ + { + "language": "en-us", + "text": "Example Operation object" + }, + { + "language": "de", + "text": "Beispiel Operation Element" + } + ] + }, + { + "modelType": "Capability", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Capabilities/ExampleCapability" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleCapability", + "description": [ + { + "language": "en-us", + "text": "Example Capability object" + }, + { + "language": "de", + "text": "Beispiel Capability Element" + } + ] + }, + { + "modelType": "BasicEventElement", + "observed": { + "keys": [ + { + "type": "Submodel", + "value": "https://acplt.org/Test_Submodel_Template" + }, + { + "type": "Operation", + "value": "ExampleOperation" + }, + { + "type": "Property", + "value": "ExampleProperty" + } + ], + "type": "ModelReference" + }, + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Events/ExampleBasicEvent" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBasicEvent", + "direction": "output", + "state": "off", + "description": [ + { + "language": "en-us", + "text": "Example BasicEvent object" + }, + { + "language": "de", + "text": "Beispiel BasicEvent Element" + } + ] + }, + { + "modelType": "SubmodelElementList", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementLists/ExampleSubmodelElementListOrdered" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementListOrdered", + "orderRelevant": true, + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementListOrdered object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementListOrdered Element" + } + ], + "typeValueListElement": "SubmodelElement", + "value": [ + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Properties/ExampleProperty" + } + ], + "type": "ExternalReference" + }, + "valueType": "xs:string", + "category": "CONSTANT", + "idShort": "ExampleProperty", + "description": [ + { + "language": "en-us", + "text": "Example Property object" + }, + { + "language": "de", + "text": "Beispiel Property Element" + } + ] + }, + { + "modelType": "MultiLanguageProperty", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/MultiLanguageProperties/ExampleMultiLanguageProperty" + } + ], + "type": "ExternalReference" + }, + "category": "CONSTANT", + "idShort": "ExampleMultiLanguageProperty", + "description": [ + { + "language": "en-us", + "text": "Example MultiLanguageProperty object" + }, + { + "language": "de", + "text": "Beispiel MultiLanguageProperty Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "max": "100", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + }, + { + "modelType": "Range", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Ranges/ExampleRange" + } + ], + "type": "ExternalReference" + }, + "min": "0", + "valueType": "xs:int", + "category": "PARAMETER", + "idShort": "ExampleRange2", + "description": [ + { + "language": "en-us", + "text": "Example Range object" + }, + { + "language": "de", + "text": "Beispiel Range Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ], + "value": [ + { + "modelType": "Blob", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Blobs/ExampleBlob" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleBlob", + "description": [ + { + "language": "en-us", + "text": "Example Blob object" + }, + { + "language": "de", + "text": "Beispiel Blob Element" + } + ] + }, + { + "modelType": "File", + "contentType": "application/pdf", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/Files/ExampleFile" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleFile", + "description": [ + { + "language": "en-us", + "text": "Example File object" + }, + { + "language": "de", + "text": "Beispiel File Element" + } + ] + }, + { + "modelType": "ReferenceElement", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/ReferenceElements/ExampleReferenceElement" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleReferenceElement", + "description": [ + { + "language": "en-us", + "text": "Example Reference Element object" + }, + { + "language": "de", + "text": "Beispiel Reference Element Element" + } + ] + } + ] + }, + { + "modelType": "SubmodelElementCollection", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "http://acplt.org/SubmodelElementCollections/ExampleSubmodelElementCollection" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "ExampleSubmodelElementCollection2", + "description": [ + { + "language": "en-us", + "text": "Example SubmodelElementCollection object" + }, + { + "language": "de", + "text": "Beispiel SubmodelElementCollection Element" + } + ] + } + ], + "description": [ + { + "language": "en-us", + "text": "An example submodel for the test application" + }, + { + "language": "de", + "text": "Ein Beispiel-Teilmodell für eine Test-Anwendung" + } + ] + } + ] +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/application.yml b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/application.yml new file mode 100644 index 000000000..0771c5ec6 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/application.yml @@ -0,0 +1,55 @@ +--- +events: + sink: log +description: + profiles: https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-001 +springdoc: + api-docs: + path: /api-docs +springfox: + documentation: + enabled: true + # open-api.v3.path: /api-docs +management: + endpoints: + web: + exposure: + include: "health,metrics" +logging: + level: + root: INFO +server: + shutdown: graceful + port: 8080 + error: + whitelabel: + enabled: false +spring: + application: + name: Basyx AAS Registry + data: + mongodb: + uri: mongodb://mongoAdmin:mongoPassword@localhost:27017 + database: aasregistry-ci-test + elasticsearch: + uris: http://localhost:9200 + username: elastic + password: vtzJFt1b + jackson: + date-format: org.eclipse.digitaltwin.basyx.aasregistry.service.RFC3339DateFormat + serialization: + WRITE_DATES_AS_TIMESTAMPS: false + profiles: + active: logEvents,mongoDbStorage + +registry: + type: mongoDb + +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" + feature: + search: + enabled: true + indexname: aasdesc-index-ci-test \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json index 452a43f6b..f2cbe3d96 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/resources/query.json @@ -22,12 +22,12 @@ ] }, { - "$startsWith": [ + "$ends-with": [ { "$field": "$aasdesc#idShort" }, { - "$strVal": "Test" + "$strVal": "Mandatory" } ] } diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 2f42a0e79..6c10eb934 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import static org.junit.Assert.assertEquals; @@ -68,7 +69,7 @@ public class TestSearchSubmodelRegistry { @BeforeClass - public static void setUp() throws FileNotFoundException, IOException, DeserializationException { + public static void setUp() throws IOException, DeserializationException { appContext = new SpringApplication(DummySearchSubmodelRegistryComponent.class).run(new String[] {}); storage = appContext.getBean(SearchSubmodelRegistryStorage.class); searchAPI = appContext.getBean(SearchSubmodelRegistryApiHTTPController.class); @@ -85,7 +86,7 @@ public static void tearDown() { @Test public void testRepo() throws FileNotFoundException, DeserializationException { - File file = new File(TestSearchSubmodelRegistry.class.getResource("/query.json").getFile()); + File file = new File(Objects.requireNonNull(TestSearchSubmodelRegistry.class.getResource("/query.json")).getFile()); AASQuery query = queryFromFile(file); ResponseEntity result = searchAPI.querySubmodelDescriptors(100, new Base64UrlEncodedCursor(""), query); QueryResponse response = result.getBody(); @@ -109,7 +110,7 @@ private static void preloadSmds() throws FileNotFoundException, DeserializationE Environment env = envFromFile(file); for(Submodel sm : env.getSubmodels()) { Endpoint endpoint = new Endpoint("AAS-3.0", createProtocolInformation(sm.getId())); - SubmodelDescriptor descriptor = new SubmodelDescriptor(sm.getId(), Arrays.asList(endpoint)); + SubmodelDescriptor descriptor = new SubmodelDescriptor(sm.getId(), List.of(endpoint)); if(sm.getAdministration() != null) { AdministrativeInformation administration = new AdministrativeInformation(); administration.setVersion(sm.getAdministration().getVersion()); @@ -118,20 +119,16 @@ private static void preloadSmds() throws FileNotFoundException, DeserializationE } descriptor.setIdShort(sm.getIdShort()); - List descriptions = new ArrayList<>(); + List descriptions = new ArrayList<>(); for(org.eclipse.digitaltwin.aas4j.v3.model.LangStringTextType el : sm.getDescription()){ - LangStringTextType description = new LangStringTextType(); - description.setLanguage(el.getLanguage()); - description.setText(el.getText()); + LangStringTextType description = new LangStringTextType(el.getLanguage(), el.getText()); descriptions.add(description); } List displayNames = new ArrayList<>(); for(org.eclipse.digitaltwin.aas4j.v3.model.LangStringNameType el : sm.getDisplayName()) { - LangStringNameType displayName = new LangStringNameType(); - displayName.setLanguage(el.getLanguage()); - displayName.setText(el.getText()); + LangStringNameType displayName = new LangStringNameType(el.getLanguage(), el.getText()); displayNames.add(displayName); } From 70299521bb246f9ee976ecef0d7e18aabede69e4 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 13:58:14 +0200 Subject: [PATCH 33/52] Adds missing parameter --- .../feature/search/SearchSubmodelRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java index d1ef29adf..c05a2300e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/SearchSubmodelRepository.java @@ -161,8 +161,8 @@ public File getFileByPathSubmodel(String submodelId, String idShortPath) throws } @Override - public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - decorated.setFileValue(submodelId, idShortPath, fileName, inputStream); + public void setFileValue(String submodelId, String idShortPath, String fileName, String contentType, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + decorated.setFileValue(submodelId, idShortPath, fileName, contentType, inputStream); reindexSM(submodelId); } From 5109adfe49159972b8b4f509a71bf2ada93f13bc Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 14:03:54 +0200 Subject: [PATCH 34/52] Fixes docker-compose.yml --- ci/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index a231f503e..c54e902e7 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -1,5 +1,5 @@ include: - docker-compose-elastic.yml + - docker-compose-elastic.yml services: nginx-proxy: From 2c87155e59b5d0fc63c9a1ceafad802cb9407b47 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 14:14:36 +0200 Subject: [PATCH 35/52] Reverts changes in application.properties --- .../src/main/resources/application.properties | 36 ++++++++----------- .../src/main/resources/application.properties | 24 ++++++------- .../src/main/resources/application.properties | 14 ++------ 3 files changed, 28 insertions(+), 46 deletions(-) diff --git a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties index 81fd138ec..d264f0740 100644 --- a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties +++ b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/application.properties @@ -1,16 +1,9 @@ server.port=8081 spring.application.name=AAS Environment -#basyx.backend = InMemory -basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 -basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD -basyx.backend = MongoDB -spring.data.mongodb.host=127.0.0.1 -spring.data.mongodb.port=27017 -spring.data.mongodb.database=aasenv -spring.data.mongodb.authentication-database=admin -spring.data.mongodb.username=mongoAdmin -spring.data.mongodb.password=mongoPassword +basyx.backend = InMemory + +#basyx.backend = MongoDB # spring.data.mongodb.host=mongo # or spring.data.mongodb.host=127.0.0.1 # spring.data.mongodb.port=27017 @@ -85,9 +78,8 @@ spring.data.mongodb.password=mongoPassword #################################################################################### # Feature: Registry Integration #################################################################################### -basyx.submodelrepository.feature.registryintegration=http://localhost:8083 -basyx.aasrepository.feature.registryintegration=http://localhost:8082 -basyx.externalurl=http://localhost:8081 +#basyx.aasrepository.feature.registryintegration=http://localhost:8050 +#basyx.externalurl=http://localhost:8081 #basyx.aasrepository.feature.registryintegration.authorization.enabled=true #basyx.aasrepository.feature.registryintegration.authorization.token-endpoint=http://localhost/realms/BaSyx/protocol/openid-connect/token #basyx.aasrepository.feature.registryintegration.authorization.grant-type = CLIENT_CREDENTIALS @@ -111,12 +103,12 @@ basyx.externalurl=http://localhost:8081 #springdoc.api-docs.enabled=false -basyx.aasrepository.feature.search.enabled=true -basyx.aasrepository.feature.search.indexname=aas-index-test -basyx.cdrepository.feature.search.enabled=true -basyx.cdrepository.feature.search.indexname=cd-index-test -basyx.submodelrepository.feature.search.enabled=true -basyx.submodelrepository.feature.search.indexname=sm-index-test -spring.elasticsearch.uris=http://localhost:9200 -spring.elasticsearch.username=elastic -spring.elasticsearch.password=vtzJFt1b \ No newline at end of file +# basyx.aasrepository.feature.search.enabled=true +# basyx.aasrepository.feature.search.indexname=aas-index-test +# basyx.cdrepository.feature.search.enabled=true +# basyx.cdrepository.feature.search.indexname=cd-index-test +# basyx.submodelrepository.feature.search.enabled=true +# basyx.submodelrepository.feature.search.indexname=sm-index-test +# spring.elasticsearch.uris=http://localhost:9200 +# spring.elasticsearch.username=elastic +# spring.elasticsearch.password=vtzJFt1b \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties index bba676ef1..d57e19532 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties +++ b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties @@ -4,26 +4,26 @@ server.error.path=/error spring.application.name=AAS Repository basyx.aasrepo.name=aas-repo -#basyx.backend = InMemory +basyx.backend = InMemory -basyx.backend = MongoDB -spring.data.mongodb.host=127.0.0.1 -spring.data.mongodb.port=27017 -spring.data.mongodb.database=aas -spring.data.mongodb.authentication-database=admin -spring.data.mongodb.username=mongoAdmin -spring.data.mongodb.password=mongoPassword -basyx.aasrepository.feature.registryintegration=http://localhost:8080 -basyx.externalurl=http://localhost:8081 + +#basyx.backend = MongoDB +#spring.data.mongodb.host=127.0.0.1 +#spring.data.mongodb.port=27017 +#spring.data.mongodb.database=aas +#spring.data.mongodb.authentication-database=admin +#spring.data.mongodb.username=mongoAdmin +#spring.data.mongodb.password=mongoPassword + # basyx.aasrepository.feature.mqtt.enabled = true # mqtt.clientId=TestClient # mqtt.hostname = localhost # mqtt.port = 1883 -basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 -basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 +# basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD #################################################################################### # Authorization diff --git a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties index 4779fb543..4b40a24a4 100644 --- a/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties +++ b/basyx.submodelrepository/basyx.submodelrepository.component/src/main/resources/application.properties @@ -2,18 +2,8 @@ server.port=8081 spring.application.name=Submodel Repository basyx.smrepo.name = sm-repo -basyx.backend = MongoDB -spring.data.mongodb.host=127.0.0.1 -spring.data.mongodb.port=27017 -spring.data.mongodb.database=aasenv -spring.data.mongodb.authentication-database=admin -spring.data.mongodb.username=mongoAdmin -spring.data.mongodb.password=mongoPassword -basyx.submodelrepository.feature.search.enabled=true -basyx.submodelrepository.feature.search.indexname=sm-index-test -spring.elasticsearch.uris=http://localhost:9200 -spring.elasticsearch.username=elastic -spring.elasticsearch.password=vtzJFt1b +basyx.backend = InMemory + # basyx.submodelrepository.feature.registryintegration=http://localhost:8060 # basyx.externalurl=http://localhost:8081 From cd8ee8873b111228acd28b4f1fb75130bc09305d Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 14:25:43 +0200 Subject: [PATCH 36/52] Adds missing .env entries - Removes unused files --- aas.schema.json | 1532 ----------------------- basyx.aasregistry/.vscode/settings.json | 3 - ci/.env | 20 +- ql.schema.json | 849 ------------- 4 files changed, 17 insertions(+), 2387 deletions(-) delete mode 100644 aas.schema.json delete mode 100644 basyx.aasregistry/.vscode/settings.json delete mode 100644 ql.schema.json diff --git a/aas.schema.json b/aas.schema.json deleted file mode 100644 index 845129371..000000000 --- a/aas.schema.json +++ /dev/null @@ -1,1532 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "IDTA-01001-3-1 AAS JSON Schema", - "type": "object", - "allOf": [ - { - "$ref": "#/definitions/Environment" - } - ], - "$id": "https://admin-shell.io/aas/3/1", - "definitions": { - "AasSubmodelElements": { - "type": "string", - "enum": [ - "AnnotatedRelationshipElement", - "BasicEventElement", - "Blob", - "Capability", - "DataElement", - "Entity", - "EventElement", - "File", - "MultiLanguageProperty", - "Operation", - "Property", - "Range", - "ReferenceElement", - "RelationshipElement", - "SubmodelElement", - "SubmodelElementCollection", - "SubmodelElementList" - ] - }, - "AbstractLangString": { - "type": "object", - "properties": { - "language": { - "type": "string", - "pattern": "^(([a-zA-Z]{2,3}(-[a-zA-Z]{3}(-[a-zA-Z]{3}){0,2})?|[a-zA-Z]{4}|[a-zA-Z]{5,8})(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-(([a-zA-Z0-9]){5,8}|[0-9]([a-zA-Z0-9]){3}))*(-[0-9A-WY-Za-wy-z](-([a-zA-Z0-9]){2,8})+)*(-[xX](-([a-zA-Z0-9]){1,8})+)?|[xX](-([a-zA-Z0-9]){1,8})+|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$" - }, - "text": { - "type": "string", - "minLength": 1, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - } - }, - "required": [ - "language", - "text" - ] - }, - "AdministrativeInformation": { - "allOf": [ - { - "$ref": "#/definitions/HasDataSpecification" - }, - { - "properties": { - "version": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 4 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^(0|[1-9][0-9]*)$" - } - ] - }, - "revision": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 4 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^(0|[1-9][0-9]*)$" - } - ] - }, - "creator": { - "$ref": "#/definitions/Reference" - }, - "templateId": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - } - } - } - ] - }, - "AnnotatedRelationshipElement": { - "allOf": [ - { - "$ref": "#/definitions/RelationshipElement_abstract" - }, - { - "properties": { - "annotations": { - "type": "array", - "items": { - "$ref": "#/definitions/DataElement_choice" - }, - "minItems": 1 - }, - "modelType": { - "const": "AnnotatedRelationshipElement" - } - } - } - ] - }, - "AssetAdministrationShell": { - "allOf": [ - { - "$ref": "#/definitions/Identifiable" - }, - { - "$ref": "#/definitions/HasDataSpecification" - }, - { - "properties": { - "derivedFrom": { - "$ref": "#/definitions/Reference" - }, - "assetInformation": { - "$ref": "#/definitions/AssetInformation" - }, - "submodels": { - "type": "array", - "items": { - "$ref": "#/definitions/Reference" - }, - "minItems": 1 - }, - "modelType": { - "const": "AssetAdministrationShell" - } - }, - "required": [ - "assetInformation" - ] - } - ] - }, - "AssetInformation": { - "type": "object", - "properties": { - "assetKind": { - "$ref": "#/definitions/AssetKind" - }, - "globalAssetId": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "specificAssetIds": { - "type": "array", - "items": { - "$ref": "#/definitions/SpecificAssetId" - }, - "minItems": 1 - }, - "assetType": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "defaultThumbnail": { - "$ref": "#/definitions/Resource" - } - }, - "required": [ - "assetKind" - ] - }, - "AssetKind": { - "type": "string", - "enum": [ - "Instance", - "NotApplicable", - "Role", - "Type" - ] - }, - "BasicEventElement": { - "allOf": [ - { - "$ref": "#/definitions/EventElement" - }, - { - "properties": { - "observed": { - "$ref": "#/definitions/Reference" - }, - "direction": { - "$ref": "#/definitions/Direction" - }, - "state": { - "$ref": "#/definitions/StateOfEvent" - }, - "messageTopic": { - "type": "string", - "minLength": 1, - "maxLength": 255, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "messageBroker": { - "$ref": "#/definitions/Reference" - }, - "lastUpdate": { - "type": "string", - "pattern": "^-?(([1-9][0-9][0-9][0-9]+)|(0[0-9][0-9][0-9]))-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))T(((([01][0-9])|(2[0-3])):[0-5][0-9]:([0-5][0-9])(\\.[0-9]+)?)|24:00:00(\\.0+)?)(Z|\\+00:00|-00:00)$" - }, - "minInterval": { - "type": "string", - "pattern": "^-?P((([0-9]+Y([0-9]+M)?([0-9]+D)?|([0-9]+M)([0-9]+D)?|([0-9]+D))(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S)))?)|(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S))))$" - }, - "maxInterval": { - "type": "string", - "pattern": "^-?P((([0-9]+Y([0-9]+M)?([0-9]+D)?|([0-9]+M)([0-9]+D)?|([0-9]+D))(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S)))?)|(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S))))$" - }, - "modelType": { - "const": "BasicEventElement" - } - }, - "required": [ - "observed", - "direction", - "state" - ] - } - ] - }, - "Blob": { - "allOf": [ - { - "$ref": "#/definitions/DataElement" - }, - { - "properties": { - "value": { - "type": "string", - "contentEncoding": "base64" - }, - "contentType": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 128 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\\x80-\\xff])|\\\\([\\t !-~]|[\\x80-\\xff]))*\"))*$" - } - ] - }, - "modelType": { - "const": "Blob" - } - } - } - ] - }, - "Capability": { - "allOf": [ - { - "$ref": "#/definitions/SubmodelElement" - }, - { - "properties": { - "modelType": { - "const": "Capability" - } - } - } - ] - }, - "ConceptDescription": { - "allOf": [ - { - "$ref": "#/definitions/Identifiable" - }, - { - "$ref": "#/definitions/HasDataSpecification" - }, - { - "properties": { - "isCaseOf": { - "type": "array", - "items": { - "$ref": "#/definitions/Reference" - }, - "minItems": 1 - }, - "modelType": { - "const": "ConceptDescription" - } - } - } - ] - }, - "DataElement": { - "$ref": "#/definitions/SubmodelElement" - }, - "DataElement_choice": { - "oneOf": [ - { - "$ref": "#/definitions/Blob" - }, - { - "$ref": "#/definitions/File" - }, - { - "$ref": "#/definitions/MultiLanguageProperty" - }, - { - "$ref": "#/definitions/Property" - }, - { - "$ref": "#/definitions/Range" - }, - { - "$ref": "#/definitions/ReferenceElement" - } - ] - }, - "DataSpecificationContent": { - "type": "object", - "properties": { - "modelType": { - "$ref": "#/definitions/ModelType" - } - }, - "required": [ - "modelType" - ] - }, - "DataSpecificationContent_choice": { - "oneOf": [ - { - "$ref": "#/definitions/DataSpecificationIec61360" - } - ] - }, - "DataSpecificationIec61360": { - "allOf": [ - { - "$ref": "#/definitions/DataSpecificationContent" - }, - { - "properties": { - "preferredName": { - "type": "array", - "items": { - "$ref": "#/definitions/LangStringPreferredNameTypeIec61360" - }, - "minItems": 1 - }, - "shortName": { - "type": "array", - "items": { - "$ref": "#/definitions/LangStringShortNameTypeIec61360" - }, - "minItems": 1 - }, - "unit": { - "type": "string", - "minLength": 1, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "unitId": { - "$ref": "#/definitions/Reference" - }, - "sourceOfDefinition": { - "type": "string", - "minLength": 1, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "symbol": { - "type": "string", - "minLength": 1, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "dataType": { - "$ref": "#/definitions/DataTypeIec61360" - }, - "definition": { - "type": "array", - "items": { - "$ref": "#/definitions/LangStringDefinitionTypeIec61360" - }, - "minItems": 1 - }, - "valueFormat": { - "type": "string", - "minLength": 1, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "valueList": { - "$ref": "#/definitions/ValueList" - }, - "value": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "levelType": { - "$ref": "#/definitions/LevelType" - }, - "modelType": { - "const": "DataSpecificationIec61360" - } - }, - "required": [ - "preferredName" - ] - } - ] - }, - "DataTypeDefXsd": { - "type": "string", - "enum": [ - "xs:anyURI", - "xs:base64Binary", - "xs:boolean", - "xs:byte", - "xs:date", - "xs:dateTime", - "xs:decimal", - "xs:double", - "xs:duration", - "xs:float", - "xs:gDay", - "xs:gMonth", - "xs:gMonthDay", - "xs:gYear", - "xs:gYearMonth", - "xs:hexBinary", - "xs:int", - "xs:integer", - "xs:long", - "xs:negativeInteger", - "xs:nonNegativeInteger", - "xs:nonPositiveInteger", - "xs:positiveInteger", - "xs:short", - "xs:string", - "xs:time", - "xs:unsignedByte", - "xs:unsignedInt", - "xs:unsignedLong", - "xs:unsignedShort" - ] - }, - "DataTypeIec61360": { - "type": "string", - "enum": [ - "BLOB", - "BOOLEAN", - "DATE", - "FILE", - "HTML", - "INTEGER_COUNT", - "INTEGER_CURRENCY", - "INTEGER_MEASURE", - "IRDI", - "IRI", - "RATIONAL", - "RATIONAL_MEASURE", - "REAL_COUNT", - "REAL_CURRENCY", - "REAL_MEASURE", - "STRING", - "STRING_TRANSLATABLE", - "TIME", - "TIMESTAMP" - ] - }, - "Direction": { - "type": "string", - "enum": [ - "input", - "output" - ] - }, - "EmbeddedDataSpecification": { - "type": "object", - "properties": { - "dataSpecification": { - "$ref": "#/definitions/Reference" - }, - "dataSpecificationContent": { - "$ref": "#/definitions/DataSpecificationContent_choice" - } - }, - "required": [ - "dataSpecification", - "dataSpecificationContent" - ] - }, - "Entity": { - "allOf": [ - { - "$ref": "#/definitions/SubmodelElement" - }, - { - "properties": { - "statements": { - "type": "array", - "items": { - "$ref": "#/definitions/SubmodelElement_choice" - }, - "minItems": 1 - }, - "entityType": { - "$ref": "#/definitions/EntityType" - }, - "globalAssetId": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "specificAssetIds": { - "type": "array", - "items": { - "$ref": "#/definitions/SpecificAssetId" - }, - "minItems": 1 - }, - "modelType": { - "const": "Entity" - } - } - } - ] - }, - "EntityType": { - "type": "string", - "enum": [ - "CoManagedEntity", - "SelfManagedEntity" - ] - }, - "Environment": { - "type": "object", - "properties": { - "assetAdministrationShells": { - "type": "array", - "items": { - "$ref": "#/definitions/AssetAdministrationShell" - }, - "minItems": 1 - }, - "submodels": { - "type": "array", - "items": { - "$ref": "#/definitions/Submodel" - }, - "minItems": 1 - }, - "conceptDescriptions": { - "type": "array", - "items": { - "$ref": "#/definitions/ConceptDescription" - }, - "minItems": 1 - } - } - }, - "EventElement": { - "$ref": "#/definitions/SubmodelElement" - }, - "EventPayload": { - "type": "object", - "properties": { - "source": { - "$ref": "#/definitions/Reference" - }, - "sourceSemanticId": { - "$ref": "#/definitions/Reference" - }, - "observableReference": { - "$ref": "#/definitions/Reference" - }, - "observableSemanticId": { - "$ref": "#/definitions/Reference" - }, - "topic": { - "type": "string", - "minLength": 1, - "maxLength": 255, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "subjectId": { - "$ref": "#/definitions/Reference" - }, - "timeStamp": { - "type": "string", - "pattern": "^-?(([1-9][0-9][0-9][0-9]+)|(0[0-9][0-9][0-9]))-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))T(((([01][0-9])|(2[0-3])):[0-5][0-9]:([0-5][0-9])(\\.[0-9]+)?)|24:00:00(\\.0+)?)(Z|\\+00:00|-00:00)$" - }, - "payload": { - "type": "string", - "contentEncoding": "base64" - } - }, - "required": [ - "source", - "observableReference", - "timeStamp" - ] - }, - "Extension": { - "allOf": [ - { - "$ref": "#/definitions/HasSemantics" - }, - { - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "valueType": { - "$ref": "#/definitions/DataTypeDefXsd" - }, - "value": { - "type": "string", - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "refersTo": { - "type": "array", - "items": { - "$ref": "#/definitions/Reference" - }, - "minItems": 1 - } - }, - "required": [ - "name" - ] - } - ] - }, - "File": { - "allOf": [ - { - "$ref": "#/definitions/DataElement" - }, - { - "properties": { - "value": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 2048 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^([a-zA-Z][a-zA-Z0-9+\\-.]*:((//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;?:@&=+$,])(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)|(//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;@&=+$,])+(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?)?(#(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?$" - } - ] - }, - "contentType": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 128 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\\x80-\\xff])|\\\\([\\t !-~]|[\\x80-\\xff]))*\"))*$" - } - ] - }, - "modelType": { - "const": "File" - } - } - } - ] - }, - "HasDataSpecification": { - "type": "object", - "properties": { - "embeddedDataSpecifications": { - "type": "array", - "items": { - "$ref": "#/definitions/EmbeddedDataSpecification" - }, - "minItems": 1 - } - } - }, - "HasExtensions": { - "type": "object", - "properties": { - "extensions": { - "type": "array", - "items": { - "$ref": "#/definitions/Extension" - }, - "minItems": 1 - } - } - }, - "HasKind": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/definitions/ModellingKind" - } - } - }, - "HasSemantics": { - "type": "object", - "properties": { - "semanticId": { - "$ref": "#/definitions/Reference" - }, - "supplementalSemanticIds": { - "type": "array", - "items": { - "$ref": "#/definitions/Reference" - }, - "minItems": 1 - } - } - }, - "Identifiable": { - "allOf": [ - { - "$ref": "#/definitions/Referable" - }, - { - "properties": { - "administration": { - "$ref": "#/definitions/AdministrativeInformation" - }, - "id": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - } - }, - "required": [ - "id" - ] - } - ] - }, - "Key": { - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/KeyTypes" - }, - "value": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - } - }, - "required": [ - "type", - "value" - ] - }, - "KeyTypes": { - "type": "string", - "enum": [ - "AnnotatedRelationshipElement", - "AssetAdministrationShell", - "BasicEventElement", - "Blob", - "Capability", - "ConceptDescription", - "DataElement", - "Entity", - "EventElement", - "File", - "FragmentReference", - "GlobalReference", - "Identifiable", - "MultiLanguageProperty", - "Operation", - "Property", - "Range", - "Referable", - "ReferenceElement", - "RelationshipElement", - "Submodel", - "SubmodelElement", - "SubmodelElementCollection", - "SubmodelElementList" - ] - }, - "LangStringDefinitionTypeIec61360": { - "allOf": [ - { - "$ref": "#/definitions/AbstractLangString" - }, - { - "properties": { - "text": { - "maxLength": 1023 - } - } - } - ] - }, - "LangStringNameType": { - "allOf": [ - { - "$ref": "#/definitions/AbstractLangString" - }, - { - "properties": { - "text": { - "maxLength": 128 - } - } - } - ] - }, - "LangStringPreferredNameTypeIec61360": { - "allOf": [ - { - "$ref": "#/definitions/AbstractLangString" - }, - { - "properties": { - "text": { - "maxLength": 255 - } - } - } - ] - }, - "LangStringShortNameTypeIec61360": { - "allOf": [ - { - "$ref": "#/definitions/AbstractLangString" - }, - { - "properties": { - "text": { - "maxLength": 18 - } - } - } - ] - }, - "LangStringTextType": { - "allOf": [ - { - "$ref": "#/definitions/AbstractLangString" - }, - { - "properties": { - "text": { - "maxLength": 1023 - } - } - } - ] - }, - "LevelType": { - "type": "object", - "properties": { - "min": { - "type": "boolean" - }, - "nom": { - "type": "boolean" - }, - "typ": { - "type": "boolean" - }, - "max": { - "type": "boolean" - } - }, - "required": [ - "min", - "nom", - "typ", - "max" - ] - }, - "ModelType": { - "type": "string", - "enum": [ - "AnnotatedRelationshipElement", - "AssetAdministrationShell", - "BasicEventElement", - "Blob", - "Capability", - "ConceptDescription", - "DataSpecificationIec61360", - "Entity", - "File", - "MultiLanguageProperty", - "Operation", - "Property", - "Range", - "ReferenceElement", - "RelationshipElement", - "Submodel", - "SubmodelElementCollection", - "SubmodelElementList" - ] - }, - "ModellingKind": { - "type": "string", - "enum": [ - "Instance", - "Template" - ] - }, - "MultiLanguageProperty": { - "allOf": [ - { - "$ref": "#/definitions/DataElement" - }, - { - "properties": { - "value": { - "type": "array", - "items": { - "$ref": "#/definitions/LangStringTextType" - }, - "minItems": 1 - }, - "valueId": { - "$ref": "#/definitions/Reference" - }, - "modelType": { - "const": "MultiLanguageProperty" - } - } - } - ] - }, - "Operation": { - "allOf": [ - { - "$ref": "#/definitions/SubmodelElement" - }, - { - "properties": { - "inputVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/OperationVariable" - }, - "minItems": 1 - }, - "outputVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/OperationVariable" - }, - "minItems": 1 - }, - "inoutputVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/OperationVariable" - }, - "minItems": 1 - }, - "modelType": { - "const": "Operation" - } - } - } - ] - }, - "OperationVariable": { - "type": "object", - "properties": { - "value": { - "$ref": "#/definitions/SubmodelElement_choice" - } - }, - "required": [ - "value" - ] - }, - "Property": { - "allOf": [ - { - "$ref": "#/definitions/DataElement" - }, - { - "properties": { - "valueType": { - "$ref": "#/definitions/DataTypeDefXsd" - }, - "value": { - "type": "string", - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "valueId": { - "$ref": "#/definitions/Reference" - }, - "modelType": { - "const": "Property" - } - }, - "required": [ - "valueType" - ] - } - ] - }, - "Qualifiable": { - "type": "object", - "properties": { - "qualifiers": { - "type": "array", - "items": { - "$ref": "#/definitions/Qualifier" - }, - "minItems": 1 - }, - "modelType": { - "$ref": "#/definitions/ModelType" - } - }, - "required": [ - "modelType" - ] - }, - "Qualifier": { - "allOf": [ - { - "$ref": "#/definitions/HasSemantics" - }, - { - "properties": { - "kind": { - "$ref": "#/definitions/QualifierKind" - }, - "type": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "valueType": { - "$ref": "#/definitions/DataTypeDefXsd" - }, - "value": { - "type": "string", - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "valueId": { - "$ref": "#/definitions/Reference" - } - }, - "required": [ - "type", - "valueType" - ] - } - ] - }, - "QualifierKind": { - "type": "string", - "enum": [ - "ConceptQualifier", - "TemplateQualifier", - "ValueQualifier" - ] - }, - "Range": { - "allOf": [ - { - "$ref": "#/definitions/DataElement" - }, - { - "properties": { - "valueType": { - "$ref": "#/definitions/DataTypeDefXsd" - }, - "min": { - "type": "string", - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "max": { - "type": "string", - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "modelType": { - "const": "Range" - } - }, - "required": [ - "valueType" - ] - } - ] - }, - "Referable": { - "allOf": [ - { - "$ref": "#/definitions/HasExtensions" - }, - { - "properties": { - "category": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "idShort": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 128 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9_]+$" - } - ] - }, - "displayName": { - "type": "array", - "items": { - "$ref": "#/definitions/LangStringNameType" - }, - "minItems": 1 - }, - "description": { - "type": "array", - "items": { - "$ref": "#/definitions/LangStringTextType" - }, - "minItems": 1 - }, - "modelType": { - "$ref": "#/definitions/ModelType" - } - }, - "required": [ - "modelType" - ] - } - ] - }, - "Reference": { - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/ReferenceTypes" - }, - "referredSemanticId": { - "$ref": "#/definitions/Reference" - }, - "keys": { - "type": "array", - "items": { - "$ref": "#/definitions/Key" - }, - "minItems": 1 - } - }, - "required": [ - "type", - "keys" - ] - }, - "ReferenceElement": { - "allOf": [ - { - "$ref": "#/definitions/DataElement" - }, - { - "properties": { - "value": { - "$ref": "#/definitions/Reference" - }, - "modelType": { - "const": "ReferenceElement" - } - } - } - ] - }, - "ReferenceTypes": { - "type": "string", - "enum": [ - "ExternalReference", - "ModelReference" - ] - }, - "RelationshipElement": { - "allOf": [ - { - "$ref": "#/definitions/RelationshipElement_abstract" - }, - { - "properties": { - "modelType": { - "const": "RelationshipElement" - } - } - } - ] - }, - "RelationshipElement_abstract": { - "allOf": [ - { - "$ref": "#/definitions/SubmodelElement" - }, - { - "properties": { - "first": { - "$ref": "#/definitions/Reference" - }, - "second": { - "$ref": "#/definitions/Reference" - } - } - } - ] - }, - "RelationshipElement_choice": { - "oneOf": [ - { - "$ref": "#/definitions/RelationshipElement" - }, - { - "$ref": "#/definitions/AnnotatedRelationshipElement" - } - ] - }, - "Resource": { - "type": "object", - "properties": { - "path": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 2048 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^([a-zA-Z][a-zA-Z0-9+\\-.]*:((//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;?:@&=+$,])(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)|(//((((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;:&=+$,])*@)?((([a-zA-Z0-9]|[a-zA-Z0-9]([a-zA-Z0-9]|-)*[a-zA-Z0-9])\\.)*([a-zA-Z]|[a-zA-Z]([a-zA-Z0-9]|-)*[a-zA-Z0-9])(\\.)?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]*)?)?|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[$,;:@&=+])+)(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?|/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*|(([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[;@&=+$,])+(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*(/((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*(;((([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])|[:@&=+$,]))*)*)*)?)(\\?(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?)?(#(([;/?:@&=+$,]|([a-zA-Z0-9]|[-_.!~*'()])|%([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])([0-9]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF]|[aA]|[bB]|[cC]|[dD]|[eE]|[fF])))*)?$" - } - ] - }, - "contentType": { - "type": "string", - "allOf": [ - { - "minLength": 1, - "maxLength": 128 - }, - { - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - { - "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\\x80-\\xff])|\\\\([\\t !-~]|[\\x80-\\xff]))*\"))*$" - } - ] - } - }, - "required": [ - "path" - ] - }, - "SpecificAssetId": { - "allOf": [ - { - "$ref": "#/definitions/HasSemantics" - }, - { - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "value": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "externalSubjectId": { - "$ref": "#/definitions/Reference" - } - }, - "required": [ - "name", - "value" - ] - } - ] - }, - "StateOfEvent": { - "type": "string", - "enum": [ - "off", - "on" - ] - }, - "Submodel": { - "allOf": [ - { - "$ref": "#/definitions/Identifiable" - }, - { - "$ref": "#/definitions/HasKind" - }, - { - "$ref": "#/definitions/HasSemantics" - }, - { - "$ref": "#/definitions/Qualifiable" - }, - { - "$ref": "#/definitions/HasDataSpecification" - }, - { - "properties": { - "submodelElements": { - "type": "array", - "items": { - "$ref": "#/definitions/SubmodelElement_choice" - }, - "minItems": 1 - }, - "modelType": { - "const": "Submodel" - } - } - } - ] - }, - "SubmodelElement": { - "allOf": [ - { - "$ref": "#/definitions/Referable" - }, - { - "$ref": "#/definitions/HasSemantics" - }, - { - "$ref": "#/definitions/Qualifiable" - }, - { - "$ref": "#/definitions/HasDataSpecification" - } - ] - }, - "SubmodelElementCollection": { - "allOf": [ - { - "$ref": "#/definitions/SubmodelElement" - }, - { - "properties": { - "value": { - "type": "array", - "items": { - "$ref": "#/definitions/SubmodelElement_choice" - }, - "minItems": 1 - }, - "modelType": { - "const": "SubmodelElementCollection" - } - } - } - ] - }, - "SubmodelElementList": { - "allOf": [ - { - "$ref": "#/definitions/SubmodelElement" - }, - { - "properties": { - "orderRelevant": { - "type": "boolean" - }, - "semanticIdListElement": { - "$ref": "#/definitions/Reference" - }, - "typeValueListElement": { - "$ref": "#/definitions/AasSubmodelElements" - }, - "valueTypeListElement": { - "$ref": "#/definitions/DataTypeDefXsd" - }, - "value": { - "type": "array", - "items": { - "$ref": "#/definitions/SubmodelElement_choice" - }, - "minItems": 1 - }, - "modelType": { - "const": "SubmodelElementList" - } - }, - "required": [ - "typeValueListElement" - ] - } - ] - }, - "SubmodelElement_choice": { - "oneOf": [ - { - "$ref": "#/definitions/RelationshipElement" - }, - { - "$ref": "#/definitions/AnnotatedRelationshipElement" - }, - { - "$ref": "#/definitions/BasicEventElement" - }, - { - "$ref": "#/definitions/Blob" - }, - { - "$ref": "#/definitions/Capability" - }, - { - "$ref": "#/definitions/Entity" - }, - { - "$ref": "#/definitions/File" - }, - { - "$ref": "#/definitions/MultiLanguageProperty" - }, - { - "$ref": "#/definitions/Operation" - }, - { - "$ref": "#/definitions/Property" - }, - { - "$ref": "#/definitions/Range" - }, - { - "$ref": "#/definitions/ReferenceElement" - }, - { - "$ref": "#/definitions/SubmodelElementCollection" - }, - { - "$ref": "#/definitions/SubmodelElementList" - } - ] - }, - "ValueList": { - "type": "object", - "properties": { - "valueReferencePairs": { - "type": "array", - "items": { - "$ref": "#/definitions/ValueReferencePair" - }, - "minItems": 1 - } - }, - "required": [ - "valueReferencePairs" - ] - }, - "ValueReferencePair": { - "type": "object", - "properties": { - "value": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "pattern": "^([\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" - }, - "valueId": { - "$ref": "#/definitions/Reference" - } - }, - "required": [ - "value" - ] - } - } -} \ No newline at end of file diff --git a/basyx.aasregistry/.vscode/settings.json b/basyx.aasregistry/.vscode/settings.json deleted file mode 100644 index 8274afea7..000000000 --- a/basyx.aasregistry/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.compile.nullAnalysis.mode": "disabled" -} \ No newline at end of file diff --git a/ci/.env b/ci/.env index c963c225a..d9c1fcd78 100644 --- a/ci/.env +++ b/ci/.env @@ -1,3 +1,17 @@ -BASYX_VERSION=2.0.0-SNAPSHOT -AAS_WEBUI_VERSION=v2-240125 -KEYCLOAK_VERSION=24.0.4 +BASYX_VERSION=2.0.0-SNAPSHOT +AAS_WEBUI_VERSION=v2-240125 +KEYCLOAK_VERSION=24.0.4 + +START_LOCAL_VERSION=0.9.0 +ES_LOCAL_VERSION=9.0.1 +ES_LOCAL_CONTAINER_NAME=es-local-dev +ES_LOCAL_PASSWORD=vtzJFt1b +ES_LOCAL_PORT=9200 +ES_LOCAL_URL=http://localhost:${ES_LOCAL_PORT} +ES_LOCAL_HEAP_INIT=128m +ES_LOCAL_HEAP_MAX=2g +ES_LOCAL_DISK_SPACE_REQUIRED=1gb +KIBANA_LOCAL_CONTAINER_NAME=kibana-local-dev +KIBANA_LOCAL_PORT=5601 +KIBANA_LOCAL_PASSWORD=088WlnyQ +KIBANA_ENCRYPTION_KEY=HX1DOnbuaJmSQYOspJMnnMtVl3hRITx2 diff --git a/ql.schema.json b/ql.schema.json deleted file mode 100644 index ce5fc1753..000000000 --- a/ql.schema.json +++ /dev/null @@ -1,849 +0,0 @@ -{ - "$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 From 2f43b9ffbab750ad699bfc11c39c8f282649cfbd Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 14:40:18 +0200 Subject: [PATCH 37/52] Adapts output json to new converter format --- .../input/ends-with_field_strval.json | 2 +- .../expected_and_eq_eq_lt_field_strval.json | 2 +- .../output/expected_eq_field_field.json | 25 +------------------ .../output/expected_le_field_field.json | 2 +- .../output/expected_le_field_numVal.json | 2 +- .../expected_match_eq_eq_field_strval.json | 2 +- .../expected_match_specific_asset_ids.json | 2 +- 7 files changed, 7 insertions(+), 30 deletions(-) diff --git a/basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json index 69aa92479..9a91d92af 100644 --- a/basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json +++ b/basyx.common/basyx.querycore/src/test/resources/input/ends-with_field_strval.json @@ -1,6 +1,6 @@ { "$condition": { - "$starts-with": [ + "$ends-with": [ { "$field": "$aas#assetInformation.assetKind" }, { "$strVal": "NST" } ] diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json index 3e79c3744..bc31c4133 100644 --- a/basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_and_eq_eq_lt_field_strval.json @@ -1 +1 @@ -{"bool":{"must":[{"bool":{"must":[{"term":{"idShort.keyword":{"value":"TechnicalData"}}},{"term":{"value.keyword":{"value":"27-37-09-05"}}}]}},{"bool":{"must":[{"term":{"idShort.keyword":{"value":"TechnicalData"}}},{"term":{"semanticId":{"value":"0173-1#02-BAF016#006"}}},{"range":{"value.keyword":{"lt":100.0}}}]}}]}} \ No newline at end of file +{"bool":{"must":[{"bool":{"must":[{"term":{"idShort.keyword":{"value":"TechnicalData"}}},{"query_string":{"fields":["*submodelElements.ProductClassifications[*].ProductClassId.value.keyword"],"query":"27\\-37\\-09\\-05"}}]}},{"bool":{"must":[{"term":{"idShort.keyword":{"value":"TechnicalData"}}},{"query_string":{"fields":["*semanticId.keyword"],"query":"0173\\-1#02\\-BAF016#006"}},{"query_string":{"query":"*value.keyword:(<100)"}}]}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json index 38fc5abf2..7a64a2384 100644 --- a/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_eq_field_field.json @@ -1,24 +1 @@ -{ - "bool": { - "must": [ - { - "exists": { - "field": "displayName.language" - } - }, - { - "exists": { - "field": "description.language" - } - }, - { - "script": { - "script": { - "source": "doc['displayName.language.keyword'].value == doc['description.language.keyword'].value", - "lang": "painless" - } - } - } - ] - } -} \ No newline at end of file +{"bool":{"must":[{"exists":{"field":"displayName[].language.keyword"}},{"exists":{"field":"description.language.keyword"}},{"script":{"script":{"source":"doc['displayName[].language.keyword'].value == doc['description.language.keyword'].value","lang":"painless"}}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json index 05391a62f..9a31b1c67 100644 --- a/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_field.json @@ -1 +1 @@ -{"bool":{"must":[{"exists":{"field":"administration.revision"}},{"exists":{"field":"administration.version"}},{"script":{"script":{"source":"doc['administration.revision.keyword'].value <= doc['administration.version.keyword'].value","lang":"painless"}}}]}} \ No newline at end of file +{"bool":{"must":[{"exists":{"field":"administration.revision.keyword"}},{"exists":{"field":"administration.version.keyword"}},{"script":{"script":{"source":"doc['administration.revision.keyword'].value <= doc['administration.version.keyword'].value","lang":"painless"}}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json index 1b8a2be8b..928e0ef42 100644 --- a/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_le_field_numVal.json @@ -1 +1 @@ -{"range":{"administration.revision":{"lte":2.0}}} \ No newline at end of file +{"range":{"administration.revision":{"lte":2}}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json index 1fc680409..789e788a8 100644 --- a/basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_eq_eq_field_strval.json @@ -1 +1 @@ -{"bool":{"must":[{"term":{"value.keyword":{"value":"03-01"}}},{"term":{"language":{"value":"nl"}}}]}} \ No newline at end of file +{"bool":{"must":[{"query_string":{"fields":["*submodelElements.Documents[*].DocumentClassification.Class.value.keyword"],"query":"03\\-01"}},{"query_string":{"fields":["*submodelElements.Documents[*].DocumentVersion.SMLLanguages[*].language.keyword"],"query":"nl"}}]}} \ No newline at end of file diff --git a/basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json index 579a50574..dbae8e508 100644 --- a/basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json +++ b/basyx.common/basyx.querycore/src/test/resources/output/expected_match_specific_asset_ids.json @@ -1 +1 @@ -{"bool":{"minimum_should_match":"1","should":[{"bool":{"must":[{"term":{"assetInformation.specificAssetIds.name.keyword":{"value":"supplierId"}}},{"term":{"assetInformation.specificAssetIds.value.keyword":{"value":"aas-1"}}}]}},{"bool":{"must":[{"term":{"assetInformation.specificAssetIds.name.keyword":{"value":"customerId"}}},{"term":{"assetInformation.specificAssetIds.value.keyword":{"value":"aas-2"}}}]}}]}} \ No newline at end of file +{"bool":{"minimum_should_match":"1","should":[{"bool":{"must":[{"query_string":{"fields":["*assetInformation.specificAssetIds[*].name.keyword"],"query":"supplierId"}},{"query_string":{"fields":["*assetInformation.specificAssetIds[*].value.keyword"],"query":"aas\\-1"}}]}},{"bool":{"must":[{"query_string":{"fields":["*assetInformation.specificAssetIds[*].name.keyword"],"query":"customerId"}},{"query_string":{"fields":["*assetInformation.specificAssetIds[*].value.keyword"],"query":"aas\\-2"}}]}}]}} \ No newline at end of file From f96d7c46c71a460f9706dd2e7c5f43a3ab8c1652 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 14:48:21 +0200 Subject: [PATCH 38/52] Adapts application.properties - Adapts RestControllers of Registries --- .../search/SearchAasRegistryApiHTTPController.java | 2 ++ .../src/main/resources/application.properties | 12 +++++------- .../SearchSubmodelRegistryApiHTTPController.java | 2 ++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index 07edeac08..1a605e04c 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; @@ -46,6 +47,7 @@ @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @RestController +@ConditionalOnProperty(name = {SearchAasRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "true", matchIfMissing = false) public class SearchAasRegistryApiHTTPController implements SearchAasRegistryHTTPApi { private static final Logger log = LoggerFactory.getLogger(SearchAasRegistryApiHTTPController.class); diff --git a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties index d57e19532..d7df7a340 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties +++ b/basyx.aasrepository/basyx.aasrepository.component/src/main/resources/application.properties @@ -6,8 +6,6 @@ basyx.aasrepo.name=aas-repo basyx.backend = InMemory - - #basyx.backend = MongoDB #spring.data.mongodb.host=127.0.0.1 #spring.data.mongodb.port=27017 @@ -65,11 +63,11 @@ basyx.backend = InMemory #################################################################################### # Feature: Search #################################################################################### -basyx.aasrepository.feature.search.enabled=true -basyx.aasrepository.feature.search.indexname=aas-index-test -spring.elasticsearch.uris=http://localhost:9200 -spring.elasticsearch.username=elastic -spring.elasticsearch.password=vtzJFt1b +# basyx.aasrepository.feature.search.enabled=true +# basyx.aasrepository.feature.search.indexname=aas-index-test +# spring.elasticsearch.uris=http://localhost:9200 +# spring.elasticsearch.username=elastic +# spring.elasticsearch.password=vtzJFt1b #################################################################################### # Disable the Swagger UI diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java index 1d591dd54..ff230e0b4 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; @@ -47,6 +48,7 @@ @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @RestController +@ConditionalOnProperty(name = {SearchSubmodelRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "true", matchIfMissing = false) public class SearchSubmodelRegistryApiHTTPController implements SearchSubmodelRegistryHTTPApi { private static final Logger log = LoggerFactory.getLogger(SearchSubmodelRegistryApiHTTPController.class); From a0381105d6337fe03d4002376f1bd01144032c99 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 15:04:59 +0200 Subject: [PATCH 39/52] Adapts Autoconfiguration --- .../search/DisableSearchAasRegistryConfiguration.java | 10 +++++++++- .../DisableSearchAasRepositoryConfiguration.java | 6 +++++- .../DisableSearchSubmodelRegistryConfiguration.java | 10 +++++++++- .../DisableSearchSubmodelRepositoryConfiguration.java | 6 +++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java index 7b10d07df..ab452d518 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java @@ -25,12 +25,16 @@ package org.eclipse.digitaltwin.basyx.aasregistry.feature.search; +import org.springframework.boot.actuate.autoconfigure.data.elasticsearch.ElasticsearchReactiveHealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.export.elastic.ElasticMetricsExportAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration; import org.springframework.context.annotation.Configuration; /** @@ -43,7 +47,11 @@ ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, - ElasticsearchRestClientAutoConfiguration.class + ElasticsearchRestClientAutoConfiguration.class, + ElasticsearchReactiveHealthContributorAutoConfiguration.class, + ElasticMetricsExportAutoConfiguration.class, + ReactiveElasticsearchClientAutoConfiguration.class, + ReactiveElasticsearchRepositoriesAutoConfiguration.class }) public class DisableSearchAasRegistryConfiguration { } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java index 0725a4514..a87101aaa 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java @@ -30,8 +30,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.context.annotation.Configuration; @@ -46,7 +48,9 @@ ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, - ElasticsearchRestClientAutoConfiguration.class + ElasticsearchRestClientAutoConfiguration.class, + ReactiveElasticsearchClientAutoConfiguration.class, + ReactiveElasticsearchRepositoriesAutoConfiguration.class }) public class DisableSearchAasRepositoryConfiguration { } diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java index 4b4deda15..210994c9d 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java @@ -25,12 +25,16 @@ package org.eclipse.digitaltwin.basyx.submodelregistry.feature.search; +import org.springframework.boot.actuate.autoconfigure.data.elasticsearch.ElasticsearchReactiveHealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.export.elastic.ElasticMetricsExportAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration; import org.springframework.context.annotation.Configuration; /** @@ -43,7 +47,11 @@ ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, - ElasticsearchRestClientAutoConfiguration.class + ElasticsearchRestClientAutoConfiguration.class, + ElasticsearchReactiveHealthContributorAutoConfiguration.class, + ElasticMetricsExportAutoConfiguration.class, + ReactiveElasticsearchClientAutoConfiguration.class, + ReactiveElasticsearchRepositoriesAutoConfiguration.class }) public class DisableSearchSubmodelRegistryConfiguration { } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java index 38999cd32..80974f85f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java @@ -29,8 +29,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration; import org.springframework.context.annotation.Configuration; /** @@ -43,7 +45,9 @@ ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, - ElasticsearchRestClientAutoConfiguration.class + ElasticsearchRestClientAutoConfiguration.class, + ReactiveElasticsearchClientAutoConfiguration.class, + ReactiveElasticsearchRepositoriesAutoConfiguration.class }) public class DisableSearchSubmodelRepositoryConfiguration { } From 41d1f56c5fadeddb072fd3ea2bce45f17fa329e8 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 15:15:16 +0200 Subject: [PATCH 40/52] Adds missing ConditionalOnExpression --- .../feature/search/SearchAasRegistryConfigurationGuard.java | 2 ++ .../search/SearchSubmodelRegistryConfigurationGuard.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java index bf450b07d..c1d8c64dc 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryConfigurationGuard.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; /** @@ -37,6 +38,7 @@ * @author jannisjung, aaronzi */ @Component +@ConditionalOnExpression("#{${" + SearchAasRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") public class SearchAasRegistryConfigurationGuard implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(SearchAasRegistryConfigurationGuard.class); diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java index 87a18dfd9..1dc73edbc 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryConfigurationGuard.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; /** @@ -37,6 +38,7 @@ * @author jannisjung, aaronzi */ @Component +@ConditionalOnExpression("#{${" + SearchSubmodelRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") public class SearchSubmodelRegistryConfigurationGuard implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(SearchSubmodelRegistryConfigurationGuard.class); From 33d6e82f8b0af0198f91baa9f8bcbf11d18a8de0 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 15:30:15 +0200 Subject: [PATCH 41/52] Adapts --- .../feature/search/SearchAasRegistryApiHTTPController.java | 1 - .../search/DisableSearchAasRepositoryConfiguration.java | 3 --- .../search/SearchSubmodelRegistryApiHTTPController.java | 1 - 3 files changed, 5 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index 1a605e04c..8b9fb5c64 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -47,7 +47,6 @@ @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @RestController -@ConditionalOnProperty(name = {SearchAasRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "true", matchIfMissing = false) public class SearchAasRegistryApiHTTPController implements SearchAasRegistryHTTPApi { private static final Logger log = LoggerFactory.getLogger(SearchAasRegistryApiHTTPController.class); diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java index a87101aaa..2c8c7d9d4 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java @@ -26,7 +26,6 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; @@ -34,8 +33,6 @@ import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.context.annotation.Configuration; /** diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java index ff230e0b4..d89f8eff4 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java @@ -48,7 +48,6 @@ @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") @RestController -@ConditionalOnProperty(name = {SearchSubmodelRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "true", matchIfMissing = false) public class SearchSubmodelRegistryApiHTTPController implements SearchSubmodelRegistryHTTPApi { private static final Logger log = LoggerFactory.getLogger(SearchSubmodelRegistryApiHTTPController.class); From 6ef9ba7ff657f1679f6bfd791f243781aa4807d0 Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 15:31:57 +0200 Subject: [PATCH 42/52] Adapts --- .../basyx.aasrepository-feature-search/pom.xml | 4 ++++ .../search/DisableSearchAasRepositoryConfiguration.java | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml index 6c397aaa5..38a606cbd 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml @@ -85,6 +85,10 @@ elasticsearch-java 9.0.1 + + org.springframework.boot + spring-boot-actuator-autoconfigure + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java index 2c8c7d9d4..195d05d4d 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java @@ -25,6 +25,7 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; +import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchRestHealthContributorAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; @@ -47,7 +48,8 @@ ElasticsearchDataAutoConfiguration.class, ElasticsearchRestClientAutoConfiguration.class, ReactiveElasticsearchClientAutoConfiguration.class, - ReactiveElasticsearchRepositoriesAutoConfiguration.class + ReactiveElasticsearchRepositoriesAutoConfiguration.class, + ElasticsearchRestHealthContributorAutoConfiguration.class }) public class DisableSearchAasRepositoryConfiguration { } From e4a7271e660945aa235199e28e7311004c3d364a Mon Sep 17 00:00:00 2001 From: fried Date: Tue, 19 Aug 2025 15:39:34 +0200 Subject: [PATCH 43/52] Adapts --- .../feature/search/SearchAasRegistryApiHTTPController.java | 2 ++ .../feature/search/SearchSubmodelRegistryApiHTTPController.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java index 8b9fb5c64..047cec5c3 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/SearchAasRegistryApiHTTPController.java @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -46,6 +47,7 @@ import java.util.List; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") +@ConditionalOnExpression("#{${" + SearchAasRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") @RestController public class SearchAasRegistryApiHTTPController implements SearchAasRegistryHTTPApi { diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java index d89f8eff4..0c038ab05 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/SearchSubmodelRegistryApiHTTPController.java @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -47,6 +48,7 @@ import java.util.List; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2025-06-18T09:42:17.580283867Z[GMT]") +@ConditionalOnExpression("#{${" + SearchSubmodelRegistryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") @RestController public class SearchSubmodelRegistryApiHTTPController implements SearchSubmodelRegistryHTTPApi { From e937f63de5522d20f8760925c3831bd0f2c4279f Mon Sep 17 00:00:00 2001 From: fried Date: Wed, 20 Aug 2025 15:12:06 +0200 Subject: [PATCH 44/52] Fixes some issues with spring --- ...DisableSearchAasRegistryConfiguration.java | 6 +- ...DummyAuthorizedAasRepositoryComponent.java | 90 ++++++++-------- .../src/test/resources/application.properties | 82 +++++++------- ...sableSearchAasRepositoryConfiguration.java | 6 +- .../SearchAasRepositoryConfiguration.java | 27 ++++- .../search/SearchAasRepositoryFeature.java | 31 +++--- .../search/TestSearchAasRepository.java | 1 + .../http/AasRepoConfiguration.java | 100 +++++++++--------- ...isableSearchCdRepositoryConfiguration.java | 6 +- ...leSearchSubmodelRegistryConfiguration.java | 4 +- ...SearchSubmodelRepositoryConfiguration.java | 4 +- 11 files changed, 193 insertions(+), 164 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java index ab452d518..c93c5f6ba 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/DisableSearchAasRegistryConfiguration.java @@ -28,7 +28,7 @@ import org.springframework.boot.actuate.autoconfigure.data.elasticsearch.ElasticsearchReactiveHealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.elastic.ElasticMetricsExportAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; @@ -39,10 +39,10 @@ /** * Configuration to prevent Elasticsearch from being injected by Spring - * @author fried + * */ @Configuration -@ConditionalOnProperty(name = {SearchAasRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@ConditionalOnExpression("!(${" + SearchAasRegistryFeature.FEATURENAME + ".enabled:false} || ${basyx.feature.search.enabled:false})") @EnableAutoConfiguration(exclude = { ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, diff --git a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java index 83f015920..3f03b3b8d 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java @@ -1,44 +1,46 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasrepository.feature.authorization; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * Spring application configured for tests. - * - * @author danish - * - */ - -@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") -public class DummyAuthorizedAasRepositoryComponent { - - public static void main(String[] args) { - SpringApplication.run(DummyAuthorizedAasRepositoryComponent.class, args); - } -} +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.authorization; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchRestHealthContributorAutoConfiguration; + +/** + * Spring application configured for tests. + * + * @author danish + * + */ + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx", + exclude = {ElasticsearchRestHealthContributorAutoConfiguration.class}) +public class DummyAuthorizedAasRepositoryComponent { + + public static void main(String[] args) { + SpringApplication.run(DummyAuthorizedAasRepositoryComponent.class, args); + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties index ebd881a6f..e1ec7379c 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties +++ b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties @@ -1,42 +1,42 @@ -server.port=8081 -server.error.path=/error - -spring.application.name=AAS Repository -basyx.aasrepo.name=aas-repo - -basyx.backend = InMemory - -#basyx.aasrepository.feature.registryintegration=http://host.docker.internal:8050/api/v3.0 -#basyx.externalurl=http://localhost:8081 - -#basyx.backend = MongoDB -#spring.data.mongodb.host=127.0.0.1 -#spring.data.mongodb.port=27017 -#spring.data.mongodb.database=aas -#spring.data.mongodb.authentication-database=admin -#spring.data.mongodb.username=mongoAdmin -#spring.data.mongodb.password=mongoPassword - - -# basyx.aasrepository.feature.mqtt.enabled = true -# mqtt.clientId=TestClient -# mqtt.hostname = localhost -# mqtt.port = 1883 - -# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 - -#################################################################################### -# Authorization -#################################################################################### -basyx.feature.authorization.enabled = true -basyx.feature.authorization.type = rbac -basyx.feature.authorization.jwtBearerTokenProvider = keycloak -basyx.feature.authorization.rbac.file = classpath:rbac_rules.json -spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9096/realms/BaSyx - -#################################################################################### -# Operation Delegation -#################################################################################### -# This feature is enabled by default - +server.port=8081 +server.error.path=/error + +spring.application.name=AAS Repository +basyx.aasrepo.name=aas-repo + +basyx.backend = InMemory + +#basyx.aasrepository.feature.registryintegration=http://host.docker.internal:8050/api/v3.0 +#basyx.externalurl=http://localhost:8081 + +#basyx.backend = MongoDB +#spring.data.mongodb.host=127.0.0.1 +#spring.data.mongodb.port=27017 +#spring.data.mongodb.database=aas +#spring.data.mongodb.authentication-database=admin +#spring.data.mongodb.username=mongoAdmin +#spring.data.mongodb.password=mongoPassword + + +# basyx.aasrepository.feature.mqtt.enabled = true +# mqtt.clientId=TestClient +# mqtt.hostname = localhost +# mqtt.port = 1883 + +# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 + +#################################################################################### +# Authorization +#################################################################################### +basyx.feature.authorization.enabled = true +basyx.feature.authorization.type = rbac +basyx.feature.authorization.jwtBearerTokenProvider = keycloak +basyx.feature.authorization.rbac.file = classpath:rbac_rules.json +spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9096/realms/BaSyx + +#################################################################################### +# Operation Delegation +#################################################################################### +# This feature is enabled by default + #basyx.submodelrepository.feature.operation.delegation.enabled = false \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java index 195d05d4d..4946ee10c 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/DisableSearchAasRepositoryConfiguration.java @@ -27,7 +27,7 @@ import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchRestHealthContributorAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; @@ -38,10 +38,10 @@ /** * Configuration to prevent Elasticsearch from being injected by Spring - * @author fried + * */ @Configuration -@ConditionalOnProperty(name = {SearchAasRepositoryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@ConditionalOnExpression("!(${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} || ${basyx.feature.search.enabled:false})") @EnableAutoConfiguration(exclude = { ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java index f6f31d23e..c2022b7a9 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryConfiguration.java @@ -6,8 +6,7 @@ * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: + * permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. @@ -25,11 +24,31 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; -@ConditionalOnExpression("#{${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +@ConditionalOnProperty(name = SearchAasRepositoryFeature.FEATURENAME + ".enabled", havingValue = "true", matchIfMissing = false) @Configuration public class SearchAasRepositoryConfiguration { } + +/** + * Configuration to provide a safe Elasticsearch health indicator when search feature is not enabled + */ +@Configuration +@ConditionalOnProperty(name = SearchAasRepositoryFeature.FEATURENAME + ".enabled", havingValue = "false", matchIfMissing = true) +class SafeElasticsearchHealthConfiguration { + + @Bean(name = "elasticsearchHealthIndicator") + @Primary + public HealthIndicator elasticsearchHealthIndicator() { + return () -> Health.up() + .withDetail("elasticsearch", "disabled - search feature not enabled") + .build(); + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java index 479ecc0c1..59e8b0daa 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/SearchAasRepositoryFeature.java @@ -25,17 +25,23 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.search; -import co.elastic.clients.elasticsearch.ElasticsearchClient; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; import org.eclipse.digitaltwin.basyx.aasrepository.feature.AasRepositoryFeature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; -@ConditionalOnExpression("#{${" + SearchAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.search.enabled:false}}") +import co.elastic.clients.elasticsearch.ElasticsearchClient; + +/** + * Feature for AAS Repository search functionality + * + */ @Component +@ConditionalOnProperty(name = SearchAasRepositoryFeature.FEATURENAME + ".enabled", havingValue = "true", matchIfMissing = false) public class SearchAasRepositoryFeature implements AasRepositoryFeature { + public static final String FEATURENAME = "basyx.aasrepository.feature.search"; public static final String DEFAULT_INDEX = "aas-index"; private final ElasticsearchClient esclient; @@ -50,28 +56,29 @@ public class SearchAasRepositoryFeature implements AasRepositoryFeature { public SearchAasRepositoryFeature(ElasticsearchClient client) { this.esclient = client; } - + @Override - public AasRepositoryFactory decorate(AasRepositoryFactory aasServiceFactory) { - return new SearchAasRepositoryFactory(aasServiceFactory, esclient, indexName); + public AasRepositoryFactory decorate(AasRepositoryFactory aasRepositoryFactory) { + return new SearchAasRepositoryFactory(aasRepositoryFactory, esclient, indexName); } @Override - public void initialize() { + public String getName() { + return "AAS Repository Search"; } @Override - public void cleanUp() { - + public void initialize() { + // No initialization needed } @Override - public String getName() { - return "AasRepository Search"; + public void cleanUp() { + // No cleanup needed } @Override public boolean isEnabled() { - return enabled; + return true; } } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java index 4a6fe1599..e32847918 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java @@ -57,6 +57,7 @@ public static void startSmRepo() throws Exception { searchBackend = appContext.getBean(AasRepository.class); searchAPI = appContext.getBean(SearchAasRepositoryApiHTTPController.class); preloadShells(); + Thread.sleep(2000); } @Test diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java index 6df13ea48..ba2134d53 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java @@ -1,50 +1,50 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.aasrepository.http; - -import org.eclipse.digitaltwin.basyx.http.CorsPathPatternProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * - * @author schnicke - * - */ -@Configuration -public class AasRepoConfiguration { - - @Bean - public CorsPathPatternProvider getAasRepoCorsUrlProvider() { - return new CorsPathPatternProvider("/shells/**"); - } - - @Bean - public CorsPathPatternProvider getAasRepoDescriptionCorsUrlProvider() { - return new CorsPathPatternProvider("/description"); - } -} +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.aasrepository.http; + +import org.eclipse.digitaltwin.basyx.http.CorsPathPatternProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + * @author schnicke + * + */ +@Configuration +public class AasRepoConfiguration { + + @Bean + public CorsPathPatternProvider getAasRepoCorsUrlProvider() { + return new CorsPathPatternProvider("/shells/**"); + } + + @Bean + public CorsPathPatternProvider getAasRepoDescriptionCorsUrlProvider() { + return new CorsPathPatternProvider("/description"); + } +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java index f2f1fa4a2..b43a38dde 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescription/feature/search/DisableSearchCdRepositoryConfiguration.java @@ -26,7 +26,7 @@ package org.eclipse.digitaltwin.basyx.conceptdescription.feature.search; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; @@ -35,10 +35,10 @@ /** * Configuration to prevent Elasticsearch from being injected by Spring - * @author fried + * author fried */ @Configuration -@ConditionalOnProperty(name = {SearchCdRepositoryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@ConditionalOnExpression("!(${" + SearchCdRepositoryFeature.FEATURENAME + ".enabled:false} || ${basyx.feature.search.enabled:false})") @EnableAutoConfiguration(exclude = { ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java index 210994c9d..e6853d5e1 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/DisableSearchSubmodelRegistryConfiguration.java @@ -28,7 +28,7 @@ import org.springframework.boot.actuate.autoconfigure.data.elasticsearch.ElasticsearchReactiveHealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.elastic.ElasticMetricsExportAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; @@ -42,7 +42,7 @@ * @author fried */ @Configuration -@ConditionalOnProperty(name = {SearchSubmodelRegistryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@ConditionalOnExpression("!(${" + SearchSubmodelRegistryFeature.FEATURENAME + ".enabled:false} || ${basyx.feature.search.enabled:false})") @EnableAutoConfiguration(exclude = { ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java index 80974f85f..13d9ab025 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/DisableSearchSubmodelRepositoryConfiguration.java @@ -26,7 +26,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.search; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration; @@ -40,7 +40,7 @@ * @author fried */ @Configuration -@ConditionalOnProperty(name = {SearchSubmodelRepositoryFeature.FEATURENAME + ".enabled", "basyx.feature.search.enabled"}, havingValue = "false", matchIfMissing = true) +@ConditionalOnExpression("!(${" + SearchSubmodelRepositoryFeature.FEATURENAME + ".enabled:false} || ${basyx.feature.search.enabled:false})") @EnableAutoConfiguration(exclude = { ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, From 498b65b51b76932ac6f7d5a56542f7e7cd4e7f04 Mon Sep 17 00:00:00 2001 From: fried Date: Wed, 20 Aug 2025 15:25:11 +0200 Subject: [PATCH 45/52] Adds Timeout (TODO) --- .../basyx/aasregistry/feature/search/TestSearchAasRegistry.java | 2 +- .../feature/search/TestSearchCdRepository.java | 2 +- .../feature/search/TestSearchSubmodelRegistry.java | 1 + .../feature/search/TestSearchSubmodelRepository.java | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java index 28d75e3dc..4e59f99bc 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java @@ -73,7 +73,7 @@ public static void setUp() throws IOException, DeserializationException, Interru storage = appContext.getBean(SearchAasRegistryStorage.class); searchAPI = appContext.getBean(SearchAasRegistryApiHTTPController.class); preloadAasdf(); - Thread.sleep(500); + Thread.sleep(2000); } @AfterClass diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java index b784f4557..38040d390 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java @@ -107,7 +107,7 @@ private static void resetRepo() { } private static void waitForData() throws InterruptedException { - Thread.sleep(500); + Thread.sleep(2000); } } diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 6c10eb934..94fb4a966 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -74,6 +74,7 @@ public static void setUp() throws IOException, DeserializationException { storage = appContext.getBean(SearchSubmodelRegistryStorage.class); searchAPI = appContext.getBean(SearchSubmodelRegistryApiHTTPController.class); preloadSmds(); + Thread.sleep(2000); } @AfterClass diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java index a675e7072..5e9403a0d 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java @@ -58,6 +58,7 @@ public static void startSmRepo() throws Exception { searchBackend = appContext.getBean(SubmodelRepository.class); searchAPI = appContext.getBean(SearchSubmodelRepositoryApiHTTPController.class); preloadSubmodels(); + Thread.sleep(2000); } @Test From 5aafcab9f56411beeef32e3dade085e80e24593a Mon Sep 17 00:00:00 2001 From: fried Date: Wed, 20 Aug 2025 15:27:06 +0200 Subject: [PATCH 46/52] Adds missing line --- .../feature/search/TestSearchSubmodelRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 94fb4a966..3f5952b9e 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -69,7 +69,7 @@ public class TestSearchSubmodelRegistry { @BeforeClass - public static void setUp() throws IOException, DeserializationException { + public static void setUp() throws IOException, DeserializationException, InterruptedException { appContext = new SpringApplication(DummySearchSubmodelRegistryComponent.class).run(new String[] {}); storage = appContext.getBean(SearchSubmodelRegistryStorage.class); searchAPI = appContext.getBean(SearchSubmodelRegistryApiHTTPController.class); From a2118f3a265677aaf9a2e320d06aa5c33a4e6534 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 21 Aug 2025 08:42:34 +0200 Subject: [PATCH 47/52] Removes hard-coded thread.sleep and adds awaitility --- .../basyx.aasregistry-feature-search/pom.xml | 6 ++++++ .../feature/search/TestSearchAasRegistry.java | 8 ++++++-- .../basyx.aasrepository-feature-search/pom.xml | 6 ++++++ .../feature/search/TestSearchAasRepository.java | 8 ++++++-- .../pom.xml | 6 ++++++ .../feature/search/TestSearchCdRepository.java | 10 +++++----- .../basyx.submodelregistry-feature-search/pom.xml | 6 ++++++ .../feature/search/TestSearchSubmodelRegistry.java | 7 +++++-- .../basyx.submodelrepository-feature-search/pom.xml | 6 ++++++ .../feature/search/TestSearchSubmodelRepository.java | 7 ++++++- 10 files changed, 58 insertions(+), 12 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml b/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml index 86de9b94d..da4bae322 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/pom.xml @@ -54,6 +54,12 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-service-mongodb-storage + + org.awaitility + awaitility + 4.3.0 + test + \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java index 4e59f99bc..07fd49c9b 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java @@ -53,11 +53,13 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import static org.awaitility.Awaitility.await; +import static java.util.concurrent.TimeUnit.SECONDS; /** * Tests for {@link org.eclipse.digitaltwin.basyx.aasregistry.feature.search.SearchAasRegistryStorage} feature * - * @author danish + * @author zielstor, fried */ public class TestSearchAasRegistry { @@ -73,7 +75,9 @@ public static void setUp() throws IOException, DeserializationException, Interru storage = appContext.getBean(SearchAasRegistryStorage.class); searchAPI = appContext.getBean(SearchAasRegistryApiHTTPController.class); preloadAasdf(); - Thread.sleep(2000); + await().atMost(10, SECONDS).until(() -> + !storage.getAllAasDescriptors(new PaginationInfo(0, ""), new DescriptorFilter(null, null)).getResult().isEmpty() + ); } @AfterClass diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml index 38a606cbd..cbe1dc4a6 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/pom.xml @@ -89,6 +89,12 @@ org.springframework.boot spring-boot-actuator-autoconfigure + + org.awaitility + awaitility + 4.3.0 + test + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java index e32847918..1729ec2f5 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java @@ -45,6 +45,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.List; +import static org.awaitility.Awaitility.await; +import static java.util.concurrent.TimeUnit.SECONDS; public class TestSearchAasRepository { private static ConfigurableApplicationContext appContext; @@ -52,12 +54,14 @@ public class TestSearchAasRepository { private static SearchAasRepositoryApiHTTPController searchAPI; @BeforeClass - public static void startSmRepo() throws Exception { + public static void startAasRepo() throws Exception { appContext = new SpringApplicationBuilder(DummySearchAasRepositoryComponent.class).run(new String[] {}); searchBackend = appContext.getBean(AasRepository.class); searchAPI = appContext.getBean(SearchAasRepositoryApiHTTPController.class); preloadShells(); - Thread.sleep(2000); + await().atMost(10, SECONDS).until(() -> + !searchBackend.getAllAas(null, null, new PaginationInfo(0, "")).getResult().isEmpty() + ); } @Test diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml index 1918896b8..fcebebd77 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/pom.xml @@ -70,6 +70,12 @@ 1.3.2 compile + + org.awaitility + awaitility + 4.3.0 + test + \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java index 38040d390..f49234a71 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java @@ -46,6 +46,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.List; +import static org.awaitility.Awaitility.await; +import static java.util.concurrent.TimeUnit.SECONDS; public class TestSearchCdRepository { private static ConfigurableApplicationContext appContext; @@ -58,7 +60,9 @@ public static void startCdRepo() throws Exception { searchBackend = appContext.getBean(ConceptDescriptionRepository.class); searchAPI = appContext.getBean(SearchCdRepositoryApiHTTPController.class); preloadCds(); - waitForData(); + await().atMost(10, SECONDS).until(() -> + !searchBackend.getAllConceptDescriptions(new PaginationInfo(0, "")).getResult().isEmpty() + ); } @Test @@ -106,8 +110,4 @@ private static void resetRepo() { }); } - private static void waitForData() throws InterruptedException { - Thread.sleep(2000); - } - } diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml index cc8957bd5..cb26545c5 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/pom.xml @@ -64,5 +64,11 @@ mockito-core test + + org.awaitility + awaitility + 4.3.0 + test + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 3f5952b9e..4df8facc3 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -53,7 +53,8 @@ import java.util.Objects; import static org.junit.Assert.assertEquals; - +import static org.awaitility.Awaitility.await; +import static java.util.concurrent.TimeUnit.SECONDS; /** * Tests for {@link SearchSubmodelRegistryStorage} feature * @@ -74,7 +75,9 @@ public static void setUp() throws IOException, DeserializationException, Interru storage = appContext.getBean(SearchSubmodelRegistryStorage.class); searchAPI = appContext.getBean(SearchSubmodelRegistryApiHTTPController.class); preloadSmds(); - Thread.sleep(2000); + await().atMost(10, SECONDS).until(() -> + !storage.getAllSubmodelDescriptors(new PaginationInfo(0, "")).getResult().isEmpty() + ); } @AfterClass diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml index fa87ef2f8..f709491a7 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/pom.xml @@ -93,6 +93,12 @@ org.eclipse.digitaltwin.basyx basyx.submodelrepository-core + + org.awaitility + awaitility + 4.3.0 + test + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java index 5e9403a0d..42ade947e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java @@ -47,6 +47,9 @@ import java.io.FileNotFoundException; import java.util.List; +import static org.awaitility.Awaitility.await; +import static java.util.concurrent.TimeUnit.SECONDS; + public class TestSearchSubmodelRepository { private static ConfigurableApplicationContext appContext; private static SubmodelRepository searchBackend; @@ -58,7 +61,9 @@ public static void startSmRepo() throws Exception { searchBackend = appContext.getBean(SubmodelRepository.class); searchAPI = appContext.getBean(SearchSubmodelRepositoryApiHTTPController.class); preloadSubmodels(); - Thread.sleep(2000); + await().atMost(10, SECONDS).until(() -> + !searchBackend.getAllSubmodels(new PaginationInfo(0, "")).getResult().isEmpty() + ); } @Test From 9aba29c1d5f0a14bbfbc91ac4cd1b6d5118aebb7 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 21 Aug 2025 09:01:49 +0200 Subject: [PATCH 48/52] Adapts limit --- .../basyx/aasregistry/feature/search/TestSearchAasRegistry.java | 2 +- .../aasrepository/feature/search/TestSearchAasRepository.java | 2 +- .../feature/search/TestSearchCdRepository.java | 2 +- .../feature/search/TestSearchSubmodelRegistry.java | 2 +- .../feature/search/TestSearchSubmodelRepository.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java index 07fd49c9b..b87b55593 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java @@ -76,7 +76,7 @@ public static void setUp() throws IOException, DeserializationException, Interru searchAPI = appContext.getBean(SearchAasRegistryApiHTTPController.class); preloadAasdf(); await().atMost(10, SECONDS).until(() -> - !storage.getAllAasDescriptors(new PaginationInfo(0, ""), new DescriptorFilter(null, null)).getResult().isEmpty() + !storage.getAllAasDescriptors(new PaginationInfo(1, ""), new DescriptorFilter(null, null)).getResult().isEmpty() ); } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java index 1729ec2f5..ceb77f730 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java @@ -60,7 +60,7 @@ public static void startAasRepo() throws Exception { searchAPI = appContext.getBean(SearchAasRepositoryApiHTTPController.class); preloadShells(); await().atMost(10, SECONDS).until(() -> - !searchBackend.getAllAas(null, null, new PaginationInfo(0, "")).getResult().isEmpty() + !searchBackend.getAllAas(null, null, new PaginationInfo(1, "")).getResult().isEmpty() ); } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java index f49234a71..cb45a7bba 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java @@ -61,7 +61,7 @@ public static void startCdRepo() throws Exception { searchAPI = appContext.getBean(SearchCdRepositoryApiHTTPController.class); preloadCds(); await().atMost(10, SECONDS).until(() -> - !searchBackend.getAllConceptDescriptions(new PaginationInfo(0, "")).getResult().isEmpty() + !searchBackend.getAllConceptDescriptions(new PaginationInfo(1, "")).getResult().isEmpty() ); } diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 4df8facc3..44a6ff8c5 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -76,7 +76,7 @@ public static void setUp() throws IOException, DeserializationException, Interru searchAPI = appContext.getBean(SearchSubmodelRegistryApiHTTPController.class); preloadSmds(); await().atMost(10, SECONDS).until(() -> - !storage.getAllSubmodelDescriptors(new PaginationInfo(0, "")).getResult().isEmpty() + !storage.getAllSubmodelDescriptors(new PaginationInfo(1, "")).getResult().isEmpty() ); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java index 42ade947e..b25b0ca2e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java @@ -62,7 +62,7 @@ public static void startSmRepo() throws Exception { searchAPI = appContext.getBean(SearchSubmodelRepositoryApiHTTPController.class); preloadSubmodels(); await().atMost(10, SECONDS).until(() -> - !searchBackend.getAllSubmodels(new PaginationInfo(0, "")).getResult().isEmpty() + !searchBackend.getAllSubmodels(new PaginationInfo(1, "")).getResult().isEmpty() ); } From e091a09a392257d352db01b8ba6006efd020bbce Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 21 Aug 2025 09:18:28 +0200 Subject: [PATCH 49/52] Adapts test --- .../search/TestSearchSubmodelRepository.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java index b25b0ca2e..f9708d5c1 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java @@ -34,10 +34,7 @@ import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.ResponseEntity; @@ -56,13 +53,17 @@ public class TestSearchSubmodelRepository { private static SearchSubmodelRepositoryApiHTTPController searchAPI; @BeforeClass - public static void startSmRepo() throws Exception { + public static void startSmRepo(){ appContext = new SpringApplicationBuilder(DummySearchSubmodelRepositoryComponent.class).run(new String[] {}); searchBackend = appContext.getBean(SubmodelRepository.class); searchAPI = appContext.getBean(SearchSubmodelRepositoryApiHTTPController.class); + } + + @Before + public void loadSubmodels() throws FileNotFoundException, DeserializationException { preloadSubmodels(); await().atMost(10, SECONDS).until(() -> - !searchBackend.getAllSubmodels(new PaginationInfo(1, "")).getResult().isEmpty() + !searchBackend.getAllSubmodels(new PaginationInfo(100, "")).getResult().isEmpty() ); } @@ -95,15 +96,17 @@ private static void preloadSubmodels() throws FileNotFoundException, Deserializa } } - @AfterClass - public static void shutdownAasRepo() { - searchBackend.getAllSubmodels(new PaginationInfo(0, "")).getResult().forEach(submodel -> { + @After + public void clearRepository(){ + searchBackend.getAllSubmodels(new PaginationInfo(100, "")).getResult().forEach(submodel -> { try { searchBackend.deleteSubmodel(submodel.getId()); - } catch (Exception e) { - // Ignore exceptions during cleanup - } + } catch (Exception ignored) {} }); + } + + @AfterClass + public static void shutdownAasRepo() { appContext.close(); } From 32b3b2c2cdb4bfe35d04277e35ebc1952fd03225 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 21 Aug 2025 10:03:15 +0200 Subject: [PATCH 50/52] Adapts test --- .../feature/search/TestSearchAasRegistry.java | 21 ++++++++------ .../search/TestSearchAasRepository.java | 28 +++++++++++-------- .../search/TestSearchCdRepository.java | 20 ++++++++----- .../search/TestSearchSubmodelRegistry.java | 18 ++++++++---- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java index b87b55593..047365d4f 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java @@ -36,10 +36,7 @@ import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.ResponseEntity; @@ -70,22 +67,30 @@ public class TestSearchAasRegistry { @BeforeClass - public static void setUp() throws IOException, DeserializationException, InterruptedException { + public static void setUp() { appContext = new SpringApplication(DummySearchAasRegistryComponent.class).run(); storage = appContext.getBean(SearchAasRegistryStorage.class); searchAPI = appContext.getBean(SearchAasRegistryApiHTTPController.class); + } + + @AfterClass + public static void tearDown() { + appContext.close(); + } + + @Before + public void preloadData() throws FileNotFoundException, DeserializationException { preloadAasdf(); await().atMost(10, SECONDS).until(() -> !storage.getAllAasDescriptors(new PaginationInfo(1, ""), new DescriptorFilter(null, null)).getResult().isEmpty() ); } - @AfterClass - public static void tearDown() { + @After + public void resetRepo(){ List descriptors = storage.getAllAasDescriptors(PaginationInfo.NO_LIMIT, new DescriptorFilter(null, null)).getResult(); descriptors.forEach(descriptor -> storage.removeAasDescriptor(descriptor.getId())); - appContext.close(); } @Test diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java index ceb77f730..292928a64 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java @@ -33,10 +33,7 @@ import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.ResponseEntity; @@ -54,16 +51,30 @@ public class TestSearchAasRepository { private static SearchAasRepositoryApiHTTPController searchAPI; @BeforeClass - public static void startAasRepo() throws Exception { + public static void startAasRepo() { appContext = new SpringApplicationBuilder(DummySearchAasRepositoryComponent.class).run(new String[] {}); searchBackend = appContext.getBean(AasRepository.class); searchAPI = appContext.getBean(SearchAasRepositoryApiHTTPController.class); + + } + + @Before + public void preloadData() throws FileNotFoundException, DeserializationException { preloadShells(); await().atMost(10, SECONDS).until(() -> !searchBackend.getAllAas(null, null, new PaginationInfo(1, "")).getResult().isEmpty() ); } + @After + public void resetRepo(){ + searchBackend.getAllAas(null, null, new PaginationInfo(100, "")).getResult().forEach(aas -> { + try { + searchBackend.deleteAas(aas.getId()); + } catch (Exception ignored) {} + }); + } + @Test public void testRepo() throws FileNotFoundException, DeserializationException { File file = new File(TestSearchAasRepository.class.getResource("/query.json").getFile()); @@ -95,13 +106,6 @@ private static void preloadShells() throws FileNotFoundException, Deserializatio @AfterClass public static void shutdownAasRepo() { - searchBackend.getAllAas(null, null, new PaginationInfo(0, "")).getResult().forEach(aas -> { - try { - searchBackend.deleteAas(aas.getId()); - } catch (Exception e) { - // Ignore exceptions during cleanup - } - }); appContext.close(); } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java index cb45a7bba..67d62f4a1 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java @@ -34,10 +34,7 @@ import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.querycore.query.model.AASQuery; import org.eclipse.digitaltwin.basyx.querycore.query.model.QueryResponse; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.ResponseEntity; @@ -55,16 +52,26 @@ public class TestSearchCdRepository { private static SearchCdRepositoryApiHTTPController searchAPI; @BeforeClass - public static void startCdRepo() throws Exception { + public static void startCdRepo() { appContext = new SpringApplicationBuilder(DummySearchCdRepositoryComponent.class).run(new String[] {}); searchBackend = appContext.getBean(ConceptDescriptionRepository.class); searchAPI = appContext.getBean(SearchCdRepositoryApiHTTPController.class); + + } + + @Before + public void preloadData() throws FileNotFoundException, DeserializationException { preloadCds(); await().atMost(10, SECONDS).until(() -> !searchBackend.getAllConceptDescriptions(new PaginationInfo(1, "")).getResult().isEmpty() ); } + @After + public void cleareRepo(){ + resetRepo(); + } + @Test public void testRepo() throws FileNotFoundException, DeserializationException { File file = new File(TestSearchCdRepository.class.getResource("/query.json").getFile()); @@ -96,12 +103,11 @@ private static void preloadCds() throws FileNotFoundException, DeserializationEx @AfterClass public static void shutdownCdRepo() { - resetRepo(); appContext.close(); } private static void resetRepo() { - searchBackend.getAllConceptDescriptions(new PaginationInfo(0, "")).getResult().forEach(cd -> { + searchBackend.getAllConceptDescriptions(new PaginationInfo(100, "")).getResult().forEach(cd -> { try { searchBackend.deleteConceptDescription(cd.getId()); } catch (Exception e) { diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 44a6ff8c5..9c6be2234 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -70,22 +70,30 @@ public class TestSearchSubmodelRegistry { @BeforeClass - public static void setUp() throws IOException, DeserializationException, InterruptedException { + public static void setUp() { appContext = new SpringApplication(DummySearchSubmodelRegistryComponent.class).run(new String[] {}); storage = appContext.getBean(SearchSubmodelRegistryStorage.class); searchAPI = appContext.getBean(SearchSubmodelRegistryApiHTTPController.class); + } + + @AfterClass + public static void tearDown() { + appContext.close(); + } + + @Before + public void preloadData() throws FileNotFoundException, DeserializationException { preloadSmds(); await().atMost(10, SECONDS).until(() -> - !storage.getAllSubmodelDescriptors(new PaginationInfo(1, "")).getResult().isEmpty() + !storage.getAllSubmodelDescriptors(new PaginationInfo(1, "")).getResult().isEmpty() ); } - @AfterClass - public static void tearDown() { + @After + public void resetRepo(){ List descriptors = storage.getAllSubmodelDescriptors(PaginationInfo.NO_LIMIT).getResult(); descriptors.forEach(descriptor -> storage.removeSubmodelDescriptor(descriptor.getId())); - appContext.close(); } @Test From 9e2456d23e833a0a7e1259988712d74bcb86cba6 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 21 Aug 2025 11:17:48 +0200 Subject: [PATCH 51/52] Adds Thread.sleep for the moment --- .../aasregistry/feature/search/TestSearchAasRegistry.java | 6 ++---- .../feature/search/TestSearchAasRepository.java | 6 ++---- .../feature/search/TestSearchCdRepository.java | 6 ++---- .../feature/search/TestSearchSubmodelRegistry.java | 6 ++---- .../feature/search/TestSearchSubmodelRepository.java | 6 ++---- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java index 047365d4f..a95348f16 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/search/TestSearchAasRegistry.java @@ -79,11 +79,9 @@ public static void tearDown() { } @Before - public void preloadData() throws FileNotFoundException, DeserializationException { + public void preloadData() throws FileNotFoundException, DeserializationException, InterruptedException { preloadAasdf(); - await().atMost(10, SECONDS).until(() -> - !storage.getAllAasDescriptors(new PaginationInfo(1, ""), new DescriptorFilter(null, null)).getResult().isEmpty() - ); + Thread.sleep(2000); } @After diff --git a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java index 292928a64..8895cbc27 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/search/TestSearchAasRepository.java @@ -59,11 +59,9 @@ public static void startAasRepo() { } @Before - public void preloadData() throws FileNotFoundException, DeserializationException { + public void preloadData() throws FileNotFoundException, DeserializationException, InterruptedException { preloadShells(); - await().atMost(10, SECONDS).until(() -> - !searchBackend.getAllAas(null, null, new PaginationInfo(1, "")).getResult().isEmpty() - ); + Thread.sleep(2000); } @After diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java index 67d62f4a1..11e048364 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/search/TestSearchCdRepository.java @@ -60,11 +60,9 @@ public static void startCdRepo() { } @Before - public void preloadData() throws FileNotFoundException, DeserializationException { + public void preloadData() throws FileNotFoundException, DeserializationException, InterruptedException { preloadCds(); - await().atMost(10, SECONDS).until(() -> - !searchBackend.getAllConceptDescriptions(new PaginationInfo(1, "")).getResult().isEmpty() - ); + Thread.sleep(2000); } @After diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 9c6be2234..14d35173e 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -82,11 +82,9 @@ public static void tearDown() { } @Before - public void preloadData() throws FileNotFoundException, DeserializationException { + public void preloadData() throws FileNotFoundException, DeserializationException, InterruptedException { preloadSmds(); - await().atMost(10, SECONDS).until(() -> - !storage.getAllSubmodelDescriptors(new PaginationInfo(1, "")).getResult().isEmpty() - ); + Thread.sleep(2000); } @After diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java index f9708d5c1..6aadf6589 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/search/TestSearchSubmodelRepository.java @@ -60,11 +60,9 @@ public static void startSmRepo(){ } @Before - public void loadSubmodels() throws FileNotFoundException, DeserializationException { + public void loadSubmodels() throws FileNotFoundException, DeserializationException, InterruptedException { preloadSubmodels(); - await().atMost(10, SECONDS).until(() -> - !searchBackend.getAllSubmodels(new PaginationInfo(100, "")).getResult().isEmpty() - ); + Thread.sleep(2000); } @Test From 25607c67c6c12e71fe341c9957c426e9dceb0fef Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 21 Aug 2025 14:36:47 +0200 Subject: [PATCH 52/52] Review remarks --- .github/workflows/examples_test.yml | 19 +++ ...DummyAuthorizedAasRepositoryComponent.java | 92 ++++++------ .../src/test/resources/application.properties | 82 +++++----- .../http/AasRepoConfiguration.java | 100 ++++++------- basyx.conceptdescriptionrepository/pom.xml | 52 +++---- .../search/TestSearchSubmodelRegistry.java | 2 +- .../pom.xml | 140 +++++++++--------- examples/BaSyxMinimal/docker-compose.yml | 2 +- examples/BaSyxQueryLanguage/README.md | 0 9 files changed, 254 insertions(+), 235 deletions(-) delete mode 100644 examples/BaSyxQueryLanguage/README.md diff --git a/.github/workflows/examples_test.yml b/.github/workflows/examples_test.yml index f18ab42ae..b1c575e39 100644 --- a/.github/workflows/examples_test.yml +++ b/.github/workflows/examples_test.yml @@ -189,3 +189,22 @@ jobs: - name: Stop BaSyx Node-RED Example run: docker compose -f examples/BaSyxNodeRED/docker-compose.yml down + + test-basyx-query-language: + runs-on: ubuntu-latest + name: Test BaSyx Query Language Example + steps: + - uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + cache: maven + + - name: Start BaSyx Query Language Example + run: docker compose -f examples/BaSyxQueryLanguage/docker-compose.yml up -d + + - name: Stop BaSyx Query Language Example + run: docker compose -f examples/BaSyxQueryLanguage/docker-compose.yml down diff --git a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java index 3f03b3b8d..bedd45bc9 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/DummyAuthorizedAasRepositoryComponent.java @@ -1,46 +1,46 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasrepository.feature.authorization; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchRestHealthContributorAutoConfiguration; - -/** - * Spring application configured for tests. - * - * @author danish - * - */ - -@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx", - exclude = {ElasticsearchRestHealthContributorAutoConfiguration.class}) -public class DummyAuthorizedAasRepositoryComponent { - - public static void main(String[] args) { - SpringApplication.run(DummyAuthorizedAasRepositoryComponent.class, args); - } -} +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasrepository.feature.authorization; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchRestHealthContributorAutoConfiguration; + +/** + * Spring application configured for tests. + * + * @author danish + * + */ + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx", + exclude = {ElasticsearchRestHealthContributorAutoConfiguration.class}) +public class DummyAuthorizedAasRepositoryComponent { + + public static void main(String[] args) { + SpringApplication.run(DummyAuthorizedAasRepositoryComponent.class, args); + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties index e1ec7379c..ebd881a6f 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties +++ b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/resources/application.properties @@ -1,42 +1,42 @@ -server.port=8081 -server.error.path=/error - -spring.application.name=AAS Repository -basyx.aasrepo.name=aas-repo - -basyx.backend = InMemory - -#basyx.aasrepository.feature.registryintegration=http://host.docker.internal:8050/api/v3.0 -#basyx.externalurl=http://localhost:8081 - -#basyx.backend = MongoDB -#spring.data.mongodb.host=127.0.0.1 -#spring.data.mongodb.port=27017 -#spring.data.mongodb.database=aas -#spring.data.mongodb.authentication-database=admin -#spring.data.mongodb.username=mongoAdmin -#spring.data.mongodb.password=mongoPassword - - -# basyx.aasrepository.feature.mqtt.enabled = true -# mqtt.clientId=TestClient -# mqtt.hostname = localhost -# mqtt.port = 1883 - -# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 - -#################################################################################### -# Authorization -#################################################################################### -basyx.feature.authorization.enabled = true -basyx.feature.authorization.type = rbac -basyx.feature.authorization.jwtBearerTokenProvider = keycloak -basyx.feature.authorization.rbac.file = classpath:rbac_rules.json -spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9096/realms/BaSyx - -#################################################################################### -# Operation Delegation -#################################################################################### -# This feature is enabled by default - +server.port=8081 +server.error.path=/error + +spring.application.name=AAS Repository +basyx.aasrepo.name=aas-repo + +basyx.backend = InMemory + +#basyx.aasrepository.feature.registryintegration=http://host.docker.internal:8050/api/v3.0 +#basyx.externalurl=http://localhost:8081 + +#basyx.backend = MongoDB +#spring.data.mongodb.host=127.0.0.1 +#spring.data.mongodb.port=27017 +#spring.data.mongodb.database=aas +#spring.data.mongodb.authentication-database=admin +#spring.data.mongodb.username=mongoAdmin +#spring.data.mongodb.password=mongoPassword + + +# basyx.aasrepository.feature.mqtt.enabled = true +# mqtt.clientId=TestClient +# mqtt.hostname = localhost +# mqtt.port = 1883 + +# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000 + +#################################################################################### +# Authorization +#################################################################################### +basyx.feature.authorization.enabled = true +basyx.feature.authorization.type = rbac +basyx.feature.authorization.jwtBearerTokenProvider = keycloak +basyx.feature.authorization.rbac.file = classpath:rbac_rules.json +spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9096/realms/BaSyx + +#################################################################################### +# Operation Delegation +#################################################################################### +# This feature is enabled by default + #basyx.submodelrepository.feature.operation.delegation.enabled = false \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java index ba2134d53..6df13ea48 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepoConfiguration.java @@ -1,50 +1,50 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.aasrepository.http; - -import org.eclipse.digitaltwin.basyx.http.CorsPathPatternProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * - * @author schnicke - * - */ -@Configuration -public class AasRepoConfiguration { - - @Bean - public CorsPathPatternProvider getAasRepoCorsUrlProvider() { - return new CorsPathPatternProvider("/shells/**"); - } - - @Bean - public CorsPathPatternProvider getAasRepoDescriptionCorsUrlProvider() { - return new CorsPathPatternProvider("/description"); - } -} +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.aasrepository.http; + +import org.eclipse.digitaltwin.basyx.http.CorsPathPatternProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + * @author schnicke + * + */ +@Configuration +public class AasRepoConfiguration { + + @Bean + public CorsPathPatternProvider getAasRepoCorsUrlProvider() { + return new CorsPathPatternProvider("/shells/**"); + } + + @Bean + public CorsPathPatternProvider getAasRepoDescriptionCorsUrlProvider() { + return new CorsPathPatternProvider("/description"); + } +} diff --git a/basyx.conceptdescriptionrepository/pom.xml b/basyx.conceptdescriptionrepository/pom.xml index 87e68498f..342ae759f 100644 --- a/basyx.conceptdescriptionrepository/pom.xml +++ b/basyx.conceptdescriptionrepository/pom.xml @@ -1,27 +1,27 @@ - - 4.0.0 - - - org.eclipse.digitaltwin.basyx - basyx.parent - ${revision} - - - basyx.conceptdescriptionrepository - Concept Description Repository - BaSyx Concept Description Repository - pom - - - basyx.conceptdescriptionrepository-core - basyx.conceptdescriptionrepository-http - basyx.conceptdescriptionrepository-backend - basyx.conceptdescriptionrepository-backend-inmemory - basyx.conceptdescriptionrepository-backend-mongodb - basyx.conceptdescriptionrepository-feature-authorization - basyx.conceptdescriptionrepository-tck - basyx.conceptdescriptionrepository.component - basyx.conceptdescriptionrepository-feature-search - + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.parent + ${revision} + + + basyx.conceptdescriptionrepository + Concept Description Repository + BaSyx Concept Description Repository + pom + + + basyx.conceptdescriptionrepository-core + basyx.conceptdescriptionrepository-http + basyx.conceptdescriptionrepository-backend + basyx.conceptdescriptionrepository-backend-inmemory + basyx.conceptdescriptionrepository-backend-mongodb + basyx.conceptdescriptionrepository-feature-authorization + basyx.conceptdescriptionrepository-tck + basyx.conceptdescriptionrepository.component + basyx.conceptdescriptionrepository-feature-search + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java index 14d35173e..1a52c4904 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-search/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/search/TestSearchSubmodelRegistry.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml index 732c87af3..9479e4ada 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml @@ -1,70 +1,70 @@ - - - 4.0.0 - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry - ${revision} - - - - basyx.submodelregistry-service-release-log-mongodb - BaSyx Submodel Registry Service Release Log MongoDB - BaSyx Submodel Registry Service Release Log MongoDB - - - 2020.0.4 - org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication - submodel-registry-log-mongodb - - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-hierarchy - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-feature-search - - - org.eclipse.digitaltwin.basyx - basyx.authorization - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-mongodb-storage - - - org.junit.vintage - junit-vintage-engine - test - - - org.hamcrest - hamcrest-core - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.apache.commons - commons-lang3 - - - org.eclipse.digitaltwin.basyx - basyx.submodelregistry-service-basetests - test - - - - + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + + + + basyx.submodelregistry-service-release-log-mongodb + BaSyx Submodel Registry Service Release Log MongoDB + BaSyx Submodel Registry Service Release Log MongoDB + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-log-mongodb + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-search + + + org.eclipse.digitaltwin.basyx + basyx.authorization + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.commons + commons-lang3 + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + + + + diff --git a/examples/BaSyxMinimal/docker-compose.yml b/examples/BaSyxMinimal/docker-compose.yml index f5286a298..e3f8f7bf4 100644 --- a/examples/BaSyxMinimal/docker-compose.yml +++ b/examples/BaSyxMinimal/docker-compose.yml @@ -69,7 +69,7 @@ services: condition: service_healthy aas-discovery: - image: eclipsebasyx/aas-discovery:2.0.0-milestone-03.1 + image: eclipsebasyx/aas-discovery:2.0.0-SNAPSHOT ports: - 8084:8081 volumes: diff --git a/examples/BaSyxQueryLanguage/README.md b/examples/BaSyxQueryLanguage/README.md deleted file mode 100644 index e69de29bb..000000000