/* * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed 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. * * Contributors: */ package org.nuxeo.ecm.core.opencmis.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.apache.chemistry.opencmis.commons.data.ObjectData; import org.apache.chemistry.opencmis.commons.data.ObjectList; import org.apache.chemistry.opencmis.commons.data.Properties; import org.apache.chemistry.opencmis.commons.data.PropertyData; import org.apache.chemistry.opencmis.commons.data.PropertyString; import org.apache.chemistry.opencmis.commons.spi.BindingsObjectFactory; import org.apache.chemistry.opencmis.commons.spi.Holder; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.node.ArrayNode; import org.codehaus.jackson.node.ObjectNode; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.ecm.automation.core.util.DateTimeFormat; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.opencmis.impl.server.NuxeoTypeHelper; import org.nuxeo.ecm.core.schema.utils.DateParser; import org.nuxeo.ecm.core.test.annotations.Granularity; import org.nuxeo.ecm.core.test.annotations.RepositoryConfig; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.LocalDeploy; @RunWith(FeaturesRunner.class) @Features({ CmisFeature.class, CmisFeatureConfiguration.class }) @Deploy({ "org.nuxeo.ecm.webengine.core", // "org.nuxeo.ecm.automation.core" // }) @LocalDeploy("org.nuxeo.ecm.core.opencmis.tests.tests:OSGI-INF/types-contrib.xml") @RepositoryConfig(cleanup = Granularity.METHOD) public class TestCmisBindingComplexProperties extends TestCmisBindingBase { @Inject protected CoreSession coreSession; @Before public void setUp() throws Exception { setUpBinding(coreSession); setUpData(coreSession); } @After public void tearDown() { Framework.getProperties().remove(NuxeoTypeHelper.ENABLE_COMPLEX_PROPERTIES); tearDownBinding(); } protected ObjectData getObjectByPath(String path) { return objService.getObjectByPath(repositoryId, path, null, null, null, null, null, null, null); } protected Properties createProperties(String key, String value) { BindingsObjectFactory factory = binding.getObjectFactory(); PropertyString prop = factory.createPropertyStringData(key, value); return factory.createPropertiesData(Collections.<PropertyData<?>> singletonList(prop)); } @SuppressWarnings("unchecked") @Test public void testGetComplexListProperty() throws Exception { // Enable complex properties Framework.getProperties().setProperty(NuxeoTypeHelper.ENABLE_COMPLEX_PROPERTIES, "true"); // Create a complex property to encode List<Map<String, Object>> propList = createComplexPropertyList(3); // Set the property value on a document CoreSession session = coreSession; DocumentModel doc = session.createDocumentModel("/", null, "ComplexFile"); doc.setPropertyValue("complexTest:listItem", (Serializable) propList); doc = session.createDocument(doc); session.save(); doc.refresh(); assertTrue(session.exists(new IdRef(doc.getId()))); // Get the property as CMIS will see it from the object service Properties p = objService.getProperties(repositoryId, doc.getId(), null, null); assertNotNull(p); List<Object> cmisValues = (List<Object>) p.getProperties().get("complexTest:listItem").getValues(); assertEquals("Wrong number of marshaled values", propList.size(), cmisValues.size()); // Verify the JSON produced is valid and matches the original objects ObjectMapper mapper = new ObjectMapper(); for (int i = 0; i < cmisValues.size(); i++) { JsonNode jsonNode = mapper.readTree(cmisValues.get(i).toString()); Map<String, Object> orig = propList.get(i); assertComplexPropertyNodeEquals(orig, jsonNode, DateTimeFormat.W3C); } } @SuppressWarnings("unchecked") @Test public void testQueryComplexListProperty() throws Exception { // Enable complex properties Framework.getProperties().setProperty(NuxeoTypeHelper.ENABLE_COMPLEX_PROPERTIES, "true"); // Create a complex property to encode List<Map<String, Object>> propList = createComplexPropertyList(3); // Set the property value on a document CoreSession session = coreSession; DocumentModel doc = session.createDocumentModel("/", null, "ComplexFile"); doc.setPropertyValue("complexTest:listItem", (Serializable) propList); doc = session.createDocument(doc); session.save(); doc.refresh(); assertTrue(session.exists(new IdRef(doc.getId()))); // explicit select String statement = "SELECT complexTest:listItem FROM ComplexFile"; ObjectList res = discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); PropertyData<String> data = (PropertyData<String>) res.getObjects().get(0).getProperties().getProperties().get("complexTest:listItem"); assertNotNull(data); // Verify the JSON produced is valid and matches the original objects List<String> values = data.getValues(); for (int i = 0; i < values.size(); i++) { String jsonStr = values.get(i); ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(jsonStr); Map<String, Object> propMap = (Map<String, Object>) propList.get(i); assertComplexPropertyNodeEquals(propMap, jsonNode, DateTimeFormat.W3C); } // star select, doesn't return lists statement = "SELECT * FROM ComplexFile"; res = discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); data = (PropertyData<String>) res.getObjects().get(0).getProperties().getProperties().get("complexTest:listItem"); assertNull(data); } @SuppressWarnings("unchecked") @Test public void testSetComplexListProperty() throws Exception { // Enable complex properties Framework.getProperties().setProperty(NuxeoTypeHelper.ENABLE_COMPLEX_PROPERTIES, "true"); // Create some JSON to pass into the CMIS service ArrayList<ObjectNode> nodeList = createComplexNodeList(3, DateTimeFormat.TIME_IN_MILLIS); // Get a document with the right property schema CoreSession session = coreSession; DocumentModel doc = session.createDocumentModel("/", null, "ComplexFile"); doc = session.createDocument(doc); session.save(); // Set the property as a JSON string through the CMIS service BindingsObjectFactory bof = binding.getObjectFactory(); ArrayList<String> stringArr = new ArrayList<String>(); for (int i = 0; i < nodeList.size(); i++) { stringArr.add(nodeList.get(i).toString()); } PropertyString prop = bof.createPropertyStringData("complexTest:listItem", stringArr); Properties props = bof.createPropertiesData(Collections.<PropertyData<?>> singletonList(prop)); Holder<String> objectIdHolder = new Holder<String>(doc.getId()); Holder<String> changeTokenHolder = new Holder<String>(doc.getChangeToken()); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, props, null); // Verify the properties produced in Nuxeo match the input JSON session.save(); doc.refresh(); List<Object> list = (List<Object>) doc.getPropertyValue("complexTest:listItem"); assertEquals("Wrong number of elements in list", nodeList.size(), list.size()); for (int i = 0; i < list.size(); i++) { JsonNode orig = nodeList.get(i); Map<String, Object> obj = (Map<String, Object>) list.get(i); assertComplexPropertyNodeEquals(obj, orig, DateTimeFormat.TIME_IN_MILLIS); } } @Test public void testGetComplexProperty() throws Exception { // Enable complex properties Framework.getProperties().setProperty(NuxeoTypeHelper.ENABLE_COMPLEX_PROPERTIES, "true"); // Create a complex property to encode List<Map<String, Object>> list = createComplexPropertyList(1); Map<String, Object> propMap = list.get(0); // Set the property value on a document CoreSession session = coreSession; DocumentModel doc = session.createDocumentModel("/", null, "ComplexFile"); doc.setPropertyValue("complexTest:complexItem", (Serializable) propMap); Blob blob = Blobs.createBlob("Test content"); blob.setFilename("test.txt"); doc.setProperty("file", "content", blob); doc = session.createDocument(doc); session.save(); doc.refresh(); assertTrue(session.exists(new IdRef(doc.getId()))); // Get the property as CMIS will see it from the object service Properties p = objService.getProperties(repositoryId, doc.getId(), null, null); assertNotNull(p); String jsonStr = p.getProperties().get("complexTest:complexItem").getFirstValue().toString(); assertEquals("Complex item should get marshaled as a single string value", 1, p.getProperties().get("complexTest:complexItem").getValues().size()); // Verify the JSON produced is valid and matches the original objects ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(jsonStr); assertComplexPropertyNodeEquals(propMap, jsonNode, DateTimeFormat.W3C); } @SuppressWarnings("unchecked") @Test public void testQueryComplexProperty() throws Exception { // Enable complex properties Framework.getProperties().setProperty(NuxeoTypeHelper.ENABLE_COMPLEX_PROPERTIES, "true"); // Create a complex property to encode List<Map<String, Object>> list = createComplexPropertyList(1); Map<String, Object> propMap = list.get(0); // Set the property value on a document CoreSession session = coreSession; DocumentModel doc = session.createDocumentModel("/", null, "ComplexFile"); doc.setPropertyValue("complexTest:complexItem", (Serializable) propMap); Blob blob = Blobs.createBlob("Test content"); blob.setFilename("test.txt"); doc.setProperty("file", "content", blob); doc = session.createDocument(doc); session.save(); doc.refresh(); assertTrue(session.exists(new IdRef(doc.getId()))); // explicit select String statement = "SELECT complexTest:complexItem FROM ComplexFile"; ObjectList res = discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); // Verify the JSON produced is valid and matches the original objects PropertyData<String> data = (PropertyData<String>) res.getObjects().get(0).getProperties().getProperties().get("complexTest:complexItem"); String jsonStr = data.getFirstValue(); ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(jsonStr); assertComplexPropertyNodeEquals(propMap, jsonNode, DateTimeFormat.W3C); // star select, doesn't return strings from complex props statement = "SELECT * FROM ComplexFile"; res = discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); data = (PropertyData<String>) res.getObjects().get(0).getProperties().getProperties().get("complexTest:complexItem"); assertNull(data); } @SuppressWarnings("unchecked") @Test public void testSetComplexProperty() throws Exception { // Enable complex properties Framework.getProperties().setProperty(NuxeoTypeHelper.ENABLE_COMPLEX_PROPERTIES, "true"); // Create some JSON to pass into the CMIS service ArrayList<ObjectNode> nodeList = createComplexNodeList(1, DateTimeFormat.TIME_IN_MILLIS); ObjectNode jsonObj = nodeList.get(0); String jsonStr = jsonObj.toString(); // Get a document with the right property schema CoreSession session = coreSession; DocumentModel doc = session.createDocumentModel("/", null, "ComplexFile"); doc = session.createDocument(doc); session.save(); // Set the property as a JSON string through the CMIS service Properties props = createProperties("complexTest:complexItem", jsonStr); Holder<String> objectIdHolder = new Holder<String>(doc.getId()); Holder<String> changeTokenHolder = new Holder<String>(doc.getChangeToken()); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, props, null); // Verify the properties produced in Nuxeo match the input JSON session.save(); doc.refresh(); Map<String, Object> propMap = (Map<String, Object>) doc.getPropertyValue("complexTest:complexItem"); ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(jsonStr); assertComplexPropertyNodeEquals(propMap, jsonNode, DateTimeFormat.TIME_IN_MILLIS); } /** * Test that complex types are not exposed unless the enabled property is set */ public void testEnableComplexProperties() throws Exception { // Don't enable complex properties for this test // Set a complex property on a document HashMap<String, Object> propMap = new HashMap<String, Object>(); propMap.put("stringProp", "testString"); // Set the property value on a document CoreSession session = coreSession; DocumentModel doc = session.createDocumentModel("/", null, "ComplexFile"); doc.setPropertyValue("complexTest:complexItem", propMap); doc = session.createDocument(doc); session.save(); doc.refresh(); assertTrue(session.exists(new IdRef(doc.getId()))); // Get the property as CMIS will see it from the object service Properties p = objService.getProperties(repositoryId, doc.getId(), null, null); assertNotNull(p); assertNull("Complex property should not be exposed when not enabled in framework properties", p.getProperties().get("complexTest:complexItem")); } private List<Map<String, Object>> createComplexPropertyList(int listSize) { List<Map<String, Object>> list = new ArrayList<>(); Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(1234500000000L); for (int i = 1; i <= listSize; i++) { HashMap<String, Object> map = new HashMap<String, Object>(); list.add(map); map.put("stringProp", "testString" + i); map.put("dateProp", cal); map.put("enumProp", "ValueA"); List<String> arrayProp = new ArrayList<String>(); map.put("arrayProp", arrayProp); for (int j = 1; j <= i; j++) { arrayProp.add(Integer.toString(j)); } map.put("intProp", Integer.valueOf(123)); map.put("boolProp", Boolean.TRUE); map.put("floatProp", Double.valueOf(123.45)); } return list; } private ArrayList<ObjectNode> createComplexNodeList(int listSize, DateTimeFormat dateTimeFormat) { ObjectMapper mapper = new ObjectMapper(); ArrayList<ObjectNode> jsonObjects = new ArrayList<ObjectNode>(); for (int i = 1; i <= listSize; i++) { ObjectNode jsonObj = mapper.createObjectNode(); jsonObj.put("stringProp", "testString" + i); if (dateTimeFormat.equals(DateTimeFormat.TIME_IN_MILLIS)) { jsonObj.put("dateProp", 1234500000000L); } else { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(1234500000000L); String dateStr = DateParser.formatW3CDateTime(cal.getTime()); jsonObj.put("dateProp", dateStr); } jsonObj.put("enumProp", "ValueA"); ArrayNode jsonArray = mapper.createArrayNode(); jsonObj.put("arrayProp", jsonArray); for (int j = 1; j <= i; j++) { jsonArray.add(Integer.toString(j)); } jsonObj.put("intProp", 123); jsonObj.put("boolProp", true); jsonObj.put("floatProp", 123.45d); jsonObjects.add(jsonObj); } return jsonObjects; } private void assertComplexPropertyNodeEquals(Map<String, Object> propMap, JsonNode jsonNode, DateTimeFormat dateTimeFormat) throws IOException { List<String> nodeKeys = copyIterator(jsonNode.getFieldNames()); Set<String> propKeys = propMap.keySet(); assertEquals(nodeKeys.size(), propKeys.size()); nodeKeys.containsAll(propKeys); for (String key : propKeys) { Object origVal = propMap.get(key); if (origVal instanceof ArrayList || origVal instanceof Object[]) { List<Object> origList; if (origVal instanceof ArrayList) { @SuppressWarnings("unchecked") List<Object> l = (List<Object>) origVal; origList = l; } else { origList = Arrays.asList((Object[]) origVal); } ArrayNode jsonArray = (ArrayNode) jsonNode.get(key); for (int i = 0; i < origList.size(); i++) { assertEquals("Wrong value at key [" + key + "] index [" + i + "]", origList.get(i).toString(), jsonArray.get(i).getValueAsText()); } } else { if (origVal instanceof Calendar) { if (DateTimeFormat.TIME_IN_MILLIS.equals(dateTimeFormat)) { origVal = Long.valueOf(((Calendar) origVal).getTimeInMillis()); } else { origVal = DateParser.formatW3CDateTime(((Calendar) origVal).getTime()); } } assertEquals("Wrong value at key [" + key + "]", origVal.toString(), jsonNode.get(key).getValueAsText()); } } } private <T> List<T> copyIterator(Iterator<T> iter) { List<T> copy = new ArrayList<T>(); while (iter.hasNext()) copy.add(iter.next()); return copy; } }