Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit
Browse files Browse the repository at this point in the history
eschleb committed Sep 23, 2024
0 parents commit 340c8bd
Showing 32 changed files with 2,496 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .github/actions/mvn-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: "Setup maven"
description: "Configures maven settings"

inputs:
mgnl_nexus_user:
description: “Username for magnolia nexus”
required: true
mgnl_nexus_pass:
description: “Password for magnolia nexus”
required: true

runs:
using: "composite"
steps:
- uses: s4u/maven-settings-action@v2
with:
servers: |
[{
"id": "magnolia.enterprise.group",
"username": "${{ inputs.mgnl_nexus_user }}",
"password": "${{ inputs.mgnl_nexus_pass }}"
}]
79 changes: 79 additions & 0 deletions .github/workflows/release-and-deploy-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: release and deploy

on:
push:
branches:
- main

jobs:
release:

runs-on: ubuntu-latest

steps:
# Checkout source code
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: '0'
# Setup Java environment
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Maven setup
uses: ./.github/actions/mvn-setup
with:
mgnl_nexus_user: ${{secrets.MGNL_NEXUS_USER}}
mgnl_nexus_pass: ${{secrets.MGNL_NEXUS_PASS}}
# Install xmllint
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install libxml2-utils
# Set git user name and email
- name: Set up Git
run: |
chmod +x ci/setup-git.sh
ci/setup-git.sh
# Release, set correct versions and create tag
- name: Release (versioning/tag)
run: |
chmod +x ci/mvn-release.sh
ci/mvn-release.sh
deploy-release:

needs: release
runs-on: ubuntu-latest

steps:
# Checkout source code
- name: Checkout
uses: actions/checkout@v2
with:
ref: 'main'
# Setup Java environment
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
server-id: central
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
gpg-passphrase: MAVEN_GPG_PASSPHRASE
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
- name: Maven setup
uses: ./.github/actions/mvn-setup
with:
mgnl_nexus_user: ${{secrets.MGNL_NEXUS_USER}}
mgnl_nexus_pass: ${{secrets.MGNL_NEXUS_PASS}}
# Run maven verify
- name: Maven verify
run: mvn verify --batch-mode
# Publish
- name: Release Maven package (SNAPSHOT)
run: mvn deploy -Pdeploy
env:
MAVEN_USERNAME: ${{ secrets.SONATYPE_USER }}
MAVEN_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
30 changes: 30 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: verify

on:
push:
branches-ignore:
- main

jobs:
verify:

runs-on: ubuntu-latest

steps:
# Checkout source code
- name: Checkout
uses: actions/checkout@v2
# Setup Java environment
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Maven setup
uses: ./.github/actions/mvn-setup
with:
mgnl_nexus_user: ${{secrets.MGNL_NEXUS_USER}}
mgnl_nexus_pass: ${{secrets.MGNL_NEXUS_PASS}}
# Run maven verify
- name: Maven verify
run: mvn verify --batch-mode
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# OS
.DS_Store
Thumbs.db

target

.idea
*.iml

#JRebel
rebel.xml
codesigning.asc
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Magnolia setup task

Setup task to help bootstrap magnolia.

## Implementation

```java
import info.magnolia.module.InstallContext;
import info.magnolia.module.model.Version;

import java.util.Optional;

import javax.annotation.Nullable;

import com.merkle.oss.magnolia.setup.task.type.InstallAndUpdateTask;
import com.merkle.oss.magnolia.setup.task.type.VersionAwareTask;

public class SomeTask implements InstallAndUpdateTask {

@Override
public String getName() {
return "someTask";
}

@Override
public String getDescription() {
return "someTask description";
}

@Override
public void execute(InstallContext installContext) {
//do stuff
}

//Optional
@Override
public boolean test(final Version forVersion, @Nullable final Version fromVersion) {
return true;
}

//Optional
@Override
public Optional<VersionAwareTask> dependsOn() {
return Optional.empty();
}
}

```


## Setup
### Guice Set-Binding
```java
import com.google.inject.Binder;
import com.google.inject.multibindings.Multibinder;

import info.magnolia.objectfactory.guice.AbstractGuiceComponentConfigurer;

public class CustomGuiceComponentConfigurer extends AbstractGuiceComponentConfigurer {
@Override
protected void configure() {
super.configure();
final Multibinder<InstallTask> installTaskSetBinder = Multibinder.newSetBinder(binder(), InstallTask.class, Names.named("myModule"));
installTaskSetBinder.addBinding().to(SomeInstallTask.class);
...
}
}
```

