blob: b335add9fcdddbf199ed8d6fb7b1c00d11da9f24 [file] [log] [blame]
/*
* 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.resource.internal;
import static org.apache.sling.jcr.resource.internal.AssertCalendar.assertEqualsCalendar;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.NodeType;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.jcr.resource.internal.helper.jcr.SlingRepositoryTestBase;
public class JcrModifiableValueMapTest extends SlingRepositoryTestBase {
private Node rootNode;
public static final byte[] TEST_BYTE_ARRAY = {'T', 'e', 's', 't'};
public static final String TEST_BYTE_ARRAY_TO_STRING = new String(TEST_BYTE_ARRAY);
@Override
protected void setUp() throws Exception {
super.setUp();
String rootPath = "/test_" + System.currentTimeMillis();
rootNode = getSession().getRootNode().addNode(rootPath.substring(1),
"nt:unstructured");
final Map<String, Object> values = this.initialSet();
for (Map.Entry<String, Object> entry : values.entrySet()) {
setProperty(rootNode, entry.getKey(), entry.getValue());
}
getSession().save();
}
private void setProperty(final Node node,
final String propertyName,
final Object propertyValue)
throws RepositoryException {
if (propertyValue == null) {
node.setProperty(propertyName, (String) null);
} else if (propertyValue.getClass().isArray()) {
final int length = Array.getLength(propertyValue);
final Value[] setValues = new Value[length];
for (int i = 0; i < length; i++) {
final Object value = Array.get(propertyValue, i);
setValues[i] = createValue(value, node.getSession());
}
node.setProperty(propertyName, setValues);
} else {
node.setProperty(propertyName, createValue(propertyValue, node.getSession()));
}
}
Value createValue(final Object value, final Session session)
throws RepositoryException {
Value val;
ValueFactory fac = session.getValueFactory();
if (value instanceof Calendar) {
val = fac.createValue((Calendar) value);
} else if (value instanceof InputStream) {
val = fac.createValue(fac.createBinary((InputStream) value));
} else if (value instanceof Node) {
val = fac.createValue((Node) value);
} else if (value instanceof BigDecimal) {
val = fac.createValue((BigDecimal) value);
} else if (value instanceof Long) {
val = fac.createValue((Long) value);
} else if (value instanceof Short) {
val = fac.createValue((Short) value);
} else if (value instanceof Integer) {
val = fac.createValue((Integer) value);
} else if (value instanceof Number) {
val = fac.createValue(((Number) value).doubleValue());
} else if (value instanceof Boolean) {
val = fac.createValue((Boolean) value);
} else if (value instanceof String) {
val = fac.createValue((String) value);
} else {
val = null;
}
return val;
}
@Override
protected void tearDown() throws Exception {
if (rootNode != null) {
rootNode.remove();
getSession().save();
}
super.tearDown();
}
private HelperData getHelperData() {
return new HelperData(new AtomicReference<>(), new AtomicReference<>());
}
private Map<String, Object> initialSet() {
final Map<String, Object> values = new HashMap<>();
values.put("string", "test");
values.put("long", 1L);
values.put("bool", Boolean.TRUE);
return values;
}
public void testStreams() throws Exception {
final ModifiableValueMap pvm = new JcrModifiableValueMap(this.rootNode, getHelperData());
InputStream stream = new ByteArrayInputStream(TEST_BYTE_ARRAY);
pvm.put("binary", stream);
getSession().save();
final ValueMap valueMap2 = new JcrValueMap(this.rootNode, getHelperData());
assertEquals("The read stream is not what we wrote.", IOUtils.toString(valueMap2.get("binary", InputStream.class)), TEST_BYTE_ARRAY_TO_STRING);
}
public void testPut()
throws Exception {
getSession().refresh(false);
final ModifiableValueMap pvm = new JcrModifiableValueMap(this.rootNode, getHelperData());
assertContains(pvm, initialSet());
assertNull(pvm.get("something"));
// now put two values and check set again
pvm.put("something", "Another value");
pvm.put("string", "overwrite");
final Map<String, Object> currentlyStored = this.initialSet();
currentlyStored.put("something", "Another value");
currentlyStored.put("string", "overwrite");
assertContains(pvm, currentlyStored);
getSession().save();
assertContains(pvm, currentlyStored);
final ValueMap pvm2 = new JcrValueMap(this.rootNode, getHelperData());
assertContains(pvm2, currentlyStored);
}
public void testRemove()
throws Exception {
getSession().refresh(false);
final ModifiableValueMap pvm = new JcrModifiableValueMap(this.rootNode, getHelperData());
final String key = "removeMe";
final Long longValue = 5L;
pvm.put(key, longValue);
final Object removedValue = pvm.remove(key);
assertTrue(removedValue instanceof Long);
assertSame(removedValue, longValue);
assertFalse(pvm.containsKey(key));
}
public void testSerializable()
throws Exception {
this.rootNode.getSession().refresh(false);
final ModifiableValueMap pvm = new JcrModifiableValueMap(this.rootNode, getHelperData());
assertContains(pvm, initialSet());
assertNull(pvm.get("something"));
// now put a serializable object
final List<String> strings = new ArrayList<>();
strings.add("a");
strings.add("b");
pvm.put("something", strings);
// check if we get the list again
@SuppressWarnings("unchecked") final List<String> strings2 = (List<String>) pvm.get("something");
assertEquals(strings, strings2);
getSession().save();
final ValueMap pvm2 = new JcrValueMap(this.rootNode, getHelperData());
// check if we get the list again
@SuppressWarnings("unchecked") final List<String> strings3 = (List<String>) pvm2.get("something", Serializable.class);
assertEquals(strings, strings3);
}
public void testExceptions() throws Exception {
this.rootNode.getSession().refresh(false);
final ModifiableValueMap pvm = new JcrModifiableValueMap(this.rootNode, getHelperData());
try {
pvm.put(null, "something");
fail("Put with null key");
} catch (NullPointerException iae) {
}
try {
pvm.put("something", null);
fail("Put with null value");
} catch (NullPointerException iae) {
}
try {
pvm.put("something", pvm);
fail("Put with non serializable");
} catch (IllegalArgumentException iae) {
}
}
private Set<String> getMixinNodeTypes(final Node node) throws RepositoryException {
final Set<String> mixinTypes = new HashSet<>();
for (final NodeType mixinNodeType : node.getMixinNodeTypes()) {
mixinTypes.add(mixinNodeType.getName());
}
return mixinTypes;
}
public void testMixins() throws Exception {
this.rootNode.getSession().refresh(false);
final Node testNode = this.rootNode.addNode("testMixins" + System.currentTimeMillis());
testNode.getSession().save();
final ModifiableValueMap pvm = new JcrModifiableValueMap(testNode, getHelperData());
final String[] types = pvm.get("jcr:mixinTypes", String[].class);
final Set<String> exNodeTypes = getMixinNodeTypes(testNode);
assertEquals(exNodeTypes.size(), (types == null ? 0 : types.length));
if (types != null) {
for (final String name : types) {
assertTrue(exNodeTypes.contains(name));
}
String[] newTypes = new String[types.length + 1];
System.arraycopy(types, 0, newTypes, 0, types.length);
newTypes[types.length] = "mix:referenceable";
pvm.put("jcr:mixinTypes", newTypes);
} else {
pvm.put("jcr:mixinTypes", "mix:referenceable");
}
getSession().save();
final Set<String> newNodeTypes = getMixinNodeTypes(testNode);
assertEquals(newNodeTypes.size(), exNodeTypes.size() + 1);
}
private static final String TEST_PATH = "a<a";
private static final String VALUE = "value";
private static final String VALUE1 = "value";
private static final String VALUE2 = "value";
private static final String VALUE3 = "my title";
private static final String PROP1 = "-prop";
private static final String PROP2 = "1prop";
private static final String PROP3 = "jcr:title";
public void testNamesReverse() throws Exception {
this.rootNode.getSession().refresh(false);
final Node testNode = this.rootNode.addNode("nameTest" + System.currentTimeMillis());
testNode.getSession().save();
final ModifiableValueMap pvm = new JcrModifiableValueMap(testNode, getHelperData());
pvm.put(TEST_PATH, VALUE);
pvm.put(PROP1, VALUE1);
pvm.put(PROP2, VALUE2);
pvm.put(PROP3, VALUE3);
getSession().save();
// read with property map
final ValueMap vm = new JcrValueMap(testNode, getHelperData());
assertEquals(VALUE, vm.get(TEST_PATH));
assertEquals(VALUE1, vm.get(PROP1));
assertEquals(VALUE2, vm.get(PROP2));
assertEquals(VALUE3, vm.get(PROP3));
// read properties
assertEquals(VALUE, testNode.getProperty(TEST_PATH).getString());
assertEquals(VALUE1, testNode.getProperty(PROP1).getString());
assertEquals(VALUE2, testNode.getProperty(PROP2).getString());
assertEquals(VALUE3, testNode.getProperty(PROP3).getString());
}
/**
* Checks property names encoding, see SLING-2502.
*/
public void testNamesUpdate() throws Exception {
this.rootNode.getSession().refresh(false);
final Node testNode = this.rootNode.addNode("nameUpdateTest"
+ System.currentTimeMillis());
testNode.setProperty(PROP3, VALUE);
testNode.getSession().save();
final ModifiableValueMap pvm = new JcrModifiableValueMap(testNode, getHelperData());
pvm.put(PROP3, VALUE3);
pvm.put("jcr:a:b", VALUE3);
pvm.put("jcr:", VALUE3);
getSession().save();
// read with property map
final ValueMap vm = new JcrValueMap(testNode, getHelperData());
assertEquals(VALUE3, vm.get(PROP3));
assertEquals(VALUE3, vm.get("jcr:a:b"));
assertEquals(VALUE3, vm.get("jcr:"));
// read properties
assertEquals(VALUE3, testNode.getProperty(PROP3).getString());
assertEquals(VALUE3, testNode.getProperty("jcr:" + Text.escapeIllegalJcrChars("a:b")).getString());
assertEquals(VALUE3, testNode.getProperty(Text.escapeIllegalJcrChars("jcr:")).getString());
assertFalse(testNode.hasProperty(Text.escapeIllegalJcrChars(PROP3)));
assertFalse(testNode.hasProperty(Text.escapeIllegalJcrChars("jcr:a:b")));
}
/**
* Check that the value map contains all supplied values
*/
private void assertContains(ValueMap map, Map<String, Object> values) {
for (Map.Entry<String, Object> entry : values.entrySet()) {
final Object stored = map.get(entry.getKey());
assertEquals(values.get(entry.getKey()), stored);
}
}
/**
* Test date conversions from Date to Calendar and vice versa when reading or writing from value maps.
*/
public void testDateConversion() throws Exception {
this.rootNode.getSession().refresh(false);
// prepare some date values
Date dateValue1 = new Date(10000);
Calendar calendarValue1 = Calendar.getInstance();
calendarValue1.setTime(dateValue1);
Date dateValue2 = new Date(20000);
Calendar calendarValue2 = Calendar.getInstance();
calendarValue2.setTime(dateValue2);
Date dateValue3 = new Date(30000);
Calendar calendarValue3 = Calendar.getInstance();
calendarValue3.setTime(dateValue3);
// write property
final Node testNode = this.rootNode.addNode("dateConversionTest" + System.currentTimeMillis());
testNode.setProperty(PROP1, calendarValue1);
testNode.getSession().save();
// write with property map
final ModifiableValueMap pvm = new JcrModifiableValueMap(testNode, getHelperData());
pvm.put(PROP2, dateValue2);
pvm.put(PROP3, calendarValue3);
getSession().save();
// read with property map
final ValueMap vm = new JcrValueMap(testNode, getHelperData());
assertEquals(dateValue1, vm.get(PROP1, Date.class));
assertEqualsCalendar(calendarValue1, vm.get(PROP1, Calendar.class));
assertEquals(dateValue2, vm.get(PROP2, Date.class));
assertEqualsCalendar(calendarValue2, vm.get(PROP2, Calendar.class));
assertEquals(dateValue3, vm.get(PROP3, Date.class));
assertEqualsCalendar(calendarValue3, vm.get(PROP3, Calendar.class));
// check types
assertTrue(vm.get(PROP1) instanceof Calendar);
assertTrue(vm.get(PROP2) instanceof InputStream);
assertTrue(vm.get(PROP3) instanceof Calendar);
// read properties
assertEqualsCalendar(calendarValue1, testNode.getProperty(PROP1).getDate());
assertEqualsCalendar(calendarValue3, testNode.getProperty(PROP3).getDate());
}
/**
* Test conversions involving ZonedDateTime
*/
public void testZonedDateTimeConversions() throws Exception {
this.rootNode.getSession().refresh(false);
String dateAsIso8601 = "2019-07-04T14:05:37.123+02:00";
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateAsIso8601, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(zonedDateTime.getOffset()));
calendar.setTimeInMillis(zonedDateTime.toInstant().toEpochMilli());
// write property
final Node testNode = this.rootNode.addNode("dateConversionTest" + System.currentTimeMillis());
testNode.setProperty(PROP1, calendar);
testNode.getSession().save();
// write with property map
final ModifiableValueMap pvm = new JcrModifiableValueMap(testNode, getHelperData());
pvm.put(PROP2, zonedDateTime);
pvm.put(PROP3, dateAsIso8601);
getSession().save();
// read with property map
final ValueMap vm = new JcrValueMap(testNode, getHelperData());
// check types
assertTrue(vm.get(PROP1) instanceof Calendar);
assertTrue(vm.get(PROP2) instanceof Calendar);
assertTrue(vm.get(PROP3) instanceof String);
// to ZonedDateTime
assertEquals(zonedDateTime, vm.get(PROP1, ZonedDateTime.class)); // from Calendar
assertEquals(zonedDateTime, vm.get(PROP2, ZonedDateTime.class)); // from ZonedDateTime
assertEquals(zonedDateTime, vm.get(PROP3, ZonedDateTime.class)); // from ISO-8601 String
// from ZonedDateTime
assertEqualsCalendar(calendar, vm.get(PROP2, Calendar.class)); // to Calendar
assertEquals(calendar.getTime(), vm.get(PROP2, Date.class)); // to Date
assertEquals(dateAsIso8601, vm.get(PROP2, String.class)); // to String
// read properties
assertEqualsCalendar(calendar, testNode.getProperty(PROP1).getDate());
assertEqualsCalendar(calendar, testNode.getProperty(PROP2).getDate());
assertEqualsCalendar(calendar, testNode.getProperty(PROP3).getDate());
}
}