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