MMKB-5027: Code improvements + changes in version structure

This commit is contained in:
Alexander Graf 2024-11-27 16:51:30 +01:00
parent 44fb96c2b4
commit 99ea732b94
7 changed files with 248 additions and 176 deletions

View File

@ -1,9 +1,6 @@
package at.ucs.magnolia.updates;
import at.ucs.magnolia.updates.util.TaskWrapper;
import at.ucs.magnolia.updates.util.VersionComparator;
import at.ucs.magnolia.updates.util.VersionNumberUtil;
import at.ucs.magnolia.updates.util.VersionUtil;
import at.ucs.magnolia.updates.util.*;
import info.magnolia.module.DefaultModuleVersionHandler;
import info.magnolia.module.InstallContext;
import info.magnolia.module.delta.Delta;
@ -15,7 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import javax.jcr.RepositoryException;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.*;
@ -27,13 +23,13 @@ import java.util.stream.IntStream;
public class IntranetUpdateModuleVersionHandler extends DefaultModuleVersionHandler {
protected IntranetUpdateModuleConfig moduleConfig;
protected IntranetUpdateModuleVersionHandlerContext context = new IntranetUpdateModuleVersionHandlerContext();
public IntranetUpdateModuleVersionHandler(IntranetUpdateModuleConfig moduleConfig) {
super();
this.moduleConfig = moduleConfig;
}
@Override
protected List<Task> getExtraInstallTasks(InstallContext installContext) {
return getModuleConfig().getInitialUpdateTasks();
@ -53,6 +49,7 @@ public class IntranetUpdateModuleVersionHandler extends DefaultModuleVersionHand
protected List<Delta> getUpdateDeltas(InstallContext installContext, Version from) {
try {
context.initFromJCR(installContext);
return new Reflections(
getModuleConfig().getUpdateTaskPackage(),
new SubTypesScanner()
@ -60,7 +57,7 @@ public class IntranetUpdateModuleVersionHandler extends DefaultModuleVersionHand
.map(this::initiateModuleUpdate)
.filter(Objects::nonNull)
.sorted(new VersionComparator())
.map(updateModule -> buildDelta(installContext, updateModule))
.map(updateModule -> buildDelta(context, updateModule))
.toList();
} catch (Exception e) {
log.error("Could not register update tasks", e);
@ -80,27 +77,16 @@ public class IntranetUpdateModuleVersionHandler extends DefaultModuleVersionHand
return null;
}
private Predicate<TaskWrapper> installNecessary(InstallContext installContext) {
return (wrapper) -> {
try {
return !VersionUtil.containsVersion(installContext, wrapper.getVersion());
} catch (RepositoryException e) {
return false;
}
};
}
private Delta buildDelta(InstallContext installContext, ModuleUpdate moduleUpdate) {
private Delta buildDelta(IntranetUpdateModuleVersionHandlerContext context, ModuleUpdate moduleUpdate) {
final String version = VersionNumberUtil.getVersion(moduleUpdate);
List<Task> moduleUpdateTasks = moduleUpdate.getAllTasks(moduleConfig, version);
List<Task> wrappedTasks = IntStream.range(0, moduleUpdateTasks.size())
.mapToObj(i -> {
final String wrappedVersion = MessageFormat.format("{0}_{1}", version, i);
final Task task = moduleUpdateTasks.get(i);
return new TaskWrapper(task, wrappedVersion);
return new TaskWrapper(task, version, wrappedVersion, context);
})
.filter(installNecessary(installContext))
.filter(Predicate.not(context::alreadyApplied))
.map(Task.class::cast)
.toList();
// Fake sem version

View File

@ -0,0 +1,127 @@
package at.ucs.magnolia.updates.util;
import info.magnolia.jcr.util.NodeTypes;
import info.magnolia.jcr.util.NodeUtil;
import info.magnolia.module.InstallContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.jcr.*;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.*;
@Slf4j
public class IntranetUpdateModuleVersionHandlerContext {
public static final String CONFIG_VERSIONS = "config/versions";
public static final String INSTALLED_UPDATES = "installedUpdates";
public static final String INSTALLED_TASKS = "installedTasks";
@Data
private static class ModuleUpdateContext {
private BigDecimal index;
private Set<String> taskVersions;
}
private BigDecimal currentUpdate = BigDecimal.ZERO;
private final Set<String> alreadyInstalledVersions = new HashSet<>();
private final Map<String, List<ModuleUpdateContext>> moduleUpdateContexts = new HashMap<>();
public void initFromJCR(InstallContext installContext) {
try {
// make sure we have the /modules node
if (!installContext.hasModulesNode()) {
final Session session = installContext.getConfigJCRSession();
session.getRootNode().addNode("modules", NodeTypes.Content.NAME);
}
final Node moduleNode = installContext.getOrCreateCurrentModuleNode();
if (moduleNode.hasNode(CONFIG_VERSIONS)) {
Node versionsNode = moduleNode.getNode(CONFIG_VERSIONS);
currentUpdate = versionsNode.getProperty(INSTALLED_UPDATES).getDecimal();
NodeIterator nodeIterator = moduleNode.getNode(CONFIG_VERSIONS).getNodes("*_*");
while (nodeIterator.hasNext()) {
Node node = nodeIterator.nextNode();
addNode(node);
}
}
} catch (RepositoryException e) {
log.error("Could not load versions from JCR", e);
}
}
private void addNode(Node versionFolderNode) throws RepositoryException {
PropertyIterator propertyIterator = versionFolderNode.getProperties("*_*");
while (propertyIterator.hasNext()) {
Property property = propertyIterator.nextProperty();
alreadyInstalledVersions.add(property.getName());
}
}
private void addJCRVersion(InstallContext installContext, BigDecimal index, TaskWrapper taskWrapper) {
String folderPath = MessageFormat.format("config/versions/{0}_{1}", index, taskWrapper.getUpdateVersion());
try {
final Node moduleNode = installContext.getOrCreateCurrentModuleNode();
final Node folderNode = NodeUtil.createPath(moduleNode, folderPath, NodeTypes.Content.NAME);
folderNode.setProperty(taskWrapper.getVersion(), taskWrapper.getName());
final Node versionsNode = NodeUtil.createPath(moduleNode, "/config/versions", NodeTypes.Content.NAME);
versionsNode.setProperty(INSTALLED_UPDATES, index);
if (folderNode.hasProperty(INSTALLED_TASKS)) {
Property installedTasks = folderNode.getProperty(INSTALLED_TASKS);
folderNode.setProperty(INSTALLED_TASKS, installedTasks.getDecimal().add(BigDecimal.ONE));
} else {
folderNode.setProperty(INSTALLED_TASKS, BigDecimal.ONE);
}
} catch (RepositoryException e) {
log.error("Could not add version to JCR", e);
}
}
private void registerNewModuleUpdate(InstallContext installContext, TaskWrapper task) {
currentUpdate = currentUpdate.add(BigDecimal.ONE);
var moduleUpdateContext = new ModuleUpdateContext();
moduleUpdateContext.taskVersions = new HashSet<>();
moduleUpdateContext.getTaskVersions().add(task.getVersion());
moduleUpdateContext.setIndex(currentUpdate);
List<ModuleUpdateContext> contexts = moduleUpdateContexts.getOrDefault(task.getUpdateVersion(), new ArrayList<>());
contexts.add(moduleUpdateContext);
moduleUpdateContexts.put(task.getUpdateVersion(), contexts);
addJCRVersion(installContext, currentUpdate, task);
}
private void updateModuleUpdateContext(InstallContext installContext, BigDecimal moduleUpdateToUpdate, TaskWrapper task) {
List<ModuleUpdateContext> contexts = moduleUpdateContexts.get(task.getUpdateVersion());
for (ModuleUpdateContext ctx : contexts) {
if (ctx.getIndex().equals(moduleUpdateToUpdate)) {
ctx.getTaskVersions().add(task.getVersion());
}
}
moduleUpdateContexts.put(task.getUpdateVersion(), contexts);
addJCRVersion(installContext, moduleUpdateToUpdate, task);
}
public boolean alreadyApplied(TaskWrapper task) {
return alreadyInstalledVersions.contains(task.getVersion());
}
public void registerTask(InstallContext installContext, TaskWrapper task) {
Optional<BigDecimal> index = moduleUpdateContexts.getOrDefault(task.getUpdateVersion(), Collections.emptyList()).stream()
.map(ModuleUpdateContext::getIndex)
.max(BigDecimal::compareTo);
if (index.isEmpty() || !index.get().equals(currentUpdate)) {
registerNewModuleUpdate(installContext, task);
} else {
updateModuleUpdateContext(installContext, index.get(), task);
}
}
}

View File

@ -8,19 +8,20 @@ import info.magnolia.module.delta.TaskExecutionException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.jcr.RepositoryException;
@Getter
@Slf4j
public class TaskWrapper extends AbstractTask {
private final Task task;
private final String updateVersion;
private final String version;
private final IntranetUpdateModuleVersionHandlerContext context;
public TaskWrapper(Task task, String version) {
public TaskWrapper(Task task, String updateVersion, String version, IntranetUpdateModuleVersionHandlerContext context) {
super(task.getName(), task.getDescription());
this.task = task;
this.updateVersion = updateVersion;
this.version = version;
this.context = context;
}
@Override
@ -28,11 +29,7 @@ public class TaskWrapper extends AbstractTask {
task.execute(installContext);
if (installContext.getStatus() != InstallStatus.installFailed) {
try {
VersionUtil.addVersion(installContext, getVersion(), task.getName());
} catch (RepositoryException e) {
log.info(e.getMessage());
}
context.registerTask(installContext, this);
}
}
}

View File

@ -1,42 +0,0 @@
package at.ucs.magnolia.updates.util;
import info.magnolia.jcr.util.NodeTypes;
import info.magnolia.jcr.util.NodeUtil;
import info.magnolia.module.InstallContext;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
public class VersionUtil {
private static Node getVersionsNode(InstallContext installContext) throws RepositoryException {
// make sure we have the /modules node
if (!installContext.hasModulesNode()) {
final Session session = installContext.getConfigJCRSession();
session.getRootNode().addNode("modules", NodeTypes.Content.NAME);
}
final Node moduleNode = installContext.getOrCreateCurrentModuleNode();
if (!moduleNode.hasNode("config/versions")) {
NodeUtil.createPath(moduleNode, "config/versions", "mgnl:content");
}
return moduleNode.getNode("config/versions");
}
public static boolean containsVersion(InstallContext installContext, String version) throws RepositoryException {
Node versionNode = getVersionsNode(installContext);
return versionNode != null && versionNode.hasProperty(version);
}
public static void addVersion(InstallContext installContext, String version, String name) throws RepositoryException {
Node versionsNode = getVersionsNode(installContext);
if (versionsNode != null) {
versionsNode.setProperty(version, name);
}
}
}

View File

@ -1,15 +1,13 @@
package at.ucs.magnolia.updates;
import at.ucs.magnolia.updates.util.IntranetUpdateModuleVersionHandlerContext;
import at.ucs.magnolia.updates.util.TaskWrapper;
import at.ucs.magnolia.updates.util.VersionUtil;
import info.magnolia.module.InstallContext;
import info.magnolia.module.delta.Delta;
import info.magnolia.module.delta.Task;
import info.magnolia.module.model.ModuleDefinition;
import info.magnolia.module.model.Version;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import javax.jcr.RepositoryException;
@ -18,19 +16,23 @@ import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
public class IntranetUpdateModuleVersionHandlerTest {
private final IntranetUpdateModuleVersionHandlerContext context = Mockito.mock(IntranetUpdateModuleVersionHandlerContext.class);
private static final List<String> appliedUpdates = new ArrayList<>();
@Test
public void testGetDeltas_mustReturnSimpleTask() throws RepositoryException {
try (MockedStatic<VersionUtil> utilities = Mockito.mockStatic(VersionUtil.class)) {
mockVersionUtil(utilities);
mockVersionUtil(context);
InstallContext installContext = Mockito.mock(InstallContext.class);
ModuleDefinition moduleDefinition = Mockito.mock(ModuleDefinition.class);
Mockito.when(installContext.getCurrentModuleDefinition()).thenReturn(moduleDefinition);
Mockito.when(moduleDefinition.getVersion()).thenReturn(Version.parseVersion("9.9.9"));
when(installContext.getCurrentModuleDefinition()).thenReturn(moduleDefinition);
when(moduleDefinition.getVersion()).thenReturn(Version.parseVersion("9.9.9"));
IntranetUpdateModuleConfig config = new IntranetUpdateModuleConfig() {
@Override
public List<Task> getInitialUpdateTasks() {
@ -47,24 +49,21 @@ public class IntranetUpdateModuleVersionHandlerTest {
return "at.ucs.magnolia.updates.test.updates1";
}
};
List<Delta> deltas = new IntranetUpdateModuleVersionHandler(config).getDeltas(installContext, installContext.getCurrentModuleDefinition().getVersion());
List<Delta> deltas = new TestIntranetUpdateModuleVersionHandler(config, context).getDeltas(installContext, installContext.getCurrentModuleDefinition().getVersion());
assertThat(deltas.size(), equalTo(1));
// the TestTask contained by the class V20241125113012_TestUpdate1 should be returned
List<Task> matching = deltas.stream().flatMap(delta -> delta.getTasks().stream()).filter( task -> (task instanceof TaskWrapper && ((TaskWrapper)task).getTask() instanceof TestTask) || (task instanceof TestTask)).toList();
assertThat("count of testtask must be 1", matching.size(), equalTo(1));
}
}
@Test
public void testGetDeltas_mustReturnAlsoInstallTasks() throws RepositoryException {
try (MockedStatic<VersionUtil> utilities = Mockito.mockStatic(VersionUtil.class)) {
mockVersionUtil(utilities);
mockVersionUtil(context);
InstallContext installContext = Mockito.mock(InstallContext.class);
ModuleDefinition moduleDefinition = Mockito.mock(ModuleDefinition.class);
Mockito.when(installContext.getCurrentModuleDefinition()).thenReturn(moduleDefinition);
Mockito.when(moduleDefinition.getVersion()).thenReturn(null);
when(installContext.getCurrentModuleDefinition()).thenReturn(moduleDefinition);
when(moduleDefinition.getVersion()).thenReturn(null);
IntranetUpdateModuleConfig config = new IntranetUpdateModuleConfig() {
@Override
public List<Task> getInitialUpdateTasks() {
@ -80,24 +79,21 @@ public class IntranetUpdateModuleVersionHandlerTest {
return "at.ucs.magnolia.updates.test.updates1";
}
};
List<Delta> deltas = new IntranetUpdateModuleVersionHandler(config).getDeltas(installContext, installContext.getCurrentModuleDefinition().getVersion());
List<Delta> deltas = new TestIntranetUpdateModuleVersionHandler(config, context).getDeltas(installContext, installContext.getCurrentModuleDefinition().getVersion());
assertThat(deltas.size(), equalTo(2));
// the TestTask contained by the class V20241125113012_TestUpdate1 must be returned
// The TestTask returned by the IntranetUpdateModuleConfig#getInitialUpdateTasks must be contained
List<Task> matching = deltas.stream().flatMap(delta -> delta.getTasks().stream()).filter( task -> (task instanceof TaskWrapper && ((TaskWrapper)task).getTask() instanceof TestTask) || (task instanceof TestTask)).toList();
assertThat("count of testtask must be 2", matching.size(), equalTo(2));
}
}
@Test
public void testGetDeltas_mustNotReturnInstallTasks() throws RepositoryException {
try (MockedStatic<VersionUtil> utilities = Mockito.mockStatic(VersionUtil.class)) {
mockVersionUtil(utilities);
mockVersionUtil(context);
InstallContext installContext = Mockito.mock(InstallContext.class);
ModuleDefinition moduleDefinition = Mockito.mock(ModuleDefinition.class);
Mockito.when(installContext.getCurrentModuleDefinition()).thenReturn(moduleDefinition);
Mockito.when(moduleDefinition.getVersion()).thenReturn(Version.parseVersion("9.9.9"));
when(installContext.getCurrentModuleDefinition()).thenReturn(moduleDefinition);
when(moduleDefinition.getVersion()).thenReturn(Version.parseVersion("9.9.9"));
IntranetUpdateModuleConfig config = new IntranetUpdateModuleConfig() {
@Override
public List<Task> getInitialUpdateTasks() {
@ -112,39 +108,37 @@ public class IntranetUpdateModuleVersionHandlerTest {
return "at.ucs.magnolia.updates.test.updates1";
}
};
List<Delta> deltas = new IntranetUpdateModuleVersionHandler(config).getDeltas(installContext, installContext.getCurrentModuleDefinition().getVersion());
List<Delta> deltas = new TestIntranetUpdateModuleVersionHandler(config, context).getDeltas(installContext, installContext.getCurrentModuleDefinition().getVersion());
assertThat(deltas.size(), equalTo(1));
// the TestTask contained by the class V20241125113012_TestUpdate1 should be returned
// The TestTask returned by the IntranetUpdateModuleConfig#getInitialUpdateTasks must not be contained
List<Task> matching = deltas.stream().flatMap(delta -> delta.getTasks().stream()).filter( task -> (task instanceof TaskWrapper && ((TaskWrapper)task).getTask() instanceof TestTask) || (task instanceof TestTask)).toList();
assertThat("count of testtask must be 1", matching.size(), equalTo(1));
}
}
private void mockVersionUtil(MockedStatic<VersionUtil> utilities) throws RepositoryException {
utilities.when(() -> VersionUtil.containsVersion(Mockito.any(), Mockito.any()))
private void mockVersionUtil(IntranetUpdateModuleVersionHandlerContext contextMock) throws RepositoryException {
doNothing().when(contextMock).initFromJCR(Mockito.any());
when(contextMock.alreadyApplied(Mockito.any()))
.thenAnswer(
invocation -> {
String version = invocation.getArgument(1, String.class);
String version = invocation.getArgument(0, TaskWrapper.class).getVersion();
return appliedUpdates.contains(version);
}
);
utilities.when(() -> VersionUtil.addVersion(Mockito.any(), Mockito.any(), Mockito.any()))
.thenAnswer(
doAnswer(
invocation -> {
String version = invocation.getArgument(1, String.class);
return appliedUpdates.add(version);
String version = invocation.getArgument(1, TaskWrapper.class).getVersion();
appliedUpdates.add(version);
return null;
}
);
).when(contextMock).registerTask(Mockito.any(), Mockito.any());
String testVersion = "123456";
assertThat(VersionUtil.containsVersion(null, testVersion), equalTo(false));
VersionUtil.addVersion(null, testVersion, null);
assertThat(VersionUtil.containsVersion(null, testVersion), equalTo(true));
assertFalse(contextMock.alreadyApplied(new TaskWrapper(new TestTask("does not matter", "does not matter"), null, testVersion, null)));
contextMock.registerTask(null, new TaskWrapper(new TestTask("does not matter", "does not matter"), null, testVersion, null));
assertTrue(contextMock.alreadyApplied(new TaskWrapper(new TestTask("does not matter", "does not matter"), null, testVersion, null)));
// cleanup versions
appliedUpdates.clear();
}

View File

@ -0,0 +1,10 @@
package at.ucs.magnolia.updates;
import at.ucs.magnolia.updates.util.IntranetUpdateModuleVersionHandlerContext;
public class TestIntranetUpdateModuleVersionHandler extends IntranetUpdateModuleVersionHandler {
public TestIntranetUpdateModuleVersionHandler(IntranetUpdateModuleConfig moduleConfig, IntranetUpdateModuleVersionHandlerContext context) {
super(moduleConfig);
this.context = context;
}
}