### Module version handler
```xml
<module>
<name>myModule</name>
<versionHandler>...MyModuleVersionHandler</versionHandler>
...
</module>
```
```java
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

import com.merkle.oss.magnolia.setup.EnhancedModuleVersionHandler;
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;

public class MyModuleVersionHandler extends EnhancedModuleVersionHandler {

// Multibinding configured in SetupTasksGuiceComponentConfigurer
@Inject
public MyModuleVersionHandler(
@Named("myModule") final Set<InstallTask> installTasks,
@Named("myModule") final Set<UpdateTask> updateTasks,
@Named("myModule") final Set<InstallAndUpdateTask> installAndUpdateTasks,
@Named("myModule") final Set<ModuleStartupTask> moduleStartupTasks,
@Named("myModule") final Set<SnapshotStartupTask> snapshotStartupTasks,
@Named("myModule") final Set<LocalDevelopmentStartupTask> localDevelopmentStartupTasks
) {
super(installTasks, updateTasks, installAndUpdateTasks, moduleStartupTasks, snapshotStartupTasks, localDevelopmentStartupTasks);
}

@Override
protected boolean isLocalDevelopmentEnvironment() {
return false; //TODO implement
}
}
```
35 changes: 35 additions & 0 deletions common-task/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.merkle.oss.magnolia</groupId>
<artifactId>magnolia-setup-task</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>magnolia-setup-task-common</artifactId>

<dependencies>
<dependency>
<groupId>com.merkle.oss.magnolia</groupId>
<artifactId>magnolia-setup-task-core</artifactId>
</dependency>
<dependency>
<groupId>com.namics.oss.magnolia</groupId>
<artifactId>magnolia-powernode</artifactId>
</dependency>
<dependency>
<groupId>info.magnolia.scheduler</groupId>
<artifactId>magnolia-module-scheduler</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>info.magnolia.ui</groupId>
<artifactId>magnolia-ui-framework</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.merkle.oss.magnolia.setup.task.common;

import info.magnolia.jcr.nodebuilder.NodeOperation;
import info.magnolia.jcr.nodebuilder.task.ErrorHandling;
import info.magnolia.jcr.util.PropertyUtil;
import info.magnolia.module.InstallContext;
import info.magnolia.module.scheduler.JobDefinition;
import info.magnolia.repository.RepositoryConstants;

import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.value.ValueFactoryImpl;

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;

public abstract class AbstractConfigureSchedulerJobTask extends AbstractPathNodeBuilderTask implements InstallAndUpdateTask {
private static final String PATH = "/modules/scheduler/config/jobs";

private final NodeOperationFactory ops;

protected AbstractConfigureSchedulerJobTask(
final NodeOperationFactory nodeOperationFactory,
final String taskName,
final String description,
final ErrorHandling errorHandling
) {
super(taskName, description, errorHandling, RepositoryConstants.CONFIG, PATH);
this.ops = nodeOperationFactory;
}

@Override
protected NodeOperation[] getNodeOperations(InstallContext ctx) {
return getJobs().map(this::configureJob).toArray(NodeOperation[]::new);
}

protected abstract Stream<JobDefinition> getJobs();

private NodeOperation configureJob(final JobDefinition jobDefinition) {
return ops.getOrAddContentNode(jobDefinition.getName()).then(
ops.setProperty("catalog", jobDefinition.getCatalog(), ValueConverter::toValue),
ops.setProperty("command", jobDefinition.getCommand(), ValueConverter::toValue),
ops.setProperty("cron", jobDefinition.getCron(), ValueConverter::toValue),
ops.setProperty("description", Optional.ofNullable(jobDefinition.getDescription()).orElse(StringUtils.EMPTY), ValueConverter::toValue),
ops.setProperty("concurrent", jobDefinition.isConcurrent(), ValueConverter::toValue),
ops.getOrAddContentNode("params").then(
((Map<String, Object>) 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())
);
}
}
Original file line number Diff line number Diff line change
@@ -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)
)
)
)
};
}
}
Original file line number Diff line number Diff line change
@@ -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[]{};
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* Use the following properties in magnolia.properties:
* <p>
* magnolia.license.owner=
* magnolia.license.key=
* <p>
* - 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<String> getOwner() {
return getProperty("magnolia.license.owner");
}

private Optional<String> getKey() {
return getProperty("magnolia.license.key");
}

