MMKB-5027: Adapts magnolia update mechanism

This commit is contained in:
Alexander Graf
2024-11-22 17:20:04 +01:00
commit 394a5c42bf
13 changed files with 540 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
package at.ucs.magnolia.updates;
import info.magnolia.module.delta.Task;
import java.util.List;
public interface IntranetUpdateModuleConfig {
List<Task> getInitialUpdateTasks();
String getYamlUpdateDir();
String getUpdateTaskPackage();
}

View File

@@ -0,0 +1,102 @@
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.VersionUtil;
import info.magnolia.module.DefaultModuleVersionHandler;
import info.magnolia.module.InstallContext;
import info.magnolia.module.delta.Delta;
import info.magnolia.module.delta.DeltaBuilder;
import info.magnolia.module.delta.Task;
import info.magnolia.module.model.Version;
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.*;
import java.util.function.Predicate;
import java.util.stream.IntStream;
@Slf4j
public abstract class IntranetUpdateModuleVersionHandler extends DefaultModuleVersionHandler {
protected abstract IntranetUpdateModuleConfig getModuleConfig();
@Override
protected List<Task> getExtraInstallTasks(InstallContext installContext) {
return getModuleConfig().getInitialUpdateTasks();
}
@Override
public List<Delta> getDeltas(InstallContext installContext, Version from) {
List<Delta> deltaList = new ArrayList<>();
if (from == null) {
deltaList.add(getInstall(installContext));
}
deltaList.addAll(getUpdateDeltas(installContext, from));
return deltaList;
}
protected List<Delta> getUpdateDeltas(InstallContext installContext, Version from) {
try {
return new Reflections(
getModuleConfig().getUpdateTaskPackage(),
new SubTypesScanner()
).getSubTypesOf(ModuleUpdate.class).stream()
.map(this::initiateModuleUpdate)
.filter(Objects::nonNull)
.sorted(new VersionComparator())
.map(updateModule -> buildDelta(installContext, updateModule))
.toList();
} catch (Exception e) {
log.error("Could not register update tasks", e);
}
return Collections.emptyList();
}
private ModuleUpdate initiateModuleUpdate(Class<? extends ModuleUpdate> moduleUpdateClass) {
try {
ModuleUpdate moduleUpdate = moduleUpdateClass.getConstructor().newInstance();
moduleUpdate.setYamlPath(getModuleConfig().getYamlUpdateDir());
return moduleUpdate;
} catch (RuntimeException | NoSuchMethodException | InstantiationException |
IllegalAccessException |
InvocationTargetException e) {
log.error(e.getLocalizedMessage(), e);
}
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) {
final String version = moduleUpdate.getVersion();
List<Task> wrappedTasks = IntStream.range(0, moduleUpdate.getAllTasks().size())
.mapToObj(i -> {
final String wrappedVersion = MessageFormat.format("{0}_{1}", version, i);
final Task task = moduleUpdate.getAllTasks().get(i);
return new TaskWrapper(task, wrappedVersion);
})
.filter(installNecessary(installContext))
.map(Task.class::cast)
.toList();
// Fake sem version
// We don't actually need it
return DeltaBuilder.update(Version.parseVersion(0, 4, 2), "")
.addTasks(wrappedTasks);
}
}

View File

@@ -0,0 +1,42 @@
package at.ucs.magnolia.updates;
import at.ucs.magnolia.updates.util.BootstrapUpdateYamlsWithProperties;
import at.ucs.magnolia.updates.util.LoggingTask;
import info.magnolia.module.delta.Task;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Getter
@Setter
@RequiredArgsConstructor
public abstract class ModuleUpdate {
private final String version;
private String yamlPath;
public List<Task> getUpdateTasks() {
return Collections.emptyList();
}
public List<Task> getUpdateTasksAfterBootstrap() {
return Collections.emptyList();
}
public List<Task> getAllTasks() {
List<Task> tasks = new ArrayList<>();
tasks.add(new LoggingTask("updating to version: " + version, ""));
tasks.addAll(getUpdateTasks());
tasks.add(new LoggingTask("Importing bootsrap Yamls for: " + version, ""));
tasks.add(new BootstrapUpdateYamlsWithProperties(
"Import bootstrap YAML folder",
MessageFormat.format("Imports bootstrap YAML files from {0}{1}", yamlPath, version), version, yamlPath));
tasks.addAll(getUpdateTasksAfterBootstrap());
return tasks;
}
}

View File

@@ -0,0 +1,40 @@
package at.ucs.magnolia.updates.util;
import info.magnolia.cms.util.ClasspathResourcesUtil;
import info.magnolia.module.InstallContext;
import info.magnolia.module.delta.BootstrapResourcesTask;
import javax.jcr.RepositoryException;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
/**
* This task bootstraps all yamls of update-yamls in a specific version and replaces magnolia property placeholder.
*/
public class BootstrapUpdateYamlsWithProperties extends BootstrapResourcesTask {
private final String version;
private final String yamlPath;
public BootstrapUpdateYamlsWithProperties(String name, String description, String version, String yamlPath) {
super(name, description);
this.version = version;
this.yamlPath = yamlPath;
}
@Override
protected void bootstrap(InstallContext installContext, int importUUIDBehavior) throws IOException, RepositoryException {
// In the original Task super.bootstrap is called, but since we want to call our own BootstrapUtil
// we have to use code called in the super class explicitly
String[] resourcesToBootstrap = ClasspathResourcesUtil.findResources(name -> acceptResource(installContext, name));
resourcesToBootstrap = filterResourcesToBootstrap(resourcesToBootstrap);
UcsBootstrapUtil.bootstrap(resourcesToBootstrap, importUUIDBehavior);
}
private String[] filterResourcesToBootstrap(String[] resourcesToBootstrap) {
return Arrays.stream(resourcesToBootstrap)
.filter(s -> s.startsWith(MessageFormat.format("/{0}/{1}/", yamlPath, version)))
.toArray(String[]::new);
}
}

View File

@@ -0,0 +1,20 @@
package at.ucs.magnolia.updates.util;
import info.magnolia.module.InstallContext;
import info.magnolia.module.delta.AbstractTask;
import info.magnolia.module.delta.TaskExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingTask extends AbstractTask {
private static final Logger log = LoggerFactory.getLogger(LoggingTask.class.getName());
public LoggingTask(String taskName, String taskDescription) {
super(taskName, taskDescription);
}
@Override
public void execute(InstallContext installContext) throws TaskExecutionException {
log.info(getName());
}
}

View File

@@ -0,0 +1,30 @@
package at.ucs.magnolia.updates.util;
import info.magnolia.init.PropertySource;
import org.apache.commons.io.IOUtils;
import org.apache.commons.text.StringSubstitutor;
import java.io.IOException;
import java.io.InputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.text.StringSubstitutor.*;
public class MagnoliaPropertyResolver {
private MagnoliaPropertyResolver() {
}
public static InputStream resolve(PropertySource properties, InputStream in) {
try {
return IOUtils.toInputStream(replaceMagnoliaPlaceholders(properties, in), UTF_8);
} catch (IOException e) {
return null;
}
}
private static String replaceMagnoliaPlaceholders(PropertySource properties, InputStream in) throws IOException {
StringSubstitutor substitutor = new StringSubstitutor(properties::getProperty, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
return substitutor.replace(IOUtils.toString(in, UTF_8));
}
}

View File

@@ -0,0 +1,39 @@
package at.ucs.magnolia.updates.util;
import info.magnolia.module.InstallContext;
import info.magnolia.module.InstallStatus;
import info.magnolia.module.delta.AbstractTask;
import info.magnolia.module.delta.Task;
import info.magnolia.module.delta.TaskExecutionException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
@Slf4j
public class TaskWrapper extends AbstractTask {
private final Task task;
@Getter
private final String version;
public TaskWrapper(Task task, String version) {
super(task.getName(), task.getDescription());
this.task = task;
this.version = version;
}
@Override
public void execute(InstallContext installContext) throws TaskExecutionException {
task.execute(installContext);
if (installContext.getStatus() != InstallStatus.installFailed) {
try {
VersionUtil.addVersion(installContext, getVersion(), task.getName());
} catch (RepositoryException e) {
log.info(e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,59 @@
package at.ucs.magnolia.updates.util;
import info.magnolia.cms.util.StringLengthComparator;
import info.magnolia.importexport.BootstrapUtil;
import info.magnolia.init.MagnoliaConfigurationProperties;
import info.magnolia.objectfactory.Components;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* This class is exactly working like the original BootstrapUtil class of Magnolia,
* with the only difference, that it substitutes Magnolia Property placeholders
* with their actual value, since this is not implemented for bootstrap yamls by Magnolia
*/
public class UcsBootstrapUtil {
private static final Logger log = LoggerFactory.getLogger(BootstrapUtil.class);
public static void bootstrap(String[] resourceNames, int importUUIDBehavior) throws IOException, RepositoryException {
// sort by length --> import parent node firstsubPath
List<String> list = new ArrayList<>(Arrays.asList(resourceNames));
if (list.contains(null)) {
throw new IllegalArgumentException("Resource names contain a <null> entry that cannot be processed.");
}
Collections.sort(list, new StringLengthComparator());
for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) {
bootstrap(iter.next(), null, importUUIDBehavior);
}
}
public static void bootstrap(String resourceName, String subPath, int importUUIDBehavior) throws IOException, RepositoryException {
final InputStream stream = BootstrapUtil.class.getResourceAsStream(resourceName);
if (stream == null) {
throw new IOException("Can't find resource to bootstrap at " + resourceName);
}
MagnoliaConfigurationProperties magnoliaConfiguration = Components.getComponent(MagnoliaConfigurationProperties.class);
InputStream resolvedStream = MagnoliaPropertyResolver.resolve(magnoliaConfiguration, stream);
// Verify if the node already exists and execute jcr import command
bootstrap(resourceName, subPath, resolvedStream, importUUIDBehavior);
}
public static void bootstrap(String resourceName, String subPath, InputStream stream, int importUUIDBehavior) throws RepositoryException {
BootstrapUtil.bootstrap(resourceName, subPath, stream, importUUIDBehavior);
}
public static void export(Node content, File directory) throws IOException, RepositoryException {
BootstrapUtil.export(content, directory);
}
}

View File

@@ -0,0 +1,23 @@
package at.ucs.magnolia.updates.util;
import at.ucs.magnolia.updates.ModuleUpdate;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
@Slf4j
public class VersionComparator implements Comparator<ModuleUpdate> {
@Override
public int compare(ModuleUpdate o1, ModuleUpdate o2) {
var o1Version = o1.getVersion();
var o2Version = o2.getVersion();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("ddMMyyyyHHmm");
LocalDateTime first = LocalDateTime.from(dateTimeFormatter.parse(o1Version.substring(0, 12)));
LocalDateTime second = LocalDateTime.from(dateTimeFormatter.parse(o2Version.substring(0, 12)));
return first.compareTo(second);
}
}

View File

@@ -0,0 +1,41 @@
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);
}
}
}