diff --git a/.travis.yml b/.travis.yml
index b6d168a9..dc65957c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ addons:
secure: v0L2WG1NtMt4bXGRipEY9X23Glmz5ZldVCw7LaIUbUwKOB2AVghhUttZAsI0FBntsF0jRHJDk3L/4g45UZWSZOMRMl3lPm8NTHZLC4TmeDb00H/JXirh5ZghX+9KDiL1X8IF5xX6HRU8WO2XFnPhNR9kdh6JKSBwM2wEXADl8WNWLP7I8hB4hQ+gBdil1zOcT5vnyhc29zSU5EuBp4uZanlNhjry12tIOp/pZpdDO/pzF6m8T0DvGsgvlkZNWF16a0kazsLVWOYZ7QmjM8YDt6jVCNVQ+cImY/YPoq42OdvbIUfTazxiMS+P68Wel7ulH9FqzfHmz+SYPxQ1TfAQ2ltZO05ubQ5C2TScC+mipUkfbgqRr9RJUPkret+nUJ1WaQdf6+W3oZ6pTvn27s+XZFlWTbj6CcmgTPN8cCL5D+A9huvDOD2wWHkP7cquGSIuqh+Nc33K/EWR/BhY4ec2Xk/bjfoPTIE4tOuVyIZQLf64RxL0sRyitj4dZz+aRqhKiCaVKRtELqh8JoteYon+QPw/MF4UOalq+/QAFWvmgMeCouGxLwh2EPlK+3kXFO5tebRv67zLVesfVFCZgKWshTvUJ02kxKqy8HpocMTmoaIsRUHi2E5ucpbMxzKuJitpetrOHIjMNJQhfi0oW17zeB8i/oDwMIdNUGXPAU2MF1g=
script:
- - mvn test -B
+ - mvn test javadoc:javadoc -B
- sonar-scanner
cache:
diff --git a/HISTORY b/HISTORY
index 708c15d7..7a8b0940 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,3 +1,8 @@
+2019-01-16 1.8.0
+ - Groovy Console binding:
+ - Path renaming via regex
+ - Replace substrings in properties
+
2018-12-20 1.7.0
- Full-text search possibility with Omnisearch (magnifying glass in header)
- Groovy Console binding:
diff --git a/LICENSE b/LICENSE
index 99873d35..14c1d6a5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2018 Valtech GmbH
+Copyright (c) 2018 - 2019 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
index f4415601..86c545ec 100644
--- a/Readme.md
+++ b/Readme.md
@@ -157,6 +157,18 @@ You can click on any run to see the full details. This will show the status for
+## Search History
+
+AECU maintains a full-text search index for the history entries. You can search for script names and their output.
+
+Simply click on the magnifying glass in header to open the search bar:
+
+
+
+Now you can enter a search term and will see the runs that contain this text. Click on the link to see the full history entry.
+
+
+
# Extension to Groovy Console
@@ -337,6 +349,25 @@ println aecu.contentUpgradeBuilder()
.run()
```
+### Replace Property Content
+You can replace the content of String properties. This also supports multi-value properties.
+
+* doReplaceValueInAllProperties(String oldValue, String newValue): replaces the substring "oldValue" with "newValue". Applies to all String properties
+* doReplaceValueInProperties(String oldValue, String newValue, String[] propertyNames): replaces the substring "oldValue" with "newValue". Applies to all specified String properties
+* doReplaceValueInAllPropertiesRegex(String searchRegex, String replacement): checks if the property value(s) match the search pattern and replaces it with "replacement". Applies to all String properties. You can use group references such as $1 (hint: "$" needs to be escaped with "\" in Groovy).
+* doReplaceValueInPropertiesRegex(String searchRegex, String replacement, String[] propertyNames): checks if the property value(s) match the search pattern and replaces it with "replacement". Applies to specified String properties. You can use group references such as $1 (hint: "$" needs to be escaped with "\" in Groovy).
+
+```java
+println aecu.contentUpgradeBuilder()
+ .forChildResourcesOf("/content/we-retail/ca/en")
+ .filterByNodeName("jcr:content")
+ .doReplaceValueInAllProperties("old", "new")
+ .doReplaceValueInProperties("old", "new", (String[]) ["propertyName1", "propertyName2"])
+ .doReplaceValueInAllPropertiesRegex("/content/([^/]+)/(.*)", "/content/newSub/\$2")
+ .doReplaceValueInPropertiesRegex("/content/([^/]+)/(.*)", "/content/newSub/\$2", (String[]) ["propertyName1", "propertyName2"])
+ .run()
+```
+
### Copy and Move Nodes
The matching nodes can be copied/moved to a new location. You can use ".." if you want to step back in path.
@@ -344,6 +375,7 @@ The matching nodes can be copied/moved to a new location. You can use ".." if yo
* doRename(String newName): renames the resource to the given name
* doCopyResourceToRelativePath(String relativePath): copies the node to the given target path
* doMoveResourceToRelativePath(String relativePath): moves the node to the given target path
+* doMoveResourceToPathRegex(String matchPattern, String replacementExpr): moves a resource if its path matches the pattern to the target path obtained by applying the replacement expression. You can use group references such as $1 (hint: "$" needs to be escaped with "\" in Groovy).
```java
println aecu.contentUpgradeBuilder()
@@ -353,6 +385,7 @@ println aecu.contentUpgradeBuilder()
.doCopyResourceToRelativePath("subNode")
.doCopyResourceToRelativePath("../subNode")
.doMoveResourceToRelativePath("subNode")
+ .doMoveResourceToPathRegex("/content/we-retail/(\\w+)/(\\w+)/(\\w+)", "/content/somewhere/\$1/and/\$2")
.run()
```
diff --git a/api/pom.xml b/api/pom.xml
index 86755bc6..b1db2414 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -4,7 +4,7 @@
de.valtech.aecu
aecu
- 1.7.0
+ 1.8.0
aecu.api
diff --git a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java
index 88358b2b..ab55575a 100644
--- a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java
+++ b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/ContentUpgrade.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Valtech GmbH
+ * Copyright 2018 - 2019 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,
@@ -30,6 +30,7 @@
* This class provides the builder methods to perform a content upgrade.
*
* @author Roxana Muresan
+ * @author Roland Gruber
*/
@ProviderType
public interface ContentUpgrade {
@@ -215,6 +216,48 @@ public interface ContentUpgrade {
*/
ContentUpgrade doReplaceValuesOfMultiValueProperty(String name, String[] oldValues, String[] newValues);
+ /**
+ * Replaces a substring in all properties of the matching resource. Only applies to String
+ * properties.
+ *
+ * @param oldValue old value
+ * @param newValue new value
+ * @return upgrade object
+ */
+ ContentUpgrade doReplaceValueInAllProperties(String oldValue, String newValue);
+
+ /**
+ * Replaces a substring in specific properties of the matching resource. Only applies to String
+ * properties.
+ *
+ * @param oldValue old value
+ * @param newValue new value
+ * @param propertyNames property names that should be checked
+ * @return upgrade object
+ */
+ ContentUpgrade doReplaceValueInProperties(String oldValue, String newValue, String[] propertyNames);
+
+ /**
+ * Replaces a substring in all properties of the matching resource using a regular expression.
+ * Only applies to String properties.
+ *
+ * @param searchRegex regex to match old value
+ * @param replacement new value, may contain matcher groups (e.g. $1)
+ * @return upgrade object
+ */
+ ContentUpgrade doReplaceValueInAllPropertiesRegex(String searchRegex, String replacement);
+
+ /**
+ * Replaces a substring in specific properties of the matching resource using a regular
+ * expression. Only applies to String properties.
+ *
+ * @param searchRegex regex to match old value
+ * @param replacement new value, may contain matcher groups (e.g. $1)
+ * @param propertyNames property names that should be checked
+ * @return upgrade object
+ */
+ ContentUpgrade doReplaceValueInPropertiesRegex(String searchRegex, String replacement, String[] propertyNames);
+
/**
* Renames a resource to the given name.
*
@@ -239,6 +282,17 @@ public interface ContentUpgrade {
*/
ContentUpgrade doMoveResourceToRelativePath(String relativePath);
+ /**
+ * Moves a resource if its path matches the pattern to the path obtained by applying the
+ * replacement expression
+ *
+ * @param matchPattern regular expression for matching the resource path
+ * @param targetPathExpr expression to calculate the target path, can contain matched group
+ * references $1, $2, ...
+ * @return upgrade object
+ */
+ ContentUpgrade doMoveResourceToPathRegex(String matchPattern, String targetPathExpr);
+
/**
* Deletes the resource.
*
diff --git a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java
index e8c8c739..29922d14 100644
--- a/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java
+++ b/api/src/main/java/de/valtech/aecu/api/groovy/console/bindings/package-info.java
@@ -22,7 +22,7 @@
*
* @author Roxana Muresan
*/
-@Version("2.0.1")
+@Version("2.1.0")
package de.valtech.aecu.api.groovy.console.bindings;
import org.osgi.annotation.versioning.Version;
diff --git a/bundle/pom.xml b/bundle/pom.xml
index 664bdd5d..ce2c48db 100644
--- a/bundle/pom.xml
+++ b/bundle/pom.xml
@@ -5,7 +5,7 @@
de.valtech.aecu
aecu
- 1.7.0
+ 1.8.0
aecu.bundle
diff --git a/core/pom.xml b/core/pom.xml
index eb640872..b82194c4 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -4,7 +4,7 @@
de.valtech.aecu
aecu
- 1.7.0
+ 1.8.0
aecu.core
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/AddPageTagsAction.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/AddPageTagsAction.java
index 49d4d30b..d52c0735 100644
--- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/AddPageTagsAction.java
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/AddPageTagsAction.java
@@ -48,6 +48,7 @@ public class AddPageTagsAction implements Action {
* Constructor
*
* @param context binding context
+ * @param tags tag names
*/
public AddPageTagsAction(BindingContext context, String... tags) {
this.context = context;
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RemovePageTagsAction.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RemovePageTagsAction.java
index e0823814..6b4be019 100644
--- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RemovePageTagsAction.java
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RemovePageTagsAction.java
@@ -47,6 +47,7 @@ public class RemovePageTagsAction implements Action {
* Constructor
*
* @param context binding context
+ * @param tags tag names
*/
public RemovePageTagsAction(BindingContext context, String... tags) {
this.context = context;
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RenderPageAction.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RenderPageAction.java
index cb39a3c5..8eab7536 100644
--- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RenderPageAction.java
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/RenderPageAction.java
@@ -53,7 +53,10 @@ public class RenderPageAction implements Action {
/**
* Constructor
*
- * @param context binding context
+ * @param context binding context
+ * @param statusCode expected status code
+ * @param textPresent text that must be present
+ * @param textNotPresent text that must not be present
*/
public RenderPageAction(BindingContext context, int statusCode, String textPresent, String textNotPresent) {
this.context = context;
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/SetPageTagsAction.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/SetPageTagsAction.java
index 3b3eeef3..b75a734b 100644
--- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/SetPageTagsAction.java
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/page/SetPageTagsAction.java
@@ -45,6 +45,7 @@ public class SetPageTagsAction implements Action {
* Constructor
*
* @param context binding context
+ * @param tags tag names
*/
public SetPageTagsAction(BindingContext context, String... tags) {
this.context = context;
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToPathRegex.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToPathRegex.java
new file mode 100644
index 00000000..bd459269
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToPathRegex.java
@@ -0,0 +1,71 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+/**
+ * Action class for moving resources via regex
+ *
+ * @author Roxana Muresan
+ */
+public class MoveResourceToPathRegex implements Action {
+
+ private ResourceResolver resourceResolver;
+ private String matchPattern;
+ private String targetPathExpr;
+
+ /**
+ * Constructor
+ *
+ * @param matchPattern regex pattern
+ * @param targetPathExpr target regex
+ * @param resourceResolver resolver
+ */
+ public MoveResourceToPathRegex(@Nonnull String matchPattern, @Nonnull String targetPathExpr,
+ @Nonnull ResourceResolver resourceResolver) {
+ this.resourceResolver = resourceResolver;
+ this.matchPattern = matchPattern;
+ this.targetPathExpr = targetPathExpr;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ String resourcePath = resource.getPath();
+ if (resourcePath.matches(matchPattern)) {
+ String targetPath = resourcePath.replaceAll(matchPattern, targetPathExpr);
+ Resource destinationResource = resourceResolver.getResource(targetPath);
+
+ if (destinationResource != null) {
+ resourceResolver.move(resourcePath, targetPath);
+
+ return "Moved " + resourcePath + " to path " + targetPath;
+ }
+ return "WARNING: could not read move destination resource " + targetPath;
+ }
+ return "INFO: resource " + resourcePath + " does not match path regex " + matchPattern + ", skipping";
+ }
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValues.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValues.java
new file mode 100644
index 00000000..c486a3e6
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValues.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2019 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 java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.value.StringValue;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+
+import de.valtech.aecu.core.groovy.console.bindings.actions.Action;
+
+/**
+ * Replaces strings in resource properties.
+ *
+ * @author Roland Gruber
+ */
+public class ReplaceResourcePropertyValues implements Action {
+
+ private String oldValue;
+ private String newValue;
+ private List propertyNames;
+
+ /**
+ * Constructor
+ *
+ * @param oldValue old value
+ * @param newValue new value
+ * @param propertyNames property names to check
+ */
+ public ReplaceResourcePropertyValues(@Nonnull String oldValue, @Nonnull String newValue,
+ @Nonnull List propertyNames) {
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ this.propertyNames = propertyNames;
+ }
+
+ @Override
+ public String doAction(@Nonnull Resource resource) throws PersistenceException {
+ Node node = resource.adaptTo(Node.class);
+ if (node == null) {
+ return StringUtils.EMPTY;
+ }
+ boolean updated = false;
+ try {
+ PropertyIterator propertyIterator = node.getProperties();
+ while (propertyIterator.hasNext()) {
+ Property property = propertyIterator.nextProperty();
+ if (doChangeProperty(property)) {
+ if (property.isMultiple()) {
+ boolean propUpdated = updateMulti(property);
+ updated = updated || propUpdated;
+ } else {
+ boolean propUpdated = updateSingle(property);
+ updated = updated || propUpdated;
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new PersistenceException("Rename failed", e);
+ }
+ if (updated) {
+ return "Updated values from " + oldValue + " to " + newValue + " in " + resource.getPath();
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * Updates a single value property.
+ *
+ * @param property property
+ * @return property was updated
+ * @throws RepositoryException error setting property
+ */
+ private boolean updateSingle(Property property) throws RepositoryException {
+ if (!valueMatches(property.getString())) {
+ return false;
+ }
+ String newPropertyValue = getNewValue(property.getString());
+ if (property.getString().equals(newPropertyValue)) {
+ return false;
+ }
+ property.setValue(newPropertyValue);
+ return true;
+ }
+
+ /**
+ * Updates a multi value property.
+ *
+ * @param property property
+ * @return property was updated
+ * @throws RepositoryException error setting property
+ */
+ private boolean updateMulti(Property property) throws RepositoryException {
+ Value[] values = property.getValues();
+ boolean updated = false;
+ for (int i = 0; i < values.length; i++) {
+ Value value = values[i];
+ if (valueMatches(value.getString())) {
+ String newPropertyValue = getNewValue(value.getString());
+ if (value.getString().equals(newPropertyValue)) {
+ continue;
+ }
+ values[i] = new StringValue(newPropertyValue);
+ updated = true;
+ }
+ }
+ if (updated) {
+ property.setValue(values);
+ }
+ return updated;
+ }
+
+ /**
+ * Checks if the value matches the searched value.
+ *
+ * @param value content property value
+ * @return matches condition
+ */
+ protected boolean valueMatches(String value) {
+ return value.contains(oldValue);
+ }
+
+ /**
+ * Returns the new property value.
+ *
+ * @param propertyValue old property value
+ * @return new value
+ */
+ protected String getNewValue(String propertyValue) {
+ return propertyValue.replace(oldValue, newValue);
+ }
+
+ /**
+ * Checks if this property name should be updated.
+ *
+ * @param property property
+ * @return update
+ * @throws RepositoryException error reading property
+ */
+ private boolean doChangeProperty(Property property) throws RepositoryException {
+ if (property.getType() != PropertyType.STRING) {
+ return false;
+ }
+ String propertyName = property.getName();
+ return (propertyNames.isEmpty() || propertyNames.contains(propertyName));
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesRegex.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesRegex.java
new file mode 100644
index 00000000..dd2b9cf8
--- /dev/null
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesRegex.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 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 java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Replaces strings via regex in resource properties.
+ *
+ * @author Roland Gruber
+ */
+public class ReplaceResourcePropertyValuesRegex extends ReplaceResourcePropertyValues {
+
+ private String searchRegex;
+ private String replacement;
+ private Pattern searchPattern;
+
+ /**
+ * Constructor
+ *
+ * @param searchRegex search regex
+ * @param replacement replacement string with optional regex group wildcards
+ * @param propertyNames property names
+ */
+ public ReplaceResourcePropertyValuesRegex(String searchRegex, String replacement, List propertyNames) {
+ super(searchRegex, replacement, propertyNames);
+ this.searchRegex = searchRegex;
+ this.replacement = replacement;
+ this.searchPattern = Pattern.compile(searchRegex);
+ }
+
+ @Override
+ protected boolean valueMatches(String value) {
+ return searchPattern.matcher(value).find();
+ }
+
+ @Override
+ protected String getNewValue(String propertyValue) {
+ return propertyValue.replaceAll(searchRegex, replacement);
+ }
+
+}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/util/MockHttpServletResponse.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/util/MockHttpServletResponse.java
index 6eb6e04c..67117ef3 100644
--- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/util/MockHttpServletResponse.java
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/actions/util/MockHttpServletResponse.java
@@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
+import java.util.Collections;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
@@ -198,12 +199,12 @@ public String getHeader(String name) {
@Override
public Collection getHeaders(String name) {
- return null;
+ return Collections.EMPTY_LIST;
}
@Override
public Collection getHeaderNames() {
- return null;
+ return Collections.EMPTY_LIST;
}
}
diff --git a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java
index 591dfabc..1efd6865 100644
--- a/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java
+++ b/core/src/main/java/de/valtech/aecu/core/groovy/console/bindings/impl/ContentUpgradeImpl.java
@@ -1,7 +1,26 @@
+/*
+ * Copyright 2018 - 2019 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.impl;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -48,14 +67,23 @@
import de.valtech.aecu.core.groovy.console.bindings.actions.resource.CopyResourceToRelativePath;
import de.valtech.aecu.core.groovy.console.bindings.actions.resource.CustomAction;
import de.valtech.aecu.core.groovy.console.bindings.actions.resource.DeleteResource;
+import de.valtech.aecu.core.groovy.console.bindings.actions.resource.MoveResourceToPathRegex;
import de.valtech.aecu.core.groovy.console.bindings.actions.resource.MoveResourceToRelativePath;
import de.valtech.aecu.core.groovy.console.bindings.actions.resource.RenameResource;
+import de.valtech.aecu.core.groovy.console.bindings.actions.resource.ReplaceResourcePropertyValues;
+import de.valtech.aecu.core.groovy.console.bindings.actions.resource.ReplaceResourcePropertyValuesRegex;
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.ForQuery;
import de.valtech.aecu.core.groovy.console.bindings.traversers.ForResources;
import de.valtech.aecu.core.groovy.console.bindings.traversers.TraversData;
+/**
+ * Implements the content upgrade API.
+ *
+ * @author Roxana Muresan
+ * @author Roland Gruber
+ */
public class ContentUpgradeImpl implements ContentUpgrade {
private static Logger LOG = LoggerFactory.getLogger(ContentUpgrade.class);
@@ -238,6 +266,30 @@ public ContentUpgrade doReplaceValuesOfMultiValueProperty(@Nonnull String name,
return this;
}
+ @Override
+ public ContentUpgrade doReplaceValueInAllProperties(String oldValue, String newValue) {
+ actions.add(new ReplaceResourcePropertyValues(oldValue, newValue, Collections.emptyList()));
+ return this;
+ }
+
+ @Override
+ public ContentUpgrade doReplaceValueInProperties(String oldValue, String newValue, String[] propertyNames) {
+ actions.add(new ReplaceResourcePropertyValues(oldValue, newValue, Arrays.asList(propertyNames)));
+ return this;
+ }
+
+ @Override
+ public ContentUpgrade doReplaceValueInAllPropertiesRegex(String searchRegex, String replacement) {
+ actions.add(new ReplaceResourcePropertyValuesRegex(searchRegex, replacement, Collections.emptyList()));
+ return this;
+ }
+
+ @Override
+ public ContentUpgrade doReplaceValueInPropertiesRegex(String searchRegex, String replacement, String[] propertyNames) {
+ actions.add(new ReplaceResourcePropertyValuesRegex(searchRegex, replacement, Arrays.asList(propertyNames)));
+ return this;
+ }
+
@Override
public ContentUpgrade doRename(String newName) {
LOG.debug("doRename to {}", newName);
@@ -259,6 +311,13 @@ public ContentUpgrade doMoveResourceToRelativePath(@Nonnull String relativePath)
return this;
}
+ @Override
+ public ContentUpgrade doMoveResourceToPathRegex(@Nonnull String matchPattern, @Nonnull String targetPathExpr) {
+ LOG.debug("doMoveResourceToPathRegex resources matching {} to {}", matchPattern, targetPathExpr);
+ actions.add(new MoveResourceToPathRegex(matchPattern, targetPathExpr, context.getResolver()));
+ return this;
+ }
+
@Override
public ContentUpgrade doDeleteResource() {
LOG.debug("doDeleteResource");
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
index 16143087..5ab7329f 100644
--- a/core/src/main/java/de/valtech/aecu/core/history/HistoryUtil.java
+++ b/core/src/main/java/de/valtech/aecu/core/history/HistoryUtil.java
@@ -386,8 +386,8 @@ private void saveExecutionResultInHistory(ExecutionResult result, String path, R
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);
+ String parent = path.substring(0, path.lastIndexOf('/'));
+ String name = path.substring(path.lastIndexOf('/') + 1);
if (resolver.getResource(parent) == null) {
createPath(parent, resolver, primaryType);
}
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
index 95e23cdf..eb0cae79 100644
--- a/core/src/main/java/de/valtech/aecu/core/service/AecuServiceImpl.java
+++ b/core/src/main/java/de/valtech/aecu/core/service/AecuServiceImpl.java
@@ -137,7 +137,7 @@ public boolean matchesRunmodes(String name) {
return true;
}
Set runModes = slingSettings.getRunModes();
- String runModeString = name.substring(name.indexOf(".") + 1);
+ String runModeString = name.substring(name.indexOf('.') + 1);
String[] combinations = runModeString.split(";");
for (String combination : combinations) {
String[] modes = combination.split("\\.");
@@ -211,13 +211,13 @@ private ExecutionResult executeScript(ResourceResolver resolver, String path) {
* @return fallback script path
*/
protected String getFallbackScript(ResourceResolver resolver, String path) {
- String name = path.substring(path.lastIndexOf("/") + 1);
+ 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";
+ 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;
}
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
index aa89f4a0..58c5a116 100644
--- a/core/src/main/java/de/valtech/aecu/core/service/GroovyConsoleRequest.java
+++ b/core/src/main/java/de/valtech/aecu/core/service/GroovyConsoleRequest.java
@@ -23,6 +23,7 @@
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Collection;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
@@ -81,7 +82,7 @@ public String getContextPath() {
@Override
public Cookie[] getCookies() {
- return null;
+ return new Cookie[0];
}
@Override
@@ -266,7 +267,7 @@ public Enumeration getParameterNames() {
@Override
public String[] getParameterValues(String arg0) {
- return null;
+ return new String[0];
}
@Override
@@ -371,7 +372,7 @@ public RequestParameter getRequestParameter(String arg0) {
@Override
public List getRequestParameterList() {
- return null;
+ return Collections.EMPTY_LIST;
}
@Override
@@ -381,7 +382,7 @@ public RequestParameterMap getRequestParameterMap() {
@Override
public RequestParameter[] getRequestParameters(String arg0) {
- return null;
+ return new RequestParameter[0];
}
@Override
@@ -430,14 +431,18 @@ public boolean authenticate(HttpServletResponse response) throws IOException, Se
}
@Override
- public void login(String username, String password) throws ServletException {}
+ public void login(String username, String password) throws ServletException {
+ // ignore
+ }
@Override
- public void logout() throws ServletException {}
+ public void logout() throws ServletException {
+ // ignore
+ }
@Override
public Collection getParts() throws IOException, ServletException {
- return null;
+ return Collections.EMPTY_LIST;
}
@Override
@@ -451,12 +456,12 @@ public ServletContext getServletContext() {
}
@Override
- public AsyncContext startAsync() throws IllegalStateException {
+ public AsyncContext startAsync() {
return null;
}
@Override
- public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
+ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) {
return null;
}
diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToPathRegexTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToPathRegexTest.java
new file mode 100644
index 00000000..9d51f649
--- /dev/null
+++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/MoveResourceToPathRegexTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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 org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+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 static org.mockito.internal.verification.VerificationModeFactory.times;
+
+/**
+ * Tests MoveResourceToPathRegex
+ *
+ * @author Roxana Muresan
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class MoveResourceToPathRegexTest {
+
+ @Mock
+ private ResourceResolver resourceResolver;
+ @Mock
+ private Resource resource;
+
+
+ @Test
+ public void testDoAction_noMatch() {
+ MoveResourceToPathRegex underTest = createObjectUnderTest("/content/bla/(\\w+)/(\\w+)", "/content/abc/$1");
+ mockWithValues("/content/somewhere/else", "/doesnt/matter", true);
+
+ try {
+ String returnString = underTest.doAction(resource);
+ assertTrue(returnString.startsWith("INFO"));
+
+ verify(resourceResolver, never()).getResource(anyString());
+ verify(resourceResolver, never()).move(anyString(), anyString());
+
+ } catch (PersistenceException pe) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testDoAction_match_invalidDestination() {
+ MoveResourceToPathRegex underTest = createObjectUnderTest("/content/(\\w+)/mmm/(\\w+)/(\\w+)", "/content/ooo/$1/$2/i");
+ mockWithValues("/content/en/mmm/sub/resource", "/content/ooo/en/sub/i", false);
+
+ try {
+ String returnString = underTest.doAction(resource);
+ assertTrue(returnString.startsWith("WARN"));
+
+ verify(resourceResolver, times(1)).getResource(eq("/content/ooo/en/sub/i"));
+ verify(resourceResolver, never()).move(anyString(), anyString());
+
+ } catch (PersistenceException pe) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testDoAction_match_validDestination() {
+ MoveResourceToPathRegex underTest = createObjectUnderTest("/content/(\\w+)/mmm/(\\w+)/(\\w+)", "/content/ooo/$1/$2/i");
+ String resourcePath = "/content/en/mmm/sub/resource";
+ String targetPath = "/content/ooo/en/sub/i";
+ mockWithValues(resourcePath, targetPath, true);
+
+ try {
+ String returnString = underTest.doAction(resource);
+ assertTrue(returnString.startsWith("Moved"));
+
+ verify(resourceResolver, times(1)).getResource(eq(targetPath));
+ verify(resourceResolver, times(1)).move(resourcePath, targetPath);
+
+ } catch (PersistenceException pe) {
+ fail();
+ }
+ }
+
+ private MoveResourceToPathRegex createObjectUnderTest(String matchPattern, String replaceExpr) {
+ return new MoveResourceToPathRegex(matchPattern, replaceExpr, resourceResolver);
+ }
+
+ private void mockWithValues(String resourcePath, String targetPath, boolean destinationExists) {
+ when(resource.getPath()).thenReturn(resourcePath);
+ Resource destinationResource = destinationExists ? mock(Resource.class) : null;
+ when(resourceResolver.getResource(eq(targetPath))).thenReturn(destinationResource);
+ }
+}
diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesRegexTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesRegexTest.java
new file mode 100644
index 00000000..53faef88
--- /dev/null
+++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesRegexTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.value.StringValue;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Tests ReplaceResourcePropertyValuesRegex
+ *
+ * @author Roland Gruber
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ReplaceResourcePropertyValuesRegexTest {
+
+ private static final String PROP3 = "prop3";
+ private static final String PROP2 = "prop2";
+ private static final String PROP1 = "prop1";
+ private static final String NEW_VAL = "newVal";
+ private static final String OLD_VAL = "oldVal";
+ private static final String PATH = "/content/path";
+
+ @Mock
+ private Resource resource;
+
+ @Mock
+ private Node node;
+
+ @Mock
+ private PropertyIterator propertyIterator;
+
+ private Value val31 = new StringValue("val3_oldVal_suffix");
+ private Value val32 = new StringValue("val3_test1_suffix");
+
+ @Mock
+ private Property prop1;
+
+ @Mock
+ private Property prop2;
+
+ @Mock
+ private Property prop3;
+
+ @Mock
+ private Property prop4;
+
+ @Before
+ public void setup() throws RepositoryException {
+ when(resource.getPath()).thenReturn(PATH);
+ when(resource.adaptTo(Node.class)).thenReturn(node);
+ when(node.getProperties()).thenReturn(propertyIterator);
+ when(propertyIterator.hasNext()).thenReturn(true, true, true, true, false);
+ when(propertyIterator.nextProperty()).thenReturn(prop1, prop2, prop3, prop4);
+ when(prop1.getString()).thenReturn("val1_oldVal_suffix");
+ when(prop2.getString()).thenReturn("val2_test2_suffix");
+ when(prop3.isMultiple()).thenReturn(true);
+ when(prop3.getValues()).thenReturn(new Value[] {val31, val32});
+ when(prop1.getName()).thenReturn(PROP1);
+ when(prop2.getName()).thenReturn(PROP2);
+ when(prop3.getName()).thenReturn(PROP3);
+ when(prop1.getType()).thenReturn(PropertyType.STRING);
+ when(prop2.getType()).thenReturn(PropertyType.STRING);
+ when(prop3.getType()).thenReturn(PropertyType.STRING);
+ when(prop4.getType()).thenReturn(PropertyType.BOOLEAN);
+ }
+
+ @Test
+ public void valueMatches() {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValuesRegex(OLD_VAL, NEW_VAL, Arrays.asList());
+
+ assertTrue(action.valueMatches("oldVal"));
+ assertTrue(action.valueMatches("1oldVal"));
+ assertTrue(action.valueMatches("oldVal1"));
+ assertTrue(action.valueMatches("1oldVal1"));
+ assertFalse(action.valueMatches("1newVal1"));
+ }
+
+ @Test
+ public void getNewValue() {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValuesRegex(OLD_VAL, NEW_VAL, Arrays.asList());
+
+ assertEquals("newVal", action.getNewValue("oldVal"));
+ assertEquals("1newVal", action.getNewValue("1oldVal"));
+ assertEquals("newVal1", action.getNewValue("oldVal1"));
+ assertEquals("1newVal1", action.getNewValue("1oldVal1"));
+ assertEquals("1newVal11newVal1", action.getNewValue("1oldVal11oldVal1"));
+ }
+
+ @Test
+ public void getNewValue_matcherGroup() {
+ ReplaceResourcePropertyValues action =
+ new ReplaceResourcePropertyValuesRegex("(" + OLD_VAL + ")", NEW_VAL + "-$1", Arrays.asList());
+
+ assertEquals("newVal-oldVal", action.getNewValue("oldVal"));
+ assertEquals("newVal-oldVal#newVal-oldVal", action.getNewValue("oldVal#oldVal"));
+ }
+
+ @Test
+ public void doAction_allProperties() throws PersistenceException, ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValuesRegex(OLD_VAL, NEW_VAL, Arrays.asList());
+
+ String result = action.doAction(resource);
+
+ assertEquals("Updated values from " + OLD_VAL + " to " + NEW_VAL + " in " + PATH, result);
+ verify(prop1, times(1)).setValue("val1_newVal_suffix");
+ verify(prop2, never()).setValue(Mockito.anyString());
+ verify(prop3, times(1)).setValue(Mockito.any(Value[].class));
+ verify(prop4, never()).setValue(Mockito.anyString());
+ }
+
+ @Test
+ public void doAction_prop1Only() throws PersistenceException, ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValuesRegex(OLD_VAL, NEW_VAL, Arrays.asList(PROP1));
+
+ String result = action.doAction(resource);
+
+ assertEquals("Updated values from " + OLD_VAL + " to " + NEW_VAL + " in " + PATH, result);
+ verify(prop1, times(1)).setValue("val1_newVal_suffix");
+ verify(prop2, never()).setValue(Mockito.anyString());
+ verify(prop3, never()).setValue(Mockito.any(Value[].class));
+ verify(prop4, never()).setValue(Mockito.anyString());
+ }
+
+ @Test
+ public void doAction_noMatch() throws PersistenceException, ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValuesRegex("nomatch", NEW_VAL, Arrays.asList(PROP1));
+
+ String result = action.doAction(resource);
+
+ assertEquals("", result);
+ verify(prop1, never()).setValue("val1_newVal_suffix");
+ verify(prop2, never()).setValue(Mockito.anyString());
+ verify(prop3, never()).setValue(Mockito.any(Value[].class));
+ verify(prop4, never()).setValue(Mockito.anyString());
+ }
+
+ @Test
+ public void doAction_matchesButNoChange() throws PersistenceException {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValuesRegex("(oldVal)", "$1", Arrays.asList());
+
+ String result = action.doAction(resource);
+
+ assertEquals("", result);
+ }
+
+}
diff --git a/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesTest.java b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesTest.java
new file mode 100644
index 00000000..e02a74da
--- /dev/null
+++ b/core/src/test/java/de/valtech/aecu/core/groovy/console/bindings/actions/resource/ReplaceResourcePropertyValuesTest.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.groovy.console.bindings.actions.resource;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.value.StringValue;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Tests ReplaceResourcePropertyValues
+ *
+ * @author Roland Gruber
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ReplaceResourcePropertyValuesTest {
+
+ private static final String PROP3 = "prop3";
+ private static final String PROP2 = "prop2";
+ private static final String PROP1 = "prop1";
+ private static final String NEW_VAL = "newVal";
+ private static final String OLD_VAL = "oldVal";
+ private static final String PATH = "/content/path";
+
+ @Mock
+ private Resource resource;
+
+ @Mock
+ private Node node;
+
+ @Mock
+ private PropertyIterator propertyIterator;
+
+ private Value val31 = new StringValue("val3_oldVal_suffix");
+ private Value val32 = new StringValue("val3_test1_suffix");
+
+ @Mock
+ private Property prop1;
+
+ @Mock
+ private Property prop2;
+
+ @Mock
+ private Property prop3;
+
+ @Mock
+ private Property prop4;
+
+ @Before
+ public void setup() throws RepositoryException {
+ when(resource.getPath()).thenReturn(PATH);
+ when(resource.adaptTo(Node.class)).thenReturn(node);
+ when(node.getProperties()).thenReturn(propertyIterator);
+ when(propertyIterator.hasNext()).thenReturn(true, true, true, true, false);
+ when(propertyIterator.nextProperty()).thenReturn(prop1, prop2, prop3, prop4);
+ when(prop1.getString()).thenReturn("val1_oldVal_suffix");
+ when(prop2.getString()).thenReturn("val2_test2_suffix");
+ when(prop3.isMultiple()).thenReturn(true);
+ when(prop3.getValues()).thenReturn(new Value[] {val31, val32});
+ when(prop1.getName()).thenReturn(PROP1);
+ when(prop2.getName()).thenReturn(PROP2);
+ when(prop3.getName()).thenReturn(PROP3);
+ when(prop1.getType()).thenReturn(PropertyType.STRING);
+ when(prop2.getType()).thenReturn(PropertyType.STRING);
+ when(prop3.getType()).thenReturn(PropertyType.STRING);
+ when(prop4.getType()).thenReturn(PropertyType.BOOLEAN);
+ }
+
+ @Test
+ public void doAction_allProperties() throws PersistenceException, ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValues(OLD_VAL, NEW_VAL, Arrays.asList());
+
+ String result = action.doAction(resource);
+
+ assertEquals("Updated values from " + OLD_VAL + " to " + NEW_VAL + " in " + PATH, result);
+ verify(prop1, times(1)).setValue("val1_newVal_suffix");
+ verify(prop2, never()).setValue(Mockito.anyString());
+ verify(prop3, times(1)).setValue(Mockito.any(Value[].class));
+ verify(prop4, never()).setValue(Mockito.anyString());
+ }
+
+ @Test
+ public void doAction_prop1Only() throws PersistenceException, ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValues(OLD_VAL, NEW_VAL, Arrays.asList(PROP1));
+
+ String result = action.doAction(resource);
+
+ assertEquals("Updated values from " + OLD_VAL + " to " + NEW_VAL + " in " + PATH, result);
+ verify(prop1, times(1)).setValue("val1_newVal_suffix");
+ verify(prop2, never()).setValue(Mockito.anyString());
+ verify(prop3, never()).setValue(Mockito.any(Value[].class));
+ verify(prop4, never()).setValue(Mockito.anyString());
+ }
+
+ @Test
+ public void doAction_noMatch() throws PersistenceException, ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ ReplaceResourcePropertyValues action = new ReplaceResourcePropertyValues("nomatch", NEW_VAL, Arrays.asList(PROP1));
+
+ String result = action.doAction(resource);
+
+ assertEquals("", result);
+ verify(prop1, never()).setValue("val1_newVal_suffix");
+ verify(prop2, never()).setValue(Mockito.anyString());
+ verify(prop3, never()).setValue(Mockito.any(Value[].class));
+ verify(prop4, never()).setValue(Mockito.anyString());
+ }
+
+}
diff --git a/core/src/test/java/de/valtech/aecu/core/service/GroovyConsoleRequestTest.java b/core/src/test/java/de/valtech/aecu/core/service/GroovyConsoleRequestTest.java
index 22ae5aeb..60169fe5 100644
--- a/core/src/test/java/de/valtech/aecu/core/service/GroovyConsoleRequestTest.java
+++ b/core/src/test/java/de/valtech/aecu/core/service/GroovyConsoleRequestTest.java
@@ -78,7 +78,7 @@ public void setCharacterEncoding() throws UnsupportedEncodingException {
public void emptyMethods() throws IOException, ServletException {
assertNull(request.getAuthType());
assertNull(request.getContextPath());
- assertNull(request.getCookies());
+ assertEquals(0, request.getCookies().length);
assertEquals(0, request.getDateHeader(""));
assertNull(request.getHeader(""));
assertNull(request.getHeaderNames());
@@ -114,7 +114,7 @@ public void emptyMethods() throws IOException, ServletException {
assertNull(request.getParameter(""));
assertNull(request.getParameterMap());
assertNull(request.getParameterNames());
- assertNull(request.getParameterValues(""));
+ assertEquals(0, request.getParameterValues("").length);
assertNull(request.getProtocol());
assertNull(request.getReader());
assertNull(request.getRealPath(""));
@@ -132,9 +132,9 @@ public void emptyMethods() throws IOException, ServletException {
assertNull(request.getRequestDispatcher("", null));
assertNull(request.getRequestDispatcher((Resource) null, null));
assertNull(request.getRequestParameter(""));
- assertNull(request.getRequestParameterList());
+ assertEquals(0, request.getRequestParameterList().size());
assertNull(request.getRequestParameterMap());
- assertNull(request.getRequestParameters(""));
+ assertEquals(0, request.getRequestParameters("").length);
assertNull(request.getRequestPathInfo());
assertNull(request.getRequestProgressTracker());
assertNull(request.getResource());
@@ -145,7 +145,7 @@ public void emptyMethods() throws IOException, ServletException {
assertFalse(request.authenticate(null));
request.login(null, null);
request.logout();
- assertNull(request.getParts());
+ assertEquals(0, request.getParts().size());
assertNull(request.getPart(null));
assertNull(request.getServletContext());
assertNull(request.startAsync());
diff --git a/docs/images/fulltext1.png b/docs/images/fulltext1.png
new file mode 100644
index 00000000..be612201
Binary files /dev/null and b/docs/images/fulltext1.png differ
diff --git a/docs/images/fulltext2.png b/docs/images/fulltext2.png
new file mode 100644
index 00000000..6a2669bc
Binary files /dev/null and b/docs/images/fulltext2.png differ
diff --git a/examples/pom.xml b/examples/pom.xml
index 84033d87..b90825ec 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -5,7 +5,7 @@
de.valtech.aecu
aecu
- 1.7.0
+ 1.8.0
aecu.examples
diff --git a/examples/src/main/content/jcr_root/etc/groovyconsole/scripts/aecu/aecu-examples/project3/script1.groovy b/examples/src/main/content/jcr_root/etc/groovyconsole/scripts/aecu/aecu-examples/project3/script1.groovy
new file mode 100644
index 00000000..fd0f4d71
--- /dev/null
+++ b/examples/src/main/content/jcr_root/etc/groovyconsole/scripts/aecu/aecu-examples/project3/script1.groovy
@@ -0,0 +1,10 @@
+println aecu
+ .contentUpgradeBuilder()
+ // collectors
+ .forDescendantResourcesOf("/content/we-retail/ca/en/experience")
+ // filters
+ .filterByProperty("jcr:primaryType", "cq:Page")
+ // actions
+ .printPath()
+ .doMoveResourceToPathRegex("/content/we-retail/([\\w-]+)/([\\w-]+)/experience/([\\w-]+)", "/content/we-retail/it/it")
+ .dryRun()
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 013fce3f..1e3ac4f1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
de.valtech.aecu
aecu
pom
- 1.7.0
+ 1.8.0
AECU
AEM Easy COntent Upgrade
https://github.com/valtech/aem-easy-content-upgrade
diff --git a/sonar-project.properties b/sonar-project.properties
index 53f16932..7cee681f 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -2,7 +2,7 @@
sonar.projectKey=aecu
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
sonar.projectName=AEM Easy Content Upgrade
-sonar.projectVersion=1.6.1-SNAPSHOT
+sonar.projectVersion=1.7.1-SNAPSHOT
# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
diff --git a/ui.apps/pom.xml b/ui.apps/pom.xml
index b3463d92..f1716999 100644
--- a/ui.apps/pom.xml
+++ b/ui.apps/pom.xml
@@ -5,7 +5,7 @@
de.valtech.aecu
aecu
- 1.7.0
+ 1.8.0
aecu.ui.apps