private Optional<String> getProperty(final String key) {
return Optional.ofNullable(properties.getProperty(key));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
};
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* Use the following properties in magnolia.properties:
* <p>
* 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
* <p>
* - 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<String> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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> groupManager;

@Inject
public GroupManagerUtil(final SecuritySupport securitySupport) {
groupManager = securitySupport::getGroupManager;
}

public Set<Group> getGroups(final String... groupNames) {
return Arrays.stream(groupNames)
.map(this::getGroup)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
}

public Optional<Group> 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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> roleManager;

@Inject
public RoleManagerUtil(final SecuritySupport securitySupport) {
roleManager = securitySupport::getRoleManager;
}

public Optional<Role> 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<Permission> 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<Permission> filter) {
acl.getList().stream().filter(filter).forEach(permission ->
removePermission(
role,
workspace,
permission.getPermissions(),
permission.getPattern().getPatternString()
)
);
}
}
Original file line number Diff line number Diff line change
@@ -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> userManager;

public UserManagerUtil(
final SecuritySupport securitySupport,
final Realm realm
) {
userManager = () -> securitySupport.getUserManager(realm.getName());
}

public Optional<User> getUser(final String username) {
return Optional.ofNullable(userManager.get().getUser(username));
}

public Optional<User> getOrCreateUserAndSetPassword(final Credentials credentials, final Set<Group> groups, final Set<Role> 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);
}
}
}
37 changes: 37 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.merkle.oss.magnolia</groupId>
<artifactId>magnolia-setup-task</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>magnolia-setup-task-core</artifactId>

<dependencies>
<dependency>
<groupId>info.magnolia</groupId>
<artifactId>magnolia-core</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>

<!-- TESTING DEPENDENCIES -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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<InstallTask> installTasks;
private final Set<UpdateTask> updateTasks;
private final Set<InstallAndUpdateTask> installAndUpdateTasks;
private final Set<ModuleStartupTask> moduleStartupTasks;
private final Set<SnapshotStartupTask> snapshotStartupTasks;
private final Set<LocalDevelopmentStartupTask> localDevelopmentStartupTasks;

protected EnhancedModuleVersionHandler(
final Set<InstallTask> installTasks,
final Set<UpdateTask> updateTasks,
final Set<InstallAndUpdateTask> installAndUpdateTasks,
final Set<ModuleStartupTask> moduleStartupTasks,
final Set<SnapshotStartupTask> snapshotStartupTasks,
final Set<LocalDevelopmentStartupTask> localDevelopmentStartupTasks
) {
this.installTasks = installTasks;
this.updateTasks = updateTasks;
this.installAndUpdateTasks = installAndUpdateTasks;
this.moduleStartupTasks = moduleStartupTasks;
this.snapshotStartupTasks = snapshotStartupTasks;
this.localDevelopmentStartupTasks = localDevelopmentStartupTasks;
}

@Override
public List<Delta> 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<Delta> 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.<Task>empty(),
isUpdate ? getInstallAndUpdateTasks(installContext, forVersion, null) : Stream.<Task>empty(),
(isInstall || isUpdate)? getUpdateTasks(installContext, forVersion, versionFrom) : Stream.<Task>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.<Task>empty(),
isLocalDevelopmentEnvironment() ? getLocalDevelopmentStartupTasks(installContext, forVersion, versionFrom) : Stream.<Task>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<Task> getInstallTasks(final InstallContext installContext, final Version forVersion) {
return filter(installTasks, forVersion, null);
}

protected Stream<Task> getInstallAndUpdateTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
return filter(installAndUpdateTasks, forVersion, fromVersion);
}

protected Stream<Task> getUpdateTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
return filter(updateTasks, forVersion, fromVersion);
}

protected Stream<Task> getModuleStartupTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
return filter(moduleStartupTasks, forVersion, fromVersion);
}

protected Stream<Task> 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<Task> getLocalDevelopmentStartupTasks(final InstallContext installContext, final Version forVersion, @Nullable final Version fromVersion) {
return filter(localDevelopmentStartupTasks, forVersion, fromVersion);
}

protected Stream<Task> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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:
* <pre>{@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)
* }</pre>
*/
public class TaskExecutor {
private final ModuleRegistry moduleRegistry;
private final ComponentProvider componentProvider;
private final Set<Session> 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<ModuleDefinition> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Task> {
@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());

}
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.merkle.oss.magnolia.setup.task.type;

/**
* Tasks to be executed on Module Install
*/
public interface InstallTask extends VersionAwareTask {
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.merkle.oss.magnolia.setup.task.type;

/**
* Tasks to be executed on Module update
*/
public interface UpdateTask extends VersionAwareTask {
}
Original file line number Diff line number Diff line change
@@ -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<Version, Version> {

@Override
default boolean test(final Version forVersion, @Nullable final Version fromVersion) {
return true;
}

default Optional<VersionAwareTask> dependsOn() {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -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<VersionAwareTask> dependsOn() {
return Optional.of(new Task1());
}
}
private static class Task3 extends MockVersionAwareTask {
@Override
public Optional<VersionAwareTask> dependsOn() {
return Optional.of(new Task2());
}
}
private static class Task4 extends MockVersionAwareTask {
@Override
public Optional<VersionAwareTask> dependsOn() {
return Optional.of(new Task2());
}
}
private static class Task5 extends MockVersionAwareTask {
@Override
public Optional<VersionAwareTask> 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();
}
}
}
221 changes: 221 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.merkle.oss.magnolia</groupId>
<artifactId>magnolia-setup-task</artifactId>
<packaging>pom</packaging>
<version>0.0.1-SNAPSHOT</version>

<name>${project.artifactId}</name>
<url>https://github.com/merkle-open/magnolia-setup-task</url>
<description>Setup task to help bootstrap magnolia</description>

<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<name>Merkle Magnolia</name>
<email>magnolia@merkle.com</email>
<organization>Merkle DACH</organization>
<organizationUrl>https://merkleinc.ch</organizationUrl>
</developer>
</developers>

<scm>
<url>https://github.com/merkle-open/magnolia-setup-task</url>
<connection>scm:git:git@github.com:merkle-open/magnolia-setup-task.git</connection>
<developerConnection>scm:git:git@github.com:merkle-open/magnolia-setup-task.git</developerConnection>
</scm>

<modules>
<module>common-task</module>
<module>core</module>
</modules>

<properties>
<!-- check for new versions: mvn versions:display-property-updates -->
<magnolia.version>6.3.0</magnolia.version>
<jsr305.nullable.version>3.0.2</jsr305.nullable.version>
<namics.oss.powernode.version>2.1.1</namics.oss.powernode.version>

<!-- Maven Plugins -->
<mvn.compiler.plugin.version>3.11.0</mvn.compiler.plugin.version>
<mvn.source.plugin.version>3.3.0</mvn.source.plugin.version>
<mvn.javadoc.version>3.8.0</mvn.javadoc.version>
<mvn.gpg.plugin.version>3.2.5</mvn.gpg.plugin.version>
<mvn.sonatype.publishing.plugin.version>0.5.0</mvn.sonatype.publishing.plugin.version>
<mvn.surefire.plugin.version>3.5.0</mvn.surefire.plugin.version>

<!--Test dependency versions-->
<junit.version>5.11.0</junit.version>
<mockito.version>5.13.0</mockito.version>

<javaVersion>17</javaVersion>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>info.magnolia.bundle</groupId>
<artifactId>magnolia-bundle-parent</artifactId>
<version>${magnolia.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.merkle.oss.magnolia</groupId>
<artifactId>magnolia-setup-task-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.namics.oss.magnolia</groupId>
<artifactId>magnolia-powernode</artifactId>
<version>${namics.oss.powernode.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.nullable.version}</version>
</dependency>

<!-- TESTING DEPENDENCIES -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${mvn.compiler.plugin.version}</version>
<configuration>
<release>${javaVersion}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${mvn.source.plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${mvn.javadoc.version}</version>
<configuration>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${mvn.surefire.plugin.version}</version>
</plugin>
</plugins>
</build>

<repositories>
<!-- Magnolia -->
<repository>
<id>magnolia.public.group</id>
<url>https://nexus.magnolia-cms.com/content/groups/public</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>magnolia.enterprise.group</id>
<url>https://nexus.magnolia-cms.com/content/groups/enterprise</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<distributionManagement>
<!-- central portal doesn't support SNAPSHOT versions: https://central.sonatype.org/faq/snapshot-releases -->
<repository>
<id>central</id>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>

<profiles>
<profile>
<id>deploy</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${mvn.gpg.plugin.version}</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
<configuration>
<!-- Prevent `gpg` from using pinentry programs -->
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>${mvn.sonatype.publishing.plugin.version}</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
<waitUntil>published</waitUntil>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

0 comments on commit 340c8bd

Please sign in to comment.