From db7e9d8a2a14977b8fdd7c2b7b36861cf17cca23 Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Thu, 4 Jan 2024 12:06:37 +0100 Subject: [PATCH] #321: Respect imported domains/subdomains in PlantUML generator --- org.contextmapper.dsl.ide.tests/.classpath | 5 + .../META-INF/MANIFEST.MF | 3 +- .../imported-file-test-1.cml | 7 ++ .../imported-file-test-2.cml | 10 ++ .../root-file-with-imports-test-1.cml | 22 ++++ .../root-file-with-imports-test-2.cml | 22 ++++ .../simple-single-file-test.cml | 20 ++++ ...MLModelDomainAndSubdomainResolverTest.java | 107 ++++++++++++++++++ .../CMLModelDomainAndSubdomainResolver.java | 102 +++++++++++++++++ .../dsl/generator/PlantUMLGenerator.java | 29 +++-- 10 files changed, 310 insertions(+), 17 deletions(-) create mode 100644 org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-1.cml create mode 100644 org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-2.cml create mode 100644 org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-1.cml create mode 100644 org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-2.cml create mode 100644 org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/simple-single-file-test.cml create mode 100644 org.contextmapper.dsl.tests/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolverTest.java create mode 100644 org.contextmapper.dsl/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolver.java diff --git a/org.contextmapper.dsl.ide.tests/.classpath b/org.contextmapper.dsl.ide.tests/.classpath index f59ebe74..79dc4c0c 100644 --- a/org.contextmapper.dsl.ide.tests/.classpath +++ b/org.contextmapper.dsl.ide.tests/.classpath @@ -5,6 +5,11 @@ + + + + + diff --git a/org.contextmapper.dsl.tests/META-INF/MANIFEST.MF b/org.contextmapper.dsl.tests/META-INF/MANIFEST.MF index e77f61dd..b9a4622c 100644 --- a/org.contextmapper.dsl.tests/META-INF/MANIFEST.MF +++ b/org.contextmapper.dsl.tests/META-INF/MANIFEST.MF @@ -20,7 +20,8 @@ Require-Bundle: org.contextmapper.dsl, org.eclipse.osgi, org.freemarker.freemarker;bundle-version="2.3.30", junit-jupiter-api, - junit-jupiter-params + junit-jupiter-params, + org.slf4j.api;bundle-version="1.7.30" Bundle-RequiredExecutionEnvironment: JavaSE-11 Export-Package: org.contextmapper.dsl.tests;x-internal=true, org.contextmapper.tactic.dsl.tests;x-internal=true, diff --git a/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-1.cml b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-1.cml new file mode 100644 index 00000000..ebca07a4 --- /dev/null +++ b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-1.cml @@ -0,0 +1,7 @@ +Domain OtherDomain { + + Subdomain OtherSubdomain { + + Entity OtherEntity + } +} diff --git a/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-2.cml b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-2.cml new file mode 100644 index 00000000..72a64ba9 --- /dev/null +++ b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/imported-file-test-2.cml @@ -0,0 +1,10 @@ +Domain OtherDomain { + + Subdomain OtherSubdomain1 { + Entity OtherEntity1 + } + + Subdomain OtherSubdomain2 { + Entity OtherEntity1 + } +} diff --git a/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-1.cml b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-1.cml new file mode 100644 index 00000000..a3f8cbf0 --- /dev/null +++ b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-1.cml @@ -0,0 +1,22 @@ +import "./imported-file-test-1.cml" + +Domain DirectDomain { + + Subdomain DirectSubdomain { + + Entity DirectEntity + } +} + +ContextMap testMap { + +} + +BoundedContext context1 implements DirectSubdomain +BoundedContext context2 implements OtherSubdomain { + Aggregate MyAggregate { + Entity MyEntity { + aggregateRoot + } + } +} diff --git a/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-2.cml b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-2.cml new file mode 100644 index 00000000..694451d0 --- /dev/null +++ b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/root-file-with-imports-test-2.cml @@ -0,0 +1,22 @@ +import "./imported-file-test-2.cml" + +Domain DirectDomain { + + Subdomain DirectSubdomain { + + Entity DirectEntity + } +} + +ContextMap testMap { + +} + +BoundedContext context1 implements DirectSubdomain +BoundedContext context2 implements OtherDomain { + Aggregate MyAggregate { + Entity MyEntity { + aggregateRoot + } + } +} diff --git a/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/simple-single-file-test.cml b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/simple-single-file-test.cml new file mode 100644 index 00000000..dd0d4672 --- /dev/null +++ b/org.contextmapper.dsl.tests/integ-test-files/subdomain-resolving/simple-single-file-test.cml @@ -0,0 +1,20 @@ + +Domain TestDomain { + + Subdomain TestSubDomain1 { + + } + + Subdomain TestSubDomain2 { + + } + +} + +Domain OtherDomain { + + Subdomain TestSubDomain3 { + + } + +} diff --git a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolverTest.java b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolverTest.java new file mode 100644 index 00000000..3dd7639f --- /dev/null +++ b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolverTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2023 The Context Mapper Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.contextmapper.dsl.cml; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Set; +import java.util.stream.Collectors; + +import org.contextmapper.dsl.AbstractCMLInputFileTest; +import org.contextmapper.dsl.contextMappingDSL.ContextMappingModel; +import org.contextmapper.dsl.contextMappingDSL.Domain; +import org.contextmapper.dsl.contextMappingDSL.Subdomain; +import org.junit.jupiter.api.Test; + +public class CMLModelDomainAndSubdomainResolverTest extends AbstractCMLInputFileTest { + + @Test + public void canResolveSubDomainsInSingleFile() throws IOException { + // given + CMLResource input = getOriginalResourceOfTestCML("simple-single-file-test.cml"); + + // when + Set declaredSubdomains = new CMLModelDomainAndSubdomainResolver(input.getContextMappingModel()) + .resolveAllSubdomains(); + Set subdomainNames = declaredSubdomains.stream().map(Subdomain::getName).collect(Collectors.toSet()); + + // then + assertEquals(3, declaredSubdomains.size()); + assertTrue(subdomainNames.contains("TestSubDomain1")); + assertTrue(subdomainNames.contains("TestSubDomain2")); + assertTrue(subdomainNames.contains("TestSubDomain3")); + } + + @Test + public void canResolveImportedSubdomains() throws IOException { + // given + CMLResource input = getOriginalResourceOfTestCML("root-file-with-imports-test-1.cml"); + + // when + Set declaredSubdomains = new CMLModelDomainAndSubdomainResolver(input.getContextMappingModel()) + .resolveAllSubdomains(); + Set subdomainNames = declaredSubdomains.stream().map(Subdomain::getName).collect(Collectors.toSet()); + + // then + assertEquals(2, declaredSubdomains.size()); + assertTrue(subdomainNames.contains("DirectSubdomain")); + assertTrue(subdomainNames.contains("OtherSubdomain")); + } + + @Test + public void canResolveImportedDomains() throws IOException { + // given + CMLResource input = getOriginalResourceOfTestCML("root-file-with-imports-test-2.cml"); + + // when + Set declaredSubdomains = new CMLModelDomainAndSubdomainResolver(input.getContextMappingModel()) + .resolveAllSubdomains(); + Set subdomainNames = declaredSubdomains.stream().map(Subdomain::getName).collect(Collectors.toSet()); + + // then + assertEquals(3, declaredSubdomains.size()); + assertTrue(subdomainNames.contains("DirectSubdomain")); + assertTrue(subdomainNames.contains("OtherSubdomain1")); + assertTrue(subdomainNames.contains("OtherSubdomain2")); + } + + @Test + public void canResolveDomain4Subdomain() throws IOException { + // given + CMLResource input = getOriginalResourceOfTestCML("simple-single-file-test.cml"); + ContextMappingModel model = input.getContextMappingModel(); + CMLModelDomainAndSubdomainResolver resolver = new CMLModelDomainAndSubdomainResolver(model); + Set allSubdomains = resolver.resolveAllSubdomains(); + Subdomain subdomain1 = allSubdomains.stream().filter(s -> s.getName().equals("TestSubDomain1")).findAny().get(); + Subdomain subdomain3 = allSubdomains.stream().filter(s -> s.getName().equals("TestSubDomain3")).findAny().get(); + + // when + Domain domain1 = resolver.resolveDomain4Subdomain(subdomain1.getName()); + Domain domain2 = resolver.resolveDomain4Subdomain(subdomain3.getName()); + + // then + assertEquals("TestDomain", domain1.getName()); + assertEquals("OtherDomain", domain2.getName()); + } + + @Override + protected String getTestFileDirectory() { + return "/integ-test-files/subdomain-resolving/"; + } + +} diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolver.java b/org.contextmapper.dsl/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolver.java new file mode 100644 index 00000000..70e66b16 --- /dev/null +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/cml/CMLModelDomainAndSubdomainResolver.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 The Context Mapper Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.contextmapper.dsl.cml; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.contextmapper.dsl.contextMappingDSL.BoundedContext; +import org.contextmapper.dsl.contextMappingDSL.ContextMappingModel; +import org.contextmapper.dsl.contextMappingDSL.Domain; +import org.contextmapper.dsl.contextMappingDSL.Subdomain; + +public class CMLModelDomainAndSubdomainResolver { + + private final ContextMappingModel model; + private final Map declaredSubdomains; + private final Map domainsBySubdomainNames; + + public CMLModelDomainAndSubdomainResolver(final ContextMappingModel model) { + this.model = model; + this.declaredSubdomains = new HashMap<>(); + this.domainsBySubdomainNames = new HashMap<>(); + initAllDeclaredSubdomainsIncludingImportedOnes(); + } + + /** + * + * Resolves all subdomains that are: + * + * 1) declared in the "root" CML file/model + * + * 2) subdomains implemented by Bounded Contexts of the root model, but declared + * in an imported CML file/model + * + */ + public Set resolveAllSubdomains() { + Map subdomains4Model = new HashMap<>(); + for (Domain domain : model.getDomains()) { + domain.getSubdomains().forEach(subdomain -> { + subdomains4Model.put(subdomain.getName(), this.declaredSubdomains.get(subdomain.getName())); + }); + } + for (BoundedContext boundedContext : model.getBoundedContexts()) { + boundedContext.getImplementedDomainParts().forEach(domainPart -> { + if (domainPart instanceof Domain) { + Domain domain = (Domain) domainPart; + domain.getSubdomains().forEach(subdomain -> { + subdomains4Model.put(subdomain.getName(), this.declaredSubdomains.get(subdomain.getName())); + }); + } else if (domainPart instanceof Subdomain) { + Subdomain subdomain = (Subdomain) domainPart; + subdomains4Model.put(subdomain.getName(), this.declaredSubdomains.get(subdomain.getName())); + } + }); + } + return new HashSet<>(subdomains4Model.values()); + } + + /** + * + * Takes a name of the subdomain and returns the domain to which it belongs. + * + */ + public Domain resolveDomain4Subdomain(final String subdomainName) { + return this.domainsBySubdomainNames.get(subdomainName); + } + + private void initAllDeclaredSubdomainsIncludingImportedOnes() { + addDeclaredSubdomains(this.model); + if (this.model.eResource() != null) { + for (CMLResource cmlResource : new CMLImportResolver() + .resolveImportedResources(new CMLResource(this.model.eResource()))) { + addDeclaredSubdomains(cmlResource.getContextMappingModel()); + } + } + } + + private void addDeclaredSubdomains(final ContextMappingModel model) { + for (Domain domain : model.getDomains()) { + domain.getSubdomains().forEach(subdomain -> { + declaredSubdomains.put(subdomain.getName(), subdomain); + domainsBySubdomainNames.put(subdomain.getName(), domain); + }); + } + } + +} diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/generator/PlantUMLGenerator.java b/org.contextmapper.dsl/src/org/contextmapper/dsl/generator/PlantUMLGenerator.java index 18861fef..d25fe678 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/generator/PlantUMLGenerator.java +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/generator/PlantUMLGenerator.java @@ -18,13 +18,12 @@ import java.util.List; import java.util.Optional; +import org.contextmapper.dsl.cml.CMLModelDomainAndSubdomainResolver; import org.contextmapper.dsl.contextMappingDSL.Aggregate; import org.contextmapper.dsl.contextMappingDSL.BoundedContext; import org.contextmapper.dsl.contextMappingDSL.ContextMappingModel; -import org.contextmapper.dsl.contextMappingDSL.Domain; import org.contextmapper.dsl.contextMappingDSL.Flow; import org.contextmapper.dsl.contextMappingDSL.SculptorModule; -import org.contextmapper.dsl.contextMappingDSL.Subdomain; import org.contextmapper.dsl.contextMappingDSL.UseCase; import org.contextmapper.dsl.contextMappingDSL.UserRequirement; import org.contextmapper.dsl.generator.exception.GeneratorInputException; @@ -48,9 +47,12 @@ public class PlantUMLGenerator extends AbstractContextMappingModelGenerator { private static final String PLANT_UML_FILE_EXT = "puml"; + private CMLModelDomainAndSubdomainResolver subdomainResolver; + @Override protected void generateFromContextMappingModel(ContextMappingModel model, IFileSystemAccess2 fsa, URI inputFileURI) { + this.subdomainResolver = new CMLModelDomainAndSubdomainResolver(this.contextMappingModel); checkPreconditions(); String fileName = inputFileURI.trimFileExtension().lastSegment(); @@ -97,13 +99,13 @@ protected void generateFromContextMappingModel(ContextMappingModel model, IFileS } // generate class diagrams for subdomains (that have entities) - for (Domain domain : model.getDomains()) { - domain.getSubdomains().stream().filter(subdomain -> !subdomain.getEntities().isEmpty()) - .forEach(subdomain -> { - fsa.generateFile(fileName + "_SD_" + subdomain.getName() + "." + PLANT_UML_FILE_EXT, - new PlantUMLSubdomainClassDiagramCreator(domain.getName()).createDiagram(subdomain)); - }); - } + subdomainResolver.resolveAllSubdomains().stream().filter(subdomain -> !subdomain.getEntities().isEmpty()) + .forEach(subdomain -> { + fsa.generateFile(fileName + "_SD_" + subdomain.getName() + "." + PLANT_UML_FILE_EXT, + new PlantUMLSubdomainClassDiagramCreator( + subdomainResolver.resolveDomain4Subdomain(subdomain.getName()).getName()) + .createDiagram(subdomain)); + }); // generate Use Case diagram out of user requirements, if available if (!model.getUserRequirements().isEmpty()) @@ -155,13 +157,8 @@ private List getAggregatesWithStatesAndTransitions(BoundedContext bc) } private boolean modelHasSubdomainWithEntities() { - for (Domain domain : this.contextMappingModel.getDomains()) { - Optional optSubdomain = domain.getSubdomains().stream() - .filter(subdomain -> !subdomain.getEntities().isEmpty()).findAny(); - if (optSubdomain.isPresent()) - return true; - } - return false; + return subdomainResolver.resolveAllSubdomains().stream().filter(subdomain -> !subdomain.getEntities().isEmpty()) + .findAny().isPresent(); } }