Merge pull request #10 from nit23uec/master
SLING-9171 Support setting node properties via repoinit
diff --git a/pom.xml b/pom.xml
index 37960d5..bb0dc28 100644
--- a/pom.xml
+++ b/pom.xml
@@ -220,7 +220,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.repoinit.parser</artifactId>
- <version>1.5.3-SNAPSHOT</version>
+ <version>1.6.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/jcr/repoinit/impl/DoNothingVisitor.java b/src/main/java/org/apache/sling/jcr/repoinit/impl/DoNothingVisitor.java
index 29c5d75..c2ae6ed 100644
--- a/src/main/java/org/apache/sling/jcr/repoinit/impl/DoNothingVisitor.java
+++ b/src/main/java/org/apache/sling/jcr/repoinit/impl/DoNothingVisitor.java
@@ -35,8 +35,8 @@
import org.apache.sling.repoinit.parser.operations.SetAclPaths;
import org.apache.sling.repoinit.parser.operations.SetAclPrincipalBased;
import org.apache.sling.repoinit.parser.operations.SetAclPrincipals;
-import org.apache.sling.repoinit.parser.operations.AddGroupMembers;
-import org.apache.sling.repoinit.parser.operations.RemoveGroupMembers;
+import org.apache.sling.repoinit.parser.operations.SetProperties;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -132,4 +132,8 @@
@Override
public void visitRemoveGroupMembers(RemoveGroupMembers rm) {
}
+
+ @Override
+ public void visitSetProperties(SetProperties sp) {
+ }
}
diff --git a/src/main/java/org/apache/sling/jcr/repoinit/impl/JcrRepoInitOpsProcessorImpl.java b/src/main/java/org/apache/sling/jcr/repoinit/impl/JcrRepoInitOpsProcessorImpl.java
index 51347c0..be30809 100644
--- a/src/main/java/org/apache/sling/jcr/repoinit/impl/JcrRepoInitOpsProcessorImpl.java
+++ b/src/main/java/org/apache/sling/jcr/repoinit/impl/JcrRepoInitOpsProcessorImpl.java
@@ -47,7 +47,8 @@
new PrivilegeVisitor(session),
new UserVisitor(session),
new AclVisitor(session),
- new GroupMembershipVisitor(session)
+ new GroupMembershipVisitor(session),
+ new NodePropertiesVisitor(session)
};
for(OperationVisitor v : visitors) {
diff --git a/src/main/java/org/apache/sling/jcr/repoinit/impl/NodePropertiesVisitor.java b/src/main/java/org/apache/sling/jcr/repoinit/impl/NodePropertiesVisitor.java
new file mode 100644
index 0000000..79b2db9
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/repoinit/impl/NodePropertiesVisitor.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.jcr.repoinit.impl;
+
+import java.util.List;
+import java.util.Calendar;
+
+import javax.jcr.Session;
+import javax.jcr.Node;
+import javax.jcr.Value;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.value.BooleanValue;
+import org.apache.jackrabbit.value.DateValue;
+import org.apache.jackrabbit.value.DoubleValue;
+import org.apache.jackrabbit.value.LongValue;
+import org.apache.jackrabbit.value.StringValue;
+
+import org.apache.sling.repoinit.parser.operations.PropertyLine;
+import org.apache.sling.repoinit.parser.operations.SetProperties;
+
+/**
+ * OperationVisitor which processes only operations related to setting node properties. Having several such specialized visitors makes it easy to control the
+ * execution order.
+ */
+class NodePropertiesVisitor extends DoNothingVisitor {
+
+ /**
+ * Create a visitor using the supplied JCR Session.
+ *
+ * @param s must have sufficient rights to set properties on a path.
+ */
+ public NodePropertiesVisitor(Session s) {
+ super(s);
+ }
+
+ @Override
+ public void visitSetProperties(SetProperties sp) {
+ List<String> nodePaths = sp.getPaths();
+ List<PropertyLine> propertyLines = sp.getPropertyLines();
+ for (String nodePath : nodePaths) {
+ try {
+ log.info("Setting properties on nodePath '{}'", nodePath);
+ Node n = session.getNode(nodePath);
+ for (PropertyLine pl : propertyLines) {
+ String pName = pl.getPropertyName();
+ PropertyLine.PropertyType pType = pl.getPropertyType();
+ List<Object> values = pl.getPropertyValues();
+ boolean setOnlyIfNull = pl.isDefault();
+ int type = PropertyType.valueFromName(pType.name());
+ boolean isExistingNonNullProperty = n.hasProperty(pName) && n.getProperty(pName) != null;
+ if (!setOnlyIfNull || (setOnlyIfNull && !isExistingNonNullProperty)) {
+ if (values.size() > 1) {
+ Value[] pValues = convertToValues(values);
+ n.setProperty(pName, pValues, type);
+ } else {
+ Value pValue = convertToValue(values.get(0));
+ n.setProperty(pName, pValue, type);
+ }
+ } else {
+ log.info("Property '{}' is already set on path '{}'. Will not overwrite the default", pName, nodePath);
+ }
+ }
+ } catch (RepositoryException e) {
+ report(e, "Unable to set properties on path [" + nodePath + "]:" + e);
+ }
+ }
+ }
+
+ private Value[] convertToValues(List<Object> values) {
+ int size = values.size();
+ Value[] valueArray = new Value[size];
+ for(int i = 0; i < size; i++) {
+ valueArray[i] = convertToValue(values.get(i));
+ }
+ return valueArray;
+ }
+
+ private Value convertToValue(Object value) {
+ Value convertedValue = null;
+ if (value instanceof String) {
+ convertedValue = new StringValue((String) value);
+ } else if (value instanceof Double) {
+ convertedValue = new DoubleValue((Double) value);
+ } else if (value instanceof Long) {
+ convertedValue = new LongValue((Long) value);
+ } else if (value instanceof Boolean) {
+ convertedValue = new BooleanValue((Boolean) value);
+ } else if (value instanceof Calendar) {
+ convertedValue = new DateValue((Calendar) value);
+ } else {
+ throw new RuntimeException("Unable to convert " + value + " to jcr Value");
+ }
+ return convertedValue;
+ }
+}
diff --git a/src/test/java/org/apache/sling/jcr/repoinit/SetPropertiesTest.java b/src/test/java/org/apache/sling/jcr/repoinit/SetPropertiesTest.java
new file mode 100644
index 0000000..2d572fb
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/repoinit/SetPropertiesTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.jcr.repoinit;
+
+import org.apache.sling.commons.testing.jcr.RepositoryUtil;
+import org.apache.sling.jcr.repoinit.impl.TestUtil;
+import org.apache.sling.repoinit.parser.RepoInitParsingException;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFactory;
+import javax.jcr.Value;
+import java.io.IOException;
+
+
+/** Test the setting of properties on nodes */
+public class SetPropertiesTest {
+
+ @Rule
+ public final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK);
+
+ private TestUtil U;
+ final String path1 = "/one/two/three";
+ final String path2 = "/one/two/four";
+
+ @Before
+ public void setup() throws RepositoryException, IOException, RepoInitParsingException {
+ U = new TestUtil(context);
+ RepositoryUtil.registerSlingNodeTypes(U.adminSession);
+ U.parseAndExecute("create path " + path1);
+ U.assertNodeExists(path1);
+ U.parseAndExecute("create path " + path2);
+ U.assertNodeExists(path2);
+ }
+
+ @Test
+ public void setStringPropertyTest() throws Exception {
+ U.parseAndExecute("set properties on " + path1 + " \n set sling:ResourceType{String} to /x/y/z \n end");
+ ValueFactory vf = U.adminSession.getValueFactory();
+ Value expectedValue = vf.createValue("/x/y/z");
+ U.assertSVPropertyExists(path1, "sling:ResourceType", expectedValue);
+ }
+
+ @Test
+ public void setMultiplePropertiesTest() throws Exception {
+ final String setProps =
+ "set properties on " + path2 + "\n"
+ + "set sling:ResourceType{String} to /x/y/z \n"
+ + "set allowedTemplates to /d/e/f/*, m/n/* \n"
+ + "set someInteger{Long} to 42 \n"
+ + "set someFlag{Boolean} to true \n"
+ + "set someDate{Date} to \"2020-03-19T11:39:33.437+05:30\" \n"
+ + "set customSingleValueQuotedStringProp to \"hello, you!\" \n"
+ + "end"
+ ;
+ U.parseAndExecute(setProps);
+ ValueFactory vf = U.adminSession.getValueFactory();
+ Value expectedValue1 = vf.createValue("/x/y/z");
+ U.assertSVPropertyExists(path2, "sling:ResourceType", expectedValue1);
+ Value[] expectedValues2 = new Value[2];
+ expectedValues2[0] = vf.createValue("/d/e/f/*");
+ expectedValues2[1] = vf.createValue("m/n/*");
+ U.assertMVPropertyExists(path2, "allowedTemplates", expectedValues2);
+ Value expectedValue3 = vf.createValue("42", PropertyType.valueFromName("Long"));
+ U.assertSVPropertyExists(path2, "someInteger", expectedValue3);
+ Value expectedValue4 = vf.createValue("true", PropertyType.valueFromName("Boolean"));
+ U.assertSVPropertyExists(path2, "someFlag", expectedValue4);
+ Value expectedValue5 = vf.createValue("2020-03-19T11:39:33.437+05:30", PropertyType.valueFromName("Date"));
+ U.assertSVPropertyExists(path2, "someDate", expectedValue5);
+ Value expectedValue6 = vf.createValue("hello, you!");
+ U.assertSVPropertyExists(path2, "customSingleValueQuotedStringProp", expectedValue6);
+ }
+
+ @Test
+ public void setPropertyOnNonExistentPathTest() throws Exception {
+ String nonExistingPath = "/someNonExistingPath/A/B";
+ try {
+ U.parseAndExecute("set properties on " + nonExistingPath + " \n set sling:ResourceType{String} to /x/y/z \n end");
+ Assert.fail();
+ } catch (RuntimeException e) {
+ Assert.assertTrue("expected repository exception", e.getMessage().contains("Unable to set properties on path [" + nonExistingPath + "]:"));
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/jcr/repoinit/impl/TestUtil.java b/src/test/java/org/apache/sling/jcr/repoinit/impl/TestUtil.java
index 1e6c6fb..ac4c1d3 100644
--- a/src/test/java/org/apache/sling/jcr/repoinit/impl/TestUtil.java
+++ b/src/test/java/org/apache/sling/jcr/repoinit/impl/TestUtil.java
@@ -29,6 +29,8 @@
import java.util.UUID;
import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Value;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
@@ -192,6 +194,28 @@
assertEquals(message + " to be member of " + groupId, expectToBeMember, isMember);
}
+ public void assertSVPropertyExists(String path, String propertyName, Value expectedValue) throws RepositoryException {
+ final Node n = adminSession.getNode(path);
+ if(!n.hasProperty(propertyName)) {
+ fail("No " + propertyName + " property at " + path);
+ } else {
+ Property p = n.getProperty(propertyName);
+ Value actualValue = p.getValue();
+ assertEquals("Value mismatch for property: " + propertyName, actualValue, expectedValue);
+ }
+ }
+
+ public void assertMVPropertyExists(String path, String propertyName, Value[] expectedValues) throws RepositoryException {
+ final Node n = adminSession.getNode(path);
+ if(!n.hasProperty(propertyName)) {
+ fail("No " + propertyName + " property at " + path);
+ } else {
+ Property p = n.getProperty(propertyName);
+ Value[] actualValues = p.getValues();
+ assertEquals("Values mismatch for property: " + propertyName, actualValues, expectedValues);
+ }
+ }
+
public void parseAndExecute(String input) throws RepositoryException, RepoInitParsingException {
final JcrRepoInitOpsProcessorImpl p = new JcrRepoInitOpsProcessorImpl();
p.apply(adminSession, parse(input));
diff --git a/src/test/java/org/apache/sling/jcr/repoinit/it/RepoInitTextIT.java b/src/test/java/org/apache/sling/jcr/repoinit/it/RepoInitTextIT.java
index e137fed..11e2e2b 100644
--- a/src/test/java/org/apache/sling/jcr/repoinit/it/RepoInitTextIT.java
+++ b/src/test/java/org/apache/sling/jcr/repoinit/it/RepoInitTextIT.java
@@ -24,6 +24,9 @@
import java.io.InputStreamReader;
import java.util.UUID;
+import javax.jcr.PropertyType;
+import javax.jcr.ValueFactory;
+import javax.jcr.Value;
import javax.inject.Inject;
import org.apache.sling.jcr.repoinit.JcrRepoInitOpsProcessor;
@@ -49,6 +52,17 @@
private static final String GROUP_A = "grpA";
private static final String GROUP_B = "grpB";
private static final String GROUP_C = "grpC";
+ private static final String PROP_A = "pathArray";
+ private static final String PROP_B = "someInteger";
+ private static final String PROP_C = "someFlag";
+ private static final String PROP_D = "someDate";
+ private static final String PROP_E = "customSingleValueStringProp";
+ private static final String PROP_F = "customSingleValueQuotedStringProp";
+ private static final String PROP_G = "stringArray";
+ private static final String PROP_H = "quotedA";
+ private static final String PROP_I = "quotedMix";
+ private static final String PROP_NODE_PATH = "/proptest/X/Y";
+
public static final String REPO_INIT_FILE = "/repoinit.txt";
@@ -152,4 +166,49 @@
}
};
}
+
+ @Test
+ public void setProperties() throws Exception {
+ new Retry() {
+ @Override
+ public Void call() throws Exception {
+ ValueFactory vf = session.getValueFactory();
+ Value[] expectedValues1 = new Value[2];
+ expectedValues1[0] = vf.createValue("/d/e/f/*");
+ expectedValues1[1] = vf.createValue("m/n/*");
+ assertTrue("Expecting array type property " + PROP_A + " to be present ", U.hasProperty(session, PROP_NODE_PATH, PROP_A, expectedValues1));
+
+ Value expectedValue2 = vf.createValue("42", PropertyType.valueFromName("Long"));
+ assertTrue("Expecting Long type default property " + PROP_B + " to be present ", U.hasProperty(session, PROP_NODE_PATH, PROP_B, expectedValue2));
+
+ Value expectedValue3 = vf.createValue("true", PropertyType.valueFromName("Boolean"));
+ assertTrue("Expecting bool type property " + PROP_C + " to be present ", U.hasProperty(session, PROP_NODE_PATH, PROP_C, expectedValue3));
+
+ Value expectedValue4 = vf.createValue("2020-03-19T11:39:33.437+05:30", PropertyType.valueFromName("Date"));
+ assertTrue("Expecting date type property " + PROP_D + " to be present " , U.hasProperty(session, PROP_NODE_PATH, PROP_D, expectedValue4));
+
+ Value expectedValue5 = vf.createValue("test");
+ assertTrue("Expecting string type property " + PROP_E + " to be present " , U.hasProperty(session, PROP_NODE_PATH, PROP_E, expectedValue5));
+
+ Value expectedValue6 = vf.createValue("hello, you!");
+ assertTrue("Expecting quoted string type property " + PROP_F + " to be present " , U.hasProperty(session, PROP_NODE_PATH, PROP_F, expectedValue6));
+
+ Value[] expectedValues7 = new Value[2];
+ expectedValues7[0] = vf.createValue("test1");
+ expectedValues7[1] = vf.createValue("test2");
+ assertTrue("Expecting string array type property " + PROP_G + " to be present " , U.hasProperty(session, PROP_NODE_PATH, PROP_G, expectedValues7));
+
+ Value expectedValue8 = vf.createValue("Here's a \"double quoted string\" with suffix");
+ assertTrue("Expecting quoted string type property " + PROP_H + " to be present " , U.hasProperty(session, PROP_NODE_PATH, PROP_H, expectedValue8));
+
+ Value[] expectedValues9 = new Value[3];
+ expectedValues9[0] = vf.createValue("quoted");
+ expectedValues9[1] = vf.createValue("non-quoted");
+ expectedValues9[2] = vf.createValue("the last \" one");
+ assertTrue("Expecting string array type property " + PROP_I + " to be present " , U.hasProperty(session, PROP_NODE_PATH, PROP_I, expectedValues9));
+
+ return null;
+ }
+ };
+ }
}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/jcr/repoinit/it/U.java b/src/test/java/org/apache/sling/jcr/repoinit/it/U.java
index 15ffb04..1a404ff 100644
--- a/src/test/java/org/apache/sling/jcr/repoinit/it/U.java
+++ b/src/test/java/org/apache/sling/jcr/repoinit/it/U.java
@@ -17,10 +17,12 @@
package org.apache.sling.jcr.repoinit.it;
import java.util.UUID;
+import java.util.Arrays;
import javax.jcr.AccessDeniedException;
import javax.jcr.LoginException;
import javax.jcr.Node;
+import javax.jcr.Value;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
@@ -109,4 +111,28 @@
final Authorizable member = ((JackrabbitSession)session).getUserManager().getAuthorizable(userId);
return ((Group) a).isMember(member);
}
+
+ public static boolean hasProperty(Session session, String nodePath, String propertyName, Value propertyValue) throws RepositoryException {
+ final Node n = session.getNode(nodePath);
+ if (n != null) {
+ boolean isPropertyPresent = n.hasProperty(propertyName);
+ if (isPropertyPresent) {
+ Value v = n.getProperty(propertyName).getValue();
+ return isPropertyPresent && (v.equals(propertyValue));
+ }
+ }
+ return false;
+ }
+
+ public static boolean hasProperty(Session session, String nodePath, String propertyName, Value[] propertyValues) throws RepositoryException {
+ final Node n = session.getNode(nodePath);
+ if (n != null) {
+ boolean isPropertyPresent = n.hasProperty(propertyName);
+ if (isPropertyPresent) {
+ Value[] v = n.getProperty(propertyName).getValues();
+ return isPropertyPresent && Arrays.equals(v, propertyValues);
+ }
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/src/test/resources/repoinit.txt b/src/test/resources/repoinit.txt
index 2faea12..d6fd943 100644
--- a/src/test/resources/repoinit.txt
+++ b/src/test/resources/repoinit.txt
@@ -20,6 +20,9 @@
# Paths on which ACLs will be set
create path /acltest/A/B
+# Path on which node properties will be set
+create path /proptest/X/Y(nt:unstructured)
+
# Service users and ACLs for our integration tests
create service user fredWilmaService
create service user anotherService
@@ -65,4 +68,17 @@
remove bob,grpA from group grpB
create group grpC
-add grpA, bob, grpB to group grpC
\ No newline at end of file
+add grpA, bob, grpB to group grpC
+
+set properties on /proptest/X/Y
+ set pathArray to /d/e/f/*, m/n/*
+ default someInteger{Long} to 42
+ set someFlag{Boolean} to true
+ default someDate{Date} to "2020-03-19T11:39:33.437+05:30"
+ set customSingleValueStringProp to test
+ set customSingleValueQuotedStringProp to "hello, you!"
+ set stringArray to test1, test2
+ default someInteger{Long} to 65
+ set quotedA to "Here's a \"double quoted string\" with suffix"
+ set quotedMix to "quoted", non-quoted, "the last \" one"
+end
\ No newline at end of file