| /* |
| * 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()); |
| |
| } |
| |
| } |