diff --git a/app/pom.xml b/app/pom.xml index 28083136..0926733c 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -138,6 +138,11 @@ SPDX-License-Identifier: Apache-2.0 openpojo test + + org.slf4j + slf4j-simple + test + @@ -156,44 +161,44 @@ SPDX-License-Identifier: Apache-2.0 - build-postgresql + build-basex build - prod-postgresql - postgresql-quarkus-app - - src/main/docker/Dockerfile-postgresql.jvm + prod-basex + basex-quarkus-app + src/main/docker/Dockerfile-basex.jvm - src/main/docker/Dockerfile-postgresql.native + src/main/docker/Dockerfile-basex.native - ${project.version}-postgresql - latest-postgresql + ${project.version}-basex + latest-basex - build-basex + build-postgresql build - prod-basex - basex-quarkus-app - src/main/docker/Dockerfile-basex.jvm + prod-postgresql + postgresql-quarkus-app + + src/main/docker/Dockerfile-postgresql.jvm - src/main/docker/Dockerfile-basex.native + src/main/docker/Dockerfile-postgresql.native - ${project.version}-basex - latest-basex + ${project.version}-postgresql + latest-postgresql diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceBaseXConfiguration.java b/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceBaseXConfiguration.java deleted file mode 100644 index bf7a13b8..00000000 --- a/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceBaseXConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alliander N.V. -// -// SPDX-License-Identifier: Apache-2.0 -package org.lfenergy.compas.scl.data.rest; - -import io.quarkus.arc.profile.IfBuildProfile; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.lfenergy.compas.scl.data.basex.client.BaseXClientFactory; -import org.lfenergy.compas.scl.data.basex.repository.CompasSclDataBaseXRepository; -import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; -import org.lfenergy.compas.scl.data.util.SclDataModelMarshaller; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Produces; - -/** - * Create Beans from other dependencies that are used in the application. - */ -public class CompasSclDataServiceBaseXConfiguration { - @Produces - @ApplicationScoped - @IfBuildProfile("prod-basex") - public BaseXClientFactory createBaseXClientFactoryProduction(@ConfigProperty(name = "basex.host") String baseXHost, - @ConfigProperty(name = "basex.port") Integer baseXPort, - @ConfigProperty(name = "basex.username") String baseXUsername, - @ConfigProperty(name = "basex.password") String baseXPassword) { - return new BaseXClientFactory(baseXHost, baseXPort, baseXUsername, baseXPassword); - } - - @Produces - @ApplicationScoped - @IfBuildProfile("prod-basex") - public CompasSclDataRepository creatCompasSclDataRepositoryProduction(BaseXClientFactory baseXClientFactory, - SclDataModelMarshaller sclDataModelMarshaller) { - return new CompasSclDataBaseXRepository(baseXClientFactory, sclDataModelMarshaller); - } - - @Produces - @ApplicationScoped - @IfBuildProfile("dev-basex") - public BaseXClientFactory createBaseXClientFactoryDevelopment(@ConfigProperty(name = "basex.host") String baseXHost, - @ConfigProperty(name = "basex.port") Integer baseXPort, - @ConfigProperty(name = "basex.username") String baseXUsername, - @ConfigProperty(name = "basex.password") String baseXPassword) { - return createBaseXClientFactoryProduction(baseXHost, baseXPort, baseXUsername, baseXPassword); - } - - @Produces - @ApplicationScoped - @IfBuildProfile("dev-basex") - public CompasSclDataRepository creatCompasSclDataRepositoryDevelopment(BaseXClientFactory baseXClientFactory, - SclDataModelMarshaller sclDataModelMarshaller) { - return creatCompasSclDataRepositoryProduction(baseXClientFactory, sclDataModelMarshaller); - } -} diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceCommonConfiguration.java b/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceConfiguration.java similarity index 51% rename from app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceCommonConfiguration.java rename to app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceConfiguration.java index ad8bec6f..26f426b2 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceCommonConfiguration.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceConfiguration.java @@ -5,11 +5,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection; import org.lfenergy.compas.core.commons.ElementConverter; -import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; -import org.lfenergy.compas.scl.data.service.CompasSclDataService; -import org.lfenergy.compas.scl.data.service.impl.CompasSclDataServiceImpl; import org.lfenergy.compas.scl.data.util.SclDataModelMarshaller; -import org.lfenergy.compas.scl.data.util.SclElementProcessor; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; @@ -21,30 +17,16 @@ org.lfenergy.compas.core.jaxrs.model.ErrorResponse.class, org.lfenergy.compas.core.jaxrs.model.ErrorMessage.class }) -public class CompasSclDataServiceCommonConfiguration { +public class CompasSclDataServiceConfiguration { @Produces @ApplicationScoped public ElementConverter createElementConverter() { return new ElementConverter(); } - @Produces - @ApplicationScoped - public SclElementProcessor creatSclElementProcessor() { - return new SclElementProcessor(); - } - @Produces @ApplicationScoped public SclDataModelMarshaller createSclDataModelMarshaller() { return new SclDataModelMarshaller(); } - - @Produces - @ApplicationScoped - public CompasSclDataService createCompasSclDataService(CompasSclDataRepository repository, - ElementConverter converter, - SclElementProcessor sclElementProcessor) { - return new CompasSclDataServiceImpl(repository, converter, sclElementProcessor); - } } diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServicePostgreSQLConfiguration.java b/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServicePostgreSQLConfiguration.java deleted file mode 100644 index e6d64ff6..00000000 --- a/app/src/main/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServicePostgreSQLConfiguration.java +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alliander N.V. -// -// SPDX-License-Identifier: Apache-2.0 -package org.lfenergy.compas.scl.data.rest; - -import io.quarkus.arc.profile.IfBuildProfile; -import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; -import org.lfenergy.compas.scl.data.repository.postgresql.CompasSclDataPostgreSQLRepository; - -import javax.enterprise.inject.Produces; -import javax.sql.DataSource; - -/** - * Create Beans from other dependencies that are used in the application. - */ -public class CompasSclDataServicePostgreSQLConfiguration { - @Produces - @IfBuildProfile("prod-postgresql") - public CompasSclDataRepository creatCompasSclDataRepositoryProduction(DataSource dataSource) { - return new CompasSclDataPostgreSQLRepository(dataSource); - } - - @Produces - @IfBuildProfile("dev-postgresql") - public CompasSclDataRepository creatCompasSclDataRepositoryDevelopment(DataSource dataSource) { - return creatCompasSclDataRepositoryProduction(dataSource); - } -} diff --git a/app/src/main/resources/application-dev-basex.properties b/app/src/main/resources/application-dev-basex.properties index 986d43a2..7bb9b600 100644 --- a/app/src/main/resources/application-dev-basex.properties +++ b/app/src/main/resources/application-dev-basex.properties @@ -14,6 +14,10 @@ quarkus.log.category."org.lfenergy.compas.scl.data".level = DEBUG quarkus.http.auth.permission.develop-quarkus-services.paths = /compas-scl-data-service/q/swagger-ui/*,/compas-scl-data-service/index.html quarkus.http.auth.permission.develop-quarkus-services.policy = permit +# Exclude the other repository implementations from being scanned +quarkus.arc.exclude-dependency.postgresql-repository.group-id = org.lfenergy.compas.scl.data +quarkus.arc.exclude-dependency.postgresql-repository.artifact-id = repository-postgresql + # Datasource configuration for BaseX (none) quarkus.datasource.jdbc = false quarkus.datasource.devservices.enabled = false diff --git a/app/src/main/resources/application-dev-postgresql.properties b/app/src/main/resources/application-dev-postgresql.properties index b526c75b..8d2e80a7 100644 --- a/app/src/main/resources/application-dev-postgresql.properties +++ b/app/src/main/resources/application-dev-postgresql.properties @@ -14,6 +14,10 @@ quarkus.log.category."org.lfenergy.compas.scl.data".level = DEBUG quarkus.http.auth.permission.develop-quarkus-services.paths = /compas-scl-data-service/q/swagger-ui/*,/compas-scl-data-service/index.html quarkus.http.auth.permission.develop-quarkus-services.policy = permit +# Exclude the other repository implementations from being scanned +quarkus.arc.exclude-dependency.basex-repository.group-id = org.lfenergy.compas.scl.data +quarkus.arc.exclude-dependency.basex-repository.artifact-id = repository-basex + # Datasource configuration for PostgreSQL quarkus.datasource.devservices.enabled = false quarkus.datasource.db-kind = postgresql diff --git a/app/src/main/resources/application-prod-basex.properties b/app/src/main/resources/application-prod-basex.properties index 9b50e57a..0d81f53f 100644 --- a/app/src/main/resources/application-prod-basex.properties +++ b/app/src/main/resources/application-prod-basex.properties @@ -4,9 +4,13 @@ # Production BaseX configuration. +# Exclude the other repository implementations from being scanned +quarkus.arc.exclude-dependency.postgresql-repository.group-id = org.lfenergy.compas.scl.data +quarkus.arc.exclude-dependency.postgresql-repository.artifact-id = repository-postgresql + # Add scanning these dependencies for scanning, also used by native compilation. -quarkus.index-dependency.scl-data-repository.group-id = org.lfenergy.compas.scl.data -quarkus.index-dependency.scl-data-repository.artifact-id = repository-basex +quarkus.index-dependency.basex-repository.group-id = org.lfenergy.compas.scl.data +quarkus.index-dependency.basex-repository.artifact-id = repository-basex # Datasource configuration for BaseX (none) quarkus.datasource.jdbc = false diff --git a/app/src/main/resources/application-prod-postgresql.properties b/app/src/main/resources/application-prod-postgresql.properties index 5318668f..79642385 100644 --- a/app/src/main/resources/application-prod-postgresql.properties +++ b/app/src/main/resources/application-prod-postgresql.properties @@ -4,9 +4,13 @@ # Production PostgreSQL configuration. +# Exclude the other repository implementations from being scanned +quarkus.arc.exclude-dependency.basex-repository.group-id = org.lfenergy.compas.scl.data +quarkus.arc.exclude-dependency.basex-repository.artifact-id = repository-basex + # Add scanning these dependencies for scanning, also used by native compilation. -quarkus.index-dependency.scl-data-repository.group-id = org.lfenergy.compas.scl.data -quarkus.index-dependency.scl-data-repository.artifact-id = repository-postgresql +quarkus.index-dependency.postgresql-repository.group-id = org.lfenergy.compas.scl.data +quarkus.index-dependency.postgresql-repository.artifact-id = repository-postgresql # Datasource configuration for PostgreSQL quarkus.datasource.db-kind = postgresql diff --git a/app/src/main/resources/application-test.properties b/app/src/main/resources/application-test.properties index f66f08db..0ddb127d 100644 --- a/app/src/main/resources/application-test.properties +++ b/app/src/main/resources/application-test.properties @@ -4,10 +4,15 @@ # Test configuration. -# Datasource configuration for BaseX (none) -quarkus.datasource.jdbc = false -quarkus.datasource.devservices.enabled = false +# Exclude the other repository implementations from being scanned +quarkus.arc.exclude-dependency.basex-repository.group-id = org.lfenergy.compas.scl.data +quarkus.arc.exclude-dependency.basex-repository.artifact-id = repository-basex -# Flyway configuration for BaseX (none) -quarkus.flyway.migrate-at-start = false +# Datasource configuration for PostgreSQL +quarkus.datasource.devservices.enabled = true +quarkus.datasource.db-kind = postgresql + +# Flyway configuration for PostgreSQL +quarkus.flyway.migrate-at-start = true +quarkus.flyway.locations = classpath:org/lfenergy/compas/scl/data/repository/postgresql/db/migration diff --git a/app/src/main/resources/reflection-config.json.license b/app/src/main/resources/reflection-config.json.license deleted file mode 100644 index 2350840d..00000000 --- a/app/src/main/resources/reflection-config.json.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2021 Alliander N.V. - -SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceBaseXConfigurationTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceBaseXConfigurationTest.java deleted file mode 100644 index db06e314..00000000 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceBaseXConfigurationTest.java +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alliander N.V. -// -// SPDX-License-Identifier: Apache-2.0 -package org.lfenergy.compas.scl.data.rest; - -import org.junit.jupiter.api.Test; -import org.lfenergy.compas.scl.data.basex.client.BaseXClient; -import org.lfenergy.compas.scl.data.basex.client.BaseXClientFactory; -import org.lfenergy.compas.scl.data.util.SclDataModelMarshaller; - -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class CompasSclDataServiceBaseXConfigurationTest { - @Test - void createBaseXClientFactoryProduction_WhenCalled_ThenObjectReturned() { - assertNotNull(new CompasSclDataServiceBaseXConfiguration() - .createBaseXClientFactoryProduction("host", 8898, "admin", "")); - } - - @Test - void creatCompasSclDataRepositoryProduction_WhenCalled_ThenObjectReturned() throws IOException { - var baseXClientFactory = mock(BaseXClientFactory.class); - var sclDataModelMarshaller = mock(SclDataModelMarshaller.class); - - when(baseXClientFactory.createClient()).thenReturn(mock(BaseXClient.class)); - - assertNotNull(new CompasSclDataServiceBaseXConfiguration() - .creatCompasSclDataRepositoryProduction(baseXClientFactory, sclDataModelMarshaller)); - } - - @Test - void createBaseXClientFactoryDevelopment_WhenCalled_ThenObjectReturned() { - assertNotNull(new CompasSclDataServiceBaseXConfiguration() - .createBaseXClientFactoryDevelopment("host", 8898, "admin", "")); - } - - @Test - void creatCompasSclDataRepositoryDevelopment_WhenCalled_ThenObjectReturned() throws IOException { - var baseXClientFactory = mock(BaseXClientFactory.class); - var sclDataModelMarshaller = mock(SclDataModelMarshaller.class); - - when(baseXClientFactory.createClient()).thenReturn(mock(BaseXClient.class)); - - assertNotNull(new CompasSclDataServiceBaseXConfiguration() - .creatCompasSclDataRepositoryDevelopment(baseXClientFactory, sclDataModelMarshaller)); - } -} \ No newline at end of file diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceCommonConfigurationTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceCommonConfigurationTest.java deleted file mode 100644 index d21825d8..00000000 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceCommonConfigurationTest.java +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alliander N.V. -// -// SPDX-License-Identifier: Apache-2.0 -package org.lfenergy.compas.scl.data.rest; - -import org.junit.jupiter.api.Test; -import org.lfenergy.compas.core.commons.ElementConverter; -import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; -import org.lfenergy.compas.scl.data.util.SclElementProcessor; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; - -class CompasSclDataServiceCommonConfigurationTest { - @Test - void createElementConverter_WhenCalled_ThenObjectReturned() { - assertNotNull(new CompasSclDataServiceCommonConfiguration().createElementConverter()); - } - - @Test - void creatSclElementProcessor_WhenCalled_ThenObjectReturned() { - assertNotNull(new CompasSclDataServiceCommonConfiguration().creatSclElementProcessor()); - } - - @Test - void createSclDataModelMarshaller_WhenCalled_ThenObjectReturned() { - assertNotNull(new CompasSclDataServiceCommonConfiguration().createSclDataModelMarshaller()); - } - - @Test - void createCompasSclDataService_WhenCalled_ThenObjectReturned() { - var repository = mock(CompasSclDataRepository.class); - var converter = mock(ElementConverter.class); - var sclElementProcessor = mock(SclElementProcessor.class); - - assertNotNull(new CompasSclDataServiceCommonConfiguration() - .createCompasSclDataService(repository, converter, sclElementProcessor)); - } -} \ No newline at end of file diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceConfigurationTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceConfigurationTest.java new file mode 100644 index 00000000..2d1277d4 --- /dev/null +++ b/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServiceConfigurationTest.java @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2021 Alliander N.V. +// +// SPDX-License-Identifier: Apache-2.0 +package org.lfenergy.compas.scl.data.rest; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class CompasSclDataServiceConfigurationTest { + @Test + void createElementConverter_WhenCalled_ThenObjectReturned() { + assertNotNull(new CompasSclDataServiceConfiguration().createElementConverter()); + } + + @Test + void createSclDataModelMarshaller_WhenCalled_ThenObjectReturned() { + assertNotNull(new CompasSclDataServiceConfiguration().createSclDataModelMarshaller()); + } +} diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServicePostgreSQLConfigurationTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServicePostgreSQLConfigurationTest.java deleted file mode 100644 index c78e1431..00000000 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/CompasSclDataServicePostgreSQLConfigurationTest.java +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alliander N.V. -// -// SPDX-License-Identifier: Apache-2.0 -package org.lfenergy.compas.scl.data.rest; - -import org.junit.jupiter.api.Test; - -import javax.sql.DataSource; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; - -class CompasSclDataServicePostgreSQLConfigurationTest { - @Test - void creatCompasSclDataRepositoryProduction_WhenCalled_ThenObjectReturned() { - var datasource = mock(DataSource.class); - - assertNotNull(new CompasSclDataServicePostgreSQLConfiguration().creatCompasSclDataRepositoryProduction(datasource)); - } - - @Test - void creatCompasSclDataRepositoryDevelopment_WhenCalled_ThenObjectReturned() { - var datasource = mock(DataSource.class); - - assertNotNull(new CompasSclDataServicePostgreSQLConfiguration().creatCompasSclDataRepositoryDevelopment(datasource)); - } -} \ No newline at end of file diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/mock/CompasSclDataRepositoryMock.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/mock/CompasSclDataRepositoryMock.java deleted file mode 100644 index ba898b3b..00000000 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/mock/CompasSclDataRepositoryMock.java +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alliander N.V. -// -// SPDX-License-Identifier: Apache-2.0 -package org.lfenergy.compas.scl.data.rest.mock; - -import io.quarkus.test.Mock; -import org.lfenergy.compas.scl.data.model.HistoryItem; -import org.lfenergy.compas.scl.data.model.Item; -import org.lfenergy.compas.scl.data.model.SclMetaInfo; -import org.lfenergy.compas.scl.data.model.Version; -import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; -import org.lfenergy.compas.scl.extensions.model.SclFileType; - -import javax.enterprise.context.ApplicationScoped; -import java.util.List; -import java.util.UUID; - -@Mock -@ApplicationScoped -public class CompasSclDataRepositoryMock implements CompasSclDataRepository { - @Override - public List list(SclFileType type) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public List listVersionsByUUID(SclFileType type, UUID id) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public String findByUUID(SclFileType type, UUID id) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public SclMetaInfo findMetaInfoByUUID(SclFileType type, UUID id) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public String findByUUID(SclFileType type, UUID id, Version version) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public boolean hasDuplicateSclName(SclFileType type, String name) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public void create(SclFileType type, UUID id, String name, String scl, Version version, String who) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public void delete(SclFileType type, UUID id) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } - - @Override - public void delete(SclFileType type, UUID id, Version version) { - throw new IllegalStateException("Mock method using Mockito. Only needed to startup."); - } -} diff --git a/app/src/test/resources/simplelogger.properties b/app/src/test/resources/simplelogger.properties new file mode 100644 index 00000000..684f66ea --- /dev/null +++ b/app/src/test/resources/simplelogger.properties @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2022 Alliander N.V. +# +# SPDX-License-Identifier: Apache-2.0 + +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=true +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss + +org.slf4j.simpleLogger.defaultLogLevel=debug diff --git a/pom.xml b/pom.xml index 2d43afdc..6f1823af 100644 --- a/pom.xml +++ b/pom.xml @@ -25,10 +25,10 @@ SPDX-License-Identifier: Apache-2.0 0.9.3 - 2.12.3.Final + 2.13.0.Final 2.3.6 3.0 - 2.0.2 + 2.0.3 0.9.1 diff --git a/repository-basex/pom.xml b/repository-basex/pom.xml index d00cf359..8153930f 100644 --- a/repository-basex/pom.xml +++ b/repository-basex/pom.xml @@ -31,7 +31,11 @@ SPDX-License-Identifier: Apache-2.0 commons-io commons-io - + + + org.eclipse.microprofile.config + microprofile-config-api + com.sun.xml.bind jaxb-impl diff --git a/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClient.java b/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClient.java index 959620fb..2d8d6734 100644 --- a/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClient.java +++ b/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClient.java @@ -49,7 +49,7 @@ public class BaseXClient implements Closeable { * * @param host server name * @param port server port - * @param username user name + * @param username username * @param password password * @throws IOException Exception */ diff --git a/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClientFactory.java b/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClientFactory.java index a8acffd8..5d97fa53 100644 --- a/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClientFactory.java +++ b/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/client/BaseXClientFactory.java @@ -3,18 +3,24 @@ // SPDX-License-Identifier: Apache-2.0 package org.lfenergy.compas.scl.data.basex.client; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; import java.io.IOException; +@ApplicationScoped public class BaseXClientFactory { private final String baseXHost; private final Integer baseXPort; private final String baseXUsername; private final String baseXPassword; - public BaseXClientFactory(String baseXHost, - Integer baseXPort, - String baseXUsername, - String baseXPassword) { + @Inject + public BaseXClientFactory(@ConfigProperty(name = "basex.host") String baseXHost, + @ConfigProperty(name = "basex.port") Integer baseXPort, + @ConfigProperty(name = "basex.username") String baseXUsername, + @ConfigProperty(name = "basex.password") String baseXPassword) { this.baseXHost = baseXHost; this.baseXPort = baseXPort; this.baseXUsername = baseXUsername; diff --git a/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/repository/CompasSclDataBaseXRepository.java b/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/repository/CompasSclDataBaseXRepository.java index 914c458e..4a21a006 100644 --- a/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/repository/CompasSclDataBaseXRepository.java +++ b/repository-basex/src/main/java/org/lfenergy/compas/scl/data/basex/repository/CompasSclDataBaseXRepository.java @@ -18,6 +18,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.transaction.Transactional; import java.io.IOException; import java.io.StringReader; import java.nio.charset.StandardCharsets; @@ -26,6 +29,8 @@ import java.util.List; import java.util.UUID; +import static javax.transaction.Transactional.TxType.REQUIRED; +import static javax.transaction.Transactional.TxType.SUPPORTS; import static org.lfenergy.compas.scl.data.SclDataServiceConstants.SCL_DATA_SERVICE_V1_NS_URI; import static org.lfenergy.compas.scl.data.SclDataServiceConstants.SCL_NS_URI; import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.BASEX_COMMAND_ERROR_CODE; @@ -41,6 +46,7 @@ * Every entry is stored under <ID>/<Major version>/<Minor version>/<Patch version>/scl.xml. * This combination is always unique and easy to use. */ +@ApplicationScoped public class CompasSclDataBaseXRepository implements CompasSclDataRepository { private static final Logger LOGGER = LoggerFactory.getLogger(CompasSclDataBaseXRepository.class); @@ -72,7 +78,7 @@ public class CompasSclDataBaseXRepository implements CompasSclDataRepository { // Retrieve the Labels as XML Label elements from the XML. The result can be returned by the List functions. private static final String DECLARE_LABELS_FUNC = """ declare function local:createLabelsResponse($latestScl as document-node()) as xs:string* { - let $labels := $latestScl/scl:SCL/scl:Private[@type=$compasSclExtensionType]/compas:Labels/compas:Label + let $labels := distinct-values($latestScl/scl:SCL/scl:Private[@type=$compasSclExtensionType]/compas:Labels/compas:Label) for $label in $labels return ' ' }; @@ -81,6 +87,7 @@ public class CompasSclDataBaseXRepository implements CompasSclDataRepository { private final BaseXClientFactory baseXClientFactory; private final SclDataModelMarshaller sclDataMarshaller; + @Inject public CompasSclDataBaseXRepository(BaseXClientFactory baseXClientFactory, SclDataModelMarshaller sclDataMarshaller) { this.baseXClientFactory = baseXClientFactory; @@ -101,6 +108,7 @@ public CompasSclDataBaseXRepository(BaseXClientFactory baseXClientFactory, } @Override + @Transactional(SUPPORTS) public List list(SclFileType type) { return executeQuery(type, """ %s @@ -127,6 +135,7 @@ public List list(SclFileType type) { } @Override + @Transactional(SUPPORTS) public List listVersionsByUUID(SclFileType type, UUID id) { return executeQuery(type, """ %s @@ -154,6 +163,7 @@ public List listVersionsByUUID(SclFileType type, UUID id) { } @Override + @Transactional(SUPPORTS) public String findByUUID(SclFileType type, UUID id) { // This find method always searches for the latest version and returns this. var result = executeQuery(type, """ @@ -173,6 +183,7 @@ public String findByUUID(SclFileType type, UUID id) { } @Override + @Transactional(SUPPORTS) public String findByUUID(SclFileType type, UUID id, Version version) { // This find method searches for a specific version. var result = executeQuery(type, """ @@ -190,11 +201,13 @@ public String findByUUID(SclFileType type, UUID id, Version version) { } @Override + @Transactional(SUPPORTS) public boolean hasDuplicateSclName(SclFileType type, String name) { return false; } @Override + @Transactional(SUPPORTS) public SclMetaInfo findMetaInfoByUUID(SclFileType type, UUID id) { // This find method always searches for the latest version. // Extracts the needed information from the document and returns this. @@ -225,7 +238,8 @@ public SclMetaInfo findMetaInfoByUUID(SclFileType type, UUID id) { } @Override - public void create(SclFileType type, UUID id, String name, String scl, Version version, String who) { + @Transactional(REQUIRED) + public void create(SclFileType type, UUID id, String name, String scl, Version version, String who, List labels) { // Who is ignored in the BaseX implementation. var inputStream = new ReaderInputStream(new StringReader(scl), StandardCharsets.UTF_8); executeCommand(client -> { @@ -237,6 +251,7 @@ public void create(SclFileType type, UUID id, String name, String scl, Version v } @Override + @Transactional(REQUIRED) public void delete(SclFileType type, UUID id) { executeCommand(client -> { client.executeXQuery("db:delete('%s', '%s')".formatted(type, id)); @@ -245,6 +260,7 @@ public void delete(SclFileType type, UUID id) { } @Override + @Transactional(REQUIRED) public void delete(SclFileType type, UUID id, Version version) { executeCommand(client -> { client.executeXQuery("db:delete('%s', '%s')".formatted(type, createDocumentPath(id, version))); diff --git a/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java b/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java index b27430f1..46394e23 100644 --- a/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java +++ b/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java @@ -13,8 +13,12 @@ import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; import org.lfenergy.compas.scl.extensions.model.SclFileType; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; import javax.sql.DataSource; +import javax.transaction.Transactional; import java.sql.Array; +import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -22,9 +26,11 @@ import java.util.List; import java.util.UUID; +import static javax.transaction.Transactional.TxType.REQUIRED; +import static javax.transaction.Transactional.TxType.SUPPORTS; import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.*; -import static org.lfenergy.compas.scl.extensions.commons.CompasExtensionsConstants.COMPAS_SCL_EXTENSION_TYPE; +@ApplicationScoped public class CompasSclDataPostgreSQLRepository implements CompasSclDataRepository { private static final String ID_FIELD = "id"; private static final String MAJOR_VERSION_FIELD = "major_version"; @@ -38,18 +44,18 @@ public class CompasSclDataPostgreSQLRepository implements CompasSclDataRepositor private final DataSource dataSource; + @Inject public CompasSclDataPostgreSQLRepository(DataSource dataSource) { this.dataSource = dataSource; } @Override + @Transactional(SUPPORTS) public List list(SclFileType type) { var sql = """ select scl_file.id, scl_file.name, scl_file.major_version, scl_file.minor_version, scl_file.patch_version, - (xpath('//compas:Labels/compas:Label/text()' - , scl_data.compas_private - , ARRAY[ARRAY['compas', 'https://www.lfenergy.org/compas/extension/v1']])) as labels + scl_labels.label_values as labels from (select distinct on (scl_file.id) * from scl_file where scl_file.type = ? @@ -59,16 +65,14 @@ public List list(SclFileType type) { , scl_file.patch_version desc ) scl_file left outer join ( - select id, major_version, minor_version, patch_version, - unnest( - xpath( '(/scl:SCL/scl:Private[@type="' || ? || '"])[1]' - , scl_data::xml - , ARRAY[ARRAY['scl', 'http://www.iec.ch/61850/2003/SCL']])) as compas_private - from scl_file) scl_data - on scl_data.id = scl_file.id - and scl_data.major_version = scl_file.major_version - and scl_data.minor_version = scl_file.minor_version - and scl_data.patch_version = scl_file.patch_version + select scl_label.scl_id, scl_label.major_version, scl_label.minor_version, scl_label.patch_version, + array_agg(scl_label.label_value) AS label_values + from scl_label + group by scl_label.scl_id, scl_label.major_version, scl_label.minor_version, scl_label.patch_version) scl_labels + on scl_labels.scl_id = scl_file.id + and scl_labels.major_version = scl_file.major_version + and scl_labels.minor_version = scl_file.minor_version + and scl_labels.patch_version = scl_file.patch_version order by scl_file.name, scl_file.major_version, scl_file.minor_version, scl_file.patch_version """; @@ -76,7 +80,6 @@ left outer join ( try (var connection = dataSource.getConnection(); var stmt = connection.prepareStatement(sql)) { stmt.setString(1, type.name()); - stmt.setString(2, COMPAS_SCL_EXTENSION_TYPE); try (var resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -93,6 +96,7 @@ left outer join ( } @Override + @Transactional(SUPPORTS) public List listVersionsByUUID(SclFileType type, UUID id) { var sql = """ select scl_file.id, scl_file.name @@ -142,6 +146,7 @@ left outer join ( } @Override + @Transactional(SUPPORTS) public String findByUUID(SclFileType type, UUID id) { // Use the find meta info to retrieve info about the latest version. var metaInfo = findMetaInfoByUUID(type, id); @@ -150,6 +155,7 @@ public String findByUUID(SclFileType type, UUID id) { } @Override + @Transactional(SUPPORTS) public String findByUUID(SclFileType type, UUID id, Version version) { var sql = """ select scl_file.scl_data @@ -182,6 +188,7 @@ public String findByUUID(SclFileType type, UUID id, Version version) { } @Override + @Transactional(SUPPORTS) public boolean hasDuplicateSclName(SclFileType type, String name) { var sql = """ select distinct on (scl_file.id) * @@ -210,6 +217,7 @@ select distinct on (scl_file.id) * } @Override + @Transactional(SUPPORTS) public SclMetaInfo findMetaInfoByUUID(SclFileType type, UUID id) { var sql = """ select scl_file.id, scl_file.name, scl_file.major_version, scl_file.minor_version, scl_file.patch_version @@ -240,29 +248,64 @@ public SclMetaInfo findMetaInfoByUUID(SclFileType type, UUID id) { } @Override - public void create(SclFileType type, UUID id, String name, String scl, Version version, String who) { - var sql = """ + @Transactional(REQUIRED) + public void create(SclFileType type, UUID id, String name, String scl, Version version, String who, List labels) { + var createSclSQL = """ insert into scl_file(id, major_version, minor_version, patch_version, type, name, created_by, scl_data) values (?, ?, ?, ?, ?, ?, ?, ?) """; try (var connection = dataSource.getConnection(); - var stmt = connection.prepareStatement(sql)) { - stmt.setObject(1, id); - stmt.setInt(2, version.getMajorVersion()); - stmt.setInt(3, version.getMinorVersion()); - stmt.setInt(4, version.getPatchVersion()); - stmt.setString(5, type.name()); - stmt.setString(6, name); - stmt.setString(7, who); - stmt.setString(8, scl); - stmt.executeUpdate(); + var sclStmt = connection.prepareStatement(createSclSQL)) { + // First add the SCL XML File to the SCL_FILE Table. + sclStmt.setObject(1, id); + sclStmt.setInt(2, version.getMajorVersion()); + sclStmt.setInt(3, version.getMinorVersion()); + sclStmt.setInt(4, version.getPatchVersion()); + sclStmt.setString(5, type.name()); + sclStmt.setString(6, name); + sclStmt.setString(7, who); + sclStmt.setString(8, scl); + sclStmt.executeUpdate(); + + // Add the label to the database, if there are any. + createLabels(connection, id, version, labels); } catch (SQLException exp) { - throw new CompasSclDataServiceException(POSTGRES_INSERT_ERROR_CODE, "Error inserting SCL to database!", exp); + throw new CompasSclDataServiceException(POSTGRES_INSERT_ERROR_CODE, "Error adding SCL to database!", exp); + } + } + + void createLabels(Connection connection, UUID id, Version version, List labels) { + if (labels != null && !labels.isEmpty()) { + // Now add the extracted labels from the header to the SCL_LABEL table (in batch) + var createLabelSQL = """ + insert into scl_label(scl_id, major_version, minor_version, patch_version, label_value) + values (?, ?, ?, ?, ?) + """; + try (var labelsStmt = connection.prepareStatement(createLabelSQL)) { + labels.stream().distinct().forEach(label -> { + try { + labelsStmt.setObject(1, id); + labelsStmt.setInt(2, version.getMajorVersion()); + labelsStmt.setInt(3, version.getMinorVersion()); + labelsStmt.setInt(4, version.getPatchVersion()); + labelsStmt.setString(5, label); + labelsStmt.addBatch(); + labelsStmt.clearParameters(); + } catch (SQLException exp) { + throw new CompasSclDataServiceException(POSTGRES_INSERT_ERROR_CODE, "Error adding Label to database!", exp); + } + }); + // Execute the insert commands in batch now. + labelsStmt.executeBatch(); + } catch (SQLException exp) { + throw new CompasSclDataServiceException(POSTGRES_INSERT_ERROR_CODE, "Error adding Labels to database!", exp); + } } } @Override + @Transactional(REQUIRED) public void delete(SclFileType type, UUID id) { var sql = """ delete from scl_file @@ -281,6 +324,7 @@ public void delete(SclFileType type, UUID id) { } @Override + @Transactional(REQUIRED) public void delete(SclFileType type, UUID id, Version version) { var sql = """ delete from scl_file @@ -314,7 +358,7 @@ private String createVersion(ResultSet resultSet) throws SQLException { private List createLabelList(Array sqlArray) throws SQLException { var labelsList = new ArrayList(); // Sadly no generics in JDBC so we need to check what the Array() method returns. - if (sqlArray.getArray() instanceof Object[] objectArray) { + if (sqlArray != null && sqlArray.getArray() instanceof Object[] objectArray) { Arrays.stream(objectArray) .forEach(arrayObject -> // Just use toString() to return the value of the PostgreSQL Object. diff --git a/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_3__create_scl_label.sql b/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_3__create_scl_label.sql new file mode 100644 index 00000000..017cffe4 --- /dev/null +++ b/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_3__create_scl_label.sql @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: 2021 Alliander N.V. + * + * SPDX-License-Identifier: Apache-2.0 +*/ + +-- +-- Creating table to hold SCL Labels. Labels should be unique, because they are part of the PK. +-- +create table scl_label ( + scl_id uuid not null, + major_version smallint not null, + minor_version smallint not null, + patch_version smallint not null, + label_value varchar(255) not null, + primary key (scl_id, major_version, minor_version, patch_version, label_value), + constraint fk_label_to_scl + foreign key(scl_id, major_version, minor_version, patch_version) + references scl_file(id, major_version, minor_version, patch_version) + on delete cascade +); + +comment on table scl_label is 'Table holding all the labels. The combination id, version and label is unique (pk).'; +comment on column scl_label.scl_id is 'Unique ID generated according to standards'; +comment on column scl_label.major_version is 'Versioning according to Semantic Versioning (Major Position)'; +comment on column scl_label.minor_version is 'Versioning according to Semantic Versioning (Minor Position)'; +comment on column scl_label.patch_version is 'Versioning according to Semantic Versioning (Patch Position)'; +comment on column scl_label.label_value is 'The label stored'; diff --git a/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_4__fill_scl_label.sql b/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_4__fill_scl_label.sql new file mode 100644 index 00000000..9a8cb674 --- /dev/null +++ b/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_4__fill_scl_label.sql @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: 2021 Alliander N.V. + * + * SPDX-License-Identifier: Apache-2.0 +*/ + +-- +-- Creating table to hold SCL Labels. Labels should be unique, because they are part of the PK. +-- +insert into scl_label(scl_id, major_version, minor_version, patch_version, label_value) +select id, major_version, minor_version, patch_version, + unnest( + xpath( '(/scl:SCL/scl:Private[@type="compas_scl"]/compas:Labels/compas:Label/text())' + , scl_data::xml + , ARRAY[ARRAY['scl', 'http://www.iec.ch/61850/2003/SCL'], + ARRAY['compas', 'https://www.lfenergy.org/compas/extension/v1']]) + )::text as label_value + from scl_file + -- If the query is executed again, only if a file hasn't been processed will be included. + where not exists (select 1 + from scl_label + where scl_label.scl_id = scl_file.id + and scl_label.major_version = scl_file.major_version + and scl_label.minor_version = scl_file.minor_version + and scl_label.patch_version = scl_file.patch_version + ) + group by id, major_version, minor_version, patch_version, label_value -- make sure the label_value are unique per file. + order by id, major_version, minor_version, patch_version, label_value -- Just some nice order to insert rows diff --git a/repository-postgresql/src/test/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepositoryTest.java b/repository-postgresql/src/test/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepositoryTest.java index a9e32f14..6f4c54e1 100644 --- a/repository-postgresql/src/test/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepositoryTest.java +++ b/repository-postgresql/src/test/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepositoryTest.java @@ -37,7 +37,7 @@ void hasDuplicateSclName_WhenUsingSclNameThatHasBeenUsedYet_ThenDuplicateIsFound var expectedVersion = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, expectedVersion, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, expectedVersion, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, expectedVersion, WHO, LABELS); assertTrue(getRepository().hasDuplicateSclName(TYPE, NAME_1)); } diff --git a/repository/pom.xml b/repository/pom.xml index e9fa8d26..16ce3fb9 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -45,6 +45,10 @@ SPDX-License-Identifier: Apache-2.0 jakarta.transaction jakarta.transaction-api + + jakarta.enterprise + jakarta.enterprise.cdi-api + org.slf4j diff --git a/repository/src/main/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceErrorCode.java b/repository/src/main/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceErrorCode.java index ba296fc7..fc648e3e 100644 --- a/repository/src/main/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceErrorCode.java +++ b/repository/src/main/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceErrorCode.java @@ -8,7 +8,6 @@ public class CompasSclDataServiceErrorCode { throw new UnsupportedOperationException("CompasSclDataRepositoryErrorCode class"); } - public static final String UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE = "SDS-0001"; public static final String CREATION_ERROR_CODE = "SDS-0002"; public static final String UNMARSHAL_ERROR_CODE = "SDS-0003"; public static final String HEADER_NOT_FOUND_ERROR_CODE = "SDS-0004"; diff --git a/repository/src/main/java/org/lfenergy/compas/scl/data/model/Version.java b/repository/src/main/java/org/lfenergy/compas/scl/data/model/Version.java index d52ef126..10ea5948 100644 --- a/repository/src/main/java/org/lfenergy/compas/scl/data/model/Version.java +++ b/repository/src/main/java/org/lfenergy/compas/scl/data/model/Version.java @@ -4,14 +4,13 @@ package org.lfenergy.compas.scl.data.model; import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.lfenergy.compas.scl.data.exception.CompasSclDataServiceException; import java.util.Objects; -import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE; - @Schema(description = "Presenting the version logic used in CoMPAS.") -public class Version { +public class Version implements Comparable { + public static final String PATTERN = "([1-9]\\d*)\\.(\\d+)\\.(\\d+)"; + @Schema(description = "The major version.", example = "2") private final int majorVersion; @Schema(description = "The minor version.", example = "1") @@ -40,7 +39,7 @@ private void validate(String version) { if (version == null || version.isEmpty()) { throw new IllegalArgumentException("Version can't be null or empty"); } - if (!version.matches("([1-9]\\d*)\\.(\\d+)\\.(\\d+)")) { + if (!version.matches(PATTERN)) { throw new IllegalArgumentException("Version is in the wrong format. Must consist of 3 number separated by dot (1.3.5)"); } } @@ -50,16 +49,11 @@ public Version getNextVersion(ChangeSetType changeSetType) { throw new IllegalArgumentException("ChangeSetType can't be null or empty"); } - switch (changeSetType) { - case MAJOR: - return new Version(majorVersion + 1, 0, 0); - case MINOR: - return new Version(majorVersion, minorVersion + 1, 0); - case PATCH: - return new Version(majorVersion, minorVersion, patchVersion + 1); - default: - throw new CompasSclDataServiceException(UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE, "Unhandled ChangeSetType " + changeSetType); - } + return switch (changeSetType) { + case MAJOR -> new Version(majorVersion + 1, 0, 0); + case MINOR -> new Version(majorVersion, minorVersion + 1, 0); + case PATCH -> new Version(majorVersion, minorVersion, patchVersion + 1); + }; } public int getMajorVersion() { @@ -92,6 +86,17 @@ public boolean equals(final Object obj) { } } + @Override + public int compareTo(Version otherVersion) { + if (this.majorVersion == otherVersion.getMajorVersion()) { + if (this.minorVersion == otherVersion.getMinorVersion()) { + return Integer.compare(this.patchVersion, otherVersion.getPatchVersion()); + } + return Integer.compare(this.minorVersion, otherVersion.getMinorVersion()); + } + return Integer.compare(this.majorVersion, otherVersion.getMajorVersion()); + } + @Override public String toString() { return majorVersion + "." + minorVersion + "." + patchVersion; diff --git a/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java b/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java index 49674960..46ade466 100644 --- a/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java +++ b/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java @@ -9,13 +9,9 @@ import org.lfenergy.compas.scl.data.model.Version; import org.lfenergy.compas.scl.extensions.model.SclFileType; -import javax.transaction.Transactional; import java.util.List; import java.util.UUID; -import static javax.transaction.Transactional.TxType.REQUIRED; -import static javax.transaction.Transactional.TxType.SUPPORTS; - /** * Repository class that will be used to handle SCL File for a specific type of storage. * The repository class needs to be able to create and delete entries and find/list entries. @@ -27,7 +23,6 @@ public interface CompasSclDataRepository { * @param type The type of SCL to search for. * @return The list of entries found for the passed type. */ - @Transactional(SUPPORTS) List list(SclFileType type); /** @@ -37,7 +32,6 @@ public interface CompasSclDataRepository { * @param id The ID of the SCL to search for. * @return The list of versions found for that specific sCl Entry. */ - @Transactional(SUPPORTS) List listVersionsByUUID(SclFileType type, UUID id); /** @@ -47,7 +41,6 @@ public interface CompasSclDataRepository { * @param id The ID of the SCL to search for. * @return The SCL XML File Content that is search for. */ - @Transactional(SUPPORTS) String findByUUID(SclFileType type, UUID id); /** @@ -57,7 +50,6 @@ public interface CompasSclDataRepository { * @param id The ID of the SCL to search for. * @return The Meta Info of SCL Entry that is search for. */ - @Transactional(SUPPORTS) SclMetaInfo findMetaInfoByUUID(SclFileType type, UUID id); /** @@ -68,7 +60,6 @@ public interface CompasSclDataRepository { * @param version The version of the ScL to search for. * @return The SCL XML File Content that is search for. */ - @Transactional(SUPPORTS) String findByUUID(SclFileType type, UUID id, Version version); /** @@ -78,7 +69,6 @@ public interface CompasSclDataRepository { * @param name The name of the SCL used for checking duplicates. * @return True if name is already used by another SCL File of the same File type, otherwise false. */ - @Transactional(SUPPORTS) boolean hasDuplicateSclName(SclFileType type, String name); /** @@ -94,26 +84,24 @@ public interface CompasSclDataRepository { * @param scl The SCL XML File content to store. * @param version The version of the new entry to be created. * @param who The user that created the new entry. + * @param labels The list of Labels extracted from the SCL XML File. */ - @Transactional(REQUIRED) - void create(SclFileType type, UUID id, String name, String scl, Version version, String who); + void create(SclFileType type, UUID id, String name, String scl, Version version, String who, List labels); /** - * Delete all versions for a specific SCL File using it's ID. + * Delete all versions for a specific SCL File using its ID. * * @param type The type of SCL where to find the SCL File * @param id The ID of the SCL File to delete. */ - @Transactional(REQUIRED) void delete(SclFileType type, UUID id); /** - * Delete passed versions for a specific SCL File using it's ID. + * Delete passed versions for a specific SCL File using its ID. * * @param type The type of SCL where to find the SCL File * @param id The ID of the SCL File to delete. * @param version The version of that SCL File to delete. */ - @Transactional(REQUIRED) void delete(SclFileType type, UUID id, Version version); } diff --git a/repository/src/main/java/org/lfenergy/compas/scl/data/util/SclElementProcessor.java b/repository/src/main/java/org/lfenergy/compas/scl/data/util/SclElementProcessor.java index 2453cf3c..88d6db2c 100644 --- a/repository/src/main/java/org/lfenergy/compas/scl/data/util/SclElementProcessor.java +++ b/repository/src/main/java/org/lfenergy/compas/scl/data/util/SclElementProcessor.java @@ -8,6 +8,7 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; +import javax.enterprise.context.ApplicationScoped; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -22,6 +23,7 @@ * Support class to work with the SCL XML in a generic way as W3C Element/Node class. * This way multiple versions of the SCL XSD can easily be supported. */ +@ApplicationScoped public class SclElementProcessor { /** * Search for the SCL Header in the SCL Root Element and return that. @@ -96,9 +98,50 @@ public Element addCompasElement(Element compasPrivate, String localName, String return element; } + /** + * The method will remove all newer Hitem Element, including the version passed from the History Element. + * It will search for Hitem Element where the Revision Attribute is empty and the version has the + * pattern "Major version"."Minor version"."Patch version". + *

+ * If the version is the same or newer the Hitem will be removed from the History Element. + * + * @param headerElement The Header Element containing the History Items. + * @param version The version from which to remove. + */ + public void cleanupHistoryItem(Element headerElement, Version version) { + var history = getChildNodesByName(headerElement, SCL_HISTORY_ELEMENT_NAME, SCL_NS_URI).stream().findFirst(); + history.ifPresent(historyElement -> + getChildNodesByName(historyElement, SCL_HITEM_ELEMENT_NAME, SCL_NS_URI) + .stream() + .filter(hItemElement -> hItemElement.getAttribute("revision").isBlank()) + .forEach(hItemElement -> { + if (shouldRemoveHItem(hItemElement, version)) { + historyElement.removeChild(hItemElement); + } + }) + ); + } + + /** + * Check if the version uses the pattern that matches the one used by CoMPAS and if this is the case + * compare the two versions and determine if the HItem version is smaller or the same as the new one + * being created. + * + * @param hItemElement The HItem Element to check the version attribute from. + * @param version The new version that will be created. + * @return True if the HItem has a smaller or the same version and should be removed. + */ + boolean shouldRemoveHItem(Element hItemElement, Version version) { + var hItemVersion = hItemElement.getAttribute("version"); + if (hItemVersion.isBlank() || !hItemVersion.matches(Version.PATTERN)) { + return false; + } + return version.compareTo(new Version(hItemVersion)) <= 0; + } + /** * Add a Hitem to the History Element from the Header. If the Header doesn't contain a History Element - * this Element will also be created. The revision attribute will be empty, the when will be set to the + * this Element will also be created. The revision attribute will be empty, the "when" will be set to the * current date. * * @param header The Header Element from SCL under which the History Element can be found/added. @@ -138,7 +181,7 @@ public Element addHistoryItem(Element header, String who, String fullmessage, Ve */ public Optional getAttributeValue(Element element, String attributeName) { var value = element.getAttribute(attributeName); - return (value != null && !value.isBlank()) ? Optional.of(value) : Optional.empty(); + return (!value.isBlank()) ? Optional.of(value) : Optional.empty(); } /** diff --git a/repository/src/test/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceExceptionTest.java b/repository/src/test/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceExceptionTest.java index 1dce9665..5ba8184d 100644 --- a/repository/src/test/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceExceptionTest.java +++ b/repository/src/test/java/org/lfenergy/compas/scl/data/exception/CompasSclDataServiceExceptionTest.java @@ -6,14 +6,14 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE; +import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.CREATION_ERROR_CODE; class CompasSclDataServiceExceptionTest { @Test void constructor_WhenCalledWithOnlyMessage_ThenMessageCanBeRetrieved() { String expectedMessage = "The message"; CompasSclDataServiceException exception = - new CompasSclDataServiceException(UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE, expectedMessage); + new CompasSclDataServiceException(CREATION_ERROR_CODE, expectedMessage); assertEquals(expectedMessage, exception.getMessage()); } @@ -23,7 +23,7 @@ void constructor_WhenCalledWithCauseAndMessage_ThenCauseAndMessageCanBeRetrieved String expectedMessage = "The message"; Exception expectedCause = new RuntimeException(); CompasSclDataServiceException exception = - new CompasSclDataServiceException(UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE, expectedMessage, expectedCause); + new CompasSclDataServiceException(CREATION_ERROR_CODE, expectedMessage, expectedCause); assertEquals(expectedMessage, exception.getMessage()); assertEquals(expectedCause, exception.getCause()); diff --git a/repository/src/test/java/org/lfenergy/compas/scl/data/model/VersionTest.java b/repository/src/test/java/org/lfenergy/compas/scl/data/model/VersionTest.java index 90f4a9c8..ff96f076 100644 --- a/repository/src/test/java/org/lfenergy/compas/scl/data/model/VersionTest.java +++ b/repository/src/test/java/org/lfenergy/compas/scl/data/model/VersionTest.java @@ -13,7 +13,6 @@ void constructor_WhenCalledWithNullOrEmptyValue_ThenExceptionThrown() { var expectedMessage = "Version can't be null or empty"; assertThrows(IllegalArgumentException.class, () -> new Version(null), expectedMessage); - assertThrows(IllegalArgumentException.class, () -> new Version(""), expectedMessage); } @@ -93,4 +92,25 @@ void equals_WhenTwoSameObject_ThenEqualsShouldAlsoBeTheSame() { void toString_WhenCalled_ThenCorrectStringReturned() { assertEquals("1.5.8", new Version(1, 5, 8).toString()); } + + @Test + void compareTo_WhenWhenMajorVersionAreDifferent_ThenCorrectResult() { + assertEquals(-1, new Version("1.0.0").compareTo(new Version("2.0.0"))); + assertEquals(0, new Version("1.0.0").compareTo(new Version("1.0.0"))); + assertEquals(1, new Version("2.0.0").compareTo(new Version("1.0.0"))); + } + + @Test + void compareTo_WhenWhenMinorVersionAreDifferent_ThenCorrectResult() { + assertEquals(-1, new Version("1.1.0").compareTo(new Version("1.2.0"))); + assertEquals(0, new Version("1.1.0").compareTo(new Version("1.1.0"))); + assertEquals(1, new Version("1.2.0").compareTo(new Version("1.1.0"))); + } + + @Test + void compareTo_WhenWhenPathVersionAreDifferent_ThenCorrectResult() { + assertEquals(-1, new Version("1.1.1").compareTo(new Version("1.1.2"))); + assertEquals(0, new Version("1.1.1").compareTo(new Version("1.1.1"))); + assertEquals(1, new Version("1.1.2").compareTo(new Version("1.1.1"))); + } } \ No newline at end of file diff --git a/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java b/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java index 51cd112a..f16b2fb3 100644 --- a/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java +++ b/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java @@ -12,6 +12,7 @@ import org.lfenergy.compas.scl.data.util.SclElementProcessor; import org.lfenergy.compas.scl.extensions.model.SclFileType; +import java.util.List; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; @@ -26,6 +27,7 @@ public abstract class AbstractCompasSclDataRepositoryTest { protected static final String NAME_1 = "SCL-NAME1"; protected static final String NAME_2 = "SCL-NAME2"; protected static final String WHO = "User 1"; + protected static final List LABELS = List.of("Label-1", "Label-2"); // Use different types, so tests don't conflict with each other. protected static final SclFileType LIST1_TYPE = SclFileType.CID; @@ -51,7 +53,7 @@ void list_WhenRecordAdded_ThenRecordFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readCompasSCL(uuid, version, NAME_1); - getRepository().create(LIST2_TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(LIST2_TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var items = getRepository().list(LIST2_TYPE); @@ -71,10 +73,10 @@ void list_WhenTwoRecordAdded_ThenBothRecordsFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(LIST3_TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(LIST3_TYPE, uuid, NAME_1, scl, version, WHO, LABELS); uuid = UUID.randomUUID(); scl = readCompasSCL(uuid, version, NAME_2); - getRepository().create(LIST3_TYPE, uuid, NAME_2, scl, version, WHO); + getRepository().create(LIST3_TYPE, uuid, NAME_2, scl, version, WHO, LABELS); var items = getRepository().list(LIST3_TYPE); @@ -87,10 +89,10 @@ void list_WhenTwoVersionsOfARecordAdded_ThenLatestRecordFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readCompasSCL(uuid, version, NAME_1); - getRepository().create(LIST4_TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(LIST4_TYPE, uuid, NAME_1, scl, version, WHO, LABELS); version = new Version(1, 1, 0); scl = readCompasSCL(uuid, version, NAME_2); - getRepository().create(LIST4_TYPE, uuid, NAME_2, scl, version, WHO); + getRepository().create(LIST4_TYPE, uuid, NAME_2, scl, version, WHO, LABELS); var items = getRepository().list(LIST4_TYPE); @@ -121,10 +123,10 @@ void listVersionsByUUID_WhenTwoVersionsOfARecordAdded_ThenAllRecordAreFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); version = new Version(1, 1, 0); scl = readStandardSCL(uuid, version, NAME_2); - getRepository().create(TYPE, uuid, NAME_2, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_2, scl, version, WHO, LABELS); var items = getRepository().listVersionsByUUID(TYPE, uuid); @@ -156,10 +158,10 @@ void findByUUID_WhenTwoVersionsOfARecordAdded_ThenLastRecordIsFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); version = new Version(1, 1, 0); scl = readStandardSCL(uuid, version, NAME_2); - getRepository().create(TYPE, uuid, NAME_2, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_2, scl, version, WHO, LABELS); var foundScl = getRepository().findByUUID(TYPE, uuid); @@ -175,7 +177,7 @@ void findByUUIDWithVersion_WhenCalledWithUnknownVersion_ThenExceptionIsThrown() var scl = readStandardSCL(uuid, version, NAME_1); var repository = getRepository(); - repository.create(TYPE, uuid, NAME_1, scl, version, WHO); + repository.create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var unknownVersion = new Version(1, 1, 1); var exception = assertThrows(CompasNoDataFoundException.class, @@ -188,10 +190,10 @@ void findByUUIDWithVersion_WhenTwoVersionsOfARecordAdded_ThenCorrectRecordIsFoun var expectedVersion = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, expectedVersion, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, expectedVersion, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, expectedVersion, WHO, LABELS); var version = new Version(1, 1, 0); scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var foundScl = getRepository().findByUUID(TYPE, uuid, expectedVersion); @@ -205,7 +207,7 @@ void hasDuplicateSclName_WhenUsingSclNameThatHasNotBeenUsedYet_ThenNoDuplicateIs var expectedVersion = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, expectedVersion, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, expectedVersion, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, expectedVersion, WHO, LABELS); assertFalse(getRepository().hasDuplicateSclName(TYPE, "Some other name")); } @@ -215,10 +217,10 @@ void findMetaInfoByUUID_WhenTwoVersionsOfARecordAdded_ThenLastRecordIsFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); version = new Version(1, 1, 0); scl = readStandardSCL(uuid, version, NAME_2); - getRepository().create(TYPE, uuid, NAME_2, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_2, scl, version, WHO, LABELS); var metaInfo = getRepository().findMetaInfoByUUID(TYPE, uuid); @@ -243,7 +245,7 @@ void createAndFind_WhenSclAdded_ThenScLStoredAndLastVersionCanBeFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var foundScl = getRepository().findByUUID(TYPE, uuid); @@ -257,11 +259,11 @@ void createAndFind_WhenMoreVersionOfSclAdded_ThenDefaultSCLLastVersionReturned() var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var nextVersion = version.getNextVersion(ChangeSetType.MAJOR); var nextScl = readStandardSCL(uuid, nextVersion, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, nextScl, nextVersion, WHO); + getRepository().create(TYPE, uuid, NAME_1, nextScl, nextVersion, WHO, LABELS); var foundScl = getRepository().findByUUID(TYPE, uuid); @@ -275,11 +277,11 @@ void createAndFind_WhenMoreVersionOfSCLAdded_ThenSCLOldVersionCanBeFound() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); var scl = readStandardSCL(uuid, version, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO); + getRepository().create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var nextVersion = version.getNextVersion(ChangeSetType.MAJOR); var nextScl = readStandardSCL(uuid, nextVersion, NAME_1); - getRepository().create(TYPE, uuid, NAME_1, nextScl, nextVersion, WHO); + getRepository().create(TYPE, uuid, NAME_1, nextScl, nextVersion, WHO, LABELS); var foundScl = getRepository().findByUUID(TYPE, uuid, version); @@ -292,10 +294,10 @@ void createAndFind_WhenMoreVersionOfSCLAdded_ThenSCLOldVersionCanBeFound() { void createAndDelete_WhenSclAddedAndDelete_ThenScLStoredAndRemoved() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); - var scl = readStandardSCL(uuid, version, NAME_1); + var scl = readCompasSCL(uuid, version, NAME_1); var repository = getRepository(); - repository.create(TYPE, uuid, NAME_1, scl, version, WHO); + repository.create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var foundScl = repository.findByUUID(TYPE, uuid); assertNotNull(foundScl); assertEquals(getIdFromHeader(scl), getIdFromHeader(foundScl)); @@ -310,17 +312,17 @@ void createAndDelete_WhenSclAddedAndDelete_ThenScLStoredAndRemoved() { void createAndDeleteAll_WhenSclAddedAndDelete_ThenScLStoredAndRemoved() { var version = new Version(1, 0, 0); var uuid = UUID.randomUUID(); - var scl = readStandardSCL(uuid, version, NAME_1); + var scl = readCompasSCL(uuid, version, NAME_1); var repository = getRepository(); - repository.create(TYPE, uuid, NAME_1, scl, version, WHO); + repository.create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); var foundScl = repository.findByUUID(TYPE, uuid); assertNotNull(foundScl); assertEquals(getIdFromHeader(scl), getIdFromHeader(foundScl)); assertEquals(getVersionFromHeader(scl), getVersionFromHeader(foundScl)); version = version.getNextVersion(ChangeSetType.MAJOR); - repository.create(TYPE, uuid, NAME_1, scl, version, WHO); + repository.create(TYPE, uuid, NAME_1, scl, version, WHO, LABELS); foundScl = repository.findByUUID(TYPE, uuid); assertNotNull(foundScl); assertEquals(getIdFromHeader(scl), getIdFromHeader(foundScl)); diff --git a/repository/src/test/java/org/lfenergy/compas/scl/data/util/SclElementProcessorTest.java b/repository/src/test/java/org/lfenergy/compas/scl/data/util/SclElementProcessorTest.java index 8c88e0c2..d5930ffc 100644 --- a/repository/src/test/java/org/lfenergy/compas/scl/data/util/SclElementProcessorTest.java +++ b/repository/src/test/java/org/lfenergy/compas/scl/data/util/SclElementProcessorTest.java @@ -9,6 +9,9 @@ import org.lfenergy.compas.scl.data.model.Version; import org.w3c.dom.Element; +import java.util.List; +import java.util.Optional; + import static org.junit.jupiter.api.Assertions.*; import static org.lfenergy.compas.scl.data.SclDataServiceConstants.*; import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.HEADER_NOT_FOUND_ERROR_CODE; @@ -188,6 +191,73 @@ void getAttributeValue_WhenCalledForNonExistingAttribute_ThenOptionalEmptyReturn assertFalse(result.isPresent()); } + @Test + void cleanupHistoryItem_WhenCalledWithVersion_ThenSameAndNewerVersionsAreRemoved() { + var scl = readSCL("scl_cleanup_history.scd"); + + assertEquals(7, getHItems(scl).size()); + processor.cleanupHistoryItem(processor.getSclHeader(scl).orElseThrow(), new Version("1.0.2")); + assertEquals(5, getHItems(scl).size()); + } + + @Test + void shouldRemoveHItem_WhenCalledWithInvalidVersion_ThenFalseReturned() { + var scl = readSCL("scl_cleanup_history.scd"); + var hItem = getHItem(scl, "Siemens"); + + assertFalse(processor.shouldRemoveHItem(hItem, new Version("1.0.2"))); + } + + @Test + void shouldRemoveHItem_WhenCalledWithEmptyVersion_ThenFalseReturned() { + var scl = readSCL("scl_cleanup_history.scd"); + var hItem = getHItem(scl, "Empty"); + + assertFalse(processor.shouldRemoveHItem(hItem, new Version("1.0.2"))); + } + + @Test + void shouldRemoveHItem_WhenCalledWithOlderVersion_ThenFalseReturned() { + var scl = readSCL("scl_cleanup_history.scd"); + var hItem = getHItem(scl, "Created"); + + assertFalse(processor.shouldRemoveHItem(hItem, new Version("1.0.2"))); + } + + @Test + void shouldRemoveHItem_WhenCalledWithSameVersion_ThenTrueReturned() { + var scl = readSCL("scl_cleanup_history.scd"); + var hItem = getHItem(scl, "Updated 1"); + + assertTrue(processor.shouldRemoveHItem(hItem, new Version("1.0.2"))); + } + + @Test + void shouldRemoveHItem_WhenCalledWithNewerVersion_ThenTrueReturned() { + var scl = readSCL("scl_cleanup_history.scd"); + var hItem = getHItem(scl, "Updated 2"); + + assertTrue(processor.shouldRemoveHItem(hItem, new Version("1.0.2"))); + } + + private List getHItems(Element scl) { + return processor.getSclHeader(scl) + .map(header -> processor.getChildNodeByName(header, SCL_HISTORY_ELEMENT_NAME, SCL_NS_URI)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(history -> processor.getChildNodesByName(history, SCL_HITEM_ELEMENT_NAME, SCL_NS_URI)) + .stream() + .flatMap(List::stream) + .toList(); + } + + private Element getHItem(Element scl, String what) { + return getHItems(scl).stream() + .filter(element -> element.getAttribute("what").equals(what)) + .findFirst() + .get(); + } + private Element readSCL(String sclFilename) { var inputStream = getClass().getResourceAsStream("/scl/" + sclFilename); assert inputStream != null; diff --git a/repository/src/test/resources/scl/scl_cleanup_history.scd b/repository/src/test/resources/scl/scl_cleanup_history.scd new file mode 100644 index 00000000..8f7dfcf3 --- /dev/null +++ b/repository/src/test/resources/scl/scl_cleanup_history.scd @@ -0,0 +1,17 @@ + + + + +

+ + + + + + + + + +
+ \ No newline at end of file diff --git a/repository/src/test/resources/scl/scl_compas.scd b/repository/src/test/resources/scl/scl_compas.scd index 222c7d2c..6753e424 100644 --- a/repository/src/test/resources/scl/scl_compas.scd +++ b/repository/src/test/resources/scl/scl_compas.scd @@ -9,6 +9,8 @@ Label-1 Label-2 + Label-2 + Label-2
diff --git a/service/pom.xml b/service/pom.xml index dec5ff7b..09f24b63 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -28,6 +28,11 @@ SPDX-License-Identifier: Apache-2.0 scl-extension + + jakarta.enterprise + jakarta.enterprise.cdi-api + + org.mockito diff --git a/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java b/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java index 6621d8ea..fbae11dd 100644 --- a/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java +++ b/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java @@ -3,41 +3,342 @@ // SPDX-License-Identifier: Apache-2.0 package org.lfenergy.compas.scl.data.service; +import org.lfenergy.compas.core.commons.ElementConverter; +import org.lfenergy.compas.core.commons.exception.CompasException; +import org.lfenergy.compas.scl.data.exception.CompasNoDataFoundException; +import org.lfenergy.compas.scl.data.exception.CompasSclDataServiceException; import org.lfenergy.compas.scl.data.model.ChangeSetType; import org.lfenergy.compas.scl.data.model.HistoryItem; import org.lfenergy.compas.scl.data.model.Item; import org.lfenergy.compas.scl.data.model.Version; +import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; +import org.lfenergy.compas.scl.data.util.SclElementProcessor; import org.lfenergy.compas.scl.extensions.model.SclFileType; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; import javax.transaction.Transactional; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import static javax.transaction.Transactional.TxType.REQUIRED; import static javax.transaction.Transactional.TxType.SUPPORTS; +import static org.lfenergy.compas.scl.data.SclDataServiceConstants.*; +import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.*; +import static org.lfenergy.compas.scl.extensions.commons.CompasExtensionsConstants.*; -public interface CompasSclDataService { +/** + * Service class that will be using a Repository instance to retrieve, create, update, delete SCL XML Files. + * These methods contain standard behaviour that is executed for every type of repository. + */ +@ApplicationScoped +public class CompasSclDataService { + private final CompasSclDataRepository repository; + private final ElementConverter converter; + private final SclElementProcessor sclElementProcessor; + + @Inject + public CompasSclDataService(CompasSclDataRepository repository, ElementConverter converter, + SclElementProcessor sclElementProcessor) { + this.repository = repository; + this.converter = converter; + this.sclElementProcessor = sclElementProcessor; + } + + /** + * List the latest version of all SCL XML Files for a specific type. + * + * @param type The type to search for. + * @return The List of Items found. + */ @Transactional(SUPPORTS) - List list(SclFileType type); + public List list(SclFileType type) { + return repository.list(type); + } + /** + * Search for all versions of a specific SCL XML File (using the UUID) for a specific type. + * + * @param type The type to search for. + * @param id The UUID of the record to search for. + * @return The list of versions found. + */ @Transactional(SUPPORTS) - List listVersionsByUUID(SclFileType type, UUID id); + public List listVersionsByUUID(SclFileType type, UUID id) { + var items = repository.listVersionsByUUID(type, id); + if (items.isEmpty()) { + var message = String.format("No versions found for type '%s' with ID '%s'", type, id); + throw new CompasNoDataFoundException(message); + } + return items; + } + /** + * Get the latest version of a specific SCL XML File (using the UUID) for a specific type. + * + * @param type The type to search for. + * @param id The UUID of the record to search for. + * @return The latest version of the SCL XML Files. + */ @Transactional(SUPPORTS) - String findByUUID(SclFileType type, UUID id); + public String findByUUID(SclFileType type, UUID id) { + return repository.findByUUID(type, id); + } + /** + * Get a specific version of a specific SCL XML File (using the UUID) for a specific type. + * + * @param type The type to search for. + * @param id The UUID of the record to search for. + * @param version The version to search for. + * @return The found version of the SCL XML Files. + */ @Transactional(SUPPORTS) - String findByUUID(SclFileType type, UUID id, Version version); + public String findByUUID(SclFileType type, UUID id, Version version) { + return repository.findByUUID(type, id, version); + } + /** + * Create a new record for the passed SCL XML File with the passed name for a specific type. + * A new UUID is generated to be set and also the CoMPAS Private Elements are added on SCL Level. + * + * @param type The type to create it for. + * @param name The name that will be stored as CoMPAS Private extension. + * @param comment Some comments that will be added to the THistory entry being added. + * @param sclData The SCL XML File to store. + * @return The ID of the new created SCL XML File in the database. + */ @Transactional(REQUIRED) - String create(SclFileType type, String name, String who, String comment, String sclData); + public String create(SclFileType type, String name, String who, String comment, String sclData) { + var scl = converter.convertToElement(new BufferedInputStream(new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8))), SCL_ELEMENT_NAME, SCL_NS_URI); + if (scl == null) { + throw new CompasException(NO_SCL_ELEMENT_FOUND_ERROR_CODE, "No valid SCL found in the passed SCL Data."); + } + + if (repository.hasDuplicateSclName(type, name)) { + throw new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, "Given name of SCL File already used."); + } + + // A unique ID is generated to store it under. + var id = UUID.randomUUID(); + // When the SCL is created the version will be set to 1.0.0 + var version = new Version(1, 0, 0); + + // Update the Header of the SCL (or create if not exists.) + var header = createOrUpdateHeader(scl, id, version); + sclElementProcessor.cleanupHistoryItem(header, version); + sclElementProcessor.addHistoryItem(header, who, createMessage("SCL created", comment), version); + + // Update or add the Compas Private Element to the SCL File. + setSclCompasPrivateElement(scl, name, type); + + // Validate the labels from the Private Element, if there are any. + var labels = validateLabels(scl); + var newSclData = converter.convertToString(scl); + repository.create(type, id, name, newSclData, version, who, labels); + return newSclData; + } + + /** + * Create a new version of a specific SCL XML File. The content will be the passed SCL XML File. + * The UUID and new version (depending on the passed ChangeSetType) are set and + * the CoMPAS Private elements will also be copied, the SCL Name will only be copied if it isn't available in the + * SCL XML File. + * + * @param type The type to update it for. + * @param id The UUID of the record to update. + * @param changeSetType The type of change to determine the new version. + * @param comment Some comments that will be added to the THistory entry being added. + * @param sclData The SCL XML File with the updated content. + */ @Transactional(REQUIRED) - String update(SclFileType type, UUID id, ChangeSetType changeSetType, String who, String comment, String sclData); + public String update(SclFileType type, UUID id, ChangeSetType changeSetType, String who, String comment, String sclData) { + var scl = converter.convertToElement(new BufferedInputStream(new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8))), SCL_ELEMENT_NAME, SCL_NS_URI); + if (scl == null) { + throw new CompasException(NO_SCL_ELEMENT_FOUND_ERROR_CODE, "No valid SCL found in the passed SCL Data."); + } + + var currentSclMetaInfo = repository.findMetaInfoByUUID(type, id); + var newFileName = getFilenameFromXML(scl); + + if (newFileName.isPresent() + && !newFileName.get().equals(currentSclMetaInfo.getName()) + && repository.hasDuplicateSclName(type, newFileName.get())) { + throw new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, "Given name of SCL File already used."); + } + + // We always add a new version to the database, so add version record to the SCL and create a new record. + var version = new Version(currentSclMetaInfo.getVersion()); + version = version.getNextVersion(changeSetType); + + // Update the Header of the SCL (or create if not exists.) + var header = createOrUpdateHeader(scl, id, version); + sclElementProcessor.cleanupHistoryItem(header, version); + sclElementProcessor.addHistoryItem(header, who, createMessage("SCL updated", comment), version); + + // Update or add the Compas Private Element to the SCL File. + var newSclName = newFileName.orElse(currentSclMetaInfo.getName()); + setSclCompasPrivateElement(scl, newSclName, type); + // Validate the labels from the Private Element, if there are any. + var labels = validateLabels(scl); + + var newSclData = converter.convertToString(scl); + repository.create(type, id, newSclName, newSclData, version, who, labels); + return newSclData; + } + + /** + * Delete all versions for a specific SCL File using it's ID. + * + * @param type The type of SCL where to find the SCL File + * @param id The ID of the SCL File to delete. + */ @Transactional(REQUIRED) - void delete(SclFileType type, UUID id); + public void delete(SclFileType type, UUID id) { + repository.delete(type, id); + } + /** + * Delete passed versions for a specific SCL File using it's ID. + * + * @param type The type of SCL where to find the SCL File + * @param id The ID of the SCL File to delete. + * @param version The version of that SCL File to delete. + */ @Transactional(REQUIRED) - void delete(SclFileType type, UUID id, Version version); + public void delete(SclFileType type, UUID id, Version version) { + repository.delete(type, id, version); + } + + /** + * Retrieve the Header from the SCL Fiel or create one if it doesn't exist and set the ID and + * version on the Header. + * + * @param scl The SCL file to edit. + * @param id The ID of the SCL File. + * @param version The Version of the SCL File. + * @return The header that was found or created. + */ + private Element createOrUpdateHeader(Element scl, UUID id, Version version) { + var header = sclElementProcessor.getSclHeader(scl) + .orElseGet(() -> sclElementProcessor.addSclHeader(scl)); + header.setAttribute(SCL_ID_ATTR, id.toString()); + header.setAttribute(SCL_VERSION_ATTR, version.toString()); + return header; + } + + /** + * Retrieve the CoMPAS SCL Filename from the private element of CoMPAS. + * + * @param scl The SCL file to edit. + * @return If there was a private SclName the value of this tag. + */ + private Optional getFilenameFromXML(Element scl) { + return sclElementProcessor.getCompasPrivate(scl) + .stream() + .map(compasPrivate -> sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_SCL_NAME_EXTENSION, + COMPAS_EXTENSION_NS_URI)) + .flatMap(Optional::stream) + .map(Element::getTextContent) + .findFirst(); + } + + /** + * Create/update the CoMPAS private element on the SCL Element for the given file. + * + * @param scl The SCL file to edit. + * @param name The name to add. + * @param fileType The file type to add. + */ + private void setSclCompasPrivateElement(Element scl, String name, SclFileType fileType) { + var compasPrivate = sclElementProcessor.getCompasPrivate(scl) + .orElseGet(() -> sclElementProcessor.addCompasPrivate(scl)); + + sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_SCL_NAME_EXTENSION, COMPAS_EXTENSION_NS_URI) + .ifPresentOrElse( + // Override the value of the element with the name passed. + element -> element.setTextContent(name), + () -> sclElementProcessor.addCompasElement(compasPrivate, COMPAS_SCL_NAME_EXTENSION, name) + ); + + // Always set the file type as private element. + sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_SCL_FILE_TYPE_EXTENSION, COMPAS_EXTENSION_NS_URI) + .ifPresentOrElse( + element -> element.setTextContent(fileType.toString()), + () -> sclElementProcessor.addCompasElement(compasPrivate, COMPAS_SCL_FILE_TYPE_EXTENSION, + fileType.toString()) + ); + } + + /** + * If a comment is added by the user, a standard message will be joined together with the comment from the user. + * + * @param standardMessage The standard message. + * @param comment The comment a user may have added. + * @return The full message to be added to the HItem. + */ + private String createMessage(String standardMessage, String comment) { + var message = standardMessage; + if (comment != null && !comment.isBlank()) { + message += ", " + comment; + } + return message; + } + + /** + * Retrieve the Private Element from the SCL and validate if all Labels are correct, if any available. + * + * @param scl The SCL file to edit. + * @return List of all Labels retrieved from the SCL XML File. + */ + List validateLabels(Element scl) { + // Get all the Label Elements from the CoMPAS Private. + var labelElements = sclElementProcessor.getCompasPrivate(scl) + .map(compasPrivate -> sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_LABELS_EXTENSION, + COMPAS_EXTENSION_NS_URI)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(labelsElement -> sclElementProcessor.getChildNodesByName(labelsElement, COMPAS_LABEL_EXTENSION, + COMPAS_EXTENSION_NS_URI)) + .stream().flatMap(List::stream) + .toList(); + if (labelElements.size() > 20) { + throw new CompasSclDataServiceException(TOO_MANY_LABEL_ERROR_CODE, + "Only 20 Labels are allowed per SCL File"); + } + + var invalidLabels = labelElements.stream() + .filter(labelElement -> !validateLabel(labelElement)) + .map(Node::getTextContent) + .toList(); + if (!invalidLabels.isEmpty()) { + throw new CompasSclDataServiceException(INVALID_LABEL_ERROR_CODE, + "Invalid label(s) passed in the CoMPAS Private Element (%s)" + .formatted(invalidLabels.stream().collect(Collectors.joining("','", "'", "'")))); + } + + return labelElements.stream() + .map(Element::getTextContent) + .toList(); + } + + /** + * Check if the label meets the expected RegEx. + * + * @param labelElement The Element which contains the Label to be checked. + * @return true, if label meets RegEx, otherwise false. + */ + boolean validateLabel(Element labelElement) { + String label = labelElement.getTextContent(); + return label.matches("[A-Za-z][0-9A-Za-z_-]*"); + } } diff --git a/service/src/main/java/org/lfenergy/compas/scl/data/service/impl/CompasSclDataServiceImpl.java b/service/src/main/java/org/lfenergy/compas/scl/data/service/impl/CompasSclDataServiceImpl.java deleted file mode 100644 index 2074f50d..00000000 --- a/service/src/main/java/org/lfenergy/compas/scl/data/service/impl/CompasSclDataServiceImpl.java +++ /dev/null @@ -1,345 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alliander N.V. -// -// SPDX-License-Identifier: Apache-2.0 - -package org.lfenergy.compas.scl.data.service.impl; - -import org.lfenergy.compas.core.commons.ElementConverter; -import org.lfenergy.compas.core.commons.exception.CompasException; -import org.lfenergy.compas.scl.data.exception.CompasNoDataFoundException; -import org.lfenergy.compas.scl.data.exception.CompasSclDataServiceException; -import org.lfenergy.compas.scl.data.model.ChangeSetType; -import org.lfenergy.compas.scl.data.model.HistoryItem; -import org.lfenergy.compas.scl.data.model.Item; -import org.lfenergy.compas.scl.data.model.Version; -import org.lfenergy.compas.scl.data.repository.CompasSclDataRepository; -import org.lfenergy.compas.scl.data.service.CompasSclDataService; -import org.lfenergy.compas.scl.data.util.SclElementProcessor; -import org.lfenergy.compas.scl.extensions.model.SclFileType; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import javax.transaction.Transactional; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -import static javax.transaction.Transactional.TxType.REQUIRED; -import static javax.transaction.Transactional.TxType.SUPPORTS; -import static org.lfenergy.compas.scl.data.SclDataServiceConstants.*; -import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.*; -import static org.lfenergy.compas.scl.extensions.commons.CompasExtensionsConstants.*; - -/** - * Service class that will be using a Repository instance to retrieve, create, update, delete SCL XML Files. - * These methods contain standard behaviour that is executed for every type of repository. - */ -public class CompasSclDataServiceImpl implements CompasSclDataService { - private final CompasSclDataRepository repository; - private final ElementConverter converter; - private final SclElementProcessor sclElementProcessor; - - public CompasSclDataServiceImpl(CompasSclDataRepository repository, ElementConverter converter, - SclElementProcessor sclElementProcessor) { - this.repository = repository; - this.converter = converter; - this.sclElementProcessor = sclElementProcessor; - } - - /** - * List the latest version of all SCL XML Files for a specific type. - * - * @param type The type to search for. - * @return The List of Items found. - */ - @Override - @Transactional(SUPPORTS) - public List list(SclFileType type) { - return repository.list(type); - } - - /** - * Search for all versions of a specific SCL XML File (using the UUID) for a specific type. - * - * @param type The type to search for. - * @param id The UUID of the record to search for. - * @return The list of versions found. - */ - @Override - @Transactional(SUPPORTS) - public List listVersionsByUUID(SclFileType type, UUID id) { - var items = repository.listVersionsByUUID(type, id); - if (items.isEmpty()) { - var message = String.format("No versions found for type '%s' with ID '%s'", type, id); - throw new CompasNoDataFoundException(message); - } - return items; - } - - /** - * Get the latest version of a specific SCL XML File (using the UUID) for a specific type. - * - * @param type The type to search for. - * @param id The UUID of the record to search for. - * @return The latest version of the SCL XML Files. - */ - @Override - @Transactional(SUPPORTS) - public String findByUUID(SclFileType type, UUID id) { - return repository.findByUUID(type, id); - } - - /** - * Get a specific version of a specific SCL XML File (using the UUID) for a specific type. - * - * @param type The type to search for. - * @param id The UUID of the record to search for. - * @param version The version to search for. - * @return The found version of the SCL XML Files. - */ - @Override - @Transactional(SUPPORTS) - public String findByUUID(SclFileType type, UUID id, Version version) { - return repository.findByUUID(type, id, version); - } - - /** - * Create a new record for the passed SCL XML File with the passed name for a specific type. - * A new UUID is generated to be set and also the CoMPAS Private Elements are added on SCL Level. - * - * @param type The type to create it for. - * @param name The name that will be stored as CoMPAS Private extension. - * @param comment Some comments that will be added to the THistory entry being added. - * @param sclData The SCL XML File to store. - * @return The ID of the new created SCL XML File in the database. - */ - @Override - @Transactional(REQUIRED) - public String create(SclFileType type, String name, String who, String comment, String sclData) { - var scl = converter.convertToElement(new BufferedInputStream(new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8))), SCL_ELEMENT_NAME, SCL_NS_URI); - if (scl == null) { - throw new CompasException(NO_SCL_ELEMENT_FOUND_ERROR_CODE, "No valid SCL found in the passed SCL Data."); - } - - if (repository.hasDuplicateSclName(type, name)) { - throw new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, "Given name of SCL File already used."); - } - - // A unique ID is generated to store it under. - var id = UUID.randomUUID(); - // When the SCL is created the version will be set to 1.0.0 - var version = new Version(1, 0, 0); - - // Update the Header of the SCL (or create if not exists.) - var header = createOrUpdateHeader(scl, id, version); - createHistoryItem(header, "SCL created", who, comment, version); - - // Update or add the Compas Private Element to the SCL File. - setSclCompasPrivateElement(scl, name, type); - - // Validate the labels from the Private Element, if there are any. - validateLabels(scl); - - var newSclData = converter.convertToString(scl); - repository.create(type, id, name, newSclData, version, who); - return newSclData; - } - - /** - * Create a new version of a specific SCL XML File. The content will be the passed SCL XML File. - * The UUID and new version (depending on the passed ChangeSetType) are set and - * the CoMPAS Private elements will also be copied, the SCL Name will only be copied if not available in the passed - * SCL XML File. - * - * @param type The type to update it for. - * @param id The UUID of the record to update. - * @param changeSetType The type of change to determine the new version. - * @param comment Some comments that will be added to the THistory entry being added. - * @param sclData The SCL XML File with the updated content. - */ - @Override - @Transactional(REQUIRED) - public String update(SclFileType type, UUID id, ChangeSetType changeSetType, String who, String comment, String sclData) { - var scl = converter.convertToElement(new BufferedInputStream(new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8))), SCL_ELEMENT_NAME, SCL_NS_URI); - if (scl == null) { - throw new CompasException(NO_SCL_ELEMENT_FOUND_ERROR_CODE, "No valid SCL found in the passed SCL Data."); - } - - var currentSclMetaInfo = repository.findMetaInfoByUUID(type, id); - var newFileName = getFilenameFromXML(scl); - - if (newFileName.isPresent() - && !newFileName.get().equals(currentSclMetaInfo.getName()) - && repository.hasDuplicateSclName(type, newFileName.get())) { - throw new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, "Given name of SCL File already used."); - } - - // We always add a new version to the database, so add version record to the SCL and create a new record. - var version = new Version(currentSclMetaInfo.getVersion()); - version = version.getNextVersion(changeSetType); - - // Update the Header of the SCL (or create if not exists.) - var header = createOrUpdateHeader(scl, id, version); - createHistoryItem(header, "SCL updated", who, comment, version); - - // Update or add the Compas Private Element to the SCL File. - var newSclName = newFileName.orElse(currentSclMetaInfo.getName()); - setSclCompasPrivateElement(scl, newSclName, type); - - // Validate the labels from the Private Element, if there are any. - validateLabels(scl); - - var newSclData = converter.convertToString(scl); - repository.create(type, id, newSclName, newSclData, version, who); - return newSclData; - } - - /** - * Delete all versions for a specific SCL File using it's ID. - * - * @param type The type of SCL where to find the SCL File - * @param id The ID of the SCL File to delete. - */ - @Override - @Transactional(REQUIRED) - public void delete(SclFileType type, UUID id) { - repository.delete(type, id); - } - - /** - * Delete passed versions for a specific SCL File using it's ID. - * - * @param type The type of SCL where to find the SCL File - * @param id The ID of the SCL File to delete. - * @param version The version of that SCL File to delete. - */ - @Override - @Transactional(REQUIRED) - public void delete(SclFileType type, UUID id, Version version) { - repository.delete(type, id, version); - } - - /** - * Retrieve the Header from the SCL Fiel or create one if it doesn't exists and set the ID and - * version on the Header. - * - * @param scl The SCL file to edit. - * @param id The ID of the SCL File. - * @param version The Version of the SCL File. - * @return The header that was found or created. - */ - private Element createOrUpdateHeader(Element scl, UUID id, Version version) { - var header = sclElementProcessor.getSclHeader(scl) - .orElseGet(() -> sclElementProcessor.addSclHeader(scl)); - header.setAttribute(SCL_ID_ATTR, id.toString()); - header.setAttribute(SCL_VERSION_ATTR, version.toString()); - return header; - } - - /** - * Retrieve the CoMPAS SCL Filename from the private element of CoMPAS. - * - * @param scl The SCL file to edit. - * @return If there was a private SclName the value of this tag. - */ - private Optional getFilenameFromXML(Element scl) { - return sclElementProcessor.getCompasPrivate(scl) - .stream() - .map(compasPrivate -> sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_SCL_NAME_EXTENSION, - COMPAS_EXTENSION_NS_URI)) - .flatMap(Optional::stream) - .map(Element::getTextContent) - .findFirst(); - } - - /** - * Create/update the CoMPAS private element on the SCL Element for the given file. - * - * @param scl The SCL file to edit. - * @param name The name to add. - * @param fileType The file type to add. - */ - private void setSclCompasPrivateElement(Element scl, String name, SclFileType fileType) { - var compasPrivate = sclElementProcessor.getCompasPrivate(scl) - .orElseGet(() -> sclElementProcessor.addCompasPrivate(scl)); - - sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_SCL_NAME_EXTENSION, COMPAS_EXTENSION_NS_URI) - .ifPresentOrElse( - // Override the value of the element with the name passed. - element -> element.setTextContent(name), - () -> sclElementProcessor.addCompasElement(compasPrivate, COMPAS_SCL_NAME_EXTENSION, name) - ); - - // Always set the file type as private element. - sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_SCL_FILE_TYPE_EXTENSION, COMPAS_EXTENSION_NS_URI) - .ifPresentOrElse( - element -> element.setTextContent(fileType.toString()), - () -> sclElementProcessor.addCompasElement(compasPrivate, COMPAS_SCL_FILE_TYPE_EXTENSION, - fileType.toString()) - ); - } - - /** - * Add a Hitem to the current or added History Element from the Header. - * - * @param header The header of SCL file to edit. - * @param message The message set on the Hitem. - * @param who The user that made the change. - * @param comment If filled the comment that will be added to the message. - * @param version The version set on the Hitem. - */ - private void createHistoryItem(Element header, String message, String who, String comment, Version version) { - var fullmessage = message; - if (comment != null && !comment.isBlank()) { - fullmessage += ", " + comment; - } - sclElementProcessor.addHistoryItem(header, who, fullmessage, version); - } - - /** - * Retrieve the Private Element from the SCL and validate if all Labels are correct, if any available. - * - * @param scl The SCL file to edit. - */ - void validateLabels(Element scl) { - // Get all the Label Elements from the CoMPAS Private. - var labelElements = sclElementProcessor.getCompasPrivate(scl) - .map(compasPrivate -> sclElementProcessor.getChildNodeByName(compasPrivate, COMPAS_LABELS_EXTENSION, - COMPAS_EXTENSION_NS_URI)) - .filter(Optional::isPresent) - .map(Optional::get) - .map(labelsElement -> sclElementProcessor.getChildNodesByName(labelsElement, COMPAS_LABEL_EXTENSION, - COMPAS_EXTENSION_NS_URI)) - .stream().flatMap(List::stream) - .toList(); - if (labelElements.size() > 20) { - throw new CompasSclDataServiceException(TOO_MANY_LABEL_ERROR_CODE, - "Only 20 Labels are allowed per SCL File"); - } - - var invalidLabels = labelElements.stream() - .filter(labelElement -> !validateLabel(labelElement)) - .map(Node::getTextContent) - .toList(); - if (!invalidLabels.isEmpty()) { - throw new CompasSclDataServiceException(INVALID_LABEL_ERROR_CODE, - "Invalid label(s) passed in the CoMPAS Private Element (%s)" - .formatted(invalidLabels.stream().collect(Collectors.joining("','", "'", "'")))); - } - } - - /** - * Check if the label meets the expected RegEx. - * - * @param labelElement The Element which contains the Label to be checked. - * @return true, if label meets RegEx, otherwise false. - */ - boolean validateLabel(Element labelElement) { - String label = labelElement.getTextContent(); - return label.matches("[A-Za-z][0-9A-Za-z_-]*"); - } -} diff --git a/service/src/test/java/org/lfenergy/compas/scl/data/service/impl/CompasSclDataServiceImplTest.java b/service/src/test/java/org/lfenergy/compas/scl/data/service/CompasSclDataServiceTest.java similarity index 94% rename from service/src/test/java/org/lfenergy/compas/scl/data/service/impl/CompasSclDataServiceImplTest.java rename to service/src/test/java/org/lfenergy/compas/scl/data/service/CompasSclDataServiceTest.java index 4dc34028..926d487d 100644 --- a/service/src/test/java/org/lfenergy/compas/scl/data/service/impl/CompasSclDataServiceImplTest.java +++ b/service/src/test/java/org/lfenergy/compas/scl/data/service/CompasSclDataServiceTest.java @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2021 Alliander N.V. // // SPDX-License-Identifier: Apache-2.0 -package org.lfenergy.compas.scl.data.service.impl; +package org.lfenergy.compas.scl.data.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,21 +36,21 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class CompasSclDataServiceImplTest { +class CompasSclDataServiceTest { private static final Version INITIAL_VERSION = new Version("1.0.0"); private static final SclFileType SCL_TYPE = SclFileType.SCD; @Mock private CompasSclDataRepository compasSclDataRepository; - private CompasSclDataServiceImpl compasSclDataService; + private CompasSclDataService compasSclDataService; private final ElementConverter converter = new ElementConverter(); private final SclElementProcessor processor = new SclElementProcessor(); @BeforeEach void beforeEach() { - compasSclDataService = new CompasSclDataServiceImpl(compasSclDataRepository, converter, processor); + compasSclDataService = new CompasSclDataService(compasSclDataRepository, converter, processor); } @Test @@ -121,14 +121,14 @@ void create_WhenCalledWithOutCompasExtension_ThenSCLReturnedWithCorrectCompasExt var scl = readSCL("scl_test_file.scd"); when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, name)).thenReturn(false); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who)); + doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); scl = compasSclDataService.create(SCL_TYPE, name, who, comment, scl); assertNotNull(scl); assertCompasExtension(scl, name); - assertHistoryItem(scl, INITIAL_VERSION, comment); - verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who)); + assertHistoryItem(scl, 2, INITIAL_VERSION, comment); + verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); verify(compasSclDataRepository, times(1)).hasDuplicateSclName(SCL_TYPE, name); } @@ -142,14 +142,14 @@ void create_WhenCalledWithCompasExtension_ThenSCLReturnedWithCorrectCompasExtens scl = createCompasPrivate(scl, "JUSTANOTHERNAME"); when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, name)).thenReturn(false); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who)); + doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); scl = compasSclDataService.create(SCL_TYPE, name, who, comment, scl); assertNotNull(scl); assertCompasExtension(scl, name); - assertHistoryItem(scl, INITIAL_VERSION, comment); - verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who)); + assertHistoryItem(scl, 2, INITIAL_VERSION, comment); + verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); verify(compasSclDataRepository, times(1)).hasDuplicateSclName(SCL_TYPE, name); } @@ -195,14 +195,14 @@ void update_WhenCalledWithoutCompasElements_ThenSCLReturnedWithCorrectCompasExte var sclMetaInfo = new SclMetaInfo(uuid.toString(), previousName, INITIAL_VERSION.toString()); when(compasSclDataRepository.findMetaInfoByUUID(SCL_TYPE, uuid)).thenReturn(sclMetaInfo); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who)); + doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); scl = compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); assertNotNull(scl); assertCompasExtension(scl, previousName); - assertHistoryItem(scl, nextVersion, null); - verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who)); + assertHistoryItem(scl, 4, nextVersion, null); + verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); verify(compasSclDataRepository, times(1)).findMetaInfoByUUID(SCL_TYPE, uuid); verify(compasSclDataRepository, never()).hasDuplicateSclName(SCL_TYPE, previousName); } @@ -221,15 +221,15 @@ void update_WhenCalledWithCompasElementsAndNewName_ThenSCLReturnedWithCorrectCom var sclMetaInfo = new SclMetaInfo(uuid.toString(), previousName, INITIAL_VERSION.toString()); when(compasSclDataRepository.findMetaInfoByUUID(SCL_TYPE, uuid)).thenReturn(sclMetaInfo); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who)); + doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, newName)).thenReturn(false); scl = compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); assertNotNull(scl); assertCompasExtension(scl, newName); - assertHistoryItem(scl, nextVersion, null); - verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who)); + assertHistoryItem(scl, 4, nextVersion, null); + verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); verify(compasSclDataRepository, times(1)).findMetaInfoByUUID(SCL_TYPE, uuid); verify(compasSclDataRepository, times(1)).hasDuplicateSclName(SCL_TYPE, newName); } @@ -269,14 +269,14 @@ void update_WhenCalledWithCompasElementsAndSameName_ThenSCLReturnedWithCorrectCo var sclMetaInfo = new SclMetaInfo(uuid.toString(), previousName, INITIAL_VERSION.toString()); when(compasSclDataRepository.findMetaInfoByUUID(SCL_TYPE, uuid)).thenReturn(sclMetaInfo); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who)); + doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); scl = compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); assertNotNull(scl); assertCompasExtension(scl, previousName); - assertHistoryItem(scl, nextVersion, null); - verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who)); + assertHistoryItem(scl, 4, nextVersion, null); + verify(compasSclDataRepository, times(1)).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); verify(compasSclDataRepository, times(1)).findMetaInfoByUUID(SCL_TYPE, uuid); verify(compasSclDataRepository, never()).hasDuplicateSclName(SCL_TYPE, previousName); } @@ -386,7 +386,7 @@ private void assertCompasExtension(String sclData, String name) { assertEquals(SCL_TYPE.toString(), typeElement.get().getTextContent()); } - private void assertHistoryItem(String sclData, Version version, String comment) { + private void assertHistoryItem(String sclData, int expectedHItems, Version version, String comment) { var scl = converter.convertToElement(sclData, SCL_ELEMENT_NAME, SCL_NS_URI); var header = processor.getSclHeader(scl); assertTrue(header.isPresent()); @@ -395,7 +395,7 @@ private void assertHistoryItem(String sclData, Version version, String comment) assertTrue(history.isPresent()); var items = processor.getChildNodesByName(history.get(), SCL_HITEM_ELEMENT_NAME, SCL_NS_URI); - assertFalse(items.isEmpty()); + assertEquals(expectedHItems, items.size()); // The last item should be the one added. var item = items.get(items.size() - 1); assertEquals(version.toString(), item.getAttribute(SCL_VERSION_ATTR)); diff --git a/service/src/test/resources/scl/scl_test_file.scd b/service/src/test/resources/scl/scl_test_file.scd index 4dc2b380..9434cc04 100644 --- a/service/src/test/resources/scl/scl_test_file.scd +++ b/service/src/test/resources/scl/scl_test_file.scd @@ -3,8 +3,17 @@ - -
+ +
+ + + + + + +
@@ -59,7 +68,8 @@ - +