diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..1c45fb5d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,78 @@
+# Created by https://www.gitignore.io/api/eclipse,java,maven
+
+### Eclipse ###
+*.pydevproject
+.metadata
+.gradle
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+
+# Eclipse Core
+.project
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# TeXlipse plugin
+.texlipse
+
+
+### Java ###
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+### Vault ###
+.vlt
+
+### IntelliJ ###
+.idea/
+*.iml
+
+.DS_Store
diff --git a/LICENSE b/LICENSE
index d168c1ff..99873d35 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2018 valtech.se
+Copyright (c) 2018 Valtech GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 00000000..a22e85b1
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,307 @@
+# AEM Easy Content Upgrade (AECU)
+
+AECU simplifies content migrations by executing migration scripts during package installation. It is built on top of [Groovy Console](https://github.com/OlsonDigital/aem-groovy-console).
+
+
+Features:
+
+* GUI to run scripts and see history of runs
+* Run mode support
+* Fallback scripts in case of errors
+* Extension of Groovy Console bindings
+* Service API
+* Health Checks
+
+Table of contents
+1. [Requirements](#requirements)
+2. [Installation](#installation)
+3. [Execution of Migration Scripts](#execution)
+ 1. [Install Hook](#installHook)
+ 2. [Manual Execution](#manualExecution)
+4. [History of Past Runs](#history)
+5. [Extension to Groovy Console](#groovy)
+6. [JMX Interface](#jmx)
+7. [Health Checks](#healthchecks)
+8. [License](#license)
+
+
+
+
+# Requirements
+
+AECU requires Java 8 and AEM 6.3 or above. Groovy Console can be installed manually if [bundle install](#bundleInstall) is not used.
+
+
+
+# Installation
+
+TODO
+
+
+
+## Bundle Installation
+
+To simplify installation we provide a bundle package that already includes the Groovy Console. This makes sure there are no compatibility issues.
+
+TODO
+
+
+
+
+# Execution of Migration Scripts
+
+
+
+## Install Hook
+
+This is the preferred method to execute your scripts. It allows to run them without any user interaction. Just package them with a content package and do a regular deployment.
+
+You can add the install hook by adding de.valtech.aecu.core.installhook.AecuInstallHook as a hook to your package properties. The AECU package and Groovy Console need to be installed beforehand.
+
+```xml
+
+ com.day.jcr.vault
+ content-package-maven-plugin
+ true
+
+ src/main/content/META-INF/vault/filter.xml
+ true
+ true
+ Valtech
+
+ de.valtech.aecu.core.installhook.AecuInstallHook
+
+
+
+```
+
+
+
+## Manual Execution
+
+Manual script execution is useful in case you want to manually rerun a script (e.g. because it failed before). You can find the execute feature in AECU's tools menu.
+
+
+
+Execution is done in two simple steps:
+
+1. Select the base path and run the search. This will show a list of runnable scripts.
+2. Run all scripts in batch or just single ones. If you run all you can change the order before (drag and drop with marker at the right).
+
+Once execution is done you will see if the script(s) succeeded. Click on the history link to see the details.
+
+
+
+
+
+# History of Past Runs
+
+You can find the history in AECU's tools menu.
+
+
+
+The history shows all runs that were executed via package install hook, manual run and JMX. It will not display scripts that were executed directly via Groovy Console.
+
+
+
+You can click on any run to see the full details. This will show the status for each script. You can also see the output of all scripts.
+
+
+
+
+
+# Extension to Groovy Console
+
+AECU adds its own binding to Groovy Console. You can reach it using "aecu" in your script. This provides methods to perform common tasks like property modification or node deletion.
+
+It follows a collect, filter, execute process.
+
+## Collect Options
+In the collect phase you define which nodes should be checked for a migration.
+
+* forResources(String[] paths): use the given paths without any subnodes
+* forChildResourcesOf(String path): use all direct childs of the given path (but no grandchilds)
+* forDescendantResourcesOf(String path): use the whole subtree under this path
+
+You can call these methods multiple times and combine them. They will be merged together.
+
+Example:
+
+```java
+println aecu.contentUpgradeBuilder()
+ .forResources((String[])["/content/we-retail/ca/en"])
+ .forChildResourcesOf("/content/we-retail/us/en")
+ .forDescendantResourcesOf("/content/we-retail/us/en/experience")
+ .doSetProperty("name", "value")
+ .run()
+```
+
+## Filter options
+These methods can be used to filter the nodes that were collected above. Multiple filters can be applied for one run.
+
+### Filter by Properties
+Use this to filter by a list of property values (e.g. sling:resourceType).
+
+```java
+filterByProperties(Map properties)
+```
+
+Example:
+
+```java
+def conditionMap = [:]
+conditionMap["sling:resourceType"] = "weretail/components/structure/page"
+
+println aecu.contentUpgradeBuilder()
+ .forChildResourcesOf("/content/we-retail/ca/en")
+ .filterByProperties(conditionMap)
+ .doSetProperty("name", "value")
+ .run()
+```
+
+
+
+### Filter by Node Name
+
+You can also filter nodes by their name.
+
+* filterByNodeName(String name): process only nodes which have this exact name
+* filterByNodeNameRegex(String regex): process nodes that have a name that matches the given regular expression
+
+```java
+println aecu.contentUpgradeBuilder()
+ .forChildResourcesOf("/content/we-retail/ca/en")
+ .filterByNodeName("jcr:content")
+ .filterByNodeNameRegex("jcr.*")
+ .doSetProperty("name", "value")
+ .run()
+```
+
+
+
+### Combine Multiple Filters
+You can combine filters with AND and OR to build more complex filters.
+
+```java
+def conditionMap_type = [:]
+conditionMap_type['sling:resourceType'] = "weretail/components/content/heroimage"
+def conditionMap_file = [:]
+conditionMap_file['fileReference'] = "/content/dam/we-retail/en/activities/running/fitness-woman.jpg"
+def conditionMap_page = [:]
+conditionMap_page['jcr:primaryType'] = "cq:PageContent"
+
+def complexFilter = new ORFilter(
+ [ new FilterByProperties(conditionMap_page),
+ new ANDFilter( [
+ new FilterByProperties(conditionMap_type),
+ new FilterByProperties(conditionMap_file)
+ ] )
+ ])
+
+println aecu.contentUpgradeBuilder()
+ .forDescendantResourcesOf("/content/we-retail/ca/en")
+ .filterWith(complexFilter)
+ .doSetProperty("name", "value")
+ .run()
+```
+
+## Execute Options
+
+### Update Single-value Properies
+
+* doSetProperty(String name, Object value): sets the given property to the value. Any existing value is overwritten.
+* doDeleteProperty(String name): removes the property with the given name if existing.
+* doRenameProperty(String oldName, String newName): renames the given property if existing. If the new property name already exists it will be overwritten.
+
+```java
+println aecu.contentUpgradeBuilder()
+ .forChildResourcesOf("/content/we-retail/ca/en")
+ .filterByNodeName("jcr:content")
+ .doSetProperty("name", "value")
+ .doDeleteProperty("nameToDelete")
+ .doRenameProperty("oldName", "newName")
+ .run()
+```
+
+### Update Multi-value Properties
+
+* doAddValuesToMultiValueProperty(String name, String[] values): adds the list of values to a property. The property is created if it does not yet exist.
+* doRemoveValuesOfMultiValueProperty(String name, String[] values): removes the list of values from a given property.
+* doReplaceValuesOfMultiValueProperty(String name, String[] oldValues, String[] newValues): removes the old values and adds the new values in a given property.
+
+```java
+println aecu.contentUpgradeBuilder()
+ .forChildResourcesOf("/content/we-retail/ca/en")
+ .filterByNodeName("jcr:content")
+ .doAddValuesToMultiValueProperty("name", (String[])["value1", "value2"])
+ .doRemoveValuesOfMultiValueProperty("name", (String[])["value1", "value2"])
+ .doReplaceValuesOfMultiValueProperty("name", (String[])["old1", "old2"], (String[])["new1", "new2"])
+ .run()
+```
+
+### Copy and Move Properties
+
+This will copy or move a property to a subnode. You can also change the property name.
+
+* doCopyPropertyToRelativePath(String name, String newName, String relativeResourcePath): copy the property to the given path under the new name.
+* doMovePropertyToRelativePath(String name, String newName, String relativeResourcePath): move the property to the given path under the new name.
+
+```java
+println aecu.contentUpgradeBuilder()
+ .forChildResourcesOf("/content/we-retail/ca/en")
+ .filterByNodeName("jcr:content")
+ .doCopyPropertyToRelativePath("name", "newName", "subnode")
+ .doMovePropertyToRelativePath("name", "newName", "subnode")
+ .run()
+```
+
+TODO
+
+# JMX Interface
+
+
+
+AECU provides JMX methods for executing scripts and reading the history. You can also check the version here.
+
+## Execute
+
+This will execute the given script or folder. If a folder is specified then all files (incl. any subfolders) are executed. AECU will respect runmodes during execution.
+
+Parameters:
+ * Path: file or folder to execute
+
+## GetHistory
+
+Prints the history of the specified last runs. The entries are sorted by date and start with the last run.
+
+Parameters:
+ * Start index: starts with 0 (= latest history entry)
+ * Count: number of entries to print
+
+## GetFiles
+
+This will print all files that are executable for a given path. You can use this to check which scripts of a given folder would be executed.
+
+Parameters:
+* Path: file or folder to check
+
+
+
+
+# Health Checks
+
+Health checks show you the status of AECU itself and the last migration run.
+You can access them on the [status page](http://localhost:4502/libs/granite/operations/content/healthreports/healthreportlist.html/system/sling/monitoring/mbeans/org/apache/sling/healthcheck/HealthCheck/aecuHealthCheckmBean).
+For the status of older runs use AECU's history page.
+
+
+
+
+
+# License
+
+The AC Tool is licensed under the [MIT LICENSE](LICENSE).
+
+# Developers
+
+See our [developer zone](docs/developers.md)
\ No newline at end of file
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 00000000..d1053df9
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+
+ de.valtech.aecu
+ aecu
+ 0.9
+
+
+ aecu.api
+ bundle
+ AECU - API
+ Api bundle for AECU
+
+
+
+
+ org.apache.sling
+ maven-sling-plugin
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+
+ javax.inject;version=0.0.0,*
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+
+
+
+
+ org.osgi
+ osgi.core
+
+
+ org.osgi
+ osgi.cmpn
+
+
+ org.osgi
+ osgi.annotation
+
+
+ org.slf4j
+ slf4j-api
+
+
+ javax.jcr
+ jcr
+
+
+ javax.servlet
+ servlet-api
+
+
+ com.adobe.aem
+ uber-jar
+ apis
+
+
+ org.apache.sling
+ org.apache.sling.models.api
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ junit
+ junit
+
+
+ org.mockito
+ mockito-core
+
+
+ junit-addons
+ junit-addons
+
+
+
diff --git a/api/src/main/java/de/valtech/aecu/service/AecuException.java b/api/src/main/java/de/valtech/aecu/service/AecuException.java
new file mode 100644
index 00000000..02a8746e
--- /dev/null
+++ b/api/src/main/java/de/valtech/aecu/service/AecuException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.service;
+
+/**
+ * Thrown when the AECU service faces an error.
+ *
+ * @author Roland Gruber
+ */
+public class AecuException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor
+ *
+ * @param message error message
+ * @param e original exception
+ */
+ public AecuException(String message, Throwable e) {
+ super(message, e);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param message error message
+ */
+ public AecuException(String message) {
+ super(message);
+ }
+
+}
diff --git a/api/src/main/java/de/valtech/aecu/service/AecuService.java b/api/src/main/java/de/valtech/aecu/service/AecuService.java
new file mode 100644
index 00000000..c39151a7
--- /dev/null
+++ b/api/src/main/java/de/valtech/aecu/service/AecuService.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.service;
+
+import java.util.List;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Service interface for AECU. Use this to execute scripts or query the history.
+ *
+ * How to perform an execution:
+ *
+ *
Get a list of files to execute using {@link #getFiles(String) getFiles}. This will filter all
+ * files that do not match the run mode and any fallback scripts.
+ *
Start a new history entry to store your results using {@link #createHistoryEntry()
+ * createHistoryEntry}. This store a new run with in-progress state.
+ *
Execute your files one by one with {@link #execute(String) execute}
+ *
Store each script run in history using
+ * {@link #storeExecutionInHistory(HistoryEntry, ExecutionResult) storeExecutionInHistory}
+ *
Mark the run as done by closing the history with {@link #finishHistoryEntry(HistoryEntry)
+ * finishHistoryEntry}
+ *
+ *
+ * @author Roland Gruber
+ */
+@ProviderType
+public interface AecuService {
+
+ /**
+ * Returns the AECU version.
+ *
+ * @return version
+ */
+ String getVersion();
+
+ /**
+ * Returns a list of files that can be executed in the given path.
+ *
+ * @param path file or folder
+ * @return list of files that are executable
+ * @throws AecuException error finding files (e.g. invalid path)
+ */
+ List getFiles(String path) throws AecuException;
+
+ /**
+ * Checks if the folder matches the system's run modes if specified in folder name.
+ *
+ * @param name resource name
+ * @return matches run modes
+ */
+ boolean matchesRunmodes(String name);
+
+ /**
+ * Checks if the name is a valid script.
+ *
+ * @param name file name
+ * @return is valid
+ */
+ boolean isValidScriptName(String name);
+
+ /**
+ * Executes the script at the given position.
+ *
+ * @param path path of script
+ * @return execution result
+ * @throws AecuException error during execution
+ */
+ ExecutionResult execute(String path) throws AecuException;
+
+ /**
+ * Starts a new history entry.
+ *
+ * @return history entry
+ * @throws AecuException error setting up entry
+ */
+ HistoryEntry createHistoryEntry() throws AecuException;
+
+ /**
+ * Stores an execution run in existing history.
+ *
+ * @param history history entry
+ * @param result script execution result
+ * @return updated history
+ * @throws AecuException error inserting history entry
+ */
+ HistoryEntry storeExecutionInHistory(HistoryEntry history, ExecutionResult result) throws AecuException;
+
+ /**
+ * Finishes the history entry.
+ *
+ * @param history open history entry
+ * @return history entry
+ * @throws AecuException error saving state
+ */
+ HistoryEntry finishHistoryEntry(HistoryEntry history) throws AecuException;
+
+ /**
+ * Returns the last history entries. The search starts at the newest entry.
+ *
+ * @param startIndex start reading at this index (first is 0)
+ * @param count number of entries to read
+ * @return history entries (newest first)
+ * @throws AecuException error reading history
+ */
+ List getHistory(int startIndex, int count) throws AecuException;
+
+}
diff --git a/api/src/main/java/de/valtech/aecu/service/ExecutionResult.java b/api/src/main/java/de/valtech/aecu/service/ExecutionResult.java
new file mode 100644
index 00000000..d23bd6fd
--- /dev/null
+++ b/api/src/main/java/de/valtech/aecu/service/ExecutionResult.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.service;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Result of a script execution.
+ *
+ * @author Roland Gruber
+ */
+public class ExecutionResult {
+
+ private boolean success;
+ private String output;
+ private String time;
+ private String result;
+ private ExecutionResult fallbackResult;
+ private String path;
+
+ /**
+ * Constructor
+ *
+ * @param success execution was successful
+ * @param time execution time
+ * @param result result
+ * @param output script output
+ * @param fallbackResult fallback script result
+ * @param path script path
+ */
+ public ExecutionResult(boolean success, String time, String result, String output, ExecutionResult fallbackResult,
+ String path) {
+ this.success = success;
+ this.output = output;
+ this.time = time;
+ this.result = result;
+ this.fallbackResult = fallbackResult;
+ this.path = path;
+ }
+
+ /**
+ * Returns if execution was successful.
+ *
+ * @return successful
+ */
+ public boolean isSuccess() {
+ return success;
+ }
+
+ /**
+ * Returns the script result.
+ *
+ * @return output
+ */
+ public String getResult() {
+ return result;
+ }
+
+ /**
+ * Returns the script output.
+ *
+ * @return output
+ */
+ public String getOutput() {
+ return output;
+ }
+
+ /**
+ * Returns the execution time.
+ *
+ * @return time
+ */
+ public String getTime() {
+ return time;
+ }
+
+ /**
+ * Returns the fallback script result if any.
+ *
+ * @return result
+ */
+ public ExecutionResult getFallbackResult() {
+ return fallbackResult;
+ }
+
+ /**
+ * Returns the script path.
+ *
+ * @return path
+ */
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringVal = new StringBuilder("Successful: " + Boolean.toString(success) + "Path: " + path);
+ if (StringUtils.isNotBlank(time)) {
+ stringVal.append("\n" + "Execution time: " + time);
+ }
+ if (StringUtils.isNotBlank(result)) {
+ stringVal.append("\n" + "Result: " + result);
+ }
+ stringVal.append("\n" + "Output: " + output);
+ if (fallbackResult != null) {
+ stringVal.append("Fallback script executed:\n" + fallbackResult.toString());
+ }
+ return stringVal.toString();
+ }
+
+}
diff --git a/api/src/main/java/de/valtech/aecu/service/HistoryEntry.java b/api/src/main/java/de/valtech/aecu/service/HistoryEntry.java
new file mode 100644
index 00000000..e3940e98
--- /dev/null
+++ b/api/src/main/java/de/valtech/aecu/service/HistoryEntry.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.service;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * History entry for an execution run.
+ *
+ * @author Roland Gruber
+ */
+public interface HistoryEntry {
+
+ /**
+ * Execution state (e.g. running)
+ */
+ public enum STATE {
+ /** Execution ongoing */
+ RUNNING("Running"),
+ /** Execution finished */
+ FINISHED("Finished");
+
+ private String label;
+
+ /**
+ * Constructor
+ *
+ * @param label label
+ */
+ private STATE(String label) {
+ this.label = label;
+ }
+
+ /**
+ * Returns the human-readable label for this result.
+ *
+ * @return label
+ */
+ public String getLabel() {
+ return label;
+ }
+ }
+
+ ;
+
+ /**
+ * Execution result (e.g. successful)
+ */
+ public enum RESULT {
+ /** All scripts executed successfully */
+ SUCCESS("Success"),
+ /** Execution of one or more scripts failed */
+ FAILURE("Failed"),
+ /** Execution not yet finished */
+ UNKNOWN("Unknown");
+
+ private String label;
+
+ /**
+ * Constructor
+ *
+ * @param label label
+ */
+ private RESULT(String label) {
+ this.label = label;
+ }
+
+ /**
+ * Returns the human-readable label for this result.
+ *
+ * @return label
+ */
+ public String getLabel() {
+ return label;
+ }
+ }
+
+ ;
+
+ /**
+ * Returns the start time of the execution.
+ *
+ * @return start
+ */
+ Date getStart();
+
+ /**
+ * Returns the end time of the execution.
+ *
+ * @return end
+ */
+ Date getEnd();
+
+ /**
+ * Returns the single script runs.
+ *
+ * @return single results
+ */
+ List getSingleResults();
+
+ /**
+ * Returns the current state of the run.
+ *
+ * @return state
+ */
+ STATE getState();
+
+ /**
+ * Returns the global result of the run.
+ *
+ * @return result
+ */
+ RESULT getResult();
+
+ /**
+ * Returns the path in repository where the history is stored.
+ *
+ * @return path
+ */
+ String getRepositoryPath();
+
+}
diff --git a/api/src/main/java/de/valtech/aecu/service/package-info.java b/api/src/main/java/de/valtech/aecu/service/package-info.java
new file mode 100644
index 00000000..e514cdd1
--- /dev/null
+++ b/api/src/main/java/de/valtech/aecu/service/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * This package contains the service API for AEM Easy Content Upgrade (AECU). You can use this to
+ * integrate AECU into your own software. See {@link de.valtech.aecu.service.AecuService} for a
+ * starting point.
+ *
+ * @author Roland Gruber
+ */
+@Version("1.0")
+package de.valtech.aecu.service;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/bundle/pom.xml b/bundle/pom.xml
new file mode 100644
index 00000000..aa33e8b8
--- /dev/null
+++ b/bundle/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+
+ de.valtech.aecu
+ aecu
+ 0.9
+
+
+ aecu.bundle
+ content-package
+ AECU - Bundle
+ Bundle package for AECU that includes Groovy Console
+
+
+ src/main/content/jcr_root
+
+
+
+ ${basedir}/src/main/content/jcr_root
+
+
+ **/.vlt
+ **/.vltignore
+ **/.gitignore
+ **/*.iml
+ **/.classpath
+ **/.project
+ **/.settings
+ **/.DS_Store
+ **/target/**
+ **/pom.xml
+
+
+
+ src/main/content/META-INF/vault/definition
+ ../vault-work/META-INF/vault/definition
+
+
+
+
+ maven-resources-plugin
+
+ true
+
+
+
+
+ com.day.jcr.vault
+ content-package-maven-plugin
+ true
+
+ src/main/content/META-INF/vault/filter.xml
+ true
+ true
+ Valtech
+
+
+ com.icfolson.aem.groovy.console
+ aem-groovy-console
+
+
+
+
+
+
+ org.apache.sling
+ htl-maven-plugin
+
+
+
+
+
+
+ com.icfolson.aem.groovy.console
+ aem-groovy-console
+ zip
+
+
+
diff --git a/bundle/src/main/content/META-INF/vault/definition/thumbnail.png b/bundle/src/main/content/META-INF/vault/definition/thumbnail.png
new file mode 100644
index 00000000..c4548ee8
Binary files /dev/null and b/bundle/src/main/content/META-INF/vault/definition/thumbnail.png differ
diff --git a/bundle/src/main/content/jcr_root/.gitignore b/bundle/src/main/content/jcr_root/.gitignore
new file mode 100644
index 00000000..3385916d
--- /dev/null
+++ b/bundle/src/main/content/jcr_root/.gitignore
@@ -0,0 +1 @@
+/META-INF/
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 00000000..f8bcd30d
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,114 @@
+
+
+ 4.0.0
+
+ de.valtech.aecu
+ aecu
+ 0.9
+
+
+ aecu.core
+ bundle
+ AECU - Core
+ Core bundle for AECU
+
+
+
+
+ org.apache.sling
+ maven-sling-plugin
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+
+ javax.inject;version=0.0.0,*
+
+ de.valtech.aecu.core
+
+
+
+
+
+
+
+
+
+ de.valtech.aecu
+ aecu.api
+ ${project.version}
+
+
+ org.codehaus.groovy
+ groovy-all
+
+
+ com.icfolson.aem.groovy.console
+ aem-groovy-console
+
+
+ org.osgi
+ osgi.core
+
+
+ org.osgi
+ osgi.cmpn
+
+
+ org.osgi
+ osgi.annotation
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-simple
+
+
+ javax.jcr
+ jcr
+
+
+ javax.servlet
+ servlet-api
+
+
+ com.adobe.aem
+ uber-jar
+ apis
+
+
+ org.apache.sling
+ org.apache.sling.models.api
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ junit
+ junit
+
+
+ org.mockito
+ mockito-core
+
+
+ junit-addons
+ junit-addons
+
+
+ com.google.code.gson
+ gson
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/ContentUpgrade.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/ContentUpgrade.java
new file mode 100644
index 00000000..daf5db84
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/ContentUpgrade.java
@@ -0,0 +1,214 @@
+package de.valtech.aecu.core.groovy.console.bindings;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.scribe.utils.MapUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+import de.valtech.aecu.core.groovy.console.bindings.actions.PrintPath;
+import de.valtech.aecu.core.groovy.console.bindings.actions.multivalue.AddMultiValues;
+import de.valtech.aecu.core.groovy.console.bindings.actions.multivalue.RemoveMultiValues;
+import de.valtech.aecu.core.groovy.console.bindings.actions.multivalue.ReplaceMultiValues;
+import de.valtech.aecu.core.groovy.console.bindings.actions.properties.CopyPropertyToRelativePath;
+import de.valtech.aecu.core.groovy.console.bindings.actions.properties.DeleteProperty;
+import de.valtech.aecu.core.groovy.console.bindings.actions.properties.MovePropertyToRelativePath;
+import de.valtech.aecu.core.groovy.console.bindings.actions.properties.RenameProperty;
+import de.valtech.aecu.core.groovy.console.bindings.actions.properties.SetProperty;
+import de.valtech.aecu.core.groovy.console.bindings.actions.resource.CopyResourceToRelativePath;
+import de.valtech.aecu.core.groovy.console.bindings.actions.resource.DeleteResource;
+import de.valtech.aecu.core.groovy.console.bindings.actions.resource.MoveResourceToRelativePath;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterBy;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterByNodeName;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterByNodeNameRegex;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterByProperties;
+import de.valtech.aecu.core.groovy.console.bindings.traversers.ForChildResourcesOf;
+import de.valtech.aecu.core.groovy.console.bindings.traversers.ForDescendantResourcesOf;
+import de.valtech.aecu.core.groovy.console.bindings.traversers.ForResources;
+import de.valtech.aecu.core.groovy.console.bindings.traversers.TraversData;
+
+public class ContentUpgrade {
+
+ private static Logger LOG = LoggerFactory.getLogger(ContentUpgrade.class);
+
+ private ResourceResolver resourceResolver = null;
+
+ private List traversals = new ArrayList<>();
+ private FilterBy filter = null;
+ private List actions = new ArrayList<>();
+
+
+ public ContentUpgrade(@Nonnull ResourceResolver resourceResolver) {
+ this.resourceResolver = resourceResolver;
+ }
+
+ /**
+ * Loops for given list of resources.
+ *
+ * @param paths list of paths
+ * @return upgrade object
+ **/
+ public ContentUpgrade forResources(@Nonnull String[] paths) {
+ LOG.debug("forResources: {}", paths.toString());
+ traversals.add(new ForResources(paths));
+ return this;
+ }
+
+ public ContentUpgrade forChildResourcesOf(@Nonnull String path) {
+ LOG.debug("forChildResourcesOf: {}", path);
+ traversals.add(new ForChildResourcesOf(path));
+ return this;
+ }
+
+ public ContentUpgrade forDescendantResourcesOf(@Nonnull String path) {
+ LOG.debug("forDescendantResourcesOf: {}", path);
+ traversals.add(new ForDescendantResourcesOf(path));
+ return this;
+ }
+
+ /**
+ * Filters by properties.
+ *
+ * @param conditionProperties properties to filter
+ * @return upgrade object
+ **/
+ public ContentUpgrade filterByProperties(@Nonnull Map conditionProperties) {
+ LOG.debug("filterByProperties: {}", MapUtils.toString(conditionProperties));
+ filter = new FilterByProperties(conditionProperties);
+ return this;
+ }
+
+ public ContentUpgrade filterByNodeName(@Nonnull String nodeName) {
+ LOG.debug("filterByNodeName: {}", nodeName);
+ filter = new FilterByNodeName(nodeName);
+ return this;
+ }
+
+ public ContentUpgrade filterByNodeNameRegex(@Nonnull String regex) {
+ LOG.debug("filterByNodeNameRegex: {}", regex);
+ filter = new FilterByNodeNameRegex(regex);
+ return this;
+ }
+
+ public ContentUpgrade filterWith(@Nonnull FilterBy filter) {
+ LOG.debug("filterWith: {}", filter);
+ this.filter = filter;
+ return this;
+ }
+
+ /**
+ * Sets a property value.
+ *
+ * @param name property name
+ * @param value property value
+ * @return upgrade object
+ **/
+ public ContentUpgrade doSetProperty(@Nonnull String name, Object value) {
+ LOG.debug("doSetProperty: {} = {}", name, value);
+ actions.add(new SetProperty(name, value));
+ return this;
+ }
+
+ public ContentUpgrade doDeleteProperty(@Nonnull String name) {
+ LOG.debug("doDeleteProperty: {}", name);
+ actions.add(new DeleteProperty(name));
+ return this;
+ }
+
+ public ContentUpgrade doRenameProperty(@Nonnull String oldName, @Nonnull String newName) {
+ LOG.debug("doRenameProperty: {} to {}", oldName, newName);
+ actions.add(new RenameProperty(oldName, newName));
+ return this;
+ }
+
+ public ContentUpgrade doCopyPropertyToRelativePath(@Nonnull String name, String newName,
+ @Nonnull String relativeResourcePath) {
+ LOG.debug("doCopyProperty: {} to {}", name, relativeResourcePath);
+ actions.add(new CopyPropertyToRelativePath(name, newName, resourceResolver, relativeResourcePath));
+ return this;
+ }
+
+ public ContentUpgrade doMovePropertyToRelativePath(@Nonnull String name, String newName,
+ @Nonnull String relativeResourcePath) {
+ LOG.debug("doMoveProperty: {} to {}", name, relativeResourcePath);
+ actions.add(new MovePropertyToRelativePath(name, newName, resourceResolver, relativeResourcePath));
+ return this;
+ }
+
+ public ContentUpgrade doAddValuesToMultiValueProperty(@Nonnull String name, @Nonnull String[] values) {
+ LOG.debug("doAddToMultiValueProperty: {} + {}", name, Arrays.toString(values));
+ actions.add(new AddMultiValues(name, values));
+ return this;
+ }
+
+ public ContentUpgrade doRemoveValuesOfMultiValueProperty(@Nonnull String name, @Nonnull String[] values) {
+ LOG.debug("doRemoveValuesFromMultiValueProperty: {} - {}", name, Arrays.toString(values));
+ actions.add(new RemoveMultiValues(name, values));
+ return this;
+ }
+
+ public ContentUpgrade doReplaceValuesOfMultiValueProperty(@Nonnull String name, @Nonnull String[] oldValues,
+ @Nonnull String[] newValues) {
+ LOG.debug("doReplaceValuesOfMultiValueProperty: {} - {}", name,
+ Arrays.toString(oldValues) + " + " + Arrays.toString(newValues));
+ actions.add(new ReplaceMultiValues(name, oldValues, newValues));
+ return this;
+ }
+
+ public ContentUpgrade doCopyResourceToRelativePath(@Nonnull String relativePath) {
+ LOG.debug("doCopyResource to {}", relativePath);
+ actions.add(new CopyResourceToRelativePath(relativePath, resourceResolver));
+ return this;
+ }
+
+ public ContentUpgrade doMoveResourceToRelativePath(@Nonnull String relativePath) {
+ LOG.debug("doMoveResource to {}", relativePath);
+ actions.add(new MoveResourceToRelativePath(relativePath, resourceResolver));
+ return this;
+ }
+
+ public ContentUpgrade doDeleteResource() {
+ LOG.debug("doDeleteResource");
+ actions.add(new DeleteResource(resourceResolver));
+ return this;
+ }
+
+ /**
+ * Print path
+ *
+ * @return upgrade object
+ */
+ public ContentUpgrade printPath() {
+ LOG.debug("printPath");
+ actions.add(new PrintPath());
+ return this;
+ }
+
+ public StringBuffer run() throws PersistenceException {
+ LOG.debug("apply content upgrade");
+ return run(false);
+ }
+
+ public StringBuffer dryRun() throws PersistenceException {
+ LOG.debug("apply content upgrade dry");
+ return run(true);
+ }
+
+ private StringBuffer run(boolean dryRun) throws PersistenceException {
+ StringBuffer stringBuffer = new StringBuffer("Running content upgrade " + (dryRun ? "DRY" : "") + "...\n");
+ for (TraversData traversal : traversals) {
+ for (Action action : actions) {
+ traversal.traverse(resourceResolver, filter, action, stringBuffer, dryRun);
+ }
+ }
+ return stringBuffer;
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/SimpleContentUpdate.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/SimpleContentUpdate.java
new file mode 100644
index 00000000..e3019216
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/SimpleContentUpdate.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * Groovy Console Bindings: Simple Content Update
+ *
+ * @author Roxana Muresan
+ */
+public class SimpleContentUpdate {
+
+ private ResourceResolver resourceResolver;
+
+
+ public SimpleContentUpdate(ResourceResolver resourceResolver) {
+ this.resourceResolver = resourceResolver;
+ }
+
+ public ContentUpgrade contentUpgradeBuilder() {
+ return new ContentUpgrade(resourceResolver);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/Action.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/Action.java
new file mode 100644
index 00000000..9be0adba
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/Action.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public interface Action {
+
+ String doAction(@Nonnull Resource resource) throws PersistenceException;
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/PrintPath.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/PrintPath.java
new file mode 100644
index 00000000..b527af8d
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/PrintPath.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public class PrintPath implements Action {
+
+ @Override
+ public String doAction(@Nonnull Resource resource) {
+ return resource.getPath();
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/AddMultiValues.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/AddMultiValues.java
new file mode 100644
index 00000000..90cb88df
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/AddMultiValues.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.multivalue;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class AddMultiValues implements Action {
+
+ private String name;
+ private String[] values;
+
+ public AddMultiValues(@Nonnull String name, @Nonnull String[] values) {
+ this.name = name;
+ this.values = Arrays.stream(values).filter(f -> f != null).collect(Collectors.toList()).toArray(new String[] {});
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
+ if (properties != null) {
+ String[] currentValues = properties.get(name, String[].class);
+ List valuesList = new ArrayList<>();
+ if (currentValues != null && currentValues.length > 0) {
+ Collections.addAll(valuesList, currentValues);
+ }
+ Collections.addAll(valuesList, values);
+ properties.put(name, valuesList.toArray(new String[] {}));
+
+ return "Adding values " + Arrays.toString(values) + " to multi-value property " + name + ": "
+ + Arrays.toString(currentValues) + " results in " + valuesList + " for resource " + resource.getPath();
+ }
+ return "WARNING: could not get ModifiableValueMap for resource " + resource.getPath();
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/RemoveMultiValues.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/RemoveMultiValues.java
new file mode 100644
index 00000000..93928cd0
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/RemoveMultiValues.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.multivalue;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class RemoveMultiValues implements Action {
+
+ private String name;
+ private String[] values;
+
+ public RemoveMultiValues(@Nonnull String name, @Nonnull String[] values) {
+ this.name = name;
+ this.values = Arrays.stream(values).filter(f -> f != null).collect(Collectors.toList()).toArray(new String[] {});
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
+ if (properties != null) {
+ String[] currentValues = properties.get(name, String[].class);
+ List valuesList = new ArrayList<>();
+ if (currentValues != null && currentValues.length > 0) {
+ Collections.addAll(valuesList, currentValues);
+ }
+ valuesList.removeAll(Arrays.asList(values));
+ properties.put(name, valuesList.toArray(new String[] {}));
+
+ return "Removing values " + Arrays.toString(values) + " from multi-value property " + name + ": "
+ + Arrays.toString(currentValues) + " results in " + valuesList + " for resource " + resource.getPath();
+ }
+ return "WARNING: could not get ModifiableValueMap for resource " + resource.getPath();
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/ReplaceMultiValues.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/ReplaceMultiValues.java
new file mode 100644
index 00000000..b975158e
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/multivalue/ReplaceMultiValues.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.multivalue;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class ReplaceMultiValues implements Action {
+
+ private String name;
+ private String[] oldValues;
+ private String[] newValues;
+
+ public ReplaceMultiValues(@Nonnull String name, @Nonnull String[] oldValues, @Nonnull String[] newValues) {
+ this.name = name;
+ this.oldValues = Arrays.stream(oldValues).filter(f -> f != null).collect(Collectors.toList()).toArray(new String[] {});
+ this.newValues = Arrays.stream(newValues).filter(f -> f != null).collect(Collectors.toList()).toArray(new String[] {});
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
+ if (properties != null) {
+ String[] currentValues = properties.get(name, String[].class);
+ List valuesList = new ArrayList<>();
+ if (currentValues != null && currentValues.length > 0) {
+ Collections.addAll(valuesList, currentValues);
+ }
+
+ String warning = (oldValues.length != newValues.length)
+ ? "WARNING: old values and new values length mismatch (old: " + Arrays.toString(oldValues) + " , new: "
+ + Arrays.toString(newValues) + ")" + " -> the smaller length will be considered\n"
+ : "";
+
+ for (int i = 0; i < oldValues.length && i < newValues.length; i++) {
+ Collections.replaceAll(valuesList, oldValues[i], newValues[i]);
+ }
+ properties.put(name, valuesList.toArray(new String[] {}));
+
+ return warning + "Replacing values " + Arrays.toString(oldValues) + " with values " + Arrays.toString(newValues)
+ + " for multi-value property " + name + ": " + Arrays.toString(currentValues) + " results in " + valuesList
+ + " for resource " + resource.getPath();
+ }
+ return "WARNING: could not get ModifiableValueMap for resource " + resource.getPath();
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/CopyPropertyToRelativePath.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/CopyPropertyToRelativePath.java
new file mode 100644
index 00000000..74719c2b
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/CopyPropertyToRelativePath.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.properties;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class CopyPropertyToRelativePath implements Action {
+
+ private String relativeResourcePath;
+ private String name;
+ private String newName;
+ private ResourceResolver resourceResolver;
+
+ public CopyPropertyToRelativePath(@Nonnull String name, String newName, @Nonnull ResourceResolver resourceResolver,
+ @Nonnull String relativeResourcePath) {
+ this.name = name;
+ this.newName = newName;
+ this.resourceResolver = resourceResolver;
+ this.relativeResourcePath = relativeResourcePath;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) {
+ ValueMap sourceProperties = resource.adaptTo(ValueMap.class);
+
+ Resource destinationResource = resourceResolver.getResource(resource, relativeResourcePath);// TODO
+ // null
+ // check!!!!
+ ModifiableValueMap destinationProperties = destinationResource.adaptTo(ModifiableValueMap.class);// TODO
+ // null
+ // check!!!!
+
+ Object propValue = sourceProperties.get(name);
+ String key = (newName != null) ? newName : name;
+ destinationProperties.put(key, propValue);
+
+ return "Coping property " + name + " from " + resource.getPath() + " to resource " + destinationResource.getPath()
+ + " as " + key;
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/DeleteProperty.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/DeleteProperty.java
new file mode 100644
index 00000000..5a391702
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/DeleteProperty.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.properties;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public class DeleteProperty implements Action {
+
+ private String name;
+
+ public DeleteProperty(@Nonnull String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) {
+ ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
+ properties.remove(name);
+ return "Deleting property " + name + " for resource " + resource.getPath();
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/MovePropertyToRelativePath.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/MovePropertyToRelativePath.java
new file mode 100644
index 00000000..60f5c818
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/MovePropertyToRelativePath.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.properties;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class MovePropertyToRelativePath implements Action {
+
+ private String relativeResourcePath;
+ private String name;
+ private String newName;
+ private ResourceResolver resourceResolver;
+
+ public MovePropertyToRelativePath(@Nonnull String name, String newName, @Nonnull ResourceResolver resourceResolver,
+ @Nonnull String relativeResourcePath) {
+ this.name = name;
+ this.newName = newName;
+ this.resourceResolver = resourceResolver;
+ this.relativeResourcePath = relativeResourcePath;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) {
+ ModifiableValueMap sourceProperties = resource.adaptTo(ModifiableValueMap.class);
+
+ Resource targetResource = resourceResolver.getResource(resource, relativeResourcePath);
+ ModifiableValueMap targetProperties = targetResource.adaptTo(ModifiableValueMap.class);
+
+ Object propValue = sourceProperties.get(name);
+ String key = (newName != null) ? newName : name;
+ targetProperties.put(key, propValue);
+ sourceProperties.remove(name);
+
+ return "Moving property " + name + " from " + resource.getPath() + " to resource " + targetResource.getPath() + " as "
+ + key;
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/RenameProperty.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/RenameProperty.java
new file mode 100644
index 00000000..dc284eed
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/RenameProperty.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.properties;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public class RenameProperty implements Action {
+
+ private String oldName;
+ private String newName;
+
+ public RenameProperty(@Nonnull String oldName, @Nonnull String newName) {
+ this.oldName = oldName;
+ this.newName = newName;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) {
+ ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
+ Object value = properties.remove(oldName);
+ properties.put(newName, value);
+ return "Renaming property " + oldName + " to " + newName + " for resource " + resource.getPath();
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/SetProperty.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/SetProperty.java
new file mode 100644
index 00000000..f3471682
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/properties/SetProperty.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.properties;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class SetProperty implements Action {
+
+ protected String name;
+ protected Object value;
+
+ public SetProperty(@Nonnull String name, Object value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) {
+ ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
+ properties.put(name, value);
+ return "Setting " + value.getClass().getSimpleName() + " property " + name + "=" + value + " for resource "
+ + resource.getPath();
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePath.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePath.java
new file mode 100644
index 00000000..1d44c3fc
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/CopyResourceToRelativePath.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.resource;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class CopyResourceToRelativePath implements Action {
+
+ private String relativePath;
+ private ResourceResolver resourceResolver;
+
+ public CopyResourceToRelativePath(@Nonnull String relativePath, @Nonnull ResourceResolver resourceResolver) {
+ this.relativePath = relativePath;
+ this.resourceResolver = resourceResolver;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ Resource destinationResource = resourceResolver.getResource(resource, relativePath);
+ String sourceAbsPAth = resource.getPath();
+ String destinationAsPath = destinationResource.getPath();
+ resourceResolver.copy(sourceAbsPAth, destinationAsPath);
+
+ return "Copied " + sourceAbsPAth + " to path " + destinationAsPath;
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/DeleteResource.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/DeleteResource.java
new file mode 100644
index 00000000..fd04374d
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/DeleteResource.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.resource;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class DeleteResource implements Action {
+
+ private ResourceResolver resourceResolver;
+
+ public DeleteResource(@Nonnull ResourceResolver resourceResolver) {
+ this.resourceResolver = resourceResolver;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ String path = resource.getPath();
+ resourceResolver.delete(resource);
+ return "Deleted resource " + path;
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToRelativePath.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToRelativePath.java
new file mode 100644
index 00000000..1b094705
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToRelativePath.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.actions.resource;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class MoveResourceToRelativePath implements Action {
+
+ private String relativePath;
+ private ResourceResolver resourceResolver;
+
+ public MoveResourceToRelativePath(@Nonnull String relativePath, @Nonnull ResourceResolver resourceResolver) {
+ this.relativePath = relativePath;
+ this.resourceResolver = resourceResolver;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ Resource destinationResource = resourceResolver.getResource(resource, relativePath);
+ String sourceAbsPAth = resource.getPath();
+ String destinationAsPath = destinationResource.getPath();
+ resourceResolver.move(sourceAbsPAth, destinationAsPath);
+
+ return "Moved " + sourceAbsPAth + " to path " + destinationAsPath;
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/ANDFilter.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/ANDFilter.java
new file mode 100644
index 00000000..154eaea8
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/ANDFilter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.filters;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public class ANDFilter implements FilterBy {
+
+ private List filters;
+
+
+ public ANDFilter(@Nonnull List filters) {
+ this.filters = filters;
+ }
+
+ @Override
+ public boolean filter(@Nonnull Resource resource) {
+ boolean foundFalse = filters.parallelStream().filter(f -> f.filter(resource) == false).findAny().isPresent();
+ return !foundFalse;
+
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterBy.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterBy.java
new file mode 100644
index 00000000..99f316f1
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterBy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.filters;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public interface FilterBy {
+
+ boolean filter(@Nonnull Resource resource);
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByNodeName.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByNodeName.java
new file mode 100644
index 00000000..d66cc60c
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByNodeName.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.filters;
+
+import org.apache.sling.api.resource.Resource;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class FilterByNodeName implements FilterBy {
+
+ private String name;
+
+ public FilterByNodeName(@Nonnull String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean filter(@Nonnull Resource resource) {
+ return resource.getName().equals(this.name);
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByNodeNameRegex.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByNodeNameRegex.java
new file mode 100644
index 00000000..1bba5942
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByNodeNameRegex.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.filters;
+
+import org.apache.sling.api.resource.Resource;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class FilterByNodeNameRegex implements FilterBy {
+
+ private String regex;
+
+ public FilterByNodeNameRegex(@Nonnull String regex) {
+ this.regex = regex;
+ }
+
+ @Override
+ public boolean filter(@Nonnull Resource resource) {
+ return resource.getName().matches(regex);
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByProperties.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByProperties.java
new file mode 100644
index 00000000..6c11bd8b
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/FilterByProperties.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.filters;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public class FilterByProperties implements FilterBy {
+
+ private Map conditionProperties = new HashMap<>();
+
+ public FilterByProperties(@Nonnull Map conditionProperties) {
+ this.conditionProperties.putAll(conditionProperties);
+ }
+
+ @Override
+ public boolean filter(@Nonnull Resource resource) {
+ ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
+ for (String key : conditionProperties.keySet()) {
+ String conditionValue = conditionProperties.get(key);
+ String propertiesValue = properties.get(key, String.class);
+
+ if ((conditionValue == null && propertiesValue != null)
+ || (conditionValue != null && !conditionValue.equals(propertiesValue))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/NOTFilter.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/NOTFilter.java
new file mode 100644
index 00000000..ba87296b
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/NOTFilter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.filters;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public class NOTFilter implements FilterBy {
+
+ private FilterBy filter;
+
+
+ public NOTFilter(@Nonnull FilterBy filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean filter(@Nonnull Resource resource) {
+ return !filter.filter(resource);
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/ORFilter.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/ORFilter.java
new file mode 100644
index 00000000..bb637d02
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/filters/ORFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.filters;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * @author Roxana Muresan
+ */
+public class ORFilter implements FilterBy {
+
+ private List filters;
+
+
+ public ORFilter(List filters) {
+ this.filters = filters;
+ }
+
+ @Override
+ public boolean filter(@Nonnull Resource resource) {
+ boolean foundTrue = filters.parallelStream().filter(f -> f.filter(resource)).findAny().isPresent();
+ return foundTrue;
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuBindingExtensionProvider.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuBindingExtensionProvider.java
new file mode 100644
index 00000000..de08676b
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuBindingExtensionProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.provider;
+
+import com.icfolson.aem.groovy.console.api.BindingExtensionProvider;
+
+import de.valtech.aecu.core.groovy.console.bindings.SimpleContentUpdate;
+import de.valtech.aecu.core.serviceuser.ServiceResourceResolverService;
+
+import groovy.lang.Binding;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.LoginException;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides additional AECU Bindings for the Groovy Console
+ *
+ * @author Roxana Muresan
+ */
+@Component(immediate = true)
+public class AecuBindingExtensionProvider implements BindingExtensionProvider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AecuBindingExtensionProvider.class);
+
+ @Reference
+ private BindingExtensionProvider defaultBindingExtensionProvider;
+ @Reference
+ private ServiceResourceResolverService resourceResolverService;
+
+
+ @Override
+ public Binding getBinding(SlingHttpServletRequest request) {
+ Binding binding = defaultBindingExtensionProvider.getBinding(request);
+ try {
+ binding.setVariable("aecu", new SimpleContentUpdate(resourceResolverService.getContentMigratorResourceResolver()));
+ } catch (LoginException e) {
+ LOG.error(
+ "Failed to get resource resolver for aecu-content-migrator, make sure you all the configurations needed for this system user are deployed.");
+ }
+ return binding;
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuStarImportExtensionProvider.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuStarImportExtensionProvider.java
new file mode 100644
index 00000000..15147d97
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/provider/AecuStarImportExtensionProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.provider;
+
+import com.icfolson.aem.groovy.console.api.StarImportExtensionProvider;
+import com.icfolson.aem.groovy.console.constants.GroovyConsoleConstants;
+
+import org.osgi.service.component.annotations.Component;
+
+import java.util.Set;
+
+@Component(immediate = true)
+public class AecuStarImportExtensionProvider implements StarImportExtensionProvider {
+
+ @Override
+ public Set getStarImports() {
+ Set imports = GroovyConsoleConstants.DEFAULT_STAR_IMPORTS;
+ imports.add("de.valtech.aecu.core.groovy.console.bindings.filters");
+ return imports;
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForChildResourcesOf.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForChildResourcesOf.java
new file mode 100644
index 00000000..86f7d690
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForChildResourcesOf.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.traversers;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterBy;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+import java.util.Iterator;
+
+/**
+ * @author Roxana Muresan
+ */
+public class ForChildResourcesOf implements TraversData {
+
+ private String path;
+
+ public ForChildResourcesOf(@Nonnull String path) {
+ this.path = path;
+ }
+
+
+ @Override
+ public void traverse(@Nonnull ResourceResolver resourceResolver, FilterBy filter, @Nonnull Action action,
+ @Nonnull StringBuffer stringBuffer, boolean dryRun) throws PersistenceException {
+ Resource parentResource = resourceResolver.getResource(path);
+ if (parentResource != null) {
+ Iterator resourceIterator = resourceResolver.listChildren(parentResource);
+ while (resourceIterator.hasNext()) {
+ Resource resource = resourceIterator.next();
+ if (filter == null || filter.filter(resource)) {
+ stringBuffer.append(action.doAction(resource) + "\n");
+ }
+ }
+ if (!dryRun) {
+ resourceResolver.commit();
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForDescendantResourcesOf.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForDescendantResourcesOf.java
new file mode 100644
index 00000000..71cf7dce
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForDescendantResourcesOf.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.traversers;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterBy;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+import java.util.Iterator;
+
+/**
+ * @author Roxana Muresan
+ */
+public class ForDescendantResourcesOf implements TraversData {
+
+ private String path;
+
+ public ForDescendantResourcesOf(@Nonnull String path) {
+ this.path = path;
+ }
+
+
+ @Override
+ public void traverse(@Nonnull ResourceResolver resourceResolver, FilterBy filter, @Nonnull Action action,
+ @Nonnull StringBuffer stringBuffer, boolean dryRun) throws PersistenceException {
+ Resource parentResource = resourceResolver.getResource(path);
+ if (parentResource != null) {
+ traverseChildResourcesRecursive(resourceResolver, parentResource, filter, action, stringBuffer, dryRun);
+ }
+ }
+
+ private void traverseChildResourcesRecursive(ResourceResolver resourceResolver, Resource resource, FilterBy filter,
+ Action action, StringBuffer stringBuffer, boolean dryRun) throws PersistenceException {
+ if (resource != null && resource.hasChildren()) {
+ Iterator childResources = resource.listChildren();
+ while (childResources.hasNext()) {
+ Resource child = childResources.next();
+ if (filter == null || filter.filter(child)) {
+ stringBuffer.append(action.doAction(child) + "\n");
+ }
+ traverseChildResourcesRecursive(resourceResolver, child, filter, action, stringBuffer, dryRun);
+ }
+ if (!dryRun) {
+ resourceResolver.commit();
+ }
+ }
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForResources.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForResources.java
new file mode 100644
index 00000000..a4f2fe41
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/ForResources.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.traversers;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterBy;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public class ForResources implements TraversData {
+
+ private String[] paths;
+
+ public ForResources(@Nonnull String[] paths) {
+ this.paths = paths;
+ }
+
+ @Override
+ public void traverse(@Nonnull ResourceResolver resourceResolver, FilterBy filter, @Nonnull Action action,
+ @Nonnull StringBuffer stringBuffer, boolean dryRun) throws PersistenceException {
+ for (String path : paths) {
+ if (path != null) {
+ Resource resource = resourceResolver.getResource(path);
+ if (filter == null || filter.filter(resource)) {
+ stringBuffer.append(action.doAction(resource) + "\n");
+ }
+ }
+ }
+ if (!dryRun) {
+ resourceResolver.commit();
+ }
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/TraversData.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/TraversData.java
new file mode 100644
index 00000000..5f0e75e1
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/traversers/TraversData.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.groovy.console.bindings.traversers;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+import de.valtech.aecu.core.groovy.console.bindings.filters.FilterBy;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Roxana Muresan
+ */
+public interface TraversData {
+
+ void traverse(@Nonnull ResourceResolver resourceResolver, FilterBy filter, @Nonnull Action action,
+ @Nonnull StringBuffer stringBuffer, boolean dryRun) throws PersistenceException;
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/healthcheck/LastRunHealthCheck.java b/core/src/main/java/de/valtech/aecu/core/healthcheck/LastRunHealthCheck.java
new file mode 100644
index 00000000..76fa6f92
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/healthcheck/LastRunHealthCheck.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.healthcheck;
+
+import java.util.List;
+
+import org.apache.sling.hc.api.HealthCheck;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.util.FormattingResultLog;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.AecuService;
+import de.valtech.aecu.service.HistoryEntry;
+
+/**
+ * Checks if the last script run was ok.
+ *
+ * @author Roland Gruber
+ */
+@Component(immediate = true, service = HealthCheck.class, property = {HealthCheck.TAGS + "=aecu",
+ HealthCheck.NAME + "=AECU Last Run", HealthCheck.MBEAN_NAME + "=aecuLastRunHCmBean"})
+public class LastRunHealthCheck implements HealthCheck {
+
+ @Reference
+ private AecuService aecuService;
+
+ @Override
+ public Result execute() {
+ final FormattingResultLog resultLog = new FormattingResultLog();
+ try {
+ List history = aecuService.getHistory(0, 1);
+ if (history.isEmpty()) {
+ resultLog.info("No runs found");
+ } else {
+ HistoryEntry entry = history.get(0);
+ switch (entry.getResult()) {
+ case FAILURE:
+ resultLog.critical("Last execution failed");
+ break;
+ case SUCCESS:
+ resultLog.info("Last run was successful");
+ break;
+ case UNKNOWN:
+ resultLog.warn("Last execution is still running");
+ break;
+ }
+ }
+ } catch (AecuException e) {
+ resultLog.critical(e.getMessage());
+ }
+ return new Result(resultLog);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/healthcheck/SelfCheckHealthCheck.java b/core/src/main/java/de/valtech/aecu/core/healthcheck/SelfCheckHealthCheck.java
new file mode 100644
index 00000000..74d2a107
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/healthcheck/SelfCheckHealthCheck.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.healthcheck;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.hc.api.HealthCheck;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.util.FormattingResultLog;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import de.valtech.aecu.core.serviceuser.ServiceResourceResolverService;
+
+/**
+ * Checks if the internal service user is ok.
+ *
+ * @author Roland Gruber
+ */
+@Component(immediate = true, service = HealthCheck.class, property = {HealthCheck.TAGS + "=aecu",
+ HealthCheck.NAME + "=AECU Self Check", HealthCheck.MBEAN_NAME + "=aecuSelfCheckHCmBean"})
+public class SelfCheckHealthCheck implements HealthCheck {
+
+ @Reference
+ private ServiceResourceResolverService resolverService;
+
+ @Override
+ public Result execute() {
+ final FormattingResultLog resultLog = new FormattingResultLog();
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ resultLog.info("Ok");
+ } catch (LoginException e) {
+ resultLog.critical("Unable to open service resource resolver {}", e.getMessage());
+ }
+ return new Result(resultLog);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/history/HistoryUtil.java b/core/src/main/java/de/valtech/aecu/core/history/HistoryUtil.java
new file mode 100644
index 00000000..3f76d869
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/history/HistoryUtil.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.history;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ResourceUtil.BatchResourceRemover;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.jcr.resource.api.JcrResourceConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.valtech.aecu.core.service.HistoryEntryImpl;
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.ExecutionResult;
+import de.valtech.aecu.service.HistoryEntry;
+import de.valtech.aecu.service.HistoryEntry.RESULT;
+import de.valtech.aecu.service.HistoryEntry.STATE;
+
+/**
+ * Reads and writes history entries.
+ *
+ * @author Roland Gruber
+ */
+public class HistoryUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(HistoryUtil.class);
+
+ private static final String HISTORY_BASE = "/var/aecu";
+
+ private static final String NODE_FALLBACK = "fallback";
+
+ private static final String ATTR_PATH = "path";
+ private static final String ATTR_RUN_OUTPUT = "runOutput";
+ private static final String ATTR_RUN_SUCCESS = "runSuccess";
+ private static final String ATTR_RUN_RESULT = "runResult";
+ private static final String ATTR_RUN_TIME = "runTime";
+ private static final String ATTR_RESULT = "result";
+ private static final String ATTR_STATE = "state";
+ private static final String ATTR_START = "start";
+ private static final String ATTR_END = "end";
+
+ /**
+ * Starts a new history entry.
+ *
+ * @param resolver resource resolver
+ * @return history entry
+ * @throws AecuException error setting up entry
+ */
+ public HistoryEntry createHistoryEntry(ResourceResolver resolver) throws AecuException {
+ HistoryEntryImpl history = new HistoryEntryImpl();
+ Calendar start = new GregorianCalendar();
+ String basePath = HISTORY_BASE + "/" + start.get(Calendar.YEAR) + "/" + (start.get(Calendar.MONTH) + 1) + "/"
+ + start.get(Calendar.DAY_OF_MONTH);
+ String nodeName = generateHistoryNodeName();
+ String nodePath = basePath + "/" + nodeName;
+ createPath(basePath, resolver, JcrResourceConstants.NT_SLING_ORDERED_FOLDER);
+ createPath(nodePath, resolver, JcrConstants.NT_UNSTRUCTURED);
+ Resource resource = resolver.getResource(nodePath);
+ ModifiableValueMap values = resource.adaptTo(ModifiableValueMap.class);
+ values.put(ATTR_START, start);
+ values.put(ATTR_STATE, STATE.RUNNING.name());
+ values.put(ATTR_RESULT, RESULT.UNKNOWN.name());
+ history.setStart(start.getTime());
+ history.setRepositoryPath(nodePath);
+ history.setState(STATE.RUNNING);
+ return history;
+ }
+
+ /**
+ * Stores an execution run in existing history.
+ *
+ * @param history history entry
+ * @param result script execution result
+ * @param resolver resource resolver
+ * @throws AecuException error inserting history entry
+ */
+ public void storeExecutionInHistory(HistoryEntry history, ExecutionResult result, ResourceResolver resolver)
+ throws AecuException {
+ String path = history.getRepositoryPath() + "/" + history.getSingleResults().size();
+ saveExecutionResultInHistory(result, path, resolver);
+ }
+
+ /**
+ * Finishes the history entry.
+ *
+ * @param history open history entry
+ * @param resolver resource resolver
+ */
+ public void finishHistoryEntry(HistoryEntry history, ResourceResolver resolver) {
+ Resource resource = resolver.getResource(history.getRepositoryPath());
+ ModifiableValueMap values = resource.adaptTo(ModifiableValueMap.class);
+ Calendar end = new GregorianCalendar();
+ values.put(ATTR_END, end);
+ values.put(ATTR_STATE, STATE.FINISHED.name());
+ values.put(ATTR_RESULT, history.getResult().name());
+ ((HistoryEntryImpl) history).setEnd(end.getTime());
+ ((HistoryEntryImpl) history).setState(STATE.FINISHED);
+ }
+
+ /**
+ * Returns the last history entries. The search starts at the newest entry.
+ *
+ * @param startIndex start reading at this index (first is 0)
+ * @param count number of entries to read
+ * @param resolver resource resolver
+ * @return history entries (newest first)
+ */
+ public List getHistory(int startIndex, int count, ResourceResolver resolver) {
+ List entries = new ArrayList<>();
+ if (count == 0) {
+ return entries;
+ }
+ Resource base = resolver.getResource(HISTORY_BASE);
+ Resource current = getLatestHistoryEntry(base);
+ if (current == null) {
+ return entries;
+ }
+ // skip up to start index
+ for (int i = 0; i < startIndex; i++) {
+ current = getPreviousHistoryEntry(current);
+ }
+ for (int i = 0; i < count; i++) {
+ if (current == null) {
+ break;
+ }
+ entries.add(readHistoryEntry(current));
+ current = getPreviousHistoryEntry(current);
+ }
+ return entries;
+ }
+
+ /**
+ * Returns the run before the given one.
+ *
+ * @param current current run
+ * @return previous run
+ */
+ private Resource getPreviousHistoryEntry(Resource current) {
+ // check if the parent has a sibling before the current node
+ Resource previous = getPreviousSibling(current);
+ if (previous != null) {
+ return previous;
+ }
+ // go down till we find an earlier sibling
+ Resource base = descendToPreviousSiblingInHistory(current.getParent());
+ // go back up the folders
+ return ascendToLastRun(base);
+ }
+
+ /**
+ * Gos up the folders to last run.
+ *
+ * @param resource current node
+ * @return last run
+ */
+ private Resource ascendToLastRun(Resource resource) {
+ if (resource == null) {
+ return null;
+ }
+ Resource last = getLastChild(resource);
+ if (last == null) {
+ // stop if there is no child at all
+ return null;
+ }
+ ValueMap values = last.adaptTo(ValueMap.class);
+ if (JcrResourceConstants.NT_SLING_ORDERED_FOLDER.equals(values.get(JcrConstants.JCR_PRIMARYTYPE, String.class))) {
+ return ascendToLastRun(last);
+ }
+ return last;
+ }
+
+ /**
+ * Descends in history till a previous sibling is found. Descending stops at history base level
+ *
+ * @param current current resource
+ * @return previous sibling
+ */
+ private Resource descendToPreviousSiblingInHistory(Resource current) {
+ if ((current == null) || HISTORY_BASE.equals(current.getPath())) {
+ return null;
+ }
+ Resource previous = getPreviousSibling(current);
+ if (previous != null) {
+ return previous;
+ }
+ previous = descendToPreviousSiblingInHistory(current.getParent());
+ return previous;
+ }
+
+ /**
+ * Returns the previous sibling of the given node.
+ *
+ * @param resource current node
+ * @return last sibling or null
+ */
+ private Resource getPreviousSibling(Resource resource) {
+ Iterator siblings = resource.getParent().listChildren();
+ Resource previous = null;
+ while (siblings.hasNext()) {
+ Resource sibling = siblings.next();
+ if (sibling.getName().equals(resource.getName())) {
+ break;
+ }
+ if (!sibling.getName().equals(AccessControlConstants.REP_POLICY)) {
+ previous = sibling;
+ }
+ }
+ return previous;
+ }
+
+ /**
+ * Returns the latest history entry.
+ *
+ * @param base base resource
+ * @return latest run resource
+ */
+ private Resource getLatestHistoryEntry(Resource base) {
+ if (base == null) {
+ return null;
+ }
+ return ascendToLastRun(base);
+ }
+
+ /**
+ * Returns the last child of the given resource.
+ *
+ * @param resource resource
+ * @return last child
+ */
+ private Resource getLastChild(Resource resource) {
+ if (resource == null) {
+ return null;
+ }
+ Resource last = null;
+ Iterator lastIterator = resource.listChildren();
+ while (lastIterator.hasNext()) {
+ Resource candidate = lastIterator.next();
+ if (!AccessControlConstants.REP_POLICY.equals(candidate.getName())) {
+ last = candidate;
+ }
+ }
+ return last;
+ }
+
+ /**
+ * Reads a history entry from JCR.
+ *
+ * @param resource history resource
+ * @return history entry
+ */
+ public HistoryEntry readHistoryEntry(Resource resource) {
+ HistoryEntryImpl entry = new HistoryEntryImpl();
+ entry.setRepositoryPath(resource.getPath());
+ ValueMap values = resource.adaptTo(ValueMap.class);
+ if (values.containsKey(ATTR_STATE)) {
+ entry.setState(STATE.valueOf(values.get(ATTR_STATE, String.class)));
+ }
+ if (values.containsKey(ATTR_START)) {
+ entry.setStart(values.get(ATTR_START, Calendar.class).getTime());
+ }
+ if (values.containsKey(ATTR_END)) {
+ entry.setEnd(values.get(ATTR_END, Calendar.class).getTime());
+ }
+ Iterable children = resource.getChildren();
+ for (Resource child : children) {
+ entry.getSingleResults().add(readHistorySingleResult(child));
+ }
+ return entry;
+ }
+
+ /**
+ * Reads a single script run from history.
+ *
+ * @param resource resource
+ * @return result
+ */
+ private ExecutionResult readHistorySingleResult(Resource resource) {
+ ExecutionResult fallback = null;
+ Resource fallbackResource = resource.getChild(NODE_FALLBACK);
+ if (fallbackResource != null) {
+ fallback = readHistorySingleResult(fallbackResource);
+ }
+ ValueMap values = resource.adaptTo(ValueMap.class);
+ String output = values.get(ATTR_RUN_OUTPUT, "");
+ String time = values.get(ATTR_RUN_TIME, "");
+ Boolean success = values.get(ATTR_RUN_SUCCESS, Boolean.FALSE);
+ String runResult = values.get(ATTR_RUN_RESULT, "");
+ String path = values.get(ATTR_PATH, "");
+ ExecutionResult result = new ExecutionResult(success, time, runResult, output, fallback, path);
+ return result;
+ }
+
+ private void saveExecutionResultInHistory(ExecutionResult result, String path, ResourceResolver resolver)
+ throws AecuException {
+ createPath(path, resolver, "nt:unstructured");
+ Resource entry = resolver.getResource(path);
+ ModifiableValueMap values = entry.adaptTo(ModifiableValueMap.class);
+ values.put(ATTR_RUN_SUCCESS, result.isSuccess());
+ values.put(ATTR_PATH, result.getPath());
+ if (StringUtils.isNotBlank(result.getOutput())) {
+ values.put(ATTR_RUN_OUTPUT, result.getOutput());
+ }
+ if (StringUtils.isNotBlank(result.getResult())) {
+ values.put(ATTR_RUN_RESULT, result.getResult());
+ }
+ if (StringUtils.isNotBlank(result.getTime())) {
+ values.put(ATTR_RUN_TIME, result.getTime());
+ }
+ if (result.getFallbackResult() != null) {
+ String fallbackPath = path + "/" + NODE_FALLBACK;
+ saveExecutionResultInHistory(result.getFallbackResult(), fallbackPath, resolver);
+ }
+ }
+
+ /**
+ * Creates the folder at the given path if not yet existing.
+ *
+ * @param path path
+ * @param resolver resource resolver
+ * @param primaryType primary type
+ * @throws AecuException error creating folder
+ */
+ protected void createPath(String path, ResourceResolver resolver, String primaryType) throws AecuException {
+ Resource folder = resolver.getResource(path);
+ if (folder == null) {
+ String parent = path.substring(0, path.lastIndexOf("/"));
+ String name = path.substring(path.lastIndexOf("/") + 1);
+ if (resolver.getResource(parent) == null) {
+ createPath(parent, resolver, primaryType);
+ }
+ Map properties = new HashMap<>();
+ properties.put(JcrConstants.JCR_PRIMARYTYPE, primaryType);
+ try {
+ resolver.create(resolver.getResource(parent), name, properties);
+ } catch (PersistenceException e) {
+ throw new AecuException("Unable to create " + path, e);
+ }
+ }
+ }
+
+ /**
+ * Generates the node name for a history entry.
+ *
+ * @return name
+ */
+ private String generateHistoryNodeName() {
+ Random random = new Random();
+ return System.currentTimeMillis() + "" + random.nextInt(100000);
+ }
+
+ /**
+ * Purges the history by keeping only entries within the set number of days.
+ *
+ * @param resolver resource resolver
+ * @param daysToKeep number of days to keep
+ * @throws PersistenceException error deleting node
+ */
+ public void purgeHistory(ResourceResolver resolver, int daysToKeep) throws PersistenceException {
+ Resource base = resolver.getResource(HISTORY_BASE);
+ Calendar calendar = new GregorianCalendar();
+ calendar.add(Calendar.DAY_OF_MONTH, -daysToKeep);
+ LOG.debug("Starting purge with limit " + calendar.getTime().toString());
+ deleteRecursive(base.listChildren(), calendar, new int[] {Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH});
+ }
+
+ /**
+ * Deletes the year resources that are too old.
+ *
+ * @param resources resources
+ * @param calendar time limit
+ * @param fields calendar fields
+ * @throws PersistenceException error deleting node
+ */
+ private void deleteRecursive(Iterator resources, Calendar calendar, int[] fields) throws PersistenceException {
+ int currentField = fields[0];
+ while (resources.hasNext()) {
+ Resource resource = resources.next();
+ String name = resource.getName();
+ // skip extra nodes such as ACLs
+ if (!StringUtils.isNumeric(name)) {
+ LOG.debug("Skipping purge of other node: " + resource.getPath());
+ continue;
+ }
+ int nodeValue = Integer.parseInt(name);
+ int limit = calendar.get(currentField);
+ if (currentField == Calendar.MONTH) {
+ // months start with 0 but are stored beginning with 1 in CRX
+ limit++;
+ }
+ if (nodeValue > limit) {
+ LOG.debug("Skipping purge of too young node: " + resource.getPath());
+ } else if (nodeValue == limit) {
+ LOG.debug("Skipping purge of too young node: " + resource.getPath());
+ // check next level
+ if (fields.length == 1) {
+ return;
+ }
+ int[] fieldsNew = new int[fields.length - 1];
+ System.arraycopy(fields, 1, fieldsNew, 0, fieldsNew.length);
+ deleteRecursive(resource.listChildren(), calendar, fieldsNew);
+ } else {
+ LOG.debug("Purging node: " + resource.getPath());
+ BatchResourceRemover remover = ResourceUtil.getBatchResourceRemover(1000);
+ remover.delete(resource);
+ }
+ }
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/installhook/AecuInstallHook.java b/core/src/main/java/de/valtech/aecu/core/installhook/AecuInstallHook.java
new file mode 100644
index 00000000..697bb09d
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/installhook/AecuInstallHook.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.installhook;
+
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.AecuService;
+import de.valtech.aecu.service.ExecutionResult;
+import de.valtech.aecu.service.HistoryEntry;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.packaging.InstallContext;
+import org.apache.jackrabbit.vault.packaging.InstallHook;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * InstallHook handling installation of groovy scripts. The InstallHook gathers groovy scripts
+ * contained in the installed vault package and executes them depending on active runmodes and if
+ * the script has been added, modified or never executed.
+ * Example usage in content-package-maven-plugin:
+ *
+ *
+ */
+public class AecuInstallHook implements InstallHook {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AecuInstallHook.class);
+
+ private final OsgiServiceProvider osgiServiceProvider;
+ private AecuTrackerListener listener;
+
+ public AecuInstallHook() {
+ this.osgiServiceProvider = new OsgiServiceProvider(this.getClass());
+ }
+
+ @Override
+ public void execute(InstallContext installContext) throws PackageException {
+ LOG.info("Executing in phase {}", installContext.getPhase());
+ ServiceReference aecuServiceReference = osgiServiceProvider.getServiceReference(AecuService.class);
+ AecuService aecuService = osgiServiceProvider.getService(aecuServiceReference);
+
+ try {
+ switch (installContext.getPhase()) {
+ case PREPARE:
+ ProgressTrackerListener originalListener = installContext.getOptions().getListener();
+ listener = new AecuTrackerListener(originalListener, aecuService);
+ installContext.getOptions().setListener(listener);
+ break;
+ case INSTALLED:
+
+ Archive archive = installContext.getPackage().getArchive();
+ List allValidScriptCandidatesInArchive = findCandidates("", archive.getJcrRoot(), aecuService);
+ List scriptsForInstallation =
+ getScriptsForExecution(allValidScriptCandidatesInArchive, installContext);
+
+ if (!scriptsForInstallation.isEmpty()) {
+ HistoryEntry installationHistory = executeScripts(scriptsForInstallation, aecuService, installContext);
+ if (!HistoryEntry.RESULT.SUCCESS.equals(installationHistory.getResult())) {
+ throw new PackageException("Failed installation, check installation history at "
+ + installationHistory.getRepositoryPath());
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ } catch (IOException | AecuException e) {
+ throw new PackageException(e);
+ } finally {
+ osgiServiceProvider.ungetService(aecuServiceReference);
+ }
+ }
+
+ private List getScriptsForExecution(List allValidScriptCandidatesInArchive, InstallContext installContext) {
+ List scriptsForExecution = new ArrayList<>();
+ List modifiedOrAddedScriptPaths = listener.getModifiedOrAddedPaths();
+ for (String groovyScriptPath : allValidScriptCandidatesInArchive) {
+ try {
+ HookExecutionHistory hookExecutionHistory =
+ new HookExecutionHistory(installContext.getSession(), groovyScriptPath);
+ if (shouldExecute(modifiedOrAddedScriptPaths, groovyScriptPath, hookExecutionHistory)) {
+ scriptsForExecution.add(groovyScriptPath);
+ }
+ } catch (AecuException e) {
+ listener.logError("Could not obtain execution history for " + groovyScriptPath, e);
+ }
+
+ }
+ return scriptsForExecution;
+ }
+
+ private boolean shouldExecute(List modifiedOrAddedScriptPaths, String groovyScriptPath,
+ HookExecutionHistory hookExecutionHistory) {
+ return modifiedOrAddedScriptPaths.contains(groovyScriptPath) || !hookExecutionHistory.hasBeenExecutedBefore();
+ }
+
+ private HistoryEntry executeScripts(List scriptsForExecution, AecuService aecuService, InstallContext installContext)
+ throws AecuException, IOException {
+ HistoryEntry installationHistory = aecuService.createHistoryEntry();
+ for (String groovyScriptPath : scriptsForExecution) {
+ HookExecutionHistory hookExecutionHistory = new HookExecutionHistory(installContext.getSession(), groovyScriptPath);
+ try {
+ installationHistory = executeScript(aecuService, installationHistory, groovyScriptPath);
+ hookExecutionHistory.setExecuted();
+ } catch (AecuException e) {
+ listener.logError("Error executing script " + groovyScriptPath, e);
+ }
+ }
+ installationHistory = aecuService.finishHistoryEntry(installationHistory);
+ return installationHistory;
+ }
+
+ private HistoryEntry executeScript(AecuService aecuService, HistoryEntry installationHistory, String groovyScriptPath)
+ throws AecuException {
+ listener.logMessage("Executing script " + groovyScriptPath);
+ ExecutionResult result = aecuService.execute(groovyScriptPath);
+ installationHistory = aecuService.storeExecutionInHistory(installationHistory, result);
+ listener.logMessage("Executed script " + groovyScriptPath + ", output: " + result.getOutput());
+ return installationHistory;
+ }
+
+ // mildly duplicated
+ private List findCandidates(String parent, Archive.Entry entry, AecuService aecuService) {
+ List candidates = new ArrayList<>();
+ if (entry == null) {
+ return candidates;
+ }
+ String entryName = entry.getName();
+ String entryPath = parent + "/" + entryName;
+
+ if (entry.isDirectory() && aecuService.matchesRunmodes(entryName)) {
+ for (Archive.Entry childEntry : entry.getChildren()) {
+ candidates.addAll(findCandidates(entryPath, childEntry, aecuService));
+ }
+ } else if (aecuService.isValidScriptName(entryName)) {
+ candidates.add(StringUtils.substringAfter(entryPath, "/jcr_root"));
+ }
+ return candidates;
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/installhook/AecuTrackerListener.java b/core/src/main/java/de/valtech/aecu/core/installhook/AecuTrackerListener.java
new file mode 100644
index 00000000..bc3c3d14
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/installhook/AecuTrackerListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.installhook;
+
+import de.valtech.aecu.service.AecuService;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Collects groovy script paths to potentially execute based on the given actions.
+ */
+public class AecuTrackerListener implements ProgressTrackerListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AecuTrackerListener.class);
+
+ private static final Set ACTIONS = new HashSet<>(Arrays.asList("A", "M", "U"));
+
+ private static final int VALID_ACTION_LENGTH = 1;
+
+ private static final String LOG_PREFIX = "AECU InstallHook: ";
+
+ private final ProgressTrackerListener originalListener;
+ private final AecuService aecuService;
+ private final List paths;
+
+ /**
+ * Constructor.
+ *
+ * @param originalListener the original ProgressTrackerListener.
+ * @param aecuService an AecuService instance.
+ */
+ public AecuTrackerListener(ProgressTrackerListener originalListener, AecuService aecuService) {
+ this.originalListener = originalListener;
+ this.aecuService = aecuService;
+ this.paths = new LinkedList<>();
+ logMessage("Starting install hook...");
+ }
+
+ /**
+ * Returns an unmodifiable list of the modified or added paths encountered during the
+ * installation phase.
+ *
+ * @return a list of modified or added paths, can be empty.
+ */
+ @Nonnull
+ public List getModifiedOrAddedPaths() {
+ return Collections.unmodifiableList(paths);
+ }
+
+ @Override
+ public void onMessage(Mode mode, String action, String path) {
+ originalListener.onMessage(mode, action, path);
+
+ if (StringUtils.length(action) != VALID_ACTION_LENGTH) {
+ // skip actions like 'Collecting import information... ', 'Package imported.' etc.
+ return;
+ }
+
+ if (StringUtils.endsWith(path, "always.groovy")) {
+ logMessage(String.format("Adding %s due to having 'always' in name.", path));
+ paths.add(path);
+ return;
+ }
+
+ if (!ACTIONS.contains(action)) {
+ logMessage(String.format("Skipping %s due to non matching action '%s'", path, action));
+ return;
+ }
+
+ // in case a script was updated the update will actually be shown on jcr:content and not on
+ // the groovy script node
+ if (StringUtils.endsWith(path, "/jcr:content")) {
+ path = StringUtils.substringBefore(path, "/jcr:content");
+ }
+
+ if (aecuService.isValidScriptName(path)) {
+ logMessage(String.format("Found valid script path '%s'", path));
+ paths.add(path);
+ }
+ }
+
+ @Override
+ public void onError(Mode mode, String action, Exception e) {
+ originalListener.onError(mode, action, e);
+ }
+
+ public void logMessage(String message) {
+ onMessage(Mode.TEXT, LOG_PREFIX + message, "");
+ }
+
+ public void logError(String message, Exception e) {
+ onError(Mode.TEXT, LOG_PREFIX + message, e);
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/installhook/HookExecutionHistory.java b/core/src/main/java/de/valtech/aecu/core/installhook/HookExecutionHistory.java
new file mode 100644
index 00000000..39a96583
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/installhook/HookExecutionHistory.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.installhook;
+
+import de.valtech.aecu.service.AecuException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.value.DateValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * Execution history for groovy scripts executed during {@link AecuInstallHook} invocation.
+ */
+public class HookExecutionHistory {
+
+ private static final Logger LOG = LoggerFactory.getLogger(HookExecutionHistory.class);
+
+ private static final String HISTORY_BASE_PATH = "/var/aecu-installhook";
+
+ private static final String PN_EXECUTED = "executed";
+
+ private final Node hookHistory;
+
+ /**
+ * Constructor.
+ *
+ * @param session a session with write permissons on {@value HISTORY_BASE_PATH}.
+ * @param groovyScriptPath the groovy script to instantiate the execution history for.
+ * @throws AecuException in case the call to JcrUtils.getOrCreateByPath fails.
+ */
+ public HookExecutionHistory(Session session, String groovyScriptPath) throws AecuException {
+ try {
+ hookHistory = JcrUtils.getOrCreateByPath(HISTORY_BASE_PATH + groovyScriptPath, false, JcrConstants.NT_UNSTRUCTURED,
+ JcrConstants.NT_UNSTRUCTURED, session, true);
+ } catch (RepositoryException e) {
+ throw new AecuException("Error getting or creating node at " + HISTORY_BASE_PATH + groovyScriptPath, e);
+ }
+ }
+
+ /**
+ * Returns if the script has been executed before. This is determined by checking existence of
+ * the property {@value PN_EXECUTED} on the history node.
+ *
+ * @return true if it has been executed previously, false otherwise.
+ */
+ public boolean hasBeenExecutedBefore() {
+ boolean hasBeenExecuted = false;
+ try {
+ hasBeenExecuted = hookHistory.hasProperty(PN_EXECUTED);
+ } catch (RepositoryException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ return hasBeenExecuted;
+ }
+
+ /**
+ * Sets {@value PN_EXECUTED} on the history node to the current date.
+ *
+ * @throws AecuException in case the property could not be saved.
+ */
+ public void setExecuted() throws AecuException {
+ try {
+ hookHistory.setProperty(PN_EXECUTED, new DateValue(Calendar.getInstance()));
+ hookHistory.getSession().save();
+ } catch (RepositoryException e) {
+ throw new AecuException("Could not set property " + PN_EXECUTED, e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/installhook/OsgiServiceProvider.java b/core/src/main/java/de/valtech/aecu/core/installhook/OsgiServiceProvider.java
new file mode 100644
index 00000000..67da6787
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/installhook/OsgiServiceProvider.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.installhook;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Service Provider for InstallHook.
+ */
+public class OsgiServiceProvider {
+
+ private final BundleContext bundleContext;
+
+ /**
+ * Constructor.
+ *
+ * @param clazz the class that was loaded over a bundle classloader.
+ */
+ public OsgiServiceProvider(@Nonnull Class clazz) {
+ Bundle currentBundle = FrameworkUtil.getBundle(clazz);
+ if (currentBundle == null) {
+ throw new IllegalStateException("The class " + clazz + " was not loaded through a bundle classloader");
+ }
+ bundleContext = currentBundle.getBundleContext();
+ if (bundleContext == null) {
+ throw new IllegalStateException("Could not get bundle context for bundle " + currentBundle);
+ }
+ }
+
+ /**
+ * Retrieves a {@link ServiceReference} for the given class.
+ *
+ * @see org.osgi.framework.BundleContext#getServiceReference(Class)
+ * @param clazz the class to retrieve a {@link ServiceReference} for.
+ * @param the type of the service.
+ * @return a {@link ServiceReference} for the requested service.
+ */
+ @Nonnull
+ ServiceReference getServiceReference(@Nonnull Class clazz) {
+ ServiceReference serviceReference = bundleContext.getServiceReference(clazz);
+ if (serviceReference == null) {
+ throw new IllegalStateException("Could not retrieve service reference for class " + clazz);
+ }
+ return serviceReference;
+ }
+
+ /**
+ * Retrieves the service object the {@link ServiceReference} is pointing to.
+ *
+ * @see org.osgi.framework.BundleContext#getService(ServiceReference)
+ * @param serviceReference the {@link ServiceReference}.
+ * @param the service type.
+ * @return the service instance.
+ */
+ @Nonnull
+ T getService(@Nonnull ServiceReference serviceReference) {
+ T service = bundleContext.getService(serviceReference);
+ if (service == null) {
+ throw new IllegalStateException("Could not get the service for reference " + serviceReference
+ + ", verify that the bundle was installed correctly!");
+ }
+ return service;
+ }
+
+ /**
+ * @see org.osgi.framework.BundleContext#ungetService(ServiceReference)
+ */
+ boolean ungetService(@Nonnull ServiceReference> serviceReference) {
+ return bundleContext.ungetService(serviceReference);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/jmx/AecuServiceMBean.java b/core/src/main/java/de/valtech/aecu/core/jmx/AecuServiceMBean.java
new file mode 100644
index 00000000..7cc6eed6
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/jmx/AecuServiceMBean.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.jmx;
+
+import java.util.List;
+
+import com.adobe.granite.jmx.annotation.Description;
+import com.adobe.granite.jmx.annotation.Name;
+
+import de.valtech.aecu.service.AecuException;
+
+/**
+ * JMX service interface.
+ *
+ * @author Roland Gruber
+ */
+@Description("AEM Easy Content Upgrade")
+public interface AecuServiceMBean {
+
+ /**
+ * Returns the AECU version.
+ *
+ * @return version
+ */
+ @Description("Version")
+ public String getVersion();
+
+ /**
+ * Returns a list of files that can be executed in the given path.
+ *
+ * @param path file or folder
+ * @return list of files that are executable
+ * @throws AecuException error finding files (e.g. invalid path)
+ */
+ @Description("Returns a list of files that can be executed in the given path")
+ List getFiles(@Name("Path") @Description("File or folder") String path) throws AecuException;
+
+ /**
+ * Executes the script at the given position.
+ *
+ * @param path path of script
+ * @return execution result
+ * @throws AecuException error during execution
+ */
+ @Description("Executes a single file or all files of a folder structure")
+ String execute(@Name("Path") @Description("Path to file/folder that should be executed") String path) throws AecuException;
+
+ /**
+ * Returns history entries.
+ *
+ * @param start start index (0 is last run)
+ * @param count number of entries to return
+ * @return history entries
+ * @throws AecuException error reading history
+ */
+ @Description("Returns the last history entries")
+ String getHistory(@Name("Start index") int start, @Name("Count") int count) throws AecuException;
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/jmx/AecuServiceMBeanImpl.java b/core/src/main/java/de/valtech/aecu/core/jmx/AecuServiceMBeanImpl.java
new file mode 100644
index 00000000..a34241fc
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/jmx/AecuServiceMBeanImpl.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.jmx;
+
+import java.util.List;
+
+import javax.management.NotCompliantMBeanException;
+
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.adobe.granite.jmx.annotation.AnnotatedStandardMBean;
+
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.AecuService;
+import de.valtech.aecu.service.ExecutionResult;
+import de.valtech.aecu.service.HistoryEntry;
+
+@Component(service = {AecuServiceMBean.class}, immediate = true,
+ property = {"jmx.objectname=de.valtech:type=AECU", "pattern=/.*"})
+public class AecuServiceMBeanImpl extends AnnotatedStandardMBean implements AecuServiceMBean {
+
+ @Reference
+ AecuService aecuService;
+
+ /**
+ * Constructor
+ *
+ * @throws NotCompliantMBeanException error setting up mbean
+ */
+ public AecuServiceMBeanImpl() throws NotCompliantMBeanException {
+ super(AecuServiceMBean.class);
+ }
+
+ @Override
+ public String getVersion() {
+ return aecuService.getVersion();
+ }
+
+ @Override
+ public List getFiles(String path) throws AecuException {
+ return aecuService.getFiles(path);
+ }
+
+ @Override
+ public String execute(String path) throws AecuException {
+ HistoryEntry history = aecuService.createHistoryEntry();
+ List files = aecuService.getFiles(path);
+ StringBuilder result = new StringBuilder("Found " + files.size() + " files to execute\n\n");
+ for (String file : files) {
+ result.append(file + "\n");
+ ExecutionResult singleResult = aecuService.execute(file);
+ aecuService.storeExecutionInHistory(history, singleResult);
+ result.append(singleResult.toString());
+ result.append("\n\n");
+ }
+ aecuService.finishHistoryEntry(history);
+ return result.toString();
+ }
+
+ @Override
+ public String getHistory(int start, int count) throws AecuException {
+ List entries = aecuService.getHistory(start, count);
+ StringBuilder output = new StringBuilder();
+ for (HistoryEntry entry : entries) {
+ output.append(entry.toString() + "\n\n");
+ }
+ return output.toString();
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/maintenance/PurgeHistoryConfiguration.java b/core/src/main/java/de/valtech/aecu/core/maintenance/PurgeHistoryConfiguration.java
new file mode 100644
index 00000000..e49dbf77
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/maintenance/PurgeHistoryConfiguration.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.maintenance;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.AttributeType;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+/**
+ * Configuration for purge task.
+ *
+ * @author Roland Gruber
+ */
+@ObjectClassDefinition(name = "AECU Purge history configuration")
+public @interface PurgeHistoryConfiguration {
+
+ @AttributeDefinition(type = AttributeType.INTEGER, name = "Days to keep",
+ description = "Entries younger than this will not be removed")
+ int daysToKeep();
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/maintenance/PurgeHistoryTask.java b/core/src/main/java/de/valtech/aecu/core/maintenance/PurgeHistoryTask.java
new file mode 100644
index 00000000..d47cd755
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/maintenance/PurgeHistoryTask.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.maintenance;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.consumer.JobExecutionContext;
+import org.apache.sling.event.jobs.consumer.JobExecutionResult;
+import org.apache.sling.event.jobs.consumer.JobExecutor;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.Designate;
+
+import com.adobe.granite.maintenance.MaintenanceConstants;
+
+import de.valtech.aecu.core.history.HistoryUtil;
+import de.valtech.aecu.core.serviceuser.ServiceResourceResolverService;
+
+/**
+ * Purges old entries from the history.
+ *
+ * @author Roland Gruber
+ */
+@Component(property = {MaintenanceConstants.PROPERTY_TASK_NAME + "=AECUPurgeHistory",
+ MaintenanceConstants.PROPERTY_TASK_TITLE + "=AECU Purge History",
+ JobExecutor.PROPERTY_TOPICS + "=" + MaintenanceConstants.TASK_TOPIC_PREFIX + "AECUPurgeHistory",})
+@Designate(ocd = PurgeHistoryConfiguration.class)
+public class PurgeHistoryTask implements JobExecutor {
+
+ private PurgeHistoryConfiguration config;
+
+ @Reference
+ private ServiceResourceResolverService resolverService;
+
+ /**
+ * Activates the service.
+ *
+ * @param config configuration
+ */
+ @Activate
+ public void activate(PurgeHistoryConfiguration config) {
+ this.config = config;
+ }
+
+ @Override
+ public JobExecutionResult process(Job job, JobExecutionContext context) {
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ HistoryUtil historyUtil = new HistoryUtil();
+ historyUtil.purgeHistory(resolver, config.daysToKeep());
+ resolver.commit();
+ return context.result().message("Purged AECU history entries").succeeded();
+ } catch (LoginException e) {
+ return context.result().message("Service resolver failed with " + e.getMessage()).failed();
+ } catch (PersistenceException e) {
+ return context.result().message("Purge failed with " + e.getMessage()).failed();
+ }
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/model/execute/ExecuteDataSource.java b/core/src/main/java/de/valtech/aecu/core/model/execute/ExecuteDataSource.java
new file mode 100644
index 00000000..459da67d
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/model/execute/ExecuteDataSource.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.model.execute;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import com.adobe.granite.ui.components.ds.ValueMapResource;
+
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.AecuService;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.SlingObject;
+
+import com.adobe.granite.ui.components.ds.DataSource;
+import com.adobe.granite.ui.components.ds.SimpleDataSource;
+
+
+/**
+ * Datasource model for execute page.
+ *
+ * @author Bryan Chavez
+ */
+@Model(adaptables = SlingHttpServletRequest.class)
+public class ExecuteDataSource {
+
+ private static final String ITEM_TYPE = "valtech/aecu/tools/execute/dataitem";
+ private static final String ALLOWED_PATH = "/etc/groovyconsole/scripts";
+
+ @SlingObject
+ SlingHttpServletRequest request;
+
+ @Inject
+ private AecuService aecuService;
+
+ @PostConstruct
+ public void setup() throws AecuException {
+
+ String path = request.getParameter("searchPath");
+ List entries = new ArrayList<>();
+
+ if (path != null && StringUtils.isNotEmpty(path) && path.startsWith(ALLOWED_PATH)) {
+ List allowedScripts = aecuService.getFiles(path);
+ ResourceResolver resourceResolver = request.getResourceResolver();
+ for (String scriptPath : allowedScripts) {
+ entries.add(new ValueMapResource(resourceResolver, scriptPath, ITEM_TYPE, null));
+ }
+ }
+
+ DataSource ds = new SimpleDataSource(entries.iterator());
+ request.setAttribute(DataSource.class.getName(), ds);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataItem.java b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataItem.java
new file mode 100644
index 00000000..1a353ee2
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataItem.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.model.history;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.SlingObject;
+
+import de.valtech.aecu.service.HistoryEntry;
+import de.valtech.aecu.service.HistoryEntry.RESULT;
+import de.valtech.aecu.service.HistoryEntry.STATE;
+
+/**
+ * Model class for a single history item.
+ *
+ * @author Roland Gruber
+ */
+@Model(adaptables = Resource.class)
+public class HistoryDataItem {
+
+ private final DateFormat format = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
+
+ @SlingObject
+ private Resource resource;
+
+ private HistoryEntry history = null;
+
+ @PostConstruct
+ public void setup() {
+ history = resource.adaptTo(ValueMap.class).get(HistoryDataSource.ATTR_HISTORY, HistoryEntry.class);
+ }
+
+ /**
+ * Returns the date of the run.
+ *
+ * @return date
+ */
+ public String getDate() {
+ return format.format(history.getEnd());
+ }
+
+ /**
+ * Returns the duration of the run.
+ *
+ * @return duration
+ */
+ public String getDuration() {
+ if (!STATE.FINISHED.equals(history.getState())) {
+ return "";
+ }
+ Duration duration = Duration.between(history.getStart().toInstant(), history.getEnd().toInstant());
+ long seconds = duration.getSeconds();
+ if (seconds > 0) {
+ return duration.getSeconds() + "s";
+ }
+ return (duration.getNano() / 1000000) + "ms";
+ }
+
+ /**
+ * Returns the status icon of the run.
+ *
+ * @return icon
+ */
+ public String getStatusIcon() {
+ if (RESULT.FAILURE.equals(history.getResult())) {
+ return "closeCircle";
+ }
+ if (RESULT.SUCCESS.equals(history.getResult())) {
+ return "checkCircle";
+ }
+ return "clock";
+ }
+
+ /**
+ * Returns the status color of the run.
+ *
+ * @return icon
+ */
+ public String getStatusColor() {
+ if (RESULT.FAILURE.equals(history.getResult())) {
+ return "fail";
+ }
+ if (RESULT.SUCCESS.equals(history.getResult())) {
+ return "ok";
+ }
+ return "inprogress";
+ }
+
+ /**
+ * Returns the path of the run.
+ *
+ * @return path
+ */
+ public String getPath() {
+ return history.getRepositoryPath();
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataSource.java b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataSource.java
new file mode 100644
index 00000000..1a53dba2
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryDataSource.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.model.history;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.OSGiService;
+import org.apache.sling.models.annotations.injectorspecific.SlingObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.adobe.cq.commerce.common.ValueMapDecorator;
+import com.adobe.granite.ui.components.ds.AbstractDataSource;
+import com.adobe.granite.ui.components.ds.DataSource;
+import com.adobe.granite.ui.components.ds.ValueMapResource;
+
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.AecuService;
+import de.valtech.aecu.service.HistoryEntry;
+
+/**
+ * Datasource model for history overview page.
+ *
+ * @author Roland Gruber
+ */
+@Model(adaptables = SlingHttpServletRequest.class)
+public class HistoryDataSource {
+
+ private static final String ITEM_TYPE = "valtech/aecu/tools/history/dataitem";
+ public static final String ATTR_HISTORY = "history";
+
+ private Logger LOG = LoggerFactory.getLogger(DataSource.class);
+
+ @SlingObject
+ SlingHttpServletRequest request;
+
+ @OSGiService
+ AecuService aecuService;
+
+ @PostConstruct
+ public void setup() {
+ String[] selectors = request.getRequestPathInfo().getSelectors();
+ int offset = 0;
+ int limit = 50;
+ if (selectors.length > 1) {
+ offset = Integer.parseInt(selectors[0]);
+ limit = Integer.parseInt(selectors[1]);
+ }
+ request.setAttribute(DataSource.class.getName(), getResourceIterator(offset, limit));
+ }
+
+ /**
+ * Returns the history entries.
+ *
+ * @param offset offset where to start reading
+ * @param limit maximum number of entries to return
+ * @return entries
+ */
+ private DataSource getResourceIterator(int offset, int limit) {
+ return new AbstractDataSource() {
+
+ @Override
+ public Iterator iterator() {
+ List entries = new ArrayList<>();
+ try {
+ List historyEntries = aecuService.getHistory(offset, limit + 1);
+ for (HistoryEntry historyEntry : historyEntries) {
+ ValueMap vm = new ValueMapDecorator(new HashMap());
+ vm.put(ATTR_HISTORY, historyEntry);
+ entries.add(new ValueMapResource(request.getResourceResolver(), historyEntry.getRepositoryPath(),
+ ITEM_TYPE, vm));
+ }
+ } catch (AecuException e) {
+ LOG.error("Unable to read history entries", e);
+ }
+ return entries.iterator();
+ }
+
+ };
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/model/history/HistoryOverview.java b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryOverview.java
new file mode 100644
index 00000000..02db4c7a
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/model/history/HistoryOverview.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.model.history;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.SlingObject;
+
+import de.valtech.aecu.core.history.HistoryUtil;
+import de.valtech.aecu.service.ExecutionResult;
+import de.valtech.aecu.service.HistoryEntry;
+
+/**
+ * Sling model for history overview area.
+ *
+ * @author Roland Gruber
+ */
+@Model(adaptables = SlingHttpServletRequest.class)
+public class HistoryOverview {
+
+ @SlingObject
+ private SlingHttpServletRequest request;
+
+ @SlingObject
+ private ResourceResolver resolver;
+
+ private HistoryEntry historyEntry;
+
+ private final DateFormat format = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
+
+ /**
+ * Reads the history entry from CRX.
+ */
+ @PostConstruct
+ public void init() {
+ RequestParameter entryParam = request.getRequestParameter("entry");
+ if (entryParam == null) {
+ return;
+ }
+ String path = entryParam.getString();
+ Resource historyResource = resolver.getResource(path);
+ if (historyResource == null) {
+ return;
+ }
+ HistoryUtil historyUtil = new HistoryUtil();
+ historyEntry = historyUtil.readHistoryEntry(historyResource);
+ }
+
+ /**
+ * Returns the history entry.
+ *
+ * @return history
+ */
+ public HistoryEntry getHistory() {
+ return historyEntry;
+ }
+
+ /**
+ * Returns the start as formatted string.
+ *
+ * @return start date
+ */
+ public String getStart() {
+ if ((historyEntry == null) || (historyEntry.getStart() == null)) {
+ return StringUtils.EMPTY;
+ }
+ return format.format(historyEntry.getStart());
+ }
+
+ /**
+ * Returns the end as formatted string.
+ *
+ * @return end date
+ */
+ public String getEnd() {
+ if ((historyEntry == null) || (historyEntry.getEnd() == null)) {
+ return StringUtils.EMPTY;
+ }
+ return format.format(historyEntry.getEnd());
+ }
+
+ /**
+ * Returns the duration.
+ *
+ * @return duration
+ */
+ public String getDuration() {
+ Duration duration = Duration.between(historyEntry.getStart().toInstant(), historyEntry.getEnd().toInstant());
+ long seconds = duration.getSeconds();
+ if (seconds > 0) {
+ return duration.getSeconds() + "s";
+ }
+ return (duration.getNano() / 1000000) + "ms";
+ }
+
+ /**
+ * Returns the percentages of successful and failed scripts.
+ *
+ * @return percentages (successful, failed)
+ */
+ public Pair getPercentages() {
+ int countAll = historyEntry.getSingleResults().size();
+ if (countAll == 0) {
+ return Pair.of("0", "0");
+ }
+ double countOk = 0;
+ double countFailed = 0;
+ for (ExecutionResult result : historyEntry.getSingleResults()) {
+ if (result.isSuccess()) {
+ countOk++;
+ } else {
+ countFailed++;
+ }
+ }
+ BigDecimal percentageOk = new BigDecimal((countOk / countAll) * 100);
+ BigDecimal percentageFailed = new BigDecimal((countFailed / countAll) * 100);
+ String valueOk = percentageOk.round(new MathContext(2)).toPlainString();
+ String valueFailed = percentageFailed.round(new MathContext(2)).toPlainString();
+ return Pair.of(valueOk, valueFailed);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/service/AecuServiceImpl.java b/core/src/main/java/de/valtech/aecu/core/service/AecuServiceImpl.java
new file mode 100644
index 00000000..0d2af5b2
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/service/AecuServiceImpl.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.jcr.resource.api.JcrResourceConstants;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.icfolson.aem.groovy.console.GroovyConsoleService;
+import com.icfolson.aem.groovy.console.response.RunScriptResponse;
+
+import de.valtech.aecu.core.history.HistoryUtil;
+import de.valtech.aecu.core.serviceuser.ServiceResourceResolverService;
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.AecuService;
+import de.valtech.aecu.service.ExecutionResult;
+import de.valtech.aecu.service.HistoryEntry;
+import de.valtech.aecu.service.HistoryEntry.STATE;
+
+/**
+ * AECU service.
+ *
+ * @author Roland Gruber
+ */
+@Component(service = AecuService.class)
+public class AecuServiceImpl implements AecuService {
+
+ @Reference
+ private ServiceResourceResolverService resolverService;
+
+ @Reference
+ private SlingSettingsService slingSettings;
+
+ @Reference
+ private GroovyConsoleService groovyConsoleService;
+
+ @Override
+ public String getVersion() {
+ return FrameworkUtil.getBundle(AecuServiceImpl.class).getVersion().toString();
+ }
+
+ @Override
+ public List getFiles(String path) throws AecuException {
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ return findCandidates(resolver, path);
+ } catch (LoginException e) {
+ throw new AecuException("Unable to get service resource resolver", e);
+ }
+ }
+
+ /**
+ * Finds all candidates for scripts to run.
+ *
+ * @param resolver service resource resolver
+ * @param path starting path
+ * @return candidate list
+ * @throws AecuException error finding candidates
+ */
+ private List findCandidates(ResourceResolver resolver, String path) throws AecuException {
+ if (path == null) {
+ throw new AecuException("Path is null");
+ }
+ Resource resource = resolver.getResource(path);
+ if (resource == null) {
+ throw new AecuException("Path is invalid");
+ }
+ List candidates = new ArrayList<>();
+ if (isFolder(resource) && matchesRunmodes(resource.getName())) {
+ for (Resource child : resource.getChildren()) {
+ candidates.addAll(findCandidates(resolver, child.getPath()));
+ }
+ } else if (isValidScriptName(resource.getName())) {
+ candidates.add(path);
+ }
+ return candidates;
+ }
+
+ /**
+ * Checks if the resource is a folder.
+ *
+ * @param resource resource
+ * @return is folder
+ */
+ private boolean isFolder(Resource resource) {
+ String type = resource.getValueMap().get(JcrConstants.JCR_PRIMARYTYPE, String.class);
+ return JcrResourceConstants.NT_SLING_FOLDER.equals(type) || JcrResourceConstants.NT_SLING_ORDERED_FOLDER.equals(type)
+ || JcrConstants.NT_FOLDER.equals(type);
+ }
+
+ @Override
+ public boolean matchesRunmodes(String name) {
+ if (!name.contains(".")) {
+ return true;
+ }
+ Set runModes = slingSettings.getRunModes();
+ String runModeString = name.substring(name.indexOf(".") + 1);
+ String[] combinations = runModeString.split(";");
+ for (String combination : combinations) {
+ String[] modes = combination.split("\\.");
+ if (runModes.containsAll(Arrays.asList(modes))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isValidScriptName(String name) {
+ if (!name.endsWith(".groovy")) {
+ return false;
+ }
+ if (name.contains(".fallback.")) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public ExecutionResult execute(String path) throws AecuException {
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ Resource resource = resolver.getResource(path);
+ if (resource == null) {
+ throw new AecuException("Path is invalid");
+ }
+ if (!isValidScriptName(resource.getName())) {
+ throw new AecuException("Invalid script name");
+ }
+ ExecutionResult result = executeScript(resolver, path);
+ return result;
+ } catch (LoginException e) {
+ throw new AecuException("Unable to get service resource resolver", e);
+ }
+ }
+
+ /**
+ * Executes the script.
+ *
+ * @param resolver resource resolver
+ * @param path path
+ * @return result
+ */
+ private ExecutionResult executeScript(ResourceResolver resolver, String path) {
+ GroovyConsoleRequest request = new GroovyConsoleRequest(resolver);
+ RunScriptResponse response = groovyConsoleService.runScript(request, path);
+ boolean success = StringUtils.isBlank(response.getExceptionStackTrace());
+ String result = response.getResult();
+ ExecutionResult fallbackResult = null;
+ if (!success && (getFallbackScript(resolver, path) != null)) {
+ fallbackResult = executeScript(resolver, getFallbackScript(resolver, path));
+ }
+ return new ExecutionResult(success, response.getRunningTime(), result,
+ response.getOutput() + response.getExceptionStackTrace(), fallbackResult, path);
+ }
+
+ /**
+ * Returns the fallback script name if any exists.
+ *
+ * @param resolver resource resolver
+ * @param path original script path
+ * @return fallback script path
+ */
+ protected String getFallbackScript(ResourceResolver resolver, String path) {
+ String name = path.substring(path.lastIndexOf("/") + 1);
+ if (name.contains(".fallback.")) {
+ // skip if script is a fallback script itself
+ return null;
+ }
+ String baseName = name.substring(0, name.indexOf("."));
+ String fallbackPath = path.substring(0, path.lastIndexOf("/") + 1) + baseName + ".fallback.groovy";
+ if (resolver.getResource(fallbackPath) != null) {
+ return fallbackPath;
+ }
+ return null;
+ }
+
+ @Override
+ public HistoryEntry createHistoryEntry() throws AecuException {
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ HistoryUtil historyUtil = new HistoryUtil();
+ HistoryEntry entry = historyUtil.createHistoryEntry(resolver);
+ resolver.commit();
+ return entry;
+ } catch (PersistenceException e) {
+ throw new AecuException("Unable to create history", e);
+ } catch (LoginException e) {
+ throw new AecuException("Unable to get service resource resolver", e);
+ }
+ }
+
+ @Override
+ public HistoryEntry finishHistoryEntry(HistoryEntry history) throws AecuException {
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ HistoryUtil historyUtil = new HistoryUtil();
+ historyUtil.finishHistoryEntry(history, resolver);
+ resolver.commit();
+ return history;
+ } catch (LoginException e) {
+ throw new AecuException("Unable to get service resource resolver", e);
+ } catch (PersistenceException e) {
+ throw new AecuException("Unable to finish history " + history.getRepositoryPath(), e);
+ }
+ }
+
+ @Override
+ public HistoryEntry storeExecutionInHistory(HistoryEntry history, ExecutionResult result) throws AecuException {
+ if ((history == null) || !STATE.RUNNING.equals(history.getState())) {
+ throw new AecuException("Invalid history entry.");
+ }
+ history.getSingleResults().add(result);
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ HistoryUtil historyUtil = new HistoryUtil();
+ historyUtil.storeExecutionInHistory(history, result, resolver);
+ resolver.commit();
+ return history;
+ } catch (LoginException e) {
+ throw new AecuException("Unable to get service resource resolver", e);
+ } catch (PersistenceException e) {
+ throw new AecuException("Unable to add history entry " + history.getRepositoryPath(), e);
+ }
+ }
+
+ @Override
+ public List getHistory(int startIndex, int count) throws AecuException {
+ try (ResourceResolver resolver = resolverService.getServiceResourceResolver()) {
+ HistoryUtil historyUtil = new HistoryUtil();
+ return historyUtil.getHistory(startIndex, count, resolver);
+ } catch (LoginException e) {
+ throw new AecuException("Unable to get service resource resolver", e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/service/GroovyConsoleRequest.java b/core/src/main/java/de/valtech/aecu/core/service/GroovyConsoleRequest.java
new file mode 100644
index 00000000..097634a2
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/service/GroovyConsoleRequest.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.service;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * Dummy request that is used to execute the script.
+ *
+ * @author Roland Gruber
+ */
+public class GroovyConsoleRequest implements SlingHttpServletRequest {
+
+ private ResourceResolver resolver;
+
+ /**
+ * Constructor
+ *
+ * @param resolver resource resolver
+ */
+ public GroovyConsoleRequest(ResourceResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ @Override
+ public String getAuthType() {
+ return null;
+ }
+
+ @Override
+ public String getContextPath() {
+ return null;
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ return null;
+ }
+
+ @Override
+ public long getDateHeader(String arg0) {
+ return 0;
+ }
+
+ @Override
+ public String getHeader(String arg0) {
+ return null;
+ }
+
+ @Override
+ public Enumeration getHeaderNames() {
+ return null;
+ }
+
+ @Override
+ public Enumeration getHeaders(String arg0) {
+ return null;
+ }
+
+ @Override
+ public int getIntHeader(String arg0) {
+ return 0;
+ }
+
+ @Override
+ public String getMethod() {
+ return null;
+ }
+
+ @Override
+ public String getPathInfo() {
+ return null;
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return null;
+ }
+
+ @Override
+ public String getQueryString() {
+ return null;
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return null;
+ }
+
+ @Override
+ public String getRequestURI() {
+ return null;
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return null;
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ return null;
+ }
+
+ @Override
+ public String getServletPath() {
+ return null;
+ }
+
+ @Override
+ public HttpSession getSession() {
+ return null;
+ }
+
+ @Override
+ public HttpSession getSession(boolean arg0) {
+ return null;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromUrl() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid() {
+ return false;
+ }
+
+ @Override
+ public boolean isUserInRole(String arg0) {
+ return false;
+ }
+
+ @Override
+ public Object getAttribute(String arg0) {
+ return null;
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return null;
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return null;
+ }
+
+ @Override
+ public int getContentLength() {
+ return 0;
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getLocalAddr() {
+ return null;
+ }
+
+ @Override
+ public String getLocalName() {
+ return null;
+ }
+
+ @Override
+ public int getLocalPort() {
+ return 0;
+ }
+
+ @Override
+ public Locale getLocale() {
+ return null;
+ }
+
+ @Override
+ public Enumeration getLocales() {
+ return null;
+ }
+
+ @Override
+ public String getParameter(String arg0) {
+ return null;
+ }
+
+ @Override
+ public Map getParameterMap() {
+ return null;
+ }
+
+ @Override
+ public Enumeration getParameterNames() {
+ return null;
+ }
+
+ @Override
+ public String[] getParameterValues(String arg0) {
+ return null;
+ }
+
+ @Override
+ public String getProtocol() {
+ return null;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getRealPath(String arg0) {
+ return null;
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return null;
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return null;
+ }
+
+ @Override
+ public int getRemotePort() {
+ return 0;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String arg0) {
+ return null;
+ }
+
+ @Override
+ public String getScheme() {
+ return null;
+ }
+
+ @Override
+ public String getServerName() {
+ return null;
+ }
+
+ @Override
+ public int getServerPort() {
+ return 0;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public void removeAttribute(String arg0) {}
+
+ @Override
+ public void setAttribute(String arg0, Object arg1) {}
+
+ @Override
+ public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException {}
+
+ @Override
+ public AdapterType adaptTo(Class arg0) {
+ return null;
+ }
+
+ @Override
+ public Cookie getCookie(String arg0) {
+ return null;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(Resource arg0) {
+ return null;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String arg0, RequestDispatcherOptions arg1) {
+ return null;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(Resource arg0, RequestDispatcherOptions arg1) {
+ return null;
+ }
+
+ @Override
+ public RequestParameter getRequestParameter(String arg0) {
+ return null;
+ }
+
+ @Override
+ public List getRequestParameterList() {
+ return null;
+ }
+
+ @Override
+ public RequestParameterMap getRequestParameterMap() {
+ return null;
+ }
+
+ @Override
+ public RequestParameter[] getRequestParameters(String arg0) {
+ return null;
+ }
+
+ @Override
+ public RequestPathInfo getRequestPathInfo() {
+ return null;
+ }
+
+ @Override
+ public RequestProgressTracker getRequestProgressTracker() {
+ return null;
+ }
+
+ @Override
+ public Resource getResource() {
+ return null;
+ }
+
+ @Override
+ public ResourceBundle getResourceBundle(Locale arg0) {
+ return null;
+ }
+
+ @Override
+ public ResourceBundle getResourceBundle(String arg0, Locale arg1) {
+ return null;
+ }
+
+ @Override
+ public ResourceResolver getResourceResolver() {
+ return resolver;
+ }
+
+ @Override
+ public String getResponseContentType() {
+ return null;
+ }
+
+ @Override
+ public Enumeration getResponseContentTypes() {
+ return null;
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/service/HistoryEntryImpl.java b/core/src/main/java/de/valtech/aecu/core/service/HistoryEntryImpl.java
new file mode 100644
index 00000000..815b078f
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/service/HistoryEntryImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import de.valtech.aecu.service.ExecutionResult;
+import de.valtech.aecu.service.HistoryEntry;
+
+/**
+ * Implementation of history entry.
+ *
+ * @author Roland Gruber
+ */
+public class HistoryEntryImpl implements HistoryEntry {
+
+ private STATE state;
+ private String path;
+ private Date start;
+ private Date end;
+ private List singleResults = new ArrayList<>();
+
+ @Override
+ public Date getStart() {
+ return start;
+ }
+
+ @Override
+ public Date getEnd() {
+ return end;
+ }
+
+ @Override
+ public List getSingleResults() {
+ return singleResults;
+ }
+
+ @Override
+ public STATE getState() {
+ return state;
+ }
+
+ @Override
+ public RESULT getResult() {
+ if (singleResults.isEmpty()) {
+ return RESULT.UNKNOWN;
+ }
+ RESULT result = RESULT.SUCCESS;
+ for (ExecutionResult singleResult : singleResults) {
+ if (!singleResult.isSuccess()) {
+ result = RESULT.FAILURE;
+ break;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public String getRepositoryPath() {
+ return path;
+ }
+
+ /**
+ * Sets the start date.
+ *
+ * @param start start date
+ */
+ public void setStart(Date start) {
+ this.start = start;
+ }
+
+ /**
+ * Sets the end date.
+ *
+ * @param end end date
+ */
+ public void setEnd(Date end) {
+ this.end = end;
+ }
+
+ /**
+ * Sets the node path.
+ *
+ * @param path node path
+ */
+ public void setRepositoryPath(String path) {
+ this.path = path;
+ }
+
+ public void setState(STATE state) {
+ this.state = state;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder output = new StringBuilder();
+ output.append("Path: " + getRepositoryPath() + "\n");
+ output.append("Start: " + getStart() + "\n");
+ output.append("End: " + getEnd() + "\n");
+ output.append("State: " + getState() + "\n");
+ output.append("Result: " + getResult() + "\n\n");
+ for (ExecutionResult singleResult : singleResults) {
+ output.append(singleResult.toString() + "\n");
+ }
+ return output.toString();
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/serviceuser/ServiceResourceResolverService.java b/core/src/main/java/de/valtech/aecu/core/serviceuser/ServiceResourceResolverService.java
new file mode 100644
index 00000000..f84c28ce
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/serviceuser/ServiceResourceResolverService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.serviceuser;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides the service resource resolver.
+ *
+ * @author Roland Gruber
+ */
+@Component(service = ServiceResourceResolverService.class)
+public class ServiceResourceResolverService {
+
+ private static final String SUBSERVICE_AECU = "aecu";
+ private static final String SUBSERVICE_AECU_CONTENT_MIGRATION = "aecu-content-migrator";
+
+ @Reference
+ ResourceResolverFactory resolverFactory;
+
+ /**
+ * Returns a resource resolver of the AECU service user.
+ *
+ * @return service resource resolver
+ * @throws LoginException error opening resource resolver
+ */
+ public ResourceResolver getServiceResourceResolver() throws LoginException {
+ final Map authenticationInfo = new HashMap<>();
+ authenticationInfo.put(ResourceResolverFactory.SUBSERVICE, SUBSERVICE_AECU);
+ return resolverFactory.getServiceResourceResolver(authenticationInfo);
+ }
+
+ /**
+ * Returns a resource resolver of the AECU content migrator user.
+ *
+ * @return service resource resolver
+ * @throws LoginException error opening resource resolver
+ */ // TODO: add /apps write rights!!!
+ public ResourceResolver getContentMigratorResourceResolver() throws LoginException {
+ final Map authenticationInfo = new HashMap<>();
+ authenticationInfo.put(ResourceResolverFactory.SUBSERVICE, SUBSERVICE_AECU_CONTENT_MIGRATION);
+ return resolverFactory.getServiceResourceResolver(authenticationInfo);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/servlets/BaseServlet.java b/core/src/main/java/de/valtech/aecu/core/servlets/BaseServlet.java
new file mode 100644
index 00000000..4be2c234
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/servlets/BaseServlet.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.servlets;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+
+/**
+ * @author Bryan Chavez
+ */
+public class BaseServlet extends SlingAllMethodsServlet {
+
+ private static final long serialVersionUID = -5240930544859160292L;
+
+ protected static final String ERROR_MESSAGE_INTERNAL_SERVER = "Internal Server Error";
+
+
+ protected void setNoCache(HttpServletResponse response) {
+ response.setHeader("Cache-control", "no-cache, no-store");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "-1");
+ }
+
+ protected void writeResult(SlingHttpServletResponse response, String json, int status) throws IOException {
+ response.setContentType("application/json");
+ response.setCharacterEncoding("utf-8");
+ response.setStatus(status);
+ response.getWriter().write(json);
+ }
+
+ protected void writeResult(SlingHttpServletResponse response, String json) throws IOException {
+ writeResult(response, json, HttpServletResponse.SC_OK);
+ }
+
+ protected void sendInternalServerError(SlingHttpServletResponse response) throws IOException {
+ writeResult(response, ERROR_MESSAGE_INTERNAL_SERVER, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+
+ protected boolean validateParameter(String param) {
+ return param != null && StringUtils.isNotEmpty(param);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/servlets/ExecutionServlet.java b/core/src/main/java/de/valtech/aecu/core/servlets/ExecutionServlet.java
new file mode 100644
index 00000000..9983cae5
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/servlets/ExecutionServlet.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.servlets;
+
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+
+import de.valtech.aecu.core.history.HistoryUtil;
+import de.valtech.aecu.service.AecuException;
+import de.valtech.aecu.service.AecuService;
+import de.valtech.aecu.service.ExecutionResult;
+import de.valtech.aecu.service.HistoryEntry;
+
+/**
+ * @author Bryan Chavez
+ */
+
+@Component(immediate = true, service = {Servlet.class}, property = {"sling.servlet.paths=/bin/public/valtech/aecu/execute",
+ "sling.servlet.extensions=json", "sling.servlet.methods=GET"})
+public class ExecutionServlet extends BaseServlet {
+
+ private static final long serialVersionUID = 1L;
+ private static final Logger LOG = LoggerFactory.getLogger(ExecutionServlet.class);
+
+ protected static final String ERROR_MESSAGE_MANDATORY =
+ "ExecutionServlet :: Make sure your are sending the correct parameters.";
+
+ @Reference
+ AecuService aecuService;
+
+
+ @Override
+ protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
+ throws ServletException, IOException {
+
+ this.setNoCache(response);
+
+ String historyEntryAction = request.getParameter("historyEntryAction");
+ String aecuScriptPath = request.getParameter("aecuScriptPath");
+ if (!this.validateParameter(aecuScriptPath) || !this.validateParameter(historyEntryAction)) {
+ writeResult(response, ERROR_MESSAGE_MANDATORY);
+ return;
+ }
+
+ try {
+ HistoryEntry historyEntry = this.getHistoryEntry(request, response, historyEntryAction);
+ ExecutionResult executionResult = aecuService.execute(aecuScriptPath);
+ aecuService.storeExecutionInHistory(historyEntry, executionResult);
+ this.finishHistoryEntry(historyEntry, historyEntryAction);
+ writeResult(response, this.prepareJson(executionResult.isSuccess(), historyEntry.getRepositoryPath()));
+
+ } catch (AecuException e) {
+ this.sendInternalServerError(response);
+ }
+
+ }
+
+ protected HistoryEntry getHistoryEntry(SlingHttpServletRequest request, SlingHttpServletResponse response,
+ String historyEntryAction) throws AecuException, IOException {
+
+ HistoryEntry historyEntry;
+
+ switch (historyEntryAction.toLowerCase()) {
+ case "use":
+ case "close":
+ // Used for "use" and "close"
+ String historyEntryPath = request.getParameter("historyEntryPath");
+ if (!this.validateParameter(historyEntryPath)) {
+ writeResult(response, ERROR_MESSAGE_MANDATORY);
+ return null;
+ }
+
+ ResourceResolver resolver = request.getResourceResolver();
+ HistoryUtil historyUtil = new HistoryUtil();
+ historyEntry = historyUtil.readHistoryEntry(resolver.getResource(historyEntryPath));
+ break;
+ default:
+ // Used for "single" and "create"
+ historyEntry = aecuService.createHistoryEntry();
+ break;
+ }
+
+ return historyEntry;
+ }
+
+ protected HistoryEntry finishHistoryEntry(HistoryEntry historyEntry, String historyEntryAction) throws AecuException {
+
+ switch (historyEntryAction.toLowerCase()) {
+ case "single":
+ case "close":
+ // Used for "single" and "close"
+ aecuService.finishHistoryEntry(historyEntry);
+ break;
+ }
+
+ return historyEntry;
+
+ }
+
+ /**
+ * This method builds the JSON String for the response. Eg: {"success":
+ * true,"historyEntryPath":"/var/aecu/2018/6/13/152892696338961314"}
+ *
+ * @param status success or fail
+ * @param historyEntryPath path to history node
+ * @return json String
+ */
+ protected String prepareJson(boolean status, String historyEntryPath) {
+ JsonObject json = new JsonObject();
+ json.addProperty("success", status);
+ json.addProperty("historyEntryPath", historyEntryPath);
+ return json.toString();
+ }
+
+}
diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/SimpleContentUpdateTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/SimpleContentUpdateTest.java
new file mode 100644
index 00000000..95173f52
--- /dev/null
+++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/SimpleContentUpdateTest.java
@@ -0,0 +1,28 @@
+package de.valtech.aecu.core.groovy.console.bindings;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SimpleContentUpdateTest {
+
+ @Mock
+ private ResourceResolver resourceResolverMock;
+
+ private SimpleContentUpdate simpleContentUpdate;
+
+
+ @Before
+ public void setUp() throws Exception {
+ simpleContentUpdate = new SimpleContentUpdate(resourceResolverMock);
+ }
+
+ @Test
+ public void toDo() {
+ // TODO!!
+ }
+}
diff --git a/core/src/test/java/de/valtech/aecu/core/history/HistoryUtilTest.java b/core/src/test/java/de/valtech/aecu/core/history/HistoryUtilTest.java
new file mode 100644
index 00000000..a32d1e25
--- /dev/null
+++ b/core/src/test/java/de/valtech/aecu/core/history/HistoryUtilTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.history;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.jcr.resource.api.JcrResourceConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import de.valtech.aecu.service.AecuException;
+
+/**
+ * Tests HistoryUtil
+ *
+ * @author Roland Gruber
+ */
+@RunWith(value = MockitoJUnitRunner.class)
+public class HistoryUtilTest {
+
+ @Spy
+ private HistoryUtil historyUtil;
+
+ @Mock
+ private ResourceResolver resolver;
+
+ @Test
+ public void createPath_Existing() throws AecuException {
+ String path = "/var/aecu/2018/5";
+ when(resolver.getResource(path)).thenReturn(mock(Resource.class));
+
+ historyUtil.createPath(path, resolver, JcrResourceConstants.NT_SLING_FOLDER);
+
+ verify(historyUtil, times(1)).createPath(anyString(), eq(resolver), eq(JcrResourceConstants.NT_SLING_FOLDER));
+ }
+
+ @Test
+ public void createPath_NotExisting() throws AecuException, PersistenceException {
+ String path = "/var/aecu/2018/5";
+ when(resolver.getResource("/var/aecu/2018")).thenReturn(mock(Resource.class));
+
+ historyUtil.createPath(path, resolver, JcrResourceConstants.NT_SLING_FOLDER);
+
+ verify(historyUtil, times(1)).createPath(anyString(), eq(resolver), eq(JcrResourceConstants.NT_SLING_FOLDER));
+ verify(resolver, times(1)).create(any(Resource.class), eq("5"), any());
+ }
+
+}
diff --git a/core/src/test/java/de/valtech/aecu/core/service/AecuServiceImplTest.java b/core/src/test/java/de/valtech/aecu/core/service/AecuServiceImplTest.java
new file mode 100644
index 00000000..be233116
--- /dev/null
+++ b/core/src/test/java/de/valtech/aecu/core/service/AecuServiceImplTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2018 Valtech GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package de.valtech.aecu.core.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.settings.SlingSettingsService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Tests AecuServiceImpl
+ *
+ * @author Roland Gruber
+ */
+@RunWith(value = MockitoJUnitRunner.class)
+public class AecuServiceImplTest {
+
+ @InjectMocks
+ @Spy
+ private AecuServiceImpl service;
+
+ @Mock
+ private SlingSettingsService settingsService;
+
+ @Mock
+ private ResourceResolver resolver;
+
+ @Before
+ public void setup() {
+ Set runModes = new HashSet<>();
+ runModes.add("author");
+ runModes.add("test");
+ runModes.add("test2");
+ runModes.add("test3");
+ when(settingsService.getRunModes()).thenReturn(runModes);
+ }
+
+ @Test
+ public void matchesRunmodes_noMode() {
+ assertTrue(service.matchesRunmodes("name"));
+ }
+
+ @Test
+ public void matchesRunmodes_nonMatching() {
+ assertFalse(service.matchesRunmodes("name.publish"));
+ }
+
+ @Test
+ public void matchesRunmodes_matching() {
+ assertTrue(service.matchesRunmodes("name.author"));
+ assertTrue(service.matchesRunmodes("name.test"));
+ }
+
+ @Test
+ public void matchesRunmodes_matchingMulti() {
+ assertTrue(service.matchesRunmodes("name.author.test"));
+ assertTrue(service.matchesRunmodes("name.author.test;testNO"));
+ assertTrue(service.matchesRunmodes("name.authorNO.test;test2.author"));
+ }
+
+ @Test
+ public void matchesRunmodes_nonMatchingMulti() {
+ assertFalse(service.matchesRunmodes("name.author.testNO"));
+ assertFalse(service.matchesRunmodes("name.author.testNO;testNO"));
+ assertFalse(service.matchesRunmodes("name.author.testNO;test2NO.author"));
+ }
+
+ @Test
+ public void isValidScriptName_ok() {
+ assertTrue(service.isValidScriptName("test.groovy"));
+ }
+
+ @Test
+ public void isValidScriptName_invalidExtension() {
+ assertFalse(service.isValidScriptName("test.txt"));
+ }
+
+ @Test
+ public void isValidScriptName_fallback() {
+ assertFalse(service.isValidScriptName("test.fallback.groovy"));
+ }
+
+ @Test
+ public void getFallbackScript_Exists() {
+ when(resolver.getResource("/path/to/script.fallback.groovy")).thenReturn(mock(Resource.class));
+
+ assertEquals("/path/to/script.fallback.groovy", service.getFallbackScript(resolver, "/path/to/script.always.groovy"));
+ assertEquals("/path/to/script.fallback.groovy", service.getFallbackScript(resolver, "/path/to/script.groovy"));
+ }
+
+ @Test
+ public void getFallbackScript_NotExists() {
+ assertNull(service.getFallbackScript(resolver, "/path/to/script.always.groovy"));
+ assertNull(service.getFallbackScript(resolver, "/path/to/script.groovy"));
+ }
+
+ @Test
+ public void getFallbackScript_Fallback() {
+ verify(resolver, never()).getResource("/path/to/script.fallback.groovy");
+
+ assertNull(service.getFallbackScript(resolver, "/path/to/script.fallback.groovy"));
+ }
+
+}
diff --git a/docs/developers.md b/docs/developers.md
new file mode 100644
index 00000000..15247c1f
--- /dev/null
+++ b/docs/developers.md
@@ -0,0 +1,27 @@
+# AEM Server Setup
+
+By default AEM is expected to listen on localhost on port 5702. This setting can be overridden by adding parameters:
+* -Daem.port=4502
+* -Daem.host=localhost
+
+You need AEM 6.3 with service pack 2.
+
+# Build and Deploy
+
+To build and deploy run this in the base (aem-easy-content-upgrade) or ui.apps/examples folder:
+
+```bash
+mvn clean install -PautoInstallPackage
+```
+
+In case you want to deploy core only you can use this command in core folder:
+
+```bash
+mvn clean install -PautoInstallBundle
+```
+
+
+# Code Formatting
+
+Please use our standard code formatters for [Eclipse](formatter/eclipse-aecu.xml)
+and [IntelliJ](formatter/intellij-aecu.xml).
diff --git a/docs/formatter/eclipse-aecu.xml b/docs/formatter/eclipse-aecu.xml
new file mode 100644
index 00000000..a7317795
--- /dev/null
+++ b/docs/formatter/eclipse-aecu.xml
@@ -0,0 +1,318 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/formatter/intellij-aecu.xml b/docs/formatter/intellij-aecu.xml
new file mode 100644
index 00000000..49df1d87
--- /dev/null
+++ b/docs/formatter/intellij-aecu.xml
@@ -0,0 +1,476 @@
+
+
+
+
");
+ }
+
+ if ($("#imports").length != 0 && $("#bindings ul li.aecu-import").length == 0) {
+ // TODO url to some public available javadoc
+ $("#imports ul").append("