org.contextmapper.dsl.refactoring.MergeAggregatesRefactoring Maven / Gradle / Ivy
/*
* Copyright 2019 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.refactoring;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.contextmapper.dsl.contextMappingDSL.Aggregate;
import org.contextmapper.dsl.contextMappingDSL.BoundedContext;
import org.contextmapper.dsl.contextMappingDSL.ContextMap;
import org.contextmapper.dsl.contextMappingDSL.SculptorModule;
import org.contextmapper.dsl.contextMappingDSL.Relationship;
import org.contextmapper.dsl.contextMappingDSL.UpstreamDownstreamRelationship;
import org.contextmapper.dsl.refactoring.exception.RefactoringInputException;
import org.contextmapper.tactic.dsl.tacticdsl.DomainObject;
import org.eclipse.xtext.EcoreUtil2;
import com.google.common.collect.Lists;
public class MergeAggregatesRefactoring extends AbstractRefactoring implements SemanticCMLRefactoring {
private String aggregate1;
private String aggregate2;
private boolean takeAttributesFromSecondAggregate = false;
public MergeAggregatesRefactoring(String aggregate1, String aggregate2) {
this.aggregate1 = aggregate1;
this.aggregate2 = aggregate2;
}
public MergeAggregatesRefactoring(String aggregate1, String aggregate2, boolean takeAttributesFromSecondAggregate) {
this(aggregate1, aggregate2);
this.takeAttributesFromSecondAggregate = takeAttributesFromSecondAggregate;
}
@Override
protected void doRefactor() {
// do nothing if the two parameters point to same aggregate
if (aggregate1.equals(aggregate2))
return;
Optional agg1Opt = getAllAggregates().stream().filter(bc -> bc.getName().equals(aggregate1)).findFirst();
Optional agg2Opt = getAllAggregates().stream().filter(bc -> bc.getName().equals(aggregate2)).findFirst();
// do nothing if one of the aggregates does not exist
if (!agg1Opt.isPresent() || !agg2Opt.isPresent())
return;
Aggregate agg1 = agg1Opt.get();
Aggregate agg2 = agg2Opt.get();
// inverse merging, if requested
if (takeAttributesFromSecondAggregate) {
agg1 = agg2Opt.get();
agg2 = agg1Opt.get();
}
// precondition check, may throw exception
checkForPossibleDomainObjectNameClashes(agg1, agg2);
// ensure there is only one aggregate root
if (containsAggregateRoot(agg1))
changeAggregateRootsToNormalObjects(agg2);
// move content from agg2 to agg1
addElementsToEList(agg1.getConsumers(), agg2.getConsumers());
addElementsToEList(agg1.getDomainObjects(), agg2.getDomainObjects());
addElementsToEList(agg1.getResources(), agg2.getResources());
addElementsToEList(agg1.getResponsibilities(), agg2.getResponsibilities());
addElementsToEList(agg1.getServices(), agg2.getServices());
addElementsToEList(agg1.getUserRequirements(), agg2.getUserRequirements());
// update context maps
for (ContextMap contextMap : getAllContextMaps()) {
handleContextMapChanges(contextMap, agg1, agg2);
}
// remove agg2 from its container
if (agg2.eContainer() instanceof BoundedContext) {
BoundedContext container = (BoundedContext) agg2.eContainer();
removeElementFromEList(container.getAggregates(), agg2);
} else if (agg2.eContainer() instanceof SculptorModule) {
removeElementFromEList(((SculptorModule) agg2.eContainer()).getAggregates(), agg2);
}
}
private void checkForPossibleDomainObjectNameClashes(Aggregate aggregate1, Aggregate aggregate2) {
List aggregate1DomainObjectNames = aggregate1.getDomainObjects().stream().map(obj -> obj.getName()).collect(Collectors.toList());
List aggregate2DomainObjectNames = aggregate2.getDomainObjects().stream().map(obj -> obj.getName()).collect(Collectors.toList());
Set commonDomainObjectNames = aggregate1DomainObjectNames.stream().distinct().filter(aggregate2DomainObjectNames::contains).collect(Collectors.toSet());
if (!commonDomainObjectNames.isEmpty())
throw new RefactoringInputException("Sorry, we cannot execute this refactoring. The selected Aggregates contain the following duplicate domain objects: "
+ String.join(", ", commonDomainObjectNames));
}
/**
* Sets aggregateRoot flag to false for all objects in given aggregate
*/
private void changeAggregateRootsToNormalObjects(Aggregate aggregate) {
getAggregateRoots(aggregate).forEach(o -> o.setAggregateRoot(false));
}
private boolean containsAggregateRoot(Aggregate aggregate) {
return !getAggregateRoots(aggregate).isEmpty();
}
private List getAggregateRoots(Aggregate aggregate) {
return aggregate.getDomainObjects().stream().filter(o -> o instanceof DomainObject).map(o -> (DomainObject) o).filter(o -> o.isAggregateRoot())
.collect(Collectors.toList());
}
private List getAllAggregates() {
List aggregates = Lists.newArrayList();
for (BoundedContext bc : getAllBoundedContexts()) {
aggregates.addAll(EcoreUtil2.getAllContentsOfType(bc, Aggregate.class));
}
return aggregates;
}
private void handleContextMapChanges(ContextMap map, Aggregate agg1, Aggregate agg2) {
for (Relationship relationship : map.getRelationships()) {
if (!(relationship instanceof UpstreamDownstreamRelationship))
continue;
UpstreamDownstreamRelationship upDownRelationship = (UpstreamDownstreamRelationship) relationship;
if (upDownRelationship.getUpstreamExposedAggregates().contains(agg2)) {
// ugly workaround (clear list and add all again); otherwise list is not
// properly updated when saving ecore model :(
List exposedAggregates = Lists.newArrayList();
exposedAggregates.addAll(upDownRelationship.getUpstreamExposedAggregates());
exposedAggregates.remove(agg2);
exposedAggregates.add(agg1);
upDownRelationship.getUpstreamExposedAggregates().clear();
upDownRelationship.getUpstreamExposedAggregates().addAll(exposedAggregates);
}
}
}
}