parts, Node startParent, Session session, String target)
@@ -120,14 +125,34 @@ public void run(Config config, Node node, HopContext context) throws RepositoryE
}
final NewNodeDescriptor descriptor = resolvePathToNewNode(parent, newName, config.conflict, context);
- if (descriptor.getParent().hasNode(descriptor.getNewChildName())) {
+ if (descriptor.targetExists) {
return;
}
- final String absolutePath = descriptor.getParent().getPath() + '/' + descriptor.getNewChildName();
- context.info("Moving node from {} to {}", node.getPath(), absolutePath);
+ final Node effectiveParent = descriptor.getParent();
+ context.info("Moving node from {} to {}", node.getPath(), descriptor.absolutePath);
- node.getSession().move(node.getPath(), absolutePath);
+ final String nextSiblingName = getNextSiblingName(node, parent, descriptor.parent);
+ node.getSession().move(node.getPath(), descriptor.absolutePath);
+
+ if (nextSiblingName != null) {
+ effectiveParent.orderBefore(descriptor.newChildName, nextSiblingName);
+ }
+ }
+
+ private static String getNextSiblingName(Node node, Node oldParent, Node newParent) throws RepositoryException {
+ if (!StringUtils.equals(oldParent.getPath(), newParent.getPath())) {
+ return null;
+ }
+ String nextSibling = null;
+ final NodeIterator siblingIterator = oldParent.getNodes();
+ while (siblingIterator.hasNext()) {
+ if (StringUtils.equals(siblingIterator.nextNode().getPath(), node.getPath()) && siblingIterator.hasNext()) {
+ nextSibling = siblingIterator.nextNode().getName();
+ break;
+ }
+ }
+ return nextSibling;
}
@Nonnull
@@ -148,7 +173,22 @@ public static class NewNodeDescriptor {
private final Node parent;
private final String newChildName;
- private final boolean needsReplacing;
+ private final String absolutePath;
+ private final boolean targetExists;
+ private final Node nodeToRemove;
+
+ /**
+ * Removes the replaced node if conflict was set to FORCE.
+ *
+ * Usually required to be called before the action is set to execute
+ *
+ * @throws RepositoryException if the removal fails
+ */
+ public void removeReplacedNode() throws RepositoryException {
+ if (nodeToRemove != null) {
+ nodeToRemove.remove();
+ }
+ }
}
@AllArgsConstructor
diff --git a/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java b/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
index e7cdc40..9ded62c 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
@@ -33,7 +33,7 @@ public void run(Config config, Node node, HopContext context) throws RepositoryE
final Map resultVars = new HashMap<>();
final String statement = context.evaluateTemplate(config.query);
resultVars.put("query", statement);
- context.trace("Running JCR query: {}", statement);
+ context.trace("Running {} JCR query: {}", config.queryType, statement);
final QueryResult result = getQueryResult(config, node, context, statement);
final RowIterator rowIterator = result.getRows();
final String[] selectors = result.getSelectorNames();
@@ -71,7 +71,8 @@ private String getSelectorName(Config config, Logger context, String... selector
return selectorName;
}
- private QueryResult getQueryResult(Config config, Node node, HopContext context, String statement) throws RepositoryException {
+ private QueryResult getQueryResult(Config config, Node node, HopContext context, String statement)
+ throws RepositoryException, HopperException {
final QueryManager qm = node.getSession().getWorkspace().getQueryManager();
final Query query = qm.createQuery(statement, config.queryType);
if (config.limit > 0) {
@@ -86,7 +87,7 @@ private QueryResult getQueryResult(Config config, Node node, HopContext context,
if (value != null) {
query.bindValue(bindVar, context.getJcrFunctions().valueFromObject(value));
} else {
- context.error("Could not bind placeholder {} as there is no known variable for it", bindVar);
+ throw new HopperException("Could not bind placeholder " + bindVar + " as there is no known variable for it");
}
}
return query.execute();
diff --git a/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java b/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java
index 680cef0..750c892 100644
--- a/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java
+++ b/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java
@@ -48,6 +48,16 @@ public Script(HopConfig... hops) {
this(Arrays.asList(hops));
}
+ /**
+ * Create a script with the given hops and log level.
+ *
+ * @param logLevel the log level verbosity for printed messages
+ * @param hops the hops to configure for this script
+ */
+ public Script(LogLevel logLevel, HopConfig... hops) {
+ this(Arrays.asList(hops), logLevel);
+ }
+
/**
* Create a script with the given hops and log level.
*
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
new file mode 100644
index 0000000..5a64370
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
@@ -0,0 +1,84 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.LogLevel;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class ChildNodesTest {
+
+ public final AemContext context = new JcrOakAemContext();
+ private RunnerBuilder builder;
+ private Session session;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/content");
+ context.create().resource("/content/child-1");
+ context.create().resource("/content/child-2");
+ context.create().resource("/content/third-child");
+ context.create().resource("/content/other-child");
+
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new ChildNodes()).runHandler(mockRunHandler);
+ session = context.resourceResolver().adaptTo(Session.class);
+ }
+
+ @Test
+ public void iterate_all() throws RepositoryException, HopperException {
+ builder.build(new Script(LogLevel.DEBUG, new ChildNodes.Config())).run(session.getNode("/content"), true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-2 on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node third-child on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node other-child on /content", null, null);
+ }
+
+ @Test
+ public void iterate_wildcard() throws RepositoryException, HopperException {
+ builder.build(new Script(LogLevel.DEBUG, new ChildNodes.Config().withNamePattern("*"))).run(session.getNode("/content"), true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-2 on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node third-child on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node other-child on /content", null, null);
+ }
+
+ @Test
+ public void iterate_pattern() throws RepositoryException, HopperException {
+ builder.build(new Script(LogLevel.DEBUG, new ChildNodes.Config().withNamePattern("child-*"))).run(session.getNode("/content"), true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-2 on /content", null, null);
+ verify(mockRunHandler, never()).log(LogLevel.DEBUG, "Found child node third-child on /content", null, null);
+ verify(mockRunHandler, never()).log(LogLevel.DEBUG, "Found child node other-child on /content", null, null);
+ }
+
+ @Test
+ public void iterate_union() throws RepositoryException, HopperException {
+ builder
+ .build(new Script(LogLevel.DEBUG, new ChildNodes.Config().withNamePattern("child-* | third-child")))
+ .run(session.getNode("/content"), true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-2 on /content", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node third-child on /content", null, null);
+ verify(mockRunHandler, never()).log(LogLevel.DEBUG, "Found child node other-child on /content", null, null);
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java
new file mode 100644
index 0000000..abd2bdd
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java
@@ -0,0 +1,226 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static com.swisscom.aem.tools.testsupport.AemUtil.childNames;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.ConflictResolution;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class CopyNodeTest {
+
+ public final AemContext context = new JcrOakAemContext();
+ private RunnerBuilder builder;
+ private Session session;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/content", "prop1", 1, "prop2", "value2");
+ context.create().resource("/content/child-1", "jcr:primaryType", "cq:Page");
+ context.create().resource("/content/child-2");
+ context.create().resource("/content/third-child");
+ context.create().resource("/content/other-child");
+ context.create().resource("/existing");
+ context.create().resource("/existing/existing-child");
+
+ builder = Runner.builder().addHop(new CopyNode(), new CreateChildNode());
+ session = context.resourceResolver().adaptTo(Session.class);
+ }
+
+ @Test
+ public void copy_empty() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content2")
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/content2"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/content2").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/content2/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_inside() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-2/content2")
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/content/child-2/content2"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/content/child-2/content2").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/content/child-2/content2/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_root() {
+ assertThrows(HopperException.class, () -> {
+ builder.build(new Script(new CopyNode.Config().withNewName("test-child"))).run(session.getRootNode(), true);
+ });
+ }
+
+ @Test
+ public void copy_existing_ignore() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("existing")
+ .withConflict(ConflictResolution.IGNORE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(Collections.singletonList("existing-child"), childNames(resolver.getResource("/existing")));
+ }
+
+ @Test
+ public void copy_existing_force() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("existing")
+ .withConflict(ConflictResolution.FORCE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/existing"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/existing").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/existing/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_existing_throw() {
+ assertThrows(HopperException.class, () -> {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("existing")
+ .withConflict(ConflictResolution.THROW)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+ });
+ }
+
+ @Test
+ public void copy_existingInside_ignore() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-1")
+ .withConflict(ConflictResolution.IGNORE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(Collections.emptyList(), childNames(resolver.getResource("/content/child-1")));
+
+ assertEquals("cq:Page", session.getNode("/content/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_existingInside_force() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-1")
+ .withConflict(ConflictResolution.FORCE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/content/child-1"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/content/child-1").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/content/child-1/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_existingInside_throw() {
+ assertThrows(HopperException.class, () -> {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-1")
+ .withConflict(ConflictResolution.THROW)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+ });
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/CreateChildNodeTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/CreateChildNodeTest.java
new file mode 100644
index 0000000..3ea001b
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/CreateChildNodeTest.java
@@ -0,0 +1,116 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static com.swisscom.aem.tools.testsupport.AemUtil.childNames;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.ConflictResolution;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
+import java.util.Arrays;
+import java.util.Collections;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class CreateChildNodeTest {
+
+ public final AemContext context = new JcrOakAemContext();
+ private RunnerBuilder builder;
+ private Session session;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/content");
+ context.create().resource("/content/child");
+ context.create().resource("/content/child/one");
+
+ builder = Runner.builder().addHop(new CreateChildNode());
+ session = context.resourceResolver().adaptTo(Session.class);
+ }
+
+ @Test
+ public void create_nonexisting() throws RepositoryException, HopperException {
+ builder.build(new Script(new CreateChildNode.Config().withName("child-two"))).run(session.getNode("/content"), true);
+
+ assertEquals(Arrays.asList("child", "child-two"), childNames(context.resourceResolver().getResource("/content")));
+ assertEquals("nt:unstructured", session.getNode("/content/child-two").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void create_existing_abort() {
+ assertThrows(HopperException.class, () -> {
+ builder
+ .build(
+ new Script(
+ new CreateChildNode.Config().withName("child").withConflict(ConflictResolution.THROW),
+ new CreateChildNode.Config().withName("child2").withConflict(ConflictResolution.THROW)
+ )
+ )
+ .run(session.getNode("/content"), true);
+ });
+
+ assertEquals(Collections.singletonList("child"), childNames(context.resourceResolver().getResource("/content")));
+ }
+
+ @Test
+ public void create_existing_ignore_no_recurse() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CreateChildNode.Config()
+ .withName("child")
+ .withConflict(ConflictResolution.IGNORE)
+ .withRunOnExistingNode(false)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("../child3"))),
+ new CreateChildNode.Config().withName("child2").withConflict(ConflictResolution.THROW)
+ )
+ )
+ .run(session.getNode("/content"), true);
+ assertEquals(Arrays.asList("child", "child2"), childNames(context.resourceResolver().getResource("/content")));
+ }
+
+ @Test
+ public void create_existing_ignore_recurse() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CreateChildNode.Config()
+ .withName("child")
+ .withConflict(ConflictResolution.IGNORE)
+ .withRunOnExistingNode(true)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("../child3"))),
+ new CreateChildNode.Config().withName("child2").withConflict(ConflictResolution.THROW)
+ )
+ )
+ .run(session.getNode("/content"), true);
+ assertEquals(Arrays.asList("child", "child3", "child2"), childNames(context.resourceResolver().getResource("/content")));
+ }
+
+ @Test
+ public void create_existing_force() throws RepositoryException, HopperException {
+ assertEquals("nt:unstructured", session.getNode("/content/child").getPrimaryNodeType().getName());
+ builder
+ .build(
+ new Script(
+ new CreateChildNode.Config()
+ .withName("child")
+ .withPrimaryType("cq:Page")
+ .withConflict(ConflictResolution.FORCE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("../child3"))),
+ new CreateChildNode.Config().withName("child2").withConflict(ConflictResolution.THROW)
+ )
+ )
+ .run(session.getNode("/content"), true);
+ assertEquals(Arrays.asList("child", "child3", "child2"), childNames(context.resourceResolver().getResource("/content")));
+ assertEquals("cq:Page", session.getNode("/content/child").getPrimaryNodeType().getName());
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/DeclareTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/DeclareTest.java
new file mode 100644
index 0000000..daec83c
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/DeclareTest.java
@@ -0,0 +1,90 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrMockAemContext;
+import java.util.Arrays;
+import java.util.Collections;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class DeclareTest {
+
+ public final AemContext context = new JcrMockAemContext();
+ private RunnerBuilder builder;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() {
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new Declare(), new RunScript(), new Each()).runHandler(mockRunHandler);
+ }
+
+ @Test
+ public void declare_basic() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Declare.Config().withDeclarations(Collections.singletonMap("var1", "1")),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(var1)")
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("1");
+ }
+
+ @Test
+ public void declare_node() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Declare.Config().withDeclarations(Collections.singletonMap("node", "{'path': 1}")),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(node.path)")
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/");
+ }
+
+ @Test
+ public void declare_override() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Declare.Config().withDeclarations(Collections.singletonMap("var1", "1")),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(`outer var1 before: ${var1}`)"),
+ new Each.Config()
+ .withExpression("[1]")
+ .withAssumeNodes(false)
+ .withHops(
+ Arrays.asList(
+ new Declare.Config().withDeclarations(Collections.singletonMap("var1", "2")),
+ new RunScript.Config()
+ .withExtension("jexl")
+ .withCode("writer.print(`inner var1: ${var1}`)")
+ )
+ ),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(`outer var1 after: ${var1}`)")
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("outer var1 before: 1");
+ verify(mockRunHandler).print("inner var1: 2");
+ verify(mockRunHandler).print("outer var1 after: 1");
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/EachTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/EachTest.java
new file mode 100644
index 0000000..fc7bc09
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/EachTest.java
@@ -0,0 +1,152 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrMockAemContext;
+import java.util.Collections;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class EachTest {
+
+ public final AemContext context = new JcrMockAemContext();
+ private RunnerBuilder builder;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/content");
+ context.create().resource("/content/child-1");
+ context.create().resource("/content/child-2");
+ context.create().resource("/content/third-child");
+ context.create().resource("/content/other-child");
+
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new Each(), new RunScript()).runHandler(mockRunHandler);
+ }
+
+ @Test
+ public void basic() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("['en-gb', 'en-us', 'en-ie']")
+ .withIterator("lang")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config()
+ .withExtension("js")
+ .withCode(
+ "var items = lang.split('-');\nwriter.print(items[1].toUpperCase() + ': ' + items[0]);"
+ )
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("GB: en");
+ verify(mockRunHandler).print("US: en");
+ verify(mockRunHandler).print("IE: en");
+ }
+
+ @Test
+ public void primitive() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("[1, 2, 3, 4]")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(item*100);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("100");
+ verify(mockRunHandler).print("200");
+ verify(mockRunHandler).print("300");
+ verify(mockRunHandler).print("400");
+ }
+
+ @Test
+ public void node_string() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("[jcr:resolve(node, '/content/child-2'), jcr:resolve(node, '/content/third-child')]")
+ .withAssumeNodes(true)
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(node.path);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/content/child-2");
+ verify(mockRunHandler).print("/content/third-child");
+ }
+
+ @Test
+ public void node_refs() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("['/content', '/content/child-1', '/content/other-child']")
+ .withAssumeNodes(true)
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(node.path);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/content");
+ verify(mockRunHandler).print("/content/child-1");
+ verify(mockRunHandler).print("/content/other-child");
+ }
+
+ @Test
+ public void node_invalid() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("[1, 2]")
+ .withAssumeNodes(true)
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(node.path);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler, never()).print(any());
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/FilterNodeTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/FilterNodeTest.java
new file mode 100644
index 0000000..3bc20ca
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/FilterNodeTest.java
@@ -0,0 +1,77 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrMockAemContext;
+import java.util.Collections;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class FilterNodeTest {
+
+ public final AemContext context = new JcrMockAemContext();
+ private RunnerBuilder builder;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/child-1");
+ context.create().resource("/other-child");
+
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new FilterNode(), new RunScript()).runHandler(mockRunHandler).addUtil("str", StringUtils.class);
+ }
+
+ @Test
+ public void matches() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new FilterNode.Config()
+ .withExpression("str:startsWith(node.name, 'child-')")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(node.path)")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/child-1").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/child-1");
+ }
+
+ @Test
+ public void doesNotMatch() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new FilterNode.Config()
+ .withExpression("str:startsWith(node.name, 'child-')")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(node.path)")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/other-child").adaptTo(Node.class), true);
+
+ verify(mockRunHandler, never()).print(any());
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/MoveNodeTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/MoveNodeTest.java
new file mode 100644
index 0000000..60143c4
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/MoveNodeTest.java
@@ -0,0 +1,59 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static com.swisscom.aem.tools.testsupport.AemUtil.childNames;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
+import java.util.Arrays;
+import java.util.Collections;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class MoveNodeTest {
+
+ public final AemContext context = new JcrOakAemContext();
+ private RunnerBuilder builder;
+ private ResourceResolver resolver;
+ private Session session;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/content");
+ context.create().resource("/content/test");
+ context.create().resource("/content/test/child2");
+ context.create().resource("/content/test/child1");
+
+ builder = Runner.builder().addHop(new MoveNode());
+
+ resolver = context.resourceResolver();
+ session = resolver.adaptTo(Session.class);
+ }
+
+ @Test
+ public void move_sameParent() throws RepositoryException, HopperException {
+ assertEquals(Arrays.asList("child2", "child1"), childNames(resolver.getResource("/content/test")));
+
+ builder.build(new Script(new MoveNode.Config().withNewName("child3"))).run(session.getNode("/content/test/child2"), true);
+
+ assertEquals(Arrays.asList("child3", "child1"), childNames(resolver.getResource("/content/test")));
+ }
+
+ @Test
+ public void move_newParent() throws RepositoryException, HopperException {
+ builder.build(new Script(new MoveNode.Config().withNewName("../test"))).run(session.getNode("/content/test"), true);
+
+ assertEquals(Arrays.asList("child2", "child1"), childNames(resolver.getResource("/test")));
+ assertEquals(Collections.emptyList(), childNames(resolver.getResource("/content")));
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/NodeQueryTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/NodeQueryTest.java
new file mode 100644
index 0000000..34f4d40
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/NodeQueryTest.java
@@ -0,0 +1,295 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.startsWith;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.LogLevel;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
+import java.util.Collections;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class NodeQueryTest {
+
+ public final AemContext context = new JcrOakAemContext();
+ private RunnerBuilder builder;
+ private Session session;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() throws RepositoryException {
+ context.create().resource("/content", "jcr:primaryType", "sling:Folder");
+ context.create().resource("/content/child", "jcr:primaryType", "cq:Page");
+ context.create().resource("/content/child/jcr:content", "jcr:primaryType", "cq:PageContent", "pageId", 1);
+ context.create().resource("/content/child/one", "jcr:primaryType", "cq:Page");
+ context.create().resource("/content/child/one/jcr:content", "jcr:primaryType", "cq:PageContent", "pageId", 2);
+
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new NodeQuery(), new Declare(), new RunScript()).runHandler(mockRunHandler);
+ session = context.resourceResolver().adaptTo(Session.class);
+ session.save();
+ }
+
+ @Test
+ public void xpath() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config().withQueryType("xpath").withQuery("/jcr:root/content//element(*, cq:Page)")
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child for xpath query /jcr:root/content//e…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for xpath query /jcr:root/content//e…", null, null);
+ verify(mockRunHandler).log(LogLevel.INFO, "Processed 2 nodes from xpath query /jcr:root/content//element(*, cq:Page)", null, null);
+ }
+
+ @Test
+ public void sql2() throws HopperException, RepositoryException {
+ builder
+ .build(new Script(LogLevel.DEBUG, new NodeQuery.Config().withQueryType(Query.JCR_SQL2).withQuery("SELECT * FROM [cq:Page]")))
+ .run(session, true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.INFO, "Processed 2 nodes from JCR-SQL2 query SELECT * FROM [cq:Page]", null, null);
+ }
+
+ @Test
+ public void sql2_preparedPlaceholder() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new Declare.Config().withDeclarations(Collections.singletonMap("pageId", "1")),
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:PageContent] WHERE pageId = $pageId")
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler, never()).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/one/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 1 nodes from JCR-SQL2 query SELECT * FROM [cq:PageContent] WHERE pageId = $pageId",
+ null,
+ null
+ );
+ }
+
+ @Test
+ public void sql2_preparedPlaceholder_missing() {
+ assertThrows(HopperException.class, () ->
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:PageContent] WHERE pageId = $pageId")
+ )
+ )
+ .run(session, true)
+ );
+ }
+
+ @Test
+ public void sql2_templateExpression() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new Declare.Config().withDeclarations(Collections.singletonMap("pageId", "2")),
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:PageContent] WHERE pageId = ${pageId}")
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/one/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler, never()).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 1 nodes from JCR-SQL2 query SELECT * FROM [cq:PageContent] WHERE pageId = 2",
+ null,
+ null
+ );
+ }
+
+ @Test
+ public void sql2_implicitJoin() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery(
+ "SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)"
+ )
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(any(), startsWith("There are 2 selectors in the JCR-SQL2 query. "), any(), any());
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/one/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 2 nodes from JCR-SQL2 query SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)",
+ null,
+ null
+ );
+ }
+
+ @Test
+ public void sql2_explicitJoin() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery(
+ "SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)"
+ )
+ .withSelectorName("page")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config()
+ .withCode(
+ "writer.print('#' + counter + ' page: ' + page.getPath() + ', pageContent: ' + pageContent.getPath())"
+ )
+ )
+ )
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler, never()).log(any(), startsWith("There are 2 selectors in the JCR-SQL2 query. "), any(), any());
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 2 nodes from JCR-SQL2 query SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)",
+ null,
+ null
+ );
+
+ verify(mockRunHandler).print("#0 page: /content/child, pageContent: /content/child/jcr:content");
+ verify(mockRunHandler).print("#1 page: /content/child/one, pageContent: /content/child/one/jcr:content");
+ }
+
+ @Test
+ public void sql2_invalidSelector() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:Page]")
+ .withSelectorName("test1")
+ )
+ )
+ .run(session, true);
+ });
+ }
+
+ @Test
+ public void sql2_customCounter() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:Page]")
+ .withCounterName("cnt")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config()
+ .withExtension("jexl")
+ .withCode("writer.print('#' + (cnt+1) + ' node: ' + node.getPath())")
+ )
+ )
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).print("#1 node: /content/child");
+ verify(mockRunHandler).print("#2 node: /content/child/one");
+ }
+
+ @Test
+ public void sql2_limitAndOffset() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:Page]")
+ .withLimit(1)
+ .withOffset(1)
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler, never()).log(LogLevel.DEBUG, "Found node /content/child for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.INFO, "Processed 1 nodes from JCR-SQL2 query SELECT * FROM [cq:Page]", null, null);
+ }
+}
diff --git a/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java b/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java
index 83f3e24..34ab949 100644
--- a/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java
+++ b/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java
@@ -71,17 +71,15 @@ class RunnerTest {
public void simple() throws RepositoryException, HopperException {
final Runner runner = RUNNER_BUILDER.build(
new Script(
- Arrays.asList(
- new SetProperty.Config().withPropertyName("test").withValue("true"),
- new CreateChildNode.Config()
- .withName("cool-item")
- .withHops(
- Collections.singletonList(
- new SetProperty.Config().withPropertyName("TestProp").withValue("'TestValue'")
- )
+ LogLevel.TRACE,
+ new SetProperty.Config().withPropertyName("test").withValue("true"),
+ new CreateChildNode.Config()
+ .withName("cool-item")
+ .withHops(
+ Collections.singletonList(
+ new SetProperty.Config().withPropertyName("TestProp").withValue("'TestValue'")
)
- ),
- LogLevel.TRACE
+ )
)
);
@@ -260,25 +258,21 @@ public void query() throws RepositoryException, HopperException {
final Runner runner = RUNNER_BUILDER.build(
new Script(
- Arrays.asList(
- new NodeQuery.Config()
- .withQuery("SELECT * FROM [nt:unstructured] as n WHERE NAME(n) = 'test-item'")
- .withCounterName("item")
- .withHops(
- Arrays.asList(
- new SetProperty.Config().withPropertyName("index").withValue("item"),
- new ChildNodes.Config()
- .withHops(
- Collections.singletonList(
- new SetProperty.Config()
- .withPropertyName("query")
- .withValue("query")
- )
+ LogLevel.TRACE,
+ new NodeQuery.Config()
+ .withQuery("SELECT * FROM [nt:unstructured] as n WHERE NAME(n) = 'test-item'")
+ .withCounterName("item")
+ .withHops(
+ Arrays.asList(
+ new SetProperty.Config().withPropertyName("index").withValue("item"),
+ new ChildNodes.Config()
+ .withHops(
+ Collections.singletonList(
+ new SetProperty.Config().withPropertyName("query").withValue("query")
)
- )
+ )
)
- ),
- LogLevel.TRACE
+ )
)
);
diff --git a/src/test/java/com/swisscom/aem/tools/jcrhopper/ScriptTest.java b/src/test/java/com/swisscom/aem/tools/jcrhopper/ScriptTest.java
index e33a4e3..baf6c9c 100644
--- a/src/test/java/com/swisscom/aem/tools/jcrhopper/ScriptTest.java
+++ b/src/test/java/com/swisscom/aem/tools/jcrhopper/ScriptTest.java
@@ -41,8 +41,8 @@ public void fromJson() throws IOException {
assertEquals(
"Script(hops=[" +
"SetProperty.Config(propertyName=sling:resourceType, value='swisscom/sdx/components/containers/tabs', conflict=FORCE), " +
- "CreateChildNode.Config(name=contents, primaryType=nt:unstructured, conflict=IGNORE, hops=[" +
- "CreateChildNode.Config(name=shared, primaryType=nt:unstructured, conflict=IGNORE, hops=[" +
+ "CreateChildNode.Config(name=contents, primaryType=nt:unstructured, conflict=IGNORE, runOnExistingNode=false, hops=[" +
+ "CreateChildNode.Config(name=shared, primaryType=nt:unstructured, conflict=IGNORE, runOnExistingNode=false, hops=[" +
"SetProperty.Config(propertyName=sling:resourceType, value='swisscom/sdx/components/responsivegrid', conflict=FORCE)" +
"])" +
"]), " +
diff --git a/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java b/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java
index cb002d2..687232a 100644
--- a/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java
+++ b/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java
@@ -52,10 +52,8 @@ public void builder_basic() throws HopperException, RepositoryException {
final Runner runner = runnerBuilder.build(
new Script(
- Collections.singletonList(
- new ResolveNode.Config().withName("/test").withHops(Collections.singletonList(new ChildNodes.Config()))
- ),
- LogLevel.DEBUG
+ LogLevel.DEBUG,
+ new ResolveNode.Config().withName("/test").withHops(Collections.singletonList(new ChildNodes.Config()))
)
);
diff --git a/src/test/java/com/swisscom/aem/tools/testsupport/AemUtil.java b/src/test/java/com/swisscom/aem/tools/testsupport/AemUtil.java
new file mode 100644
index 0000000..27027ab
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/testsupport/AemUtil.java
@@ -0,0 +1,17 @@
+package com.swisscom.aem.tools.testsupport;
+
+import java.util.List;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.apache.sling.api.resource.Resource;
+
+public final class AemUtil {
+
+ public static List childNames(Resource parent) {
+ return StreamSupport.stream(Spliterators.spliteratorUnknownSize(parent.listChildren(), Spliterator.ORDERED), false)
+ .map(Resource::getName)
+ .collect(Collectors.toList());
+ }
+}