) 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/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/GroupManagerUtil.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/GroupManagerUtil.java
new file mode 100644
index 0000000..9102420
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/GroupManagerUtil.java
@@ -0,0 +1,44 @@
+package com.merkle.oss.magnolia.setup.task.common.security.util;
+
+import info.magnolia.cms.security.AccessDeniedException;
+import info.magnolia.cms.security.Group;
+import info.magnolia.cms.security.GroupManager;
+import info.magnolia.cms.security.SecuritySupport;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GroupManagerUtil {
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private final Supplier groupManager;
+
+ @Inject
+ public GroupManagerUtil(final SecuritySupport securitySupport) {
+ groupManager = securitySupport::getGroupManager;
+ }
+
+ public Set getGroups(final String... groupNames) {
+ return Arrays.stream(groupNames)
+ .map(this::getGroup)
+ .flatMap(Optional::stream)
+ .collect(Collectors.toSet());
+ }
+
+ public Optional getGroup(final String groupName) {
+ try {
+ return Optional.ofNullable(groupManager.get().getGroup(groupName));
+ } catch (AccessDeniedException e) {
+ LOG.error("Access denied to get group " + groupName, e);
+ return Optional.empty();
+ }
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/RoleManagerUtil.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/RoleManagerUtil.java
new file mode 100644
index 0000000..0d124d7
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/RoleManagerUtil.java
@@ -0,0 +1,123 @@
+package com.merkle.oss.magnolia.setup.task.common.security.util;
+
+import info.magnolia.cms.security.Permission;
+import info.magnolia.cms.security.Role;
+import info.magnolia.cms.security.RoleManager;
+import info.magnolia.cms.security.SecuritySupport;
+import info.magnolia.cms.security.SilentSessionOp;
+import info.magnolia.cms.security.auth.ACL;
+import info.magnolia.context.MgnlContext;
+import info.magnolia.jcr.util.NodeTypes;
+import info.magnolia.jcr.util.NodeUtil;
+import info.magnolia.repository.RepositoryConstants;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class RoleManagerUtil {
+ private static final String WEB_ACCESS_WORKSPACE = "uri";
+ private final Supplier roleManager;
+
+ @Inject
+ public RoleManagerUtil(final SecuritySupport securitySupport) {
+ roleManager = securitySupport::getRoleManager;
+ }
+
+ public Optional getRole(final String name) {
+ return Optional.ofNullable(roleManager.get().getRole(name));
+ }
+
+ public Role getOrCreateRole(final String name) throws Exception {
+ return getOrCreateRole(null, name);
+ }
+
+ public Role getOrCreateRole(@Nullable final String path, final String name) throws Exception {
+ @Nullable final Role role = getRole(name).orElse(null);
+ if (role == null) {
+ final Node parent = getOrCreateNode(path);
+ return roleManager.get().createRole(parent.getPath(), name);
+ }
+ return role;
+ }
+
+ private Node getOrCreateNode(@Nullable final String path) {
+ return MgnlContext.doInSystemContext(new SilentSessionOp<>(RepositoryConstants.USER_ROLES) {
+ @Override
+ public Node doExec(final Session session) throws RepositoryException {
+ if(path != null) {
+ return NodeUtil.createPath(session.getRootNode(), StringUtils.removeStart(path, "/"), NodeTypes.Folder.NAME);
+ }
+ return session.getRootNode();
+ }
+ });
+ }
+
+ public void addWebAccess(final Role role, final long permission, final String... paths) {
+ addPermission(role, WEB_ACCESS_WORKSPACE, permission, paths);
+ }
+
+ public void removeWebAccess(final Role role, final long permission, final String... paths) {
+ removePermission(role, WEB_ACCESS_WORKSPACE, permission, paths);
+ }
+
+ public void removeAllWebAccess(final Role role) {
+ removePermissions(role, WEB_ACCESS_WORKSPACE, permission -> true);
+ }
+
+ public void setPermission(final Role role, final String workspace, final long permission, final String... paths) {
+ removePermissions(role, workspace, paths);
+ addPermission(role, workspace, permission, paths);
+ }
+
+ public void addPermission(final Role role, final String workspace, final long permission, final String... paths) {
+ Arrays.stream(paths).forEach(path ->
+ roleManager.get().addPermission(role, workspace, path, permission)
+ );
+ }
+
+ public void removePermission(final Role role, final String workspace, final long permission, final String... paths) {
+ Arrays.stream(paths).forEach(path ->
+ roleManager.get().removePermission(role, workspace, path, permission)
+ );
+ }
+
+ public void removePermissions(final Role role, final String workspace, final String... paths) {
+ removePermissions(role, workspace, permission -> Set.of(paths).contains(permission.getPattern().getPatternString()));
+ }
+
+ public void removeAllPermissions(final Role role) {
+ roleManager.get().getACLs(role.getName()).entrySet().stream()
+ .filter(entry -> !WEB_ACCESS_WORKSPACE.equals(entry.getKey()))
+ .forEach(entry ->
+ removePermissions(role, entry.getKey(), entry.getValue(), permission -> true)
+ );
+ }
+
+ private void removePermissions(final Role role, final String workspace, final Predicate filter) {
+ Optional.ofNullable(roleManager.get().getACLs(role.getName()).get(workspace)).ifPresent(acls ->
+ removePermissions(role, workspace, acls, filter)
+ );
+ }
+
+ private void removePermissions(final Role role, final String workspace, final ACL acl, final Predicate filter) {
+ acl.getList().stream().filter(filter).forEach(permission ->
+ removePermission(
+ role,
+ workspace,
+ permission.getPermissions(),
+ permission.getPattern().getPatternString()
+ )
+ );
+ }
+}
diff --git a/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/UserManagerUtil.java b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/UserManagerUtil.java
new file mode 100644
index 0000000..c4c561a
--- /dev/null
+++ b/common-task/src/main/java/com/merkle/oss/magnolia/setup/task/common/security/util/UserManagerUtil.java
@@ -0,0 +1,77 @@
+package com.merkle.oss.magnolia.setup.task.common.security.util;
+
+import info.magnolia.cms.security.Group;
+import info.magnolia.cms.security.MgnlUserManager;
+import info.magnolia.cms.security.Realm;
+import info.magnolia.cms.security.Role;
+import info.magnolia.cms.security.SecuritySupport;
+import info.magnolia.cms.security.User;
+import info.magnolia.cms.security.UserManager;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import javax.inject.Inject;
+
+import org.apache.http.auth.Credentials;
+
+public class UserManagerUtil {
+ private final Supplier userManager;
+
+ public UserManagerUtil(
+ final SecuritySupport securitySupport,
+ final Realm realm
+ ) {
+ userManager = () -> securitySupport.getUserManager(realm.getName());
+ }
+
+ public Optional getUser(final String username) {
+ return Optional.ofNullable(userManager.get().getUser(username));
+ }
+
+ public Optional getOrCreateUserAndSetPassword(final Credentials credentials, final Set groups, final Set roles) {
+ final User user = getOrCreateUserAndSetPassword(credentials.getUserPrincipal().getName(), credentials.getPassword());
+ for (String group : user.getGroups()) {
+ if(groups.stream().map(Group::getName).noneMatch(group::equals)) {
+ userManager.get().removeGroup(user, group);
+ }
+ }
+ for (Group group : groups) {
+ userManager.get().addGroup(user, group.getName());
+ }
+ for (String role : user.getRoles()) {
+ if(roles.stream().map(Role::getName).noneMatch(role::equals)) {
+ userManager.get().removeRole(user, role);
+ }
+ }
+ for (Role role : roles) {
+ userManager.get().addRole(user, role.getName());
+ }
+ return Optional.ofNullable(userManager.get().getUser(user.getName()));
+ }
+
+ private User getOrCreateUserAndSetPassword(final String name, final String password) {
+ return Optional
+ .ofNullable(userManager.get().getUser(name))
+ .map(user -> userManager.get().changePassword(user, password))
+ .orElseGet(() -> userManager.get().createUser(name, password));
+ }
+
+ public void enable(final User user) {
+ userManager.get().setProperty(user, MgnlUserManager.PROPERTY_ENABLED, "true");
+ }
+
+ public static class Factory {
+ private final SecuritySupport securitySupport;
+
+ @Inject
+ public Factory(final SecuritySupport securitySupport) {
+ this.securitySupport = securitySupport;
+ }
+
+ public UserManagerUtil create(final Realm realm) {
+ return new UserManagerUtil(securitySupport, realm);
+ }
+ }
+}
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000..0503cd5
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 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
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
\ 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..9142bdd
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/EnhancedModuleVersionHandler.java
@@ -0,0 +1,127 @@
+package com.merkle.oss.magnolia.setup;
+
+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 java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
+
+import com.merkle.oss.magnolia.setup.task.type.DepdendsOnComparator;
+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 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 Set installTasks,
+ final Set updateTasks,
+ final Set installAndUpdateTasks,
+ final Set moduleStartupTasks,
+ final Set snapshotStartupTasks,
+ final Set localDevelopmentStartupTasks
+ ) {
+ this.installTasks = installTasks;
+ this.updateTasks = updateTasks;
+ this.installAndUpdateTasks = installAndUpdateTasks;
+ this.moduleStartupTasks = moduleStartupTasks;
+ this.snapshotStartupTasks = snapshotStartupTasks;
+ this.localDevelopmentStartupTasks = localDevelopmentStartupTasks;
+ }
+
+ @Override
+ public List getDeltas(final InstallContext installContext, @Nullable final Version versionFrom) {
+ final Version forVersion = installContext.getCurrentModuleDefinition().getVersion();
+ return Stream.concat(
+ super.getDeltas(installContext, versionFrom).stream(),
+ getDeltas(installContext, forVersion, versionFrom)
+ ).toList();
+ }
+
+ private Stream getDeltas(final InstallContext installContext, final Version forVersion, @Nullable final Version versionFrom) {
+ return Stream.of(
+ getInstallAndUpdateTasksDelta(installContext, forVersion, versionFrom),
+ getStartupTasksDelta(installContext, forVersion, versionFrom)
+ );
+ }
+
+ private Delta getInstallAndUpdateTasksDelta(final InstallContext installContext, final Version forVersion, @Nullable final Version versionFrom) {
+ final boolean isUpdate = forVersion.isStrictlyAfter(versionFrom);
+ final boolean isInstall = versionFrom == null;
+
+ return DeltaBuilder.install(forVersion, "setup-task install and update").addTasks(Stream.of(
+ isInstall ? getInstallTasks(installContext, forVersion) : Stream.empty(),
+ isUpdate ? getInstallAndUpdateTasks(installContext, forVersion, null) : Stream.empty(),
+ (isInstall || isUpdate)? getUpdateTasks(installContext, forVersion, versionFrom) : Stream.empty()
+ ).flatMap(Function.identity()).sorted(new DepdendsOnComparator()).toList());
+ }
+
+ private Delta getStartupTasksDelta(final InstallContext installContext, final Version forVersion, @Nullable final Version versionFrom) {
+ return DeltaBuilder.startup(forVersion, "setup-task startup").addTasks( Stream.of(
+ getModuleStartupTasks(installContext, forVersion, versionFrom),
+ isSnapshot(forVersion) ? getSnapshotStartupTasks(installContext, forVersion, versionFrom) : Stream.empty(),
+ isLocalDevelopmentEnvironment() ? getLocalDevelopmentStartupTasks(installContext, forVersion, versionFrom) : Stream.empty()
+ ).flatMap(Function.identity()).sorted(new DepdendsOnComparator()).toList());
+ }
+
+ protected abstract boolean isLocalDevelopmentEnvironment();
+
+ private boolean isSnapshot(final Version version) {
+ return "SNAPSHOT".equalsIgnoreCase(version.getClassifier());
+ }
+
+ protected Stream getInstallTasks(final InstallContext installContext, final Version forVersion) {
+ return filter(installTasks, forVersion, null);
+ }
+
+ protected Stream getInstallAndUpdateTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
+ return filter(installAndUpdateTasks, forVersion, fromVersion);
+ }
+
+ protected Stream getUpdateTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
+ return filter(updateTasks, forVersion, fromVersion);
+ }
+
+ protected Stream getModuleStartupTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
+ return filter(moduleStartupTasks, forVersion, fromVersion);
+ }
+
+ protected Stream getSnapshotStartupTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
+ return Stream.of(
+ filter(snapshotStartupTasks, forVersion, fromVersion),
+ // execute all general install and update tasks on snapshot
+ getInstallAndUpdateTasks(installContext, forVersion, fromVersion),
+ getUpdateTasks(installContext, forVersion, fromVersion)
+ ).flatMap(Function.identity());
+ }
+
+ protected Stream getLocalDevelopmentStartupTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
+ return filter(localDevelopmentStartupTasks, forVersion, fromVersion);
+ }
+
+ protected Stream filter(final Collection extends VersionAwareTask> tasks, final Version forVersion, @Nullable final Version fromVersion) {
+ return tasks
+ .stream()
+ .filter(task -> task.test(forVersion, fromVersion))
+ .map(task -> task);
+ }
+}
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/DepdendsOnComparator.java b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/DepdendsOnComparator.java
new file mode 100644
index 0000000..1860f83
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/DepdendsOnComparator.java
@@ -0,0 +1,41 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+import info.magnolia.module.delta.Task;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+
+public class DepdendsOnComparator implements Comparator {
+ @Override
+ public int compare(final Task task1, final Task task2) {
+ if (!(task1 instanceof VersionAwareTask) && !(task2 instanceof VersionAwareTask)) {
+ return 0;
+ }
+ if (!(task1 instanceof VersionAwareTask versionAwareTask1)) {
+ return -1;
+ }
+ if (!(task2 instanceof VersionAwareTask versionAwareTask2)) {
+ return 1;
+ }
+
+ if (versionAwareTask1.dependsOn().isEmpty() && versionAwareTask2.dependsOn().isEmpty()) {
+ return 0;
+ }
+ if (versionAwareTask1.dependsOn().isEmpty()) {
+ return -1;
+ }
+ if (versionAwareTask2.dependsOn().isEmpty()) {
+ return 1;
+ }
+
+ if (Objects.equals(versionAwareTask1.dependsOn().get().getClass(), versionAwareTask2.getClass())) {
+ return 1;
+ }
+ if (Objects.equals(versionAwareTask2.dependsOn().get().getClass(), versionAwareTask1.getClass())) {
+ return -1;
+ }
+ return compare(versionAwareTask1.dependsOn().get(), versionAwareTask2.dependsOn().get());
+
+ }
+}
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..3a14d7e
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallAndUpdateTask.java
@@ -0,0 +1,7 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed on Module install and update
+ */
+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..12e5e60
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/InstallTask.java
@@ -0,0 +1,7 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed on Module Install
+ */
+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..44246c7
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/UpdateTask.java
@@ -0,0 +1,7 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+/**
+ * Tasks to be executed on Module update
+ */
+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..36410d2
--- /dev/null
+++ b/core/src/main/java/com/merkle/oss/magnolia/setup/task/type/VersionAwareTask.java
@@ -0,0 +1,22 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+import info.magnolia.module.delta.Task;
+import info.magnolia.module.model.Version;
+
+import java.util.Optional;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+import javax.annotation.Nullable;
+
+public interface VersionAwareTask extends Task, BiPredicate {
+
+ @Override
+ default boolean test(final Version forVersion, @Nullable final Version fromVersion) {
+ return true;
+ }
+
+ default Optional dependsOn() {
+ return Optional.empty();
+ }
+}
diff --git a/core/src/test/java/com/merkle/oss/magnolia/setup/task/type/DepdendsOnComparatorTest.java b/core/src/test/java/com/merkle/oss/magnolia/setup/task/type/DepdendsOnComparatorTest.java
new file mode 100644
index 0000000..1eb78d9
--- /dev/null
+++ b/core/src/test/java/com/merkle/oss/magnolia/setup/task/type/DepdendsOnComparatorTest.java
@@ -0,0 +1,80 @@
+package com.merkle.oss.magnolia.setup.task.type;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import info.magnolia.module.InstallContext;
+import info.magnolia.module.delta.Task;
+import info.magnolia.module.delta.TaskExecutionException;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+class DepdendsOnComparatorTest {
+
+ @Test
+ void sort() {
+ final VersionAwareTask task2 = new Task2();
+ final VersionAwareTask task1 = new Task1();
+ final VersionAwareTask task3 = new Task3();
+ final VersionAwareTask task4 = new Task4();
+ final VersionAwareTask task5 = new Task5();
+ final Task task6 = new MockTask();
+ assertEquals(
+ List.of(task6, task1, task2, task5, task4, task3),
+ Stream.of(task2, task4, task5, task6, task1, task3).sorted(new DepdendsOnComparator()).toList()
+ );
+
+ assertEquals(
+ List.of(task6, task1, task5, task2, task3, task4),
+ Stream.of(task3, task6, task1, task5, task2, task4).sorted(new DepdendsOnComparator()).toList()
+ );
+ }
+
+ private static class Task1 extends MockVersionAwareTask {}
+ private static class Task2 extends MockVersionAwareTask {
+ @Override
+ public Optional dependsOn() {
+ return Optional.of(new Task1());
+ }
+ }
+ private static class Task3 extends MockVersionAwareTask {
+ @Override
+ public Optional dependsOn() {
+ return Optional.of(new Task2());
+ }
+ }
+ private static class Task4 extends MockVersionAwareTask {
+ @Override
+ public Optional dependsOn() {
+ return Optional.of(new Task2());
+ }
+ }
+ private static class Task5 extends MockVersionAwareTask {
+ @Override
+ public Optional dependsOn() {
+ return Optional.of(new Task1());
+ }
+ }
+
+ private static abstract class MockVersionAwareTask extends MockTask implements VersionAwareTask {}
+
+ private static class MockTask implements Task {
+ @Override
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+ @Override
+ public String getDescription() {
+ return getClass().getSimpleName()+"_description";
+ }
+ @Override
+ public void execute(final InstallContext installContext) {}
+ @Override
+ public String toString() {
+ return getName();
+ }
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..014ad28
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,221 @@
+
+
+ 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.1
+
+
+ 3.11.0
+ 3.3.0
+ 3.8.0
+ 3.2.5
+ 0.5.0
+ 3.5.0
+
+
+ 5.11.0
+ 5.13.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.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${mvn.surefire.plugin.version}
+
+
+
+
+
+
+
+ magnolia.public.group
+ https://nexus.magnolia-cms.com/content/groups/public
+
+ true
+
+
+
+ magnolia.enterprise.group
+ https://nexus.magnolia-cms.com/content/groups/enterprise
+
+ false
+
+
+
+
+
+
+
+ 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