) jobDefinition.getParams()).entrySet().stream().map(entry ->
+ ops.setProperty(entry.getKey(), entry.getValue(), (valueConverter, property) -> Optional.of(PropertyUtil.createValue(property, ValueFactoryImpl.getInstance())))
+ ).toArray(NodeOperation[]::new)
+ ),
+ ops.setEnabledProperty(jobDefinition.isEnabled())
+ );
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/AbstractInstallDialogFieldTypesTask.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/AbstractInstallDialogFieldTypesTask.java
new file mode 100644
index 0000000..3930635
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/AbstractInstallDialogFieldTypesTask.java
@@ -0,0 +1,65 @@
+package com.merkle.oss.magnolia.setup.task.common;
+
+import info.magnolia.jcr.nodebuilder.NodeOperation;
+import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
+import info.magnolia.module.InstallContext;
+import info.magnolia.repository.RepositoryConstants;
+import info.magnolia.ui.field.ConfiguredFieldDefinition;
+import info.magnolia.ui.field.factory.AbstractFieldFactory;
+
+import java.lang.invoke.MethodHandles;
+import java.text.MessageFormat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.merkle.oss.magnolia.powernode.NodeOperationFactory;
+import com.merkle.oss.magnolia.powernode.ValueConverter;
+import com.merkle.oss.magnolia.setup.task.nodebuilder.AbstractPathNodeBuilderTask;
+
+/**
+ * Base class for dialog field type install tasks.
+ */
+public abstract class AbstractInstallDialogFieldTypesTask extends AbstractPathNodeBuilderTask {
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final String TASK_NAME = "Install Dialog Field Types";
+ private static final String TASK_DESCRIPTION = "Install Dialog Field Types";
+
+ public static final String MODULE_PATH = "modules/{0}";
+
+ private final NodeOperationFactory ops;
+ private final String fieldTypeName;
+ private final Class extends ConfiguredFieldDefinition> definitionClass;
+ private final Class extends AbstractFieldFactory> factoryClass;
+
+ protected AbstractInstallDialogFieldTypesTask(
+ final NodeOperationFactory nodeOperationFactory,
+ final String fieldTypeName,
+ final Class extends ConfiguredFieldDefinition> definitionClass,
+ final Class extends AbstractFieldFactory> factoryClass
+ ) {
+ super(TASK_NAME, TASK_DESCRIPTION, ErrorHandling.strict, RepositoryConstants.CONFIG);
+ this.ops = nodeOperationFactory;
+ this.fieldTypeName = fieldTypeName;
+ this.definitionClass = definitionClass;
+ this.factoryClass = factoryClass;
+ }
+
+ @Override
+ protected NodeOperation[] getNodeOperations(final InstallContext ctx) {
+ final String moduleName = ctx.getCurrentModuleDefinition().getName();
+ final String modulePath = MessageFormat.format(MODULE_PATH, moduleName);
+ LOG.info("installing dialogFieldType '{}' for module {}", fieldTypeName, modulePath);
+ return new NodeOperation[]{
+ ops.getOrAddContentNode(modulePath).then(
+ ops.getOrAddContentNode("fieldTypes").then(
+ ops.getOrAddContentNode(fieldTypeName).then(
+ ops.setProperty("definitionClass", definitionClass.getName(), ValueConverter::toValue),
+ ops.setProperty("factoryClass", factoryClass.getName(), ValueConverter::toValue)
+ )
+ )
+ )
+ };
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/AbstractInstallFilterTask.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/AbstractInstallFilterTask.java
new file mode 100644
index 0000000..5fafecd
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/AbstractInstallFilterTask.java
@@ -0,0 +1,85 @@
+package com.merkle.oss.magnolia.setup.task.common;
+
+import com.merkle.oss.magnolia.powernode.NodeOperationFactory;
+import com.merkle.oss.magnolia.setup.task.nodebuilder.AbstractPathNodeBuilderTask;
+import com.merkle.oss.magnolia.powernode.ValueConverter;
+import info.magnolia.cms.filters.FilterManager;
+import info.magnolia.cms.filters.MgnlFilter;
+import info.magnolia.jcr.nodebuilder.NodeOperation;
+import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
+import info.magnolia.module.InstallContext;
+import info.magnolia.module.delta.FilterOrderingTask;
+import info.magnolia.module.delta.TaskExecutionException;
+import info.magnolia.repository.RepositoryConstants;
+
+import javax.jcr.RepositoryException;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+public abstract class AbstractInstallFilterTask extends AbstractPathNodeBuilderTask {
+ protected final NodeOperationFactory ops;
+ private final Class extends MgnlFilter> filterClass;
+ private final String filterName;
+ private final String[] requiredFiltersBefore;
+
+ /**
+ * Installs a Filter into the filter chain (and replaces an existing filter at the same place with the same name)
+ *
+ * @param filterClass class of the magnolia filter
+ * @param filterName name of the node the filter should be created in. Must be a relative path below the root path of the filter chain (/server/filters)
+ * @param requiredFiltersBefore an array of filter names that must appear before the filter specified as filterName.
+ */
+ protected AbstractInstallFilterTask(
+ final NodeOperationFactory nodeOperationFactory,
+ final Class extends MgnlFilter> filterClass,
+ final String filterName,
+ final String... requiredFiltersBefore
+ ) {
+ super(
+ "Install Filter " + filterName + "(" + filterClass + ")",
+ "",
+ ErrorHandling.strict,
+ RepositoryConstants.CONFIG,
+ FilterManager.SERVER_FILTERS
+ );
+ this.ops = nodeOperationFactory;
+ this.filterClass = filterClass;
+ this.filterName = filterName;
+ this.requiredFiltersBefore = requiredFiltersBefore;
+ }
+
+ @Override
+ protected final void doExecute(final InstallContext installContext) throws RepositoryException, TaskExecutionException {
+ super.doExecute(installContext);
+ new FilterOrderingTask(filterName, requiredFiltersBefore).execute(installContext);
+ }
+
+ @Override
+ protected final NodeOperation[] getNodeOperations(InstallContext ctx) {
+ return new NodeOperation[]{
+ ops.getOrAddNode(filterName).then(
+ append(
+ getFilterNodeOperations(),
+ ops.setProperty("class", filterClass.getName(), ValueConverter::toValue),
+ ops.setProperty("enabled", true, ValueConverter::toValue)
+ )
+ )
+ };
+ }
+
+ private NodeOperation[] append(final NodeOperation[] ops1, final NodeOperation... ops2) {
+ return Stream
+ .concat(
+ Arrays.stream(ops1),
+ Arrays.stream(ops2)
+ )
+ .toArray(NodeOperation[]::new);
+ }
+
+ /**
+ * NodeOperations to be executed in the context of the newly created filter node.
+ */
+ protected NodeOperation[] getFilterNodeOperations() {
+ return new NodeOperation[]{};
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/InstallLicenseTask.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/InstallLicenseTask.java
new file mode 100644
index 0000000..aaa4501
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/InstallLicenseTask.java
@@ -0,0 +1,69 @@
+package com.merkle.oss.magnolia.setup.task.common;
+
+import info.magnolia.init.MagnoliaConfigurationProperties;
+import info.magnolia.jcr.nodebuilder.NodeOperation;
+import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
+import info.magnolia.module.InstallContext;
+import info.magnolia.repository.RepositoryConstants;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import com.merkle.oss.magnolia.powernode.NodeOperationFactory;
+import com.merkle.oss.magnolia.powernode.ValueConverter;
+import com.merkle.oss.magnolia.setup.task.nodebuilder.AbstractPathNodeBuilderTask;
+import com.merkle.oss.magnolia.setup.task.type.InstallAndUpdateTask;
+import com.merkle.oss.magnolia.setup.task.type.LocalDevelopmentStartupTask;
+
+/**
+ * Configure magnolia license.
+ *
+ * Use the following properties in magnolia.properties:
+ *
+ * magnolia.license.owner=
+ * magnolia.license.key=
+ *
+ * - Add to according 'ModuleVersionHandler' in a project
+ * - Execute as getInstallAndUpdateTask
+ */
+public class InstallLicenseTask extends AbstractPathNodeBuilderTask implements InstallAndUpdateTask {
+ private static final String TASK_NAME = "Install License Task";
+ private static final String TASK_DESCRIPTION = "This task installs the Magnolia license.";
+ private static final String PATH = "/modules/enterprise";
+
+ private final MagnoliaConfigurationProperties properties;
+ private final NodeOperationFactory ops;
+
+ @Inject
+ public InstallLicenseTask(
+ final NodeOperationFactory nodeOperationFactory,
+ final MagnoliaConfigurationProperties properties
+ ) {
+ super(TASK_NAME, TASK_DESCRIPTION, ErrorHandling.strict, RepositoryConstants.CONFIG, PATH);
+ this.ops = nodeOperationFactory;
+ this.properties = properties;
+ }
+
+ @Override
+ protected NodeOperation[] getNodeOperations(final InstallContext ctx) {
+ return getOwner().flatMap(owner -> getKey().map(key ->
+ ops.getOrAddContentNode("license").then(
+ ops.setProperty("owner", owner, ValueConverter::toValue),
+ ops.setProperty("key", key, ValueConverter::toValue)
+ )
+ )).stream().toArray(NodeOperation[]::new);
+ }
+
+ private Optional getOwner() {
+ return getProperty("magnolia.license.owner");
+ }
+
+ private Optional getKey() {
+ return getProperty("magnolia.license.key");
+ }
+
+ private Optional getProperty(final String key) {
+ return Optional.ofNullable(properties.getProperty(key));
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/ReregisterServletsTask.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/ReregisterServletsTask.java
new file mode 100644
index 0000000..95ecba1
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/ReregisterServletsTask.java
@@ -0,0 +1,60 @@
+package com.merkle.oss.magnolia.setup.task.common;
+
+import info.magnolia.jcr.util.NodeNameHelper;
+import info.magnolia.module.InstallContext;
+import info.magnolia.module.delta.ArrayDelegateTask;
+import info.magnolia.module.delta.RegisterServletTask;
+import info.magnolia.module.delta.TaskExecutionException;
+import info.magnolia.module.model.ModuleDefinition;
+import info.magnolia.module.model.ServletDefinition;
+
+import javax.inject.Inject;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import com.merkle.oss.magnolia.setup.task.type.InstallAndUpdateTask;
+
+/**
+ * Reregistering servlets. Normally they only get registered on install, but not on update (info.magnolia.module.delta.RegisterModuleServletsTask)
+ */
+public class ReregisterServletsTask extends ArrayDelegateTask implements InstallAndUpdateTask {
+ private static final String DEFAULT_SERVLET_FILTER_PATH = "server/filters/servlets";
+ private final NodeNameHelper nodeNameHelper;
+
+ @Inject
+ public ReregisterServletsTask(final NodeNameHelper nodeNameHelper) {
+ super("Reregister module servlets", "Reregisters servlets for this module.");
+ this.nodeNameHelper = nodeNameHelper;
+ }
+
+ @Override
+ public void execute(InstallContext installContext) throws TaskExecutionException {
+ final ModuleDefinition moduleDefinition = installContext.getCurrentModuleDefinition();
+ for (ServletDefinition servletDefinition : moduleDefinition.getServlets()) {
+ addTask(new ReregisterServletTask(servletDefinition, nodeNameHelper));
+ }
+ super.execute(installContext);
+ }
+
+ private static class ReregisterServletTask extends RegisterServletTask {
+ public ReregisterServletTask(ServletDefinition servletDefinition, NodeNameHelper nodeNameHelper) {
+ super(servletDefinition, nodeNameHelper);
+ }
+
+ @Override
+ public void execute(final InstallContext installContext) throws TaskExecutionException {
+ if(!isRegistered(installContext)) {
+ super.execute(installContext);
+ }
+ }
+
+ private boolean isRegistered(final InstallContext installContext) throws TaskExecutionException {
+ try {
+ final Session session = installContext.getConfigJCRSession();
+ return session.getRootNode().hasNode(DEFAULT_SERVLET_FILTER_PATH + "/" + getServletDefinition().getName());
+ } catch (RepositoryException e) {
+ throw new TaskExecutionException("Failed to reregister servlet "+getServletDefinition().getName(), e);
+ }
+ }
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/SetEmptyDefaultExtensionTask.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/SetEmptyDefaultExtensionTask.java
new file mode 100644
index 0000000..0ae2f0a
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/SetEmptyDefaultExtensionTask.java
@@ -0,0 +1,34 @@
+package com.merkle.oss.magnolia.setup.task.common;
+
+import com.merkle.oss.magnolia.powernode.NodeOperationFactory;
+import com.merkle.oss.magnolia.powernode.ValueConverter;
+import com.merkle.oss.magnolia.setup.task.nodebuilder.AbstractPathNodeBuilderTask;
+import com.merkle.oss.magnolia.setup.task.type.InstallAndUpdateTask;
+
+import info.magnolia.jcr.nodebuilder.NodeOperation;
+import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
+import info.magnolia.module.InstallContext;
+import info.magnolia.repository.RepositoryConstants;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.inject.Inject;
+
+public class SetEmptyDefaultExtensionTask extends AbstractPathNodeBuilderTask implements InstallAndUpdateTask {
+ private static final String TASK_NAME = "Set default Extension";
+ private static final String TASK_DESCRIPTION = "Set default Extension";
+ private static final String ACTIONS_PATH = "/server";
+ private final NodeOperationFactory ops;
+
+ @Inject
+ public SetEmptyDefaultExtensionTask(final NodeOperationFactory nodeOperationFactory) {
+ super(TASK_NAME, TASK_DESCRIPTION, ErrorHandling.strict, RepositoryConstants.CONFIG, ACTIONS_PATH);
+ ops = nodeOperationFactory;
+ }
+
+ @Override
+ protected NodeOperation[] getNodeOperations(final InstallContext ctx) {
+ return new NodeOperation[]{
+ ops.setProperty("defaultExtension", StringUtils.EMPTY, ValueConverter::toValue)
+ };
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/SetupSmtpTask.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/SetupSmtpTask.java
new file mode 100644
index 0000000..84a02fc
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/SetupSmtpTask.java
@@ -0,0 +1,111 @@
+package com.merkle.oss.magnolia.setup.task.common;
+
+import com.merkle.oss.magnolia.powernode.NodeOperationFactory;
+import com.merkle.oss.magnolia.powernode.PowerNode;
+import com.merkle.oss.magnolia.powernode.PowerNodeService;
+import com.merkle.oss.magnolia.powernode.ValueConverter;
+import com.merkle.oss.magnolia.setup.task.nodebuilder.AbstractPathNodeBuilderTask;
+import com.merkle.oss.magnolia.setup.task.type.InstallAndUpdateTask;
+
+import info.magnolia.init.MagnoliaConfigurationProperties;
+import info.magnolia.jcr.nodebuilder.NodeOperation;
+import info.magnolia.jcr.nodebuilder.Ops;
+import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
+import info.magnolia.module.InstallContext;
+import info.magnolia.objectfactory.Components;
+import info.magnolia.repository.RepositoryConstants;
+
+import javax.inject.Inject;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Configure SMTP server.
+ *
+ * Use the following properties in magnolia.properties:
+ *
+ * magnolia.smtp.security=none|ssl|tls
+ * magnolia.smtp.auth=null|userPassword
+ * magnolia.smtp.user=user
+ * magnolia.smtp.keystorePath=/folder/smtp # get path from the password app
+ * magnolia.smtp.server=mailgateway.sg.ch.namics.com
+ * magnolia.smtp.port=25
+ *
+ * - Add to according 'ModuleVersionHandler' in a project
+ * - Execute as getInstallAndUpdateTask
+ */
+public class SetupSmtpTask extends AbstractPathNodeBuilderTask implements InstallAndUpdateTask {
+ private static final String TASK_NAME = "SetupSmtpTask";
+ private static final String TASK_DESCRIPTION = "Set SMTP configuration from magnolia.properties";
+
+ private static final String MAIL_MODULE_CONFIG_PATH = "/modules/mail/config";
+
+ private final MagnoliaConfigurationProperties properties;
+ private final PowerNodeService powerNodeService;
+ private final NodeOperationFactory ops;
+
+ @Inject
+ public SetupSmtpTask(
+ final PowerNodeService powerNodeService,
+ final NodeOperationFactory nodeOperationFactory
+ ) {
+ super(TASK_NAME, TASK_DESCRIPTION, ErrorHandling.strict, RepositoryConstants.CONFIG, MAIL_MODULE_CONFIG_PATH);
+ this.powerNodeService = powerNodeService;
+ this.ops = nodeOperationFactory;
+ this.properties = Components.getComponent(MagnoliaConfigurationProperties.class);
+ }
+
+ @Override
+ protected NodeOperation[] getNodeOperations(final InstallContext ctx) {
+ final String security = getProperty("magnolia.smtp.security", "none");
+ final String auth = getProperty("magnolia.smtp.auth", "null");
+ final String server = getProperty("magnolia.smtp.server", "localhost");
+ final String port = getProperty("magnolia.smtp.port", "25");
+ final String user = getProperty("magnolia.smtp.user", StringUtils.EMPTY);
+ final String keystorePath = getProperty("magnolia.smtp.keystorePath", StringUtils.EMPTY);
+
+ return new NodeOperation[]{
+ ops.getOrAddContentNode("smtpConfiguration").then(
+ ops.getOrAddNode("authentication").then(ops.clearProperties().then(
+ getAuth(auth, user, keystorePath)
+ )),
+ ops.setProperty("server", server, ValueConverter::toValue),
+ ops.setProperty("port", port, ValueConverter::toValue),
+ ops.setProperty("security", security, ValueConverter::toValue)
+ )
+ };
+ }
+
+ private NodeOperation[] getAuth(final String auth, final String user, final String keystorePath) {
+ if ("userPassword".equals(auth)) {
+ return getUserPasswordAuth(user, getKeystoreId(keystorePath).orElse(null));
+ }
+ return getNullAuth();
+ }
+
+ private Optional getKeystoreId(final String keystorePath) {
+ return powerNodeService.getByPath("keystore", keystorePath).map(PowerNode::getIdentifier);
+ }
+
+ private NodeOperation[] getUserPasswordAuth(final String user, final String passwordKeyStoreId) {
+ return new NodeOperation[]{
+ ops.setProperty("class", "info.magnolia.module.mail.smtp.authentication.UsernamePasswordSmtpAuthentication", ValueConverter::toValue),
+ ops.setProperty("user", user, ValueConverter::toValue),
+ passwordKeyStoreId != null ? ops.setProperty("passwordKeyStoreId", passwordKeyStoreId, ValueConverter::toValue) : Ops.noop()
+ };
+ }
+
+ private NodeOperation[] getNullAuth() {
+ return new NodeOperation[]{
+ ops.setProperty("class", "info.magnolia.module.mail.smtp.authentication.NullSmtpAuthentication", ValueConverter::toValue)
+ };
+ }
+
+ private String getProperty(final String name, final String fallback) {
+ if (properties.hasProperty(name)) {
+ return properties.getProperty(name);
+ }
+ return fallback;
+ }
+}
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000..dc79718
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,25 @@
+
+
+ 4.0.0
+
+
+ com.merkle.oss.magnolia
+ magnolia-setup-task
+ 0.0.1-SNAPSHOT
+
+
+ magnolia-setup-task-core
+
+
+
+ info.magnolia
+ magnolia-core
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
\ No newline at end of file
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/EnhancedModuleVersionHandler.java b/core/src/main/java/com/merkle/oss/magnolia/setup/EnhancedModuleVersionHandler.java
new file mode 100644
index 0000000..b79c963
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/EnhancedModuleVersionHandler.java
@@ -0,0 +1,145 @@
+package com.merkle.oss.magnolia.setup;
+
+import info.magnolia.module.DefaultModuleVersionHandler;
+import info.magnolia.module.InstallContext;
+import info.magnolia.module.ModuleManager;
+import info.magnolia.module.delta.Task;
+import info.magnolia.module.model.Version;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import com.merkle.oss.magnolia.setup.task.type.EarlyInstallTask;
+import com.merkle.oss.magnolia.setup.task.type.InstallAndUpdateTask;
+import com.merkle.oss.magnolia.setup.task.type.InstallTask;
+import com.merkle.oss.magnolia.setup.task.type.LocalDevelopmentStartupTask;
+import com.merkle.oss.magnolia.setup.task.type.ModuleStartupTask;
+import com.merkle.oss.magnolia.setup.task.type.SnapshotStartupTask;
+import com.merkle.oss.magnolia.setup.task.type.UpdateTask;
+import com.merkle.oss.magnolia.setup.task.type.VersionAwareTask;
+
+public abstract class EnhancedModuleVersionHandler extends DefaultModuleVersionHandler {
+ private final ModuleManager moduleManager;
+ private final Set earlyInstallTasks;
+ private final Set installTasks;
+ private final Set updateTasks;
+ private final Set installAndUpdateTasks;
+ private final Set moduleStartupTasks;
+ private final Set snapshotStartupTasks;
+ private final Set localDevelopmentStartupTasks;
+
+ protected EnhancedModuleVersionHandler(
+ final ModuleManager moduleManager,
+ final Set earlyInstallTasks,
+ final Set installTasks,
+ final Set updateTasks,
+ final Set installAndUpdateTasks,
+ final Set moduleStartupTasks,
+ final Set snapshotStartupTasks,
+ final Set localDevelopmentStartupTasks
+ ) {
+ this.moduleManager = moduleManager;
+ this.earlyInstallTasks = earlyInstallTasks;
+ this.installTasks = installTasks;
+ this.updateTasks = updateTasks;
+ this.installAndUpdateTasks = installAndUpdateTasks;
+ this.moduleStartupTasks = moduleStartupTasks;
+ this.snapshotStartupTasks = snapshotStartupTasks;
+ this.localDevelopmentStartupTasks = localDevelopmentStartupTasks;
+ }
+
+ /**
+ * This Method should only be used to execute tasks at the very beginning of module installation.
+ */
+ @Override
+ protected final List getBasicInstallTasks(final InstallContext installContext) {
+ final Version forVersion = getVersionFromInstallContext(installContext);
+ return Stream.concat(
+ getEarlyInstallTasks(installContext, forVersion),
+ super.getBasicInstallTasks(installContext).stream()
+ ).toList();
+ }
+
+ @Override
+ protected final List getExtraInstallTasks(final InstallContext installContext) { // when module node does not exist
+ final Version forVersion = getVersionFromInstallContext(installContext);
+ return Stream.of(
+ super.getExtraInstallTasks(installContext).stream(),
+ getInstallAndUpdateTasks(installContext, forVersion),
+ getInstallTasks(installContext, forVersion)
+ ).flatMap(Function.identity()).toList();
+ }
+
+ @Override
+ protected final List getDefaultUpdateTasks(final Version forVersion) { // on every module update
+ final InstallContext installContext = moduleManager.getInstallContext();
+ return Stream.of(
+ super.getDefaultUpdateTasks(forVersion).stream(),
+ getInstallAndUpdateTasks(installContext, forVersion),
+ getUpdateTasks(installContext, forVersion)
+ ).flatMap(Function.identity()).toList();
+ }
+
+ @Override
+ protected final List getStartupTasks(final InstallContext installContext) { // on every startup
+ final Version forVersion = getVersionFromInstallContext(installContext);
+ return Stream.of(
+ getModuleStartupTasks(installContext, forVersion),
+ isSnapshot(forVersion) ? getSnapshotStartupTasks(installContext, forVersion) : Stream.empty(),
+ isLocalDevelopmentEnvironment() ? getLocalDevelopmentStartupTasks(installContext, forVersion) : Stream.empty()
+ ).flatMap(Function.identity()).toList();
+ }
+
+ protected Version getVersionFromInstallContext(final InstallContext installContext) {
+ return installContext.getCurrentModuleDefinition().getVersion();
+ }
+
+ protected abstract boolean isLocalDevelopmentEnvironment();
+
+ private boolean isSnapshot(final Version version) {
+ return "SNAPSHOT".equalsIgnoreCase(version.getClassifier());
+ }
+
+ protected Stream getEarlyInstallTasks(final InstallContext installContext, final Version forVersion) {
+ return filter(earlyInstallTasks, forVersion);
+ }
+
+ protected Stream getInstallTasks(final InstallContext installContext, final Version forVersion) {
+ return filter(installTasks, forVersion);
+ }
+
+ protected Stream getInstallAndUpdateTasks(final InstallContext installContext, final Version forVersion) {
+ return filter(installAndUpdateTasks, forVersion);
+ }
+
+ protected Stream getUpdateTasks(final InstallContext installContext, final Version forVersion) {
+ return filter(updateTasks, forVersion);
+ }
+
+ protected Stream getModuleStartupTasks(final InstallContext installContext, final Version forVersion) {
+ return filter(moduleStartupTasks, forVersion);
+ }
+
+ protected Stream getSnapshotStartupTasks(final InstallContext installContext, final Version forVersion) {
+ return Stream.of(
+ filter(snapshotStartupTasks, forVersion),
+ // execute all general install and update tasks on snapshot
+ getInstallAndUpdateTasks(installContext, forVersion),
+ getUpdateTasks(installContext, forVersion)
+ ).flatMap(Function.identity());
+ }
+
+ protected Stream getLocalDevelopmentStartupTasks(final InstallContext installContext, final Version forVersion) {
+ return filter(localDevelopmentStartupTasks, forVersion);
+ }
+
+ protected Stream filter(final Collection extends VersionAwareTask> tasks, final Version forVersion) {
+ return tasks
+ .stream()
+ .filter(task -> task.test(forVersion))
+ .map(Task.class::cast);
+ }
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/TaskExecutor.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/TaskExecutor.java
new file mode 100644
index 0000000..8eb37ad
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/TaskExecutor.java
@@ -0,0 +1,119 @@
+package com.merkle.oss.magnolia.setup.task;
+
+import info.magnolia.module.InstallContextImpl;
+import info.magnolia.module.InstallStatus;
+import info.magnolia.module.ModuleRegistry;
+import info.magnolia.module.delta.Delta;
+import info.magnolia.module.delta.Task;
+import info.magnolia.module.delta.TaskExecutionException;
+import info.magnolia.module.model.ModuleDefinition;
+import info.magnolia.module.model.Version;
+import info.magnolia.objectfactory.ComponentProvider;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * Can be used to execute tasks from magnolia groovy console:
+ * {@code
+ * import com.namics.common.setup.task.TaskExecutor;
+ * import info.magnolia.objectfactory.Components;
+ *
+ * final TaskExecutor executor = Components.newInstance(TaskExecutor.class);
+ * executor.execute(com.namics.snb.web.setup.task.migration.VisionTeaserTargetNodeNameMigrationTask.class)
+ * }
+ */
+public class TaskExecutor {
+ private final ModuleRegistry moduleRegistry;
+ private final ComponentProvider componentProvider;
+ private final Set sessions = new HashSet<>();
+
+ @Inject
+ public TaskExecutor(
+ final ModuleRegistry moduleRegistry,
+ final ComponentProvider componentProvider
+ ) {
+ this.moduleRegistry = moduleRegistry;
+ this.componentProvider = componentProvider;
+ }
+
+ public void execute(final Class extends Task> taskClazz) throws TaskExecutionException {
+ execute(componentProvider.newInstance(taskClazz));
+ }
+
+ public void execute(final Task task) throws TaskExecutionException {
+ execute(task, getModuleDefinition(task.getClass()).orElse(null));
+ }
+
+ public void execute(final Class extends Task> taskClazz, @Nullable final ModuleDefinition module) throws TaskExecutionException {
+ execute(componentProvider.newInstance(taskClazz), module);
+ }
+
+ public void execute(final Task task, @Nullable final ModuleDefinition module) throws TaskExecutionException {
+ execute(task, module, true);
+ }
+
+ public void execute(final Task task, @Nullable final ModuleDefinition module, final boolean saveSession) throws TaskExecutionException {
+ final InstallContextImpl installContext = new InstallContextImpl(moduleRegistry) {
+ @Override
+ public int getTotalTaskCount() {
+ return 1;
+ }
+ @Override
+ public InstallStatus getStatus() {
+ return InstallStatus.inProgress;
+ }
+ @Override
+ public Session getJCRSession(String workspaceName) throws RepositoryException {
+ final Session session = super.getJCRSession(workspaceName);
+ sessions.add(session);
+ return session;
+ }
+ };
+ if(module != null) {
+ installContext.setCurrentModule(module);
+ }
+ task.execute(installContext);
+ if(saveSession) {
+ for (Session session : sessions) {
+ try {
+ session.save();
+ } catch (Exception e) {
+ throw new TaskExecutionException("Failed to save session", e);
+ }
+ }
+ }
+ }
+
+ private Optional getModuleDefinition(final Class extends Task> taskClass) {
+ return moduleRegistry.getModuleNames().stream()
+ .map(moduleRegistry::getDefinition)
+ .filter(moduleDefinition -> contains(moduleDefinition, taskClass))
+ .findFirst();
+ }
+
+ private boolean contains(final ModuleDefinition moduleDefinition, final Class extends Task> taskClass) {
+ final InstallContextImpl installContext = new InstallContextImpl(moduleRegistry);
+ installContext.setCurrentModule(new ModuleDefinition(
+ moduleDefinition.getName(),
+ Version.parseVersion(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE),
+ moduleDefinition.getClassName(),
+ moduleDefinition.getVersionHandler()
+ ));
+ return moduleRegistry
+ .getVersionHandler(moduleDefinition.getName())
+ .getDeltas(installContext, null)
+ .stream()
+ .map(Delta::getTasks)
+ .flatMap(Collection::stream)
+ .map(Object::getClass)
+ .anyMatch(taskClass::equals);
+ }
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/nodebuilder/AbstractContentNodeBuilderTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/nodebuilder/AbstractContentNodeBuilderTask.java
new file mode 100644
index 0000000..79975c1
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/nodebuilder/AbstractContentNodeBuilderTask.java
@@ -0,0 +1,73 @@
+package com.merkle.oss.magnolia.setup.task.nodebuilder;
+
+
+import info.magnolia.jcr.nodebuilder.*;
+import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
+import info.magnolia.jcr.nodebuilder.task.TaskLogErrorHandler;
+import info.magnolia.module.InstallContext;
+import info.magnolia.module.delta.AbstractRepositoryTask;
+import info.magnolia.module.delta.TaskExecutionException;
+
+import java.lang.invoke.MethodHandles;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class AbstractContentNodeBuilderTask extends AbstractRepositoryTask {
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final ErrorHandling errorHandling;
+
+ protected AbstractContentNodeBuilderTask(String name, String description, ErrorHandling errorHandling) {
+ super(name, description);
+ this.errorHandling = errorHandling;
+ }
+
+ @Override
+ protected void doExecute(final InstallContext ctx) throws RepositoryException, TaskExecutionException {
+ final Node root = getRootNode(ctx);
+ final NodeOperation[] operations = obtainNodeOperations(ctx);
+ final ErrorHandler errorHandler = newErrorHandler(ctx);
+ final NodeBuilder nodeBuilder = new NodeBuilder(errorHandler, root, operations);
+ try {
+ nodeBuilder.exec();
+ } catch (NodeOperationException e) {
+ LOG.error("Could not execute node builder task", e);
+ throw new TaskExecutionException(e.getMessage(), e.getCause());
+ }
+ }
+
+ /**
+ * This method must be used to set NodeOperations. Use this pattern:
+ * return new NodeOperation[]{ addNode(...).then( ) };
+ *
+ * @return node operations to be used in this tasks
+ * @param ctx install context
+ */
+ protected abstract NodeOperation[] getNodeOperations(final InstallContext ctx);
+
+ protected abstract Node getRootNode(final InstallContext ctx) throws RepositoryException;
+
+ protected ErrorHandler newErrorHandler(final InstallContext ctx) {
+ if (errorHandling == ErrorHandling.strict) {
+ return new StrictErrorHandler();
+ }
+ return new TaskLogErrorHandler(ctx);
+ }
+
+ private NodeOperation[] obtainNodeOperations(final InstallContext ctx) throws TaskExecutionException {
+ final NodeOperation[] operations = getNodeOperations(ctx);
+ if (operations == null) {
+ if (errorHandling == ErrorHandling.logging) {
+ LOG.warn("No NodeOperations have been specified. Doing nothing");
+ return new NodeOperation[0];
+ }
+ throw new TaskExecutionException("Please specify NodeOperations. Can be an empty array if no operations should be done...");
+ }
+ return operations;
+ }
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/nodebuilder/AbstractPathNodeBuilderTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/nodebuilder/AbstractPathNodeBuilderTask.java
new file mode 100644
index 0000000..f5163fc
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/nodebuilder/AbstractPathNodeBuilderTask.java
@@ -0,0 +1,43 @@
+package com.merkle.oss.magnolia.setup.task.nodebuilder;
+
+import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
+import info.magnolia.module.InstallContext;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * A task using the NodeBuilder API, applying operations on a given path.
+ */
+public abstract class AbstractPathNodeBuilderTask extends AbstractContentNodeBuilderTask {
+ private final String workspaceName;
+ private final String rootPath;
+
+ protected AbstractPathNodeBuilderTask(
+ final String taskName,
+ final String description,
+ final ErrorHandling errorHandling,
+ final String workspaceName
+ ) {
+ this(taskName, description, errorHandling, workspaceName, "/");
+ }
+
+ protected AbstractPathNodeBuilderTask(
+ final String taskName,
+ final String description,
+ final ErrorHandling errorHandling,
+ final String workspaceName,
+ final String rootPath
+ ) {
+ super(taskName, description, errorHandling);
+ this.workspaceName = workspaceName;
+ this.rootPath = rootPath;
+ }
+
+ @Override
+ protected Node getRootNode(final InstallContext ctx) throws RepositoryException {
+ final Session hm = ctx.getJCRSession(workspaceName);
+ return hm.getNode(rootPath);
+ }
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/EarlyInstallTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/EarlyInstallTask.java
new file mode 100644
index 0000000..b131b0d
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/EarlyInstallTask.java
@@ -0,0 +1,7 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed at the very beginning of a Module installation, before the basic install tasks.
+ */
+public interface EarlyInstallTask extends VersionAwareTask {
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallAndUpdateTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallAndUpdateTask.java
new file mode 100644
index 0000000..3e7a8e5
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallAndUpdateTask.java
@@ -0,0 +1,8 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed on Module Install and EVERY Update Delta
+ * --> This means that if you reset the module version to 0, these tasks will be executed for every Update step from 0 to the current version.
+ */
+public interface InstallAndUpdateTask extends VersionAwareTask {
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallTask.java
new file mode 100644
index 0000000..5f9cbf7
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallTask.java
@@ -0,0 +1,8 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed on Module Install
+ * Will be executed after {@link InstallAndUpdateTask}
+ */
+public interface InstallTask extends VersionAwareTask {
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/LocalDevelopmentStartupTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/LocalDevelopmentStartupTask.java
new file mode 100644
index 0000000..3ea5f63
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/LocalDevelopmentStartupTask.java
@@ -0,0 +1,7 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed when the module starts up in local development.
+ */
+public interface LocalDevelopmentStartupTask extends VersionAwareTask {
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/ModuleStartupTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/ModuleStartupTask.java
new file mode 100644
index 0000000..88f77b8
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/ModuleStartupTask.java
@@ -0,0 +1,7 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed when the module starts up
+ */
+public interface ModuleStartupTask extends VersionAwareTask {
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/SnapshotStartupTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/SnapshotStartupTask.java
new file mode 100644
index 0000000..32f3bb7
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/SnapshotStartupTask.java
@@ -0,0 +1,7 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed when the module starts up with a SNAPSHOT version.
+ */
+public interface SnapshotStartupTask extends VersionAwareTask {
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/UpdateTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/UpdateTask.java
new file mode 100644
index 0000000..2583020
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/UpdateTask.java
@@ -0,0 +1,9 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed EVERY Update Delta
+ * --> This means that if you reset the module version to 0, these tasks will be executed for every Update step from 0 to the current version.
+ * Will be executed after {@link InstallAndUpdateTask}
+ */
+public interface UpdateTask extends VersionAwareTask {
+}
diff --git a/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/VersionAwareTask.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/VersionAwareTask.java
new file mode 100644
index 0000000..aa9e631
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/VersionAwareTask.java
@@ -0,0 +1,14 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+import info.magnolia.module.delta.Task;
+import info.magnolia.module.model.Version;
+
+import java.util.function.Predicate;
+
+public interface VersionAwareTask extends Task, Predicate {
+
+ @Override
+ default boolean test(final Version version) {
+ return true;
+ }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..eeb00eb
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,179 @@
+
+
+ 4.0.0
+
+ com.merkle.oss.magnolia
+ magnolia-setup-task
+ pom
+ 0.0.1-SNAPSHOT
+
+ ${project.artifactId}
+ https://github.com/merkle-open/magnolia-setup-task
+ Setup task to help bootstrap magnolia
+
+
+
+ MIT License
+ https://opensource.org/licenses/MIT
+ repo
+
+
+
+
+
+ Merkle Magnolia
+ magnolia@merkle.com
+ Merkle DACH
+ https://merkleinc.ch
+
+
+
+
+ https://github.com/merkle-open/magnolia-setup-task
+ scm:git:git@github.com:merkle-open/magnolia-setup-task.git
+ scm:git:git@github.com:merkle-open/magnolia-setup-task.git
+
+
+
+ common-task
+ core
+
+
+
+
+ 6.3.0
+ 3.0.2
+ 2.1.0
+
+
+ 3.11.0
+ 3.3.0
+ 3.8.0
+ 3.2.5
+ 0.5.0
+
+ 17
+ UTF-8
+
+
+
+
+
+ info.magnolia.bundle
+ magnolia-bundle-parent
+ ${magnolia.version}
+ pom
+ import
+
+
+ com.merkle.oss.magnolia
+ magnolia-setup-task-core
+ ${project.version}
+
+
+ com.namics.oss.magnolia
+ magnolia-powernode
+ ${namics.oss.powernode.version}
+
+
+ com.google.code.findbugs
+ jsr305
+ ${jsr305.nullable.version}
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${mvn.compiler.plugin.version}
+
+ ${javaVersion}
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${mvn.source.plugin.version}
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${mvn.javadoc.version}
+
+ false
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+
+
+
+ central
+ https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+
+ deploy
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${mvn.gpg.plugin.version}
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ ${mvn.sonatype.publishing.plugin.version}
+ true
+
+ central
+ true
+ published
+
+
+
+
+
+
+
\ No newline at end of file