/* * (C) Copyright 2006-2016 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: * Florent Guillaume */ package org.nuxeo.ecm.core.opencmis.impl; import static org.apache.chemistry.opencmis.commons.BasicPermissions.ALL; import static org.apache.chemistry.opencmis.commons.BasicPermissions.READ; import static org.apache.chemistry.opencmis.commons.BasicPermissions.WRITE; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_POLICY; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_APPLY_ACL_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKIN_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKOUT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_FOLDER_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_SOURCE; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_TARGET; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_TREE_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ACL_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_APPLIED_POLICIES_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_CHILDREN_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_DESCENDENTS_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PARENTS_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PROPERTIES_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_SOURCE; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_TARGET; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_POLICY; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_SET_CONTENT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_VIEW_CONTENT_OBJECT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.Ace; import org.apache.chemistry.opencmis.commons.data.Acl; import org.apache.chemistry.opencmis.commons.data.AclCapabilities; import org.apache.chemistry.opencmis.commons.data.AllowableActions; import org.apache.chemistry.opencmis.commons.data.ChangeEventInfo; import org.apache.chemistry.opencmis.commons.data.ContentStream; import org.apache.chemistry.opencmis.commons.data.ObjectData; import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer; import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; import org.apache.chemistry.opencmis.commons.data.ObjectList; import org.apache.chemistry.opencmis.commons.data.ObjectParentData; import org.apache.chemistry.opencmis.commons.data.PermissionMapping; import org.apache.chemistry.opencmis.commons.data.Principal; 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.data.RepositoryCapabilities; import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition; import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList; import org.apache.chemistry.opencmis.commons.enums.AclPropagation; import org.apache.chemistry.opencmis.commons.enums.Action; import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl; import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges; import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates; import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions; import org.apache.chemistry.opencmis.commons.enums.ChangeType; import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions; import org.apache.chemistry.opencmis.commons.enums.VersioningState; import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException; import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; import org.apache.chemistry.opencmis.commons.spi.Holder; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.common.Environment; 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.api.PathRef; import org.nuxeo.ecm.core.api.RecoverableClientException; import org.nuxeo.ecm.core.api.security.ACE; import org.nuxeo.ecm.core.api.security.ACL; import org.nuxeo.ecm.core.api.security.ACP; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.core.api.security.impl.ACLImpl; import org.nuxeo.ecm.core.api.security.impl.ACPImpl; import org.nuxeo.ecm.core.opencmis.impl.server.NuxeoRepository; import org.nuxeo.ecm.core.opencmis.impl.server.NuxeoTypeHelper; import org.nuxeo.ecm.core.opencmis.tests.Helper; import org.nuxeo.ecm.core.test.annotations.Granularity; import org.nuxeo.ecm.core.test.annotations.RepositoryConfig; import org.nuxeo.ecm.core.work.api.WorkManager; import org.nuxeo.elasticsearch.api.ElasticSearchAdmin; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.services.config.ConfigurationService; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.LocalDeploy; import org.nuxeo.runtime.test.runner.RuntimeHarness; import org.nuxeo.runtime.transaction.TransactionHelper; /** * Tests that hit directly the server APIs. * <p> * Uses CMISQL to NXQL conversion for queries, which disallows JOINs. */ @RunWith(FeaturesRunner.class) @Features({ CmisFeature.class, CmisFeatureConfiguration.class }) @LocalDeploy("org.nuxeo.ecm.core.opencmis.tests.tests:OSGI-INF/types-contrib.xml") @RepositoryConfig(cleanup = Granularity.METHOD) public class TestCmisBinding extends TestCmisBindingBase { public static final String NUXEO_ROOT_TYPE = "Root"; // from Nuxeo public static final String NUXEO_ROOT_NAME = ""; // NuxeoPropertyDataName; // stream content with non-ASCII characters public static final String STREAM_CONTENT = "Caf\u00e9 Diem\none\0two"; public static final String COMPLEX_TITLE = "Is this my/your caf\u00e9?"; @Inject protected RuntimeHarness harness; @Inject protected CoreSession coreSession; @Inject protected WorkManager workManager; @Before public void setUp() throws Exception { // wait indexing of /default-domain as we need to delete it in setUpData waitForIndexing(); setUpBinding(coreSession); setUpData(coreSession); waitForIndexing(); } @After public void tearDown() throws Exception { tearDownBinding(); waitForIndexing(); } public void reSetUp(String username) { tearDownBinding(); setUpBinding(coreSession, username); } // ----- protected String createDocument(String name, String folderId, String typeId) { return objService.createDocument(repositoryId, createBaseDocumentProperties(name, typeId), folderId, null, null, null, null, null, null); } protected String createFolder(String name, String folderId, String typeId) { return objService.createFolder(repositoryId, createBaseDocumentProperties(name, typeId), folderId, null, null, null, null); } protected Properties createBaseDocumentProperties(String name, String typeId) { List<PropertyData<?>> props = new ArrayList<>(); props.add(factory.createPropertyStringData(PropertyIds.NAME, name)); props.add(factory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, typeId)); return factory.createPropertiesData(props); } protected Properties createProperties(String key, String value) { PropertyString prop = factory.createPropertyStringData(key, value); return factory.createPropertiesData(Collections.<PropertyData<?>> singletonList(prop)); } protected ObjectData getObject(String id) { return objService.getObject(repositoryId, id, null, Boolean.FALSE, IncludeRelationships.BOTH, null, Boolean.FALSE, Boolean.FALSE, null); } protected ObjectData getObjectByPath(String path) { return objService.getObjectByPath(repositoryId, path, null, null, null, null, null, null, null); } protected ObjectList query(String statement) { return discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, null, null, null); } protected static Object getValue(ObjectData data, String key) { PropertyData<?> pd = data.getProperties().getProperties().get(key); return pd == null ? null : pd.getFirstValue(); } protected static Object getValues(ObjectData data, String key) { PropertyData<?> pd = data.getProperties().getProperties().get(key); return pd == null ? null : pd.getValues(); } protected static String getString(ObjectData data, String key) { return (String) getValue(data, key); } protected static Object getQueryValue(ObjectData data, String queryName) { Properties properties = data.getProperties(); for (PropertyData<?> pd : properties.getPropertyList()) { if (queryName.equals(pd.getQueryName())) { return pd.getFirstValue(); } } return null; } @Test public void testGetRepositoryInfos() { List<RepositoryInfo> infos = repoService.getRepositoryInfos(null); assertEquals(1, infos.size()); checkInfo(infos.get(0)); } @Test public void testGetRepositoryInfo() { RepositoryInfo info = repoService.getRepositoryInfo(repositoryId, null); checkInfo(info); } protected static Set<String> set(String... strings) { return new HashSet<>(Arrays.asList(strings)); } protected void checkInfo(RepositoryInfo info) { assertEquals(repositoryId, info.getId()); assertEquals("Nuxeo Repository " + repositoryId, info.getName()); assertEquals("Nuxeo Repository " + repositoryId, info.getDescription()); assertEquals("Nuxeo", info.getVendorName()); assertEquals("Nuxeo OpenCMIS Connector", info.getProductName()); String version = Framework.getProperty(Environment.DISTRIBUTION_VERSION, "5.5 dev"); assertEquals(version, info.getProductVersion()); assertEquals(rootFolderId, info.getRootFolderId()); assertEquals("Guest", info.getPrincipalIdAnonymous()); assertEquals("1.1", info.getCmisVersionSupported()); // TODO assertEquals("...", info.getThinClientUri()); assertNotNull(info.getLatestChangeLogToken()); assertEquals(Boolean.FALSE, info.getChangesIncomplete()); assertEquals(Arrays.asList(BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER), info.getChangesOnType()); assertEquals(SecurityConstants.EVERYONE, info.getPrincipalIdAnyone()); // capabilities RepositoryCapabilities caps = info.getCapabilities(); assertEquals(CapabilityAcl.MANAGE, caps.getAclCapability()); assertEquals(CapabilityChanges.OBJECTIDSONLY, caps.getChangesCapability()); assertEquals(CapabilityContentStreamUpdates.PWCONLY, caps.getContentStreamUpdatesCapability()); assertEquals(supportsJoins() ? CapabilityJoin.INNERANDOUTER : CapabilityJoin.NONE, caps.getJoinCapability()); assertEquals(CapabilityQuery.BOTHCOMBINED, caps.getQueryCapability()); assertEquals(CapabilityRenditions.READ, caps.getRenditionsCapability()); // ACL capabilities AclCapabilities aclCaps = info.getAclCapabilities(); assertEquals(AclPropagation.PROPAGATE, aclCaps.getAclPropagation()); assertEquals(SupportedPermissions.REPOSITORY, aclCaps.getSupportedPermissions()); Map<String, String> permDefs = new HashMap<>(); for (PermissionDefinition pd : aclCaps.getPermissions()) { permDefs.put(pd.getId(), pd.getDescription()); } Map<String, String> expectedPermDefs = new HashMap<>(); expectedPermDefs.put(READ, "Read"); expectedPermDefs.put(WRITE, "Write"); expectedPermDefs.put(ALL, "All"); expectedPermDefs.put(NuxeoRepository.NUXEO_READ_REMOVE, "Remove"); assertEquals(expectedPermDefs, permDefs); Map<String, Set<String>> permMap = new HashMap<>(); for (PermissionMapping permissonMapping : aclCaps.getPermissionMapping().values()) { String key = permissonMapping.getKey(); List<String> perms = permissonMapping.getPermissions(); permMap.put(key, new HashSet<>(perms)); } Map<String, Set<String>> expectedPermMap = new HashMap<>(); expectedPermMap.put(CAN_GET_DESCENDENTS_FOLDER, set(READ)); expectedPermMap.put(CAN_GET_CHILDREN_FOLDER, set(READ)); expectedPermMap.put(CAN_GET_PARENTS_FOLDER, set(READ)); expectedPermMap.put(CAN_GET_FOLDER_PARENT_OBJECT, set(READ)); expectedPermMap.put(CAN_CREATE_DOCUMENT_FOLDER, set(WRITE)); expectedPermMap.put(CAN_CREATE_FOLDER_FOLDER, set(WRITE)); expectedPermMap.put(CAN_CREATE_RELATIONSHIP_SOURCE, set(READ)); expectedPermMap.put(CAN_CREATE_RELATIONSHIP_TARGET, set(READ)); expectedPermMap.put(CAN_GET_PROPERTIES_OBJECT, set(READ)); expectedPermMap.put(CAN_VIEW_CONTENT_OBJECT, set(READ)); expectedPermMap.put(CAN_UPDATE_PROPERTIES_OBJECT, set(WRITE)); expectedPermMap.put(CAN_MOVE_OBJECT, set(WRITE)); expectedPermMap.put(CAN_MOVE_TARGET, set(WRITE)); expectedPermMap.put(CAN_MOVE_SOURCE, set(WRITE)); expectedPermMap.put(CAN_DELETE_OBJECT, set(WRITE)); expectedPermMap.put(CAN_DELETE_TREE_FOLDER, set(WRITE)); expectedPermMap.put(CAN_SET_CONTENT_DOCUMENT, set(WRITE)); expectedPermMap.put(CAN_DELETE_CONTENT_DOCUMENT, set(WRITE)); expectedPermMap.put(CAN_ADD_TO_FOLDER_OBJECT, set(WRITE)); expectedPermMap.put(CAN_ADD_TO_FOLDER_FOLDER, set(WRITE)); expectedPermMap.put(CAN_REMOVE_FROM_FOLDER_OBJECT, set(WRITE)); expectedPermMap.put(CAN_REMOVE_FROM_FOLDER_FOLDER, set(WRITE)); expectedPermMap.put(CAN_CHECKOUT_DOCUMENT, set(WRITE)); expectedPermMap.put(CAN_CANCEL_CHECKOUT_DOCUMENT, set(WRITE)); expectedPermMap.put(CAN_CHECKIN_DOCUMENT, set(WRITE)); expectedPermMap.put(CAN_GET_ALL_VERSIONS_VERSION_SERIES, set(READ)); expectedPermMap.put(CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, set(READ)); expectedPermMap.put(CAN_ADD_POLICY_OBJECT, set(WRITE)); expectedPermMap.put(CAN_ADD_POLICY_POLICY, set(WRITE)); expectedPermMap.put(CAN_REMOVE_POLICY_OBJECT, set(WRITE)); expectedPermMap.put(CAN_REMOVE_POLICY_POLICY, set(WRITE)); expectedPermMap.put(CAN_GET_APPLIED_POLICIES_OBJECT, set(READ)); expectedPermMap.put(CAN_GET_ACL_OBJECT, set(READ)); expectedPermMap.put(CAN_APPLY_ACL_OBJECT, set(ALL)); assertEquals(expectedPermMap, permMap); } @Test public void testGetTypeDefinition() { TypeDefinition type; type = repoService.getTypeDefinition(repositoryId, "cmis:folder", null); assertEquals(Boolean.TRUE, type.isCreatable()); assertNull(type.getParentTypeId()); assertEquals("cmis:folder", type.getLocalName()); assertTrue(type.getPropertyDefinitions().containsKey("dc:title")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:lifecycleState")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:secondaryObjectTypeIds")); assertFalse(type.getPropertyDefinitions().containsKey("nuxeo:isVersion")); assertFalse(type.getPropertyDefinitions().containsKey("nuxeo:contentStreamDigest")); type = repoService.getTypeDefinition(repositoryId, "Folder", null); assertEquals(Boolean.TRUE, type.isCreatable()); assertEquals("cmis:folder", type.getParentTypeId()); assertEquals("Folder", type.getLocalName()); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:lifecycleState")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:secondaryObjectTypeIds")); assertFalse(type.getPropertyDefinitions().containsKey("nuxeo:isVersion")); assertFalse(type.getPropertyDefinitions().containsKey("nuxeo:contentStreamDigest")); type = repoService.getTypeDefinition(repositoryId, "cmis:document", null); assertEquals(Boolean.TRUE, type.isCreatable()); assertNull(type.getParentTypeId()); assertEquals("cmis:document", type.getLocalName()); assertTrue(type.getPropertyDefinitions().containsKey("dc:title")); assertTrue(type.getPropertyDefinitions().containsKey("cmis:contentStreamFileName")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:lifecycleState")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:secondaryObjectTypeIds")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:isVersion")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:contentStreamDigest")); try { // nosuchtype, Document is mapped to cmis:document repoService.getTypeDefinition(repositoryId, "Document", null); fail(); } catch (CmisInvalidArgumentException e) { // ok } type = repoService.getTypeDefinition(repositoryId, "Note", null); assertEquals(Boolean.TRUE, type.isCreatable()); assertEquals("cmis:document", type.getParentTypeId()); assertEquals("Note", type.getLocalName()); assertTrue(type.getPropertyDefinitions().containsKey("note")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:lifecycleState")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:secondaryObjectTypeIds")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:isVersion")); assertTrue(type.getPropertyDefinitions().containsKey("nuxeo:contentStreamDigest")); type = repoService.getTypeDefinition(repositoryId, "MyForum", null); assertEquals(BaseTypeId.CMIS_FOLDER, type.getBaseTypeId()); assertEquals("cmis:folder", type.getParentTypeId()); type = repoService.getTypeDefinition(repositoryId, "MyForum2", null); assertEquals(BaseTypeId.CMIS_FOLDER, type.getBaseTypeId()); assertEquals("cmis:folder", type.getParentTypeId()); } public List<String> getTypeIds(TypeDefinitionList types) { List<String> ids = new ArrayList<>(); for (TypeDefinition type : types.getList()) { ids.add(type.getId()); } return ids; } @Test public void testGetTypeChildrenBase() { TypeDefinitionList types = repoService.getTypeChildren(repositoryId, null, Boolean.FALSE, null, null, null); List<String> ids = getTypeIds(types); assertEquals(4, ids.size()); assertTrue(ids.contains(BaseTypeId.CMIS_DOCUMENT.value())); assertTrue(ids.contains(BaseTypeId.CMIS_FOLDER.value())); assertTrue(ids.contains(BaseTypeId.CMIS_RELATIONSHIP.value())); assertTrue(ids.contains(BaseTypeId.CMIS_SECONDARY.value())); } @Test public void testGetTypeChildren() { TypeDefinitionList types = repoService.getTypeChildren(repositoryId, "cmis:folder", Boolean.FALSE, null, null, null); for (TypeDefinition type : types.getList()) { Map<String, PropertyDefinition<?>> pd = type.getPropertyDefinitions(); assertNotNull(pd); assertEquals(0, pd.size()); } List<String> ids = getTypeIds(types); assertTrue(ids.contains("Folder")); assertTrue(ids.contains("Root")); assertTrue(ids.contains("Domain")); assertTrue(ids.contains("OrderedFolder")); assertTrue(ids.contains("Workspace")); assertTrue(ids.contains("Section")); // batching types = repoService.getTypeChildren(repositoryId, "cmis:folder", Boolean.FALSE, BigInteger.valueOf(4), BigInteger.valueOf(2), null); List<String> ids2 = getTypeIds(types); assertEquals(4, ids2.size()); assertFalse(ids2.contains(ids.get(0))); assertFalse(ids2.contains(ids.get(1))); // batching beyond max size types = repoService.getTypeChildren(repositoryId, "cmis:folder", Boolean.FALSE, BigInteger.valueOf(12), BigInteger.valueOf(5), null); List<String> ids3 = getTypeIds(types); assertEquals(ids.size() - 5, ids3.size()); assertFalse(ids3.contains(ids.get(0))); assertFalse(ids3.contains(ids.get(1))); assertFalse(ids3.contains(ids.get(2))); assertFalse(ids3.contains(ids.get(3))); assertFalse(ids3.contains(ids.get(4))); // check property definition inclusion types = repoService.getTypeChildren(repositoryId, BaseTypeId.CMIS_FOLDER.value(), Boolean.TRUE, null, null, null); for (TypeDefinition type : types.getList()) { Map<String, PropertyDefinition<?>> pd = type.getPropertyDefinitions(); assertNotNull(pd); // dublincore in all types assertTrue(pd.keySet().contains("dc:title")); } ids = getTypeIds(types); assertTrue(ids.contains("MyForum")); assertTrue(ids.contains("MyForum2")); types = repoService.getTypeChildren(repositoryId, BaseTypeId.CMIS_DOCUMENT.value(), Boolean.TRUE, null, null, null); for (TypeDefinition type : types.getList()) { Map<String, PropertyDefinition<?>> pd = type.getPropertyDefinitions(); assertNotNull(pd); // dublincore in all types assertTrue(pd.keySet().contains("dc:title")); } ids = getTypeIds(types); assertTrue(ids.contains("File")); assertTrue(ids.contains("Note")); assertTrue(ids.contains("MyDocType")); // nonexistent type try { repoService.getTypeChildren(repositoryId, "nosuchtype", Boolean.TRUE, null, null, null); fail(); } catch (CmisInvalidArgumentException e) { // ok } } @Test public void testGetTypeChildrenSecondary() { TypeDefinitionList types = repoService.getTypeChildren(repositoryId, "cmis:secondary", Boolean.FALSE, null, null, null); for (TypeDefinition type : types.getList()) { Map<String, PropertyDefinition<?>> pd = type.getPropertyDefinitions(); assertNotNull(pd); assertEquals(0, pd.size()); } List<String> ids = getTypeIds(types); assertTrue(ids.contains("facet:CustomFacetWithMySchema2")); assertTrue(ids.contains("facet:CustomFacetWithoutSchema")); assertTrue(ids.contains("facet:ComplexTest")); assertTrue(ids.contains("facet:HasRelatedText")); assertTrue(ids.contains("facet:Versionable")); assertTrue(ids.contains("facet:Folderish")); } @Test public void testGetTypeDescendants() { List<TypeDefinitionContainer> desc = repoService.getTypeDescendants(repositoryId, "cmis:folder", null, Boolean.FALSE, null); assertTrue(desc.size() > 2); TypeDefinition t = null; for (TypeDefinitionContainer tc : desc) { TypeDefinition type = tc.getTypeDefinition(); if (type.getId().equals("OrderedFolder")) { t = type; } } assertNotNull(t); // nonexistent type try { repoService.getTypeDescendants(repositoryId, "nosuchtype", null, Boolean.FALSE, null); fail(); } catch (CmisInvalidArgumentException e) { // ok } } @Test public void testRoot() { ObjectData root = getObject(rootFolderId); assertNotNull(root.getId()); assertEquals(NUXEO_ROOT_TYPE, getString(root, PropertyIds.OBJECT_TYPE_ID)); assertEquals(NUXEO_ROOT_NAME, getString(root, PropertyIds.NAME)); assertEquals("/", getString(root, PropertyIds.PATH)); // root parent assertNull(getString(root, PropertyIds.PARENT_ID)); ObjectData parent = navService.getFolderParent(repositoryId, rootFolderId, null, null); assertNull(parent); List<ObjectParentData> parents = navService.getObjectParents(repositoryId, rootFolderId, null, null, null, null, null, null); assertEquals(0, parents.size()); } @Test public void testGetObjectByPath() { ObjectData ob; ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); // works by cmis:name too, needed for Adobe Drive 2 ob = getObjectByPath("/testfolder1_Title/testfile1_Title"); assertEquals("testfile1_Title", getString(ob, "dc:title")); // cannot mix both try { getObjectByPath("/testfolder1/testfile1_Title"); fail(); } catch (CmisObjectNotFoundException e) { // ok } } @Test public void testCreateDocument() { String id; ObjectData data; id = createDocument("newdoc", rootFolderId, "File"); assertNotNull(id); data = getObject(id); assertEquals(id, data.getId()); assertEquals("newdoc", getString(data, PropertyIds.NAME)); assertEquals(Boolean.TRUE, getValue(data, PropertyIds.IS_LATEST_MAJOR_VERSION)); assertEquals(Boolean.FALSE, getValue(data, PropertyIds.IS_IMMUTABLE)); assertEquals("File", getString(data, PropertyIds.OBJECT_TYPE_ID)); assertEquals(Boolean.FALSE, // ... getValue(data, NuxeoTypeHelper.NX_ISVERSION)); assertEquals("project", getValue(data, NuxeoTypeHelper.NX_LIFECYCLE_STATE)); assertEquals(rootFolderId, getValue(data, NuxeoTypeHelper.NX_PARENT_ID)); @SuppressWarnings("unchecked") List<String> facets = (List<String>) getValues(data, NuxeoTypeHelper.NX_FACETS); assertEquals(set( // "Commentable", // "Downloadable", // "HasRelatedText", // "Publishable", // "Versionable" // ), new HashSet<>(facets)); assertEquals(null, getString(data, NuxeoTypeHelper.NX_DIGEST)); @SuppressWarnings("unchecked") List<String> hashes = (List<String>) getValues(data, PropertyIds.CONTENT_STREAM_HASH); assertEquals(0, hashes.size()); // creation of a cmis:document (helps simple clients) id = createDocument("newdoc2", rootFolderId, "cmis:document"); assertNotNull(id); data = getObject(id); assertEquals(id, data.getId()); assertEquals("newdoc2", getString(data, PropertyIds.NAME)); assertEquals("File", getString(data, PropertyIds.OBJECT_TYPE_ID)); } @Test public void testCreateFolder() { String id = createFolder("newfold", rootFolderId, "Folder"); assertNotNull(id); ObjectData data = getObject(id); assertEquals(id, data.getId()); assertEquals("newfold", getString(data, PropertyIds.NAME)); assertEquals("Folder", getString(data, PropertyIds.OBJECT_TYPE_ID)); assertEquals("project", getValue(data, NuxeoTypeHelper.NX_LIFECYCLE_STATE)); assertEquals(Arrays.asList("Folderish"), getValues(data, NuxeoTypeHelper.NX_FACETS)); // creation of a cmis:folder (helps simple clients) id = createFolder("newfold2", rootFolderId, "cmis:folder"); assertNotNull(id); data = getObject(id); assertEquals(id, data.getId()); assertEquals("newfold2", getString(data, PropertyIds.NAME)); assertEquals("Folder", getString(data, PropertyIds.OBJECT_TYPE_ID)); } protected String createDocumentMyDocType() { List<PropertyData<?>> props = new ArrayList<>(); props.add(factory.createPropertyStringData(PropertyIds.NAME, COMPLEX_TITLE)); props.add(factory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, "MyDocType")); props.add(factory.createPropertyStringData("my:string", "abc")); props.add(factory.createPropertyBooleanData("my:boolean", Boolean.TRUE)); props.add(factory.createPropertyIntegerData("my:integer", BigInteger.valueOf(123))); props.add(factory.createPropertyIntegerData("my:long", BigInteger.valueOf(123))); props.add(factory.createPropertyDecimalData("my:double", BigDecimal.valueOf(123.456))); GregorianCalendar expectedDate = Helper.getCalendar(2010, 9, 30, 16, 4, 55); props.add(factory.createPropertyDateTimeData("my:date", expectedDate)); Properties properties = factory.createPropertiesData(props); String id = objService.createDocument(repositoryId, properties, rootFolderId, null, VersioningState.CHECKEDOUT, null, null, null, null); assertNotNull(id); if (TransactionHelper.isTransactionActive()) { TransactionHelper.commitOrRollbackTransaction(); TransactionHelper.startTransaction(); } return id; } @Test public void testCreateDocumentMyDocType() { String id = createDocumentMyDocType(); ObjectData data = getObject(id); assertEquals(id, data.getId()); assertEquals(COMPLEX_TITLE, getString(data, PropertyIds.NAME)); assertEquals("MyDocType", getString(data, PropertyIds.OBJECT_TYPE_ID)); assertEquals("abc", getString(data, "my:string")); assertEquals(Boolean.TRUE, getValue(data, "my:boolean")); assertEquals(BigInteger.valueOf(123), getValue(data, "my:integer")); assertEquals(BigInteger.valueOf(123), getValue(data, "my:long")); assertEquals(BigDecimal.valueOf(123.456), getValue(data, "my:double")); GregorianCalendar date = (GregorianCalendar) getValue(data, "my:date"); GregorianCalendar expectedDate = Helper.getCalendar(2010, 9, 30, 16, 4, 55); if (expectedDate.getTimeInMillis() != date.getTimeInMillis()) { // there may be a timezone difference if the database // doesn't store timezones -> try with local timezone TimeZone tz = TimeZone.getDefault(); GregorianCalendar localDate = Helper.getCalendar(2010, 9, 30, 16, 4, 55, tz); assertEquals(localDate.getTimeInMillis(), date.getTimeInMillis()); } // check path segment created from name/title List<ObjectParentData> parents = navService.getObjectParents(repositoryId, id, null, null, null, null, Boolean.TRUE, null); assertEquals(1, parents.size()); String pathSegment = parents.get(0).getRelativePathSegment(); assertEquals(COMPLEX_TITLE.replace("/", "-"), pathSegment); } @Test public void testCreateDocumentWithContentStream() throws Exception { // null filename passed on purpose, size ignored by Nuxeo ContentStream cs = new ContentStreamImpl(null, "text/plain", Helper.FILE1_CONTENT); String id = objService.createDocument(repositoryId, createBaseDocumentProperties("doc1.txt", "File"), rootFolderId, cs, VersioningState.NONE, null, null, null, null); assertNotNull(id); ObjectData data = getObject(id); assertEquals(id, data.getId()); assertEquals("doc1.txt", getString(data, PropertyIds.NAME)); assertEquals("bde9eb59c76cb432a0f8d02057a19923", getString(data, NuxeoTypeHelper.NX_DIGEST)); @SuppressWarnings("unchecked") List<String> hashes = (List<String>) getValues(data, PropertyIds.CONTENT_STREAM_HASH); assertEquals("{md5}bde9eb59c76cb432a0f8d02057a19923", hashes.get(0)); cs = objService.getContentStream(repositoryId, id, null, null, null, null); assertNotNull(cs); assertEquals("text/plain", cs.getMimeType()); assertEquals("doc1.txt", cs.getFileName()); assertEquals(Helper.FILE1_CONTENT.length(), cs.getLength()); assertEquals(Helper.FILE1_CONTENT, Helper.read(cs.getStream(), "UTF-8")); } @Test public void testCreateDocumentImplicitType() throws Exception { List<PropertyData<?>> props = new ArrayList<>(); props.add(factory.createPropertyStringData(PropertyIds.NAME, "doc.txt")); props.add(factory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, "cmis:document")); props.add(factory.createPropertyStringData("dc:description", "my doc")); Properties properties = factory.createPropertiesData(props); String id = objService.createDocument(repositoryId, properties, rootFolderId, null, VersioningState.CHECKEDOUT, null, null, null, null); ObjectData data = getObject(id); // check that the filename was enough to detect that we need a more // specific type than File assertEquals("Note", getValue(data, PropertyIds.OBJECT_TYPE_ID)); // other props were set assertEquals("my doc", getValue(data, "dc:description")); } @Test public void testCreateDocumentWithoutName() throws Exception { List<PropertyData<?>> props = new ArrayList<>(); props.add(factory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, "cmis:document")); Properties properties = factory.createPropertiesData(props); try { objService.createDocument(repositoryId, properties, rootFolderId, null, VersioningState.NONE, null, null, null, null); fail("Creation without cmis:name should fail"); } catch (CmisConstraintException e) { // ok } } protected Holder<String> getChangeTokenHolder(ObjectData ob) { return getChangeTokenHolder(ob.getProperties()); } protected Holder<String> getChangeTokenHolder(Properties p) { return new Holder<>((String) p.getProperties().get("cmis:changeToken").getFirstValue()); } @Test public void testUpdateProperties() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); Properties props = createProperties("dc:title", "new title"); Holder<String> objectIdHolder = new Holder<>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(ob); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, props, null); assertEquals(ob.getId(), objectIdHolder.getValue()); ob = getObject(ob.getId()); assertEquals("new title", getString(ob, "dc:title")); List<Holder<String>> changeTokenHolders = Arrays.asList(new Holder<>(null), new Holder<>("bogusChangeToken")); for (Holder<String> ctHolder : changeTokenHolders) { try { objService.updateProperties(repositoryId, objectIdHolder, ctHolder, props, null); fail(String.format("updateProperties with '%s' cmis:changeToken should fail", ctHolder)); } catch (CmisUpdateConflictException e) { // ok } } } @Test public void testGetProperties() throws Exception { Properties p; ObjectData ob = getObjectByPath("/testfolder1/testfile1"); p = objService.getProperties(repositoryId, ob.getId(), null, null); assertNotNull(p); assertEquals("testfile1_Title", p.getProperties().get("dc:title").getFirstValue()); // null value from nuxeo property PropertyData<?> v; v = p.getProperties().get("dc:nature"); assertNull(v.getFirstValue()); assertEquals(Collections.emptyList(), v.getValues()); // null value from NuxeoPropertyStringDataFixed v = p.getProperties().get("nuxeo:pos"); assertNull(v.getFirstValue()); assertEquals(Collections.emptyList(), v.getValues()); v = p.getProperties().get("cmis:changeToken"); Calendar lastModified = (Calendar) p.getProperties().get("dc:modified").getFirstValue(); assertEquals(Long.toString(lastModified.getTimeInMillis()), v.getFirstValue()); // with filter p = objService.getProperties(repositoryId, ob.getId(), "cmis:name", null); assertNull(p.getProperties().get("dc:title")); assertEquals("testfile1_Title", p.getProperties().get("cmis:name").getFirstValue()); } @Test public void testPropertyFromSecondaryType() throws Exception { DocumentModel doc = coreSession.getDocument(new PathRef("/testfolder1/testfile1")); doc.addFacet("CustomFacetWithMySchema2"); doc.setPropertyValue("my2:string", "foo"); coreSession.saveDocument(doc); coreSession.save(); nextTransaction(); waitForIndexing(); ObjectData ob = getObjectByPath("/testfolder1/testfile1"); Properties p = objService.getProperties(repositoryId, ob.getId(), null, null); Map<String, PropertyData<?>> properties = p.getProperties(); PropertyData<?> pd = properties.get("cmis:secondaryObjectTypeIds"); assertNotNull(pd); @SuppressWarnings("unchecked") List<String> stl = (List<String>) pd.getValues(); assertNotNull(stl); assertTrue(stl.contains("facet:CustomFacetWithMySchema2")); pd = properties.get("my2:string"); assertNotNull(pd); assertEquals("foo", pd.getFirstValue()); // change secondary prop Properties props = createProperties("my2:string", "bar"); Holder<String> objectIdHolder = new Holder<String>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(p); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, props, null); // re-fetch p = objService.getProperties(repositoryId, ob.getId(), null, null); pd = p.getProperties().get("my2:string"); assertEquals("bar", pd.getFirstValue()); } @Test public void testContentStream() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, PropertyIds.NAME)); assertEquals("bde9eb59c76cb432a0f8d02057a19923", getString(ob, NuxeoTypeHelper.NX_DIGEST)); @SuppressWarnings("unchecked") List<String> hashes = (List<String>) getValues(ob, PropertyIds.CONTENT_STREAM_HASH); assertEquals("{md5}bde9eb59c76cb432a0f8d02057a19923", hashes.get(0)); // get stream ContentStream cs = objService.getContentStream(repositoryId, ob.getId(), null, null, null, null); assertNotNull(cs); assertEquals("text/plain", cs.getMimeType()); assertEquals("testfile.txt", cs.getFileName()); assertEquals(Helper.FILE1_CONTENT.length(), cs.getLength()); assertEquals(Helper.FILE1_CONTENT, Helper.read(cs.getStream(), "UTF-8")); // set stream cs = new ContentStreamImpl("foo.txt", "text/plain; charset=UTF-8", STREAM_CONTENT); Holder<String> objectIdHolder = new Holder<>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(ob); objService.setContentStream(repositoryId, objectIdHolder, Boolean.TRUE, changeTokenHolder, cs, null); assertEquals(ob.getId(), objectIdHolder.getValue()); List<Holder<String>> changeTokenHolders = Arrays.asList(new Holder<>(null), new Holder<>("bogusChangeToken")); for (Holder<String> ctHolder : changeTokenHolders) { try { objService.setContentStream(repositoryId, objectIdHolder, Boolean.TRUE, ctHolder, cs, null); fail(String.format("setContentStream with '%s' cmis:changeToken should fail", ctHolder)); } catch (CmisUpdateConflictException e) { // ok } } // refetch cs = objService.getContentStream(repositoryId, ob.getId(), null, null, null, null); assertNotNull(cs); assertEquals("text/plain; charset=UTF-8", cs.getMimeType()); assertEquals("foo.txt", cs.getFileName()); assertEquals(STREAM_CONTENT.getBytes("UTF-8").length, cs.getLength()); assertEquals(STREAM_CONTENT, Helper.read(cs.getStream(), "UTF-8")); // delete ob = objService.getObject(repositoryId, ob.getId(), null, null, null, null, null, null, null); changeTokenHolder = getChangeTokenHolder(ob); objService.deleteContentStream(repositoryId, objectIdHolder, changeTokenHolder, null); for (Holder<String> ctHolder : changeTokenHolders) { try { objService.deleteContentStream(repositoryId, objectIdHolder, ctHolder, null); fail(String.format("deleteContentStream with '%s' cmis:changeToken should fail", ctHolder)); } catch (CmisUpdateConflictException e) { // ok } } // refetch try { cs = objService.getContentStream(repositoryId, ob.getId(), null, null, null, null); fail("Should have no content stream"); } catch (CmisConstraintException e) { // ok } } @Test public void testGetChildren() { ObjectInFolderList res; String orderBy; orderBy = "cmis:name"; res = navService.getChildren(repositoryId, rootFolderId, null, orderBy, null, null, null, null, null, null, null); assertEquals("testfolder1_Title", getValue(res.getObjects().get(0).getObject(), "cmis:name")); assertEquals("testfolder2_Title", getValue(res.getObjects().get(1).getObject(), "cmis:name")); orderBy = "cmis:name DESC"; res = navService.getChildren(repositoryId, rootFolderId, null, orderBy, null, null, null, null, null, null, null); assertEquals("testfolder2_Title", getValue(res.getObjects().get(0).getObject(), "cmis:name")); assertEquals("testfolder1_Title", getValue(res.getObjects().get(1).getObject(), "cmis:name")); } // flatten and order children protected static List<String> flatTree(List<ObjectInFolderContainer> tree) throws Exception { if (tree == null) { return null; } List<String> r = new LinkedList<>(); for (ObjectInFolderContainer child : tree) { String name = getString(child.getObject().getObject(), PropertyIds.NAME); String elem = name; List<String> sub = flatTree(child.getChildren()); if (sub != null) { elem += "[" + StringUtils.join(sub, ", ") + "]"; } r.add(elem); } Collections.sort(r); return r.isEmpty() ? null : r; } protected static String flat(List<ObjectInFolderContainer> tree) throws Exception { return StringUtils.join(flatTree(tree), ", "); } @Test public void testGetDescendants() throws Exception { List<ObjectInFolderContainer> tree; try { navService.getDescendants(repositoryId, rootFolderId, BigInteger.valueOf(0), null, null, null, null, null, null); fail("Depth 0 should be forbidden"); } catch (CmisInvalidArgumentException e) { // ok } tree = navService.getDescendants(repositoryId, rootFolderId, BigInteger.valueOf(1), null, null, null, null, null, null); assertEquals("testfolder1_Title, " // + "testfolder2_Title", flat(tree)); tree = navService.getDescendants(repositoryId, rootFolderId, BigInteger.valueOf(2), null, null, null, null, null, null); assertEquals( "testfolder1_Title[" // + /* */"testfile1_Title, " // + /* */"testfile2_Title, " // + /* */"testfile3_Title], " // + "testfolder2_Title[" // + /* */"testfolder3_Title, " // + /* */"testfolder4_Title" // + /* */(supportsProxies() ? ", title6" : "") // + "]", // flat(tree)); tree = navService.getDescendants(repositoryId, rootFolderId, BigInteger.valueOf(3), null, null, null, null, null, null); assertEquals( "testfolder1_Title[" // + /* */"testfile1_Title, " // + /* */"testfile2_Title, " // + /* */"testfile3_Title], " // + "testfolder2_Title[" // + /* */"testfolder3_Title[testfile4_Title, title6], " // + /* */"testfolder4_Title" // + /* */(supportsProxies() ? ", title6" : "") // + "]", // flat(tree)); tree = navService.getDescendants(repositoryId, rootFolderId, BigInteger.valueOf(4), null, null, null, null, null, null); assertEquals( "testfolder1_Title[" // + /* */"testfile1_Title, " // + /* */"testfile2_Title, " // + /* */"testfile3_Title], " // + "testfolder2_Title[" // + /* */"testfolder3_Title[testfile4_Title, title6], " // + /* */"testfolder4_Title" // + /* */(supportsProxies() ? ", title6" : "") // + "]", // flat(tree)); tree = navService.getDescendants(repositoryId, rootFolderId, BigInteger.valueOf(-1), null, null, null, null, null, null); assertEquals( "testfolder1_Title[testfile1_Title, " + /* */"testfile2_Title, " // + /* */"testfile3_Title], " // + "testfolder2_Title[" // + /* */"testfolder3_Title[testfile4_Title, title6], " // + /* */"testfolder4_Title" // + /* */(supportsProxies() ? ", title6" : "") // + "]", // flat(tree)); ObjectData ob = getObjectByPath("/testfolder2"); String folder2Id = ob.getId(); tree = navService.getDescendants(repositoryId, folder2Id, BigInteger.valueOf(1), null, null, null, null, null, null); assertEquals("testfolder3_Title, testfolder4_Title" + (supportsProxies() ? ", title6" : ""), flat(tree)); tree = navService.getDescendants(repositoryId, folder2Id, BigInteger.valueOf(2), null, null, null, null, null, null); assertEquals( "testfolder3_Title[testfile4_Title, title6], testfolder4_Title" + (supportsProxies() ? ", title6" : ""), flat(tree)); tree = navService.getDescendants(repositoryId, folder2Id, BigInteger.valueOf(3), null, null, null, null, null, null); assertEquals( "testfolder3_Title[testfile4_Title, title6], testfolder4_Title" + (supportsProxies() ? ", title6" : ""), flat(tree)); tree = navService.getDescendants(repositoryId, folder2Id, BigInteger.valueOf(-1), null, null, null, null, null, null); assertEquals( "testfolder3_Title[testfile4_Title, title6], testfolder4_Title" + (supportsProxies() ? ", title6" : ""), flat(tree)); } @Test public void testGetFolderTree() throws Exception { List<ObjectInFolderContainer> tree; try { navService.getFolderTree(repositoryId, rootFolderId, BigInteger.valueOf(0), null, null, null, null, null, null); fail("Depth 0 should be forbidden"); } catch (CmisInvalidArgumentException e) { // ok } tree = navService.getFolderTree(repositoryId, rootFolderId, BigInteger.valueOf(1), null, null, null, null, null, null); assertEquals("testfolder1_Title, " // + "testfolder2_Title", flat(tree)); tree = navService.getFolderTree(repositoryId, rootFolderId, BigInteger.valueOf(2), null, null, null, null, null, null); assertEquals( "testfolder1_Title, " // + "testfolder2_Title[" // + /* */"testfolder3_Title, " // + /* */"testfolder4_Title]", // flat(tree)); tree = navService.getFolderTree(repositoryId, rootFolderId, BigInteger.valueOf(3), null, null, null, null, null, null); assertEquals( "testfolder1_Title, " // + "testfolder2_Title[" // + /* */"testfolder3_Title, " // + /* */"testfolder4_Title]", // flat(tree)); tree = navService.getFolderTree(repositoryId, rootFolderId, BigInteger.valueOf(4), null, null, null, null, null, null); assertEquals( "testfolder1_Title, " // + "testfolder2_Title[" // + /* */"testfolder3_Title, " // + /* */"testfolder4_Title]", // flat(tree)); tree = navService.getFolderTree(repositoryId, rootFolderId, BigInteger.valueOf(-1), null, null, null, null, null, null); assertEquals( "testfolder1_Title, " // + "testfolder2_Title[" // + /* */"testfolder3_Title, " // + /* */"testfolder4_Title]", // flat(tree)); ObjectData ob = getObjectByPath("/testfolder2"); String folder2Id = ob.getId(); tree = navService.getFolderTree(repositoryId, folder2Id, BigInteger.valueOf(1), null, null, null, null, null, null); assertEquals("testfolder3_Title, testfolder4_Title", flat(tree)); tree = navService.getFolderTree(repositoryId, folder2Id, BigInteger.valueOf(2), null, null, null, null, null, null); assertEquals("testfolder3_Title, testfolder4_Title", flat(tree)); tree = navService.getFolderTree(repositoryId, folder2Id, BigInteger.valueOf(3), null, null, null, null, null, null); assertEquals("testfolder3_Title, testfolder4_Title", flat(tree)); tree = navService.getFolderTree(repositoryId, folder2Id, BigInteger.valueOf(-1), null, null, null, null, null, null); assertEquals("testfolder3_Title, testfolder4_Title", flat(tree)); } @Test public void testCreateDocumentFromSource() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String key = "dc:title"; String value = "new title"; Properties props = createProperties(key, value); String id = objService.createDocumentFromSource(repositoryId, ob.getId(), props, rootFolderId, null, null, null, null, null); assertNotNull(id); assertNotEquals(id, ob.getId()); // fetch ObjectData copy = getObjectByPath("/testfile1"); assertNotNull(copy); assertEquals(value, getString(copy, key)); } @Test public void testDeleteObject() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); objService.deleteObject(repositoryId, ob.getId(), Boolean.TRUE, null); try { ob = getObjectByPath("/testfolder1/testfile1"); fail("Document should be deleted"); } catch (CmisObjectNotFoundException e) { // ok } ob = getObjectByPath("/testfolder2"); try { objService.deleteObject(repositoryId, ob.getId(), Boolean.TRUE, null); fail("Should not be able to delete non-empty folder"); } catch (CmisConstraintException e) { // ok to fail, still has children } ob = getObjectByPath("/testfolder2"); assertNotNull(ob); try { objService.deleteObject(repositoryId, "nosuchid", Boolean.TRUE, null); fail("Should not be able to delete nonexistent object"); } catch (CmisObjectNotFoundException e) { // ok } } @Test public void testRemoveObjectFromFolder1() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); filingService.removeObjectFromFolder(repositoryId, ob.getId(), null, null); try { ob = getObjectByPath("/testfolder1/testfile1"); fail("Document should be deleted"); } catch (CmisObjectNotFoundException e) { // ok } } @Test public void testRemoveObjectFromFolder2() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); ObjectData folder = getObjectByPath("/testfolder1"); filingService.removeObjectFromFolder(repositoryId, ob.getId(), folder.getId(), null); try { ob = getObjectByPath("/testfolder1/testfile1"); fail("Document should be deleted"); } catch (CmisObjectNotFoundException e) { // ok } } @Test public void testDeleteTree() throws Exception { ObjectData ob = getObjectByPath("/testfolder1"); objService.deleteTree(repositoryId, ob.getId(), null, null, null, null); try { getObjectByPath("/testfolder1"); fail("Folder should be deleted"); } catch (CmisObjectNotFoundException e) { // ok } try { getObjectByPath("/testfolder1/testfile1"); fail("Folder should be deleted"); } catch (CmisObjectNotFoundException e) { // ok } assertNotNull(getObjectByPath("/testfolder2")); } @Test public void testGetAllowableActions() throws Exception { Set<Action> expected; ObjectData ob; AllowableActions aa; // folder ob = getObjectByPath("/testfolder1"); aa = objService.getAllowableActions(repositoryId, ob.getId(), null); assertNotNull(aa); expected = EnumSet.of( // Action.CAN_GET_OBJECT_PARENTS, // Action.CAN_GET_PROPERTIES, // Action.CAN_GET_DESCENDANTS, // Action.CAN_GET_FOLDER_PARENT, // Action.CAN_GET_FOLDER_TREE, // Action.CAN_GET_CHILDREN, // Action.CAN_CREATE_DOCUMENT, // Action.CAN_CREATE_FOLDER, // Action.CAN_CREATE_RELATIONSHIP, // Action.CAN_DELETE_TREE, // Action.CAN_GET_RENDITIONS, // Action.CAN_UPDATE_PROPERTIES, // Action.CAN_MOVE_OBJECT, // Action.CAN_DELETE_OBJECT); assertEquals(expected, aa.getAllowableActions()); // checked out doc ob = getObjectByPath("/testfolder1/testfile1"); aa = objService.getAllowableActions(repositoryId, ob.getId(), null); assertNotNull(aa); expected = EnumSet.of( // Action.CAN_GET_OBJECT_PARENTS, // Action.CAN_GET_PROPERTIES, // Action.CAN_GET_CONTENT_STREAM, // Action.CAN_SET_CONTENT_STREAM, // Action.CAN_DELETE_CONTENT_STREAM, // Action.CAN_UPDATE_PROPERTIES, // Action.CAN_MOVE_OBJECT, // Action.CAN_DELETE_OBJECT, // Action.CAN_ADD_OBJECT_TO_FOLDER, // Action.CAN_REMOVE_OBJECT_FROM_FOLDER, // Action.CAN_GET_RENDITIONS, // Action.CAN_GET_ALL_VERSIONS, // Action.CAN_CANCEL_CHECK_OUT, // Action.CAN_CHECK_IN); assertEquals(expected, aa.getAllowableActions()); // checked in doc Holder<String> idHolder = new Holder<>(ob.getId()); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, null, null, "comment", null, null, null, null); aa = objService.getAllowableActions(repositoryId, ob.getId(), null); assertNotNull(aa); expected = EnumSet.of( // Action.CAN_GET_OBJECT_PARENTS, // Action.CAN_GET_PROPERTIES, // Action.CAN_GET_CONTENT_STREAM, // Action.CAN_SET_CONTENT_STREAM, // Action.CAN_DELETE_CONTENT_STREAM, // Action.CAN_UPDATE_PROPERTIES, // Action.CAN_MOVE_OBJECT, // Action.CAN_DELETE_OBJECT, // Action.CAN_ADD_OBJECT_TO_FOLDER, // Action.CAN_REMOVE_OBJECT_FROM_FOLDER, // Action.CAN_GET_RENDITIONS, // Action.CAN_GET_ALL_VERSIONS, // Action.CAN_CHECK_OUT); assertEquals(expected, aa.getAllowableActions()); } @Test public void testMoveObject() throws Exception { ObjectData fold = getObjectByPath("/testfolder1"); ObjectData ob = getObjectByPath("/testfolder2/testfolder3/testfile4"); Holder<String> objectIdHolder = new Holder<>(ob.getId()); objService.moveObject(repositoryId, objectIdHolder, fold.getId(), null, null); assertEquals(ob.getId(), objectIdHolder.getValue()); try { getObjectByPath("/testfolder2/testfolder3/testfile4"); fail("Object should be moved away"); } catch (CmisObjectNotFoundException e) { // ok } ObjectData ob2 = getObjectByPath("/testfolder1/testfile4"); assertEquals(ob.getId(), ob2.getId()); } @Test public void testQueryBasic() throws Exception { String statement; ObjectList res; waitForIndexing(); statement = "SELECT cmis:objectId, cmis:name" // + " FROM File"; // no WHERE clause res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT cmis:objectId, cmis:name" // + " FROM File" // + " WHERE cmis:name <> 'testfile1_Title'"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); // spec says names are case-insensitive // statement = "SELECT CMIS:OBJECTid, DC:DESCRIPTion" // // + " FROM FILE" // // + " WHERE DC:TItle = 'testfile1_Title'"; // res = query(statement); // assertEquals(1, res.getNumItems().intValue()); // STAR statement = "SELECT * FROM cmis:document"; res = query(statement); assertEquals(supportsProxies() ? 7 : 6, res.getNumItems().intValue()); // 5 docs, 1 version, 1 proxy statement = "SELECT * FROM cmis:folder"; res = query(statement); assertEquals(returnsRootInFolderQueries() ? 5 : 4, res.getNumItems().intValue()); statement = "SELECT cmis:objectId, dc:description" // + " FROM File" // + " WHERE dc:title = 'testfile1_Title'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); statement = "SELECT cmis:objectId, dc:description" // + " FROM File" // + " WHERE dc:title = 'testfile1_Title' AND dc:description <> 'argh'" + " AND dc:coverage <> 'zzzzz'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); // IN statement = "SELECT cmis:objectId" // + " FROM File" // + " WHERE dc:title IN ('testfile1_Title', 'xyz')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); } protected String NOT_NULL = new String("__NOTNULL__"); protected void checkWhereTerm(String type, String prop, String value) { if (value == NOT_NULL) { checkQueriedValue(type, prop + " IS NOT NULL"); } else { checkQueriedValue(type, prop + " = " + value); } } protected void checkQueriedValue(String type, String term) { String statement = String.format("SELECT cmis:objectId FROM %s WHERE %s", type, term); ObjectList res = query(statement); int num = res.getNumItems().intValue(); if (num == 0) { fail("no result for: " + statement); } } @Test public void testQuerySecurity() throws Exception { String statement; ObjectList res; waitForIndexing(); statement = "SELECT cmis:objectId FROM File"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); reSetUp("bob"); statement = "SELECT cmis:objectId FROM File"; res = query(statement); // only testfile1 and testfile2 are accessible by bob assertEquals(2, res.getNumItems().intValue()); } /** * Wait for async worker completion then wait for indexing completion */ public void waitForIndexing() throws Exception { if (!useElasticsearch()) { return; } TransactionHelper.commitOrRollbackTransaction(); workManager.awaitCompletion(20, TimeUnit.SECONDS); ElasticSearchAdmin esa = Framework.getService(ElasticSearchAdmin.class); esa.prepareWaitForIndexing().get(20, TimeUnit.SECONDS); esa.refresh(); TransactionHelper.startTransaction(); } @Test public void testQueryWhereProperties() throws Exception { String statement; ObjectList res; createDocumentMyDocType(); waitForIndexing(); // STAR statement = "SELECT * FROM MyDocType"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); checkQueriedValue("MyDocType", "my:string = 'abc'"); checkQueriedValue("MyDocType", "my:string <> 'def'"); checkQueriedValue("MyDocType", "my:boolean = true"); checkQueriedValue("MyDocType", "my:boolean <> FALSE"); checkQueriedValue("MyDocType", "my:integer = 123"); checkQueriedValue("MyDocType", "my:integer <> 456"); checkQueriedValue("MyDocType", "my:double = 123.456"); checkQueriedValue("MyDocType", "my:double <> 123"); // TODO fix timezone issues (PostgreSQL) // checkQueriedValue("MyDocType", // "my:date = TIMESTAMP '2010-09-30T16:04:55-02:00'"); checkQueriedValue("MyDocType", "my:date <> TIMESTAMP '1999-09-09T01:01:01Z'"); try { statement = "SELECT cmis:objectId FROM MyDocType WHERE my:date <> TIMESTAMP 'foobar'"; query(statement); fail("Should be invalid Timestamp"); } catch (CmisRuntimeException e) { // ok } } @Test public void testQueryWhereSystemProperties() throws Exception { waitForIndexing(); // ----- Object ----- checkWhereTerm("File", PropertyIds.NAME, "'testfile1_Title'"); checkWhereTerm("File", PropertyIds.DESCRIPTION, "'testfile1_description'"); checkWhereTerm("File", PropertyIds.OBJECT_ID, NOT_NULL); checkWhereTerm("File", PropertyIds.OBJECT_TYPE_ID, "'File'"); // checkWhereTerm("File", PropertyIds.BASE_TYPE_ID, // "'cmis:document'"); checkWhereTerm("File", PropertyIds.CREATED_BY, "'michael'"); checkWhereTerm("File", PropertyIds.CREATION_DATE, NOT_NULL); checkWhereTerm("File", PropertyIds.LAST_MODIFIED_BY, "'bob'"); checkWhereTerm("File", PropertyIds.LAST_MODIFICATION_DATE, NOT_NULL); // checkWhereTerm("File", PropertyIds.CHANGE_TOKEN, null); checkWhereTerm("File", NuxeoTypeHelper.NX_ISVERSION, "false"); checkWhereTerm("File", NuxeoTypeHelper.NX_LIFECYCLE_STATE, "'project'"); checkWhereTerm("File", NuxeoTypeHelper.NX_PARENT_ID, NOT_NULL); // ----- Folder ----- checkWhereTerm("Folder", PropertyIds.PARENT_ID, NOT_NULL); checkWhereTerm("Folder", NuxeoTypeHelper.NX_LIFECYCLE_STATE, "'project'"); // checkWhereTerm("Folder", PropertyIds.PATH, NOT_NULL); // checkWhereTerm("Folder", PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS, // NOT_NULL); // ----- Document ----- // checkWhereTerm("File", PropertyIds.IS_IMMUTABLE, "FALSE"); checkWhereTerm("File", PropertyIds.IS_LATEST_VERSION, "FALSE"); // checkWhereTerm("File", PropertyIds.IS_MAJOR_VERSION, "TRUE"); checkWhereTerm("File", PropertyIds.IS_LATEST_MAJOR_VERSION, "FALSE"); // checkWhereTerm("File", PropertyIds.VERSION_LABEL, NOT_NULL); // checkWhereTerm("File", PropertyIds.VERSION_SERIES_ID, NOT_NULL); // checkWhereTerm("File", PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, // NOT_NULL); // checkWhereTerm("File", PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, // NOT_NULL); // checkWhereTerm("File", PropertyIds.CHECKIN_COMMENT, "xyz"); // checkWhereTerm("File", PropertyIds.CONTENT_STREAM_LENGTH, NOT_NULL); // checkWhereTerm("File", PropertyIds.CONTENT_STREAM_MIME_TYPE, // "text/plain"); // checkWhereTerm("File", PropertyIds.CONTENT_STREAM_FILE_NAME, // "testfile.txt"); // checkWhereTerm("File", PropertyIds.CONTENT_STREAM_ID, NOT_NULL); // checkWhereTerm("File", NuxeoTypeHelper.NX_ECM_DIGEST, // "'bde9eb59c76cb432a0f8d02057a19923'"); } protected void checkReturnedValue(String prop, Object expected) { checkReturnedValue(prop, expected, "File", "testfile1_Title"); } protected void checkReturnedValue(String prop, Object expected, String type, String name) { String statement = String.format("SELECT %s FROM %s WHERE cmis:name = '%s'", prop, type, name); ObjectList res = query(statement); assertEquals(1, res.getNumItems().intValue()); ObjectData data = res.getObjects().get(0); checkValue(prop, expected, data); } protected void checkValue(String prop, Object expected, ObjectData data) { Object value = expected instanceof List ? getValues(data, prop) : getValue(data, prop); if (expected == NOT_NULL) { assertNotNull(value); } else { assertEquals(expected, value); } } @Test public void testQueryReturnedProperties() throws Exception { waitForIndexing(); checkReturnedValue("dc:title", "testfile1_Title"); checkReturnedValue("dc:modified", NOT_NULL); checkReturnedValue("dc:lastContributor", "john"); // multi-valued checkReturnedValue("dc:subjects", Arrays.asList("foo", "gee/moo")); checkReturnedValue("dc:contributors", Arrays.asList("pete", "bob"), "File", "testfile2_Title"); } @Test public void testQueryReturnedSystemProperties() throws Exception { waitForIndexing(); // ----- Object ----- checkReturnedValue(PropertyIds.NAME, "testfile1_Title"); checkReturnedValue(PropertyIds.DESCRIPTION, "testfile1_description"); checkReturnedValue(PropertyIds.OBJECT_ID, NOT_NULL); checkReturnedValue(PropertyIds.OBJECT_TYPE_ID, "File"); checkReturnedValue(PropertyIds.BASE_TYPE_ID, "cmis:document"); checkReturnedValue(PropertyIds.CREATED_BY, "michael"); checkReturnedValue(PropertyIds.CREATION_DATE, NOT_NULL); checkReturnedValue(PropertyIds.LAST_MODIFIED_BY, "john"); checkReturnedValue(PropertyIds.LAST_MODIFICATION_DATE, NOT_NULL); checkReturnedValue(PropertyIds.CHANGE_TOKEN, NOT_NULL); checkReturnedValue(NuxeoTypeHelper.NX_PARENT_ID, NOT_NULL); // ----- Folder ----- checkReturnedValue(PropertyIds.PARENT_ID, rootFolderId, "Folder", "testfolder1_Title"); checkReturnedValue(PropertyIds.PATH, "/testfolder1", "Folder", "testfolder1_Title"); checkReturnedValue(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS, null, "Folder", "testfolder1_Title"); checkReturnedValue(NuxeoTypeHelper.NX_FACETS, NOT_NULL, "Folder", "testfolder1_Title"); checkReturnedValue(NuxeoTypeHelper.NX_LIFECYCLE_STATE, "project", "Folder", "testfolder1_Title"); // ----- Document ----- checkReturnedValue(PropertyIds.IS_IMMUTABLE, Boolean.FALSE); checkReturnedValue(PropertyIds.IS_LATEST_VERSION, Boolean.FALSE); checkReturnedValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE); checkReturnedValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE); if (useElasticsearch()) { checkReturnedValue(PropertyIds.VERSION_LABEL, "0.0"); } else { checkReturnedValue(PropertyIds.VERSION_LABEL, null); } checkReturnedValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL); checkReturnedValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE); checkReturnedValue(PropertyIds.IS_PRIVATE_WORKING_COPY, Boolean.TRUE); checkReturnedValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE); checkReturnedValue(NuxeoTypeHelper.NX_ISCHECKEDIN, Boolean.FALSE); checkReturnedValue(NuxeoTypeHelper.NX_FACETS, NOT_NULL); checkReturnedValue(NuxeoTypeHelper.NX_LIFECYCLE_STATE, "project"); checkReturnedValue(NuxeoTypeHelper.NX_DIGEST, NOT_NULL); checkReturnedValue(PropertyIds.CONTENT_STREAM_HASH, NOT_NULL); checkReturnedValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, NOT_NULL); checkReturnedValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, USERNAME); checkReturnedValue(PropertyIds.CHECKIN_COMMENT, null); checkReturnedValue(PropertyIds.CONTENT_STREAM_LENGTH, new ContentStreamImpl(null, "text/plain", Helper.FILE1_CONTENT).getBigLength()); checkReturnedValue(PropertyIds.CONTENT_STREAM_MIME_TYPE, "text/plain"); checkReturnedValue(PropertyIds.CONTENT_STREAM_FILE_NAME, "testfile.txt"); checkReturnedValue(PropertyIds.CONTENT_STREAM_ID, null); } @Test public void testQueryReturnedStar() throws Exception { waitForIndexing(); String statement = "SELECT * FROM File WHERE cmis:name = 'testfile1_Title'"; ObjectList res = query(statement); assertEquals(1, res.getNumItems().intValue()); ObjectData data = res.getObjects().get(0); checkValue(PropertyIds.OBJECT_ID, NOT_NULL, data); checkValue(PropertyIds.OBJECT_TYPE_ID, "File", data); checkValue(PropertyIds.BASE_TYPE_ID, "cmis:document", data); // returned checkValue(PropertyIds.NAME, "testfile1_Title", data); checkValue(PropertyIds.CREATED_BY, "michael", data); checkValue(PropertyIds.CREATION_DATE, NOT_NULL, data); checkValue(PropertyIds.LAST_MODIFIED_BY, "john", data); checkValue(PropertyIds.LAST_MODIFICATION_DATE, NOT_NULL, data); checkValue(PropertyIds.CHANGE_TOKEN, null, data); } @Test public void testQueryLifecycle() throws Exception { String statement; ObjectList res; waitForIndexing(); statement = "SELECT cmis:name FROM File"; res = query(statement); int initiallyQueryableFilesCount = res.getNumItems().intValue(); // all files are in state 'project' statement = "SELECT cmis:name FROM File WHERE nuxeo:lifecycleState = 'project'"; res = query(statement); assertEquals(initiallyQueryableFilesCount, res.getNumItems().intValue()); // delete another file: coreSession.followTransition(new PathRef("/testfolder1/testfile1"), "delete"); coreSession.save(); nextTransaction(); waitForIndexing(); // by default 'deleted' files are filtered out statement = "SELECT cmis:name FROM File"; res = query(statement); assertEquals(initiallyQueryableFilesCount - 1, res.getNumItems().intValue()); // but it is nevertheless possible to perform explicit queries on the // lifecycle state statement = "SELECT cmis:name FROM File WHERE nuxeo:lifecycleState = 'project'"; res = query(statement); assertEquals(initiallyQueryableFilesCount - 1, res.getNumItems().intValue()); statement = "SELECT cmis:name FROM File WHERE nuxeo:lifecycleState = 'deleted' ORDER BY cmis:name"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); assertEquals("testfile1_Title", res.getObjects().get(0).getProperties().getProperties().get(PropertyIds.NAME).getFirstValue()); // file5 was deleted in the setup function of the test case assertEquals("title5", res.getObjects().get(1).getProperties().getProperties().get(PropertyIds.NAME).getFirstValue()); statement = "SELECT cmis:name FROM File" + " WHERE nuxeo:lifecycleState IN ('project', 'deleted', 'somethingelse')"; res = query(statement); assertEquals(initiallyQueryableFilesCount + 1, res.getNumItems().intValue()); } @Test public void testQueryPathSegment() throws Exception { String statement; ObjectList res; List<ObjectData> objects; waitForIndexing(); statement = "SELECT nuxeo:pathSegment FROM File ORDER BY nuxeo:pathSegment"; res = query(statement); objects = res.getObjects(); assertEquals(3, res.getNumItems().intValue()); assertEquals("testfile1", getValue(objects.get(0), NuxeoTypeHelper.NX_PATH_SEGMENT)); assertEquals("testfile2", getValue(objects.get(1), NuxeoTypeHelper.NX_PATH_SEGMENT)); assertEquals("testfile4", getValue(objects.get(2), NuxeoTypeHelper.NX_PATH_SEGMENT)); statement = "SELECT cmis:name FROM File WHERE nuxeo:pathSegment = 'testfile1'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("testfile1_Title", getValue(res.getObjects().get(0), "cmis:name")); } @Test public void testQueryPos() throws Exception { String statement; ObjectList res; List<ObjectData> objects; DocumentModel ofolder = coreSession.createDocumentModel("/", "ordered", "OrderedFolder"); coreSession.createDocument(ofolder); DocumentModel odoc1 = coreSession.createDocumentModel("/ordered", "odoc1", "File"); coreSession.createDocument(odoc1); DocumentModel odoc2 = coreSession.createDocumentModel("/ordered", "odoc2", "File"); coreSession.createDocument(odoc2); coreSession.save(); nextTransaction(); waitForIndexing(); statement = "SELECT nuxeo:pos FROM File WHERE nuxeo:pos >= 0 ORDER BY nuxeo:pos"; res = query(statement); objects = res.getObjects(); assertEquals(2, res.getNumItems().intValue()); assertEquals(BigInteger.valueOf(0), getValue(objects.get(0), NuxeoTypeHelper.NX_POS)); assertEquals(BigInteger.valueOf(1), getValue(objects.get(1), NuxeoTypeHelper.NX_POS)); } @Test public void testQueryVersions() throws Exception { String statement; ObjectList res; waitForIndexing(); // count all documents (for reference) statement = "SELECT cmis:name FROM File"; res = query(statement); int initialFileCount = res.getNumItems().intValue(); // checkin testfile1 as an archived version ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); Holder<String> idHolder = new Holder<>(id); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, null, null, "this is the comment", null, null, null, null); waitForIndexing(); // by default CMISQL queries will return both live documents and // archived versions res = query(statement); assertEquals(initialFileCount + 1, res.getNumItems().intValue()); // it is however possible to fetch only the archived versions using the // nuxeo:isVersion system property statement = "SELECT cmis:name, nuxeo:isVersion FROM File WHERE nuxeo:isVersion = true ORDER BY cmis:name"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); checkValue(PropertyIds.NAME, "testfile1_Title", res.getObjects().get(0)); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.TRUE, res.getObjects().get(0)); // this should be equivalent to statement = "SELECT cmis:name, nuxeo:isVersion FROM File" + " WHERE nuxeo:isVersion <> false ORDER BY cmis:name"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); checkValue(PropertyIds.NAME, "testfile1_Title", res.getObjects().get(0)); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.TRUE, res.getObjects().get(0)); // conversely one can select only live documents by negating this // predicate statement = "SELECT cmis:name, nuxeo:isVersion FROM File WHERE nuxeo:isVersion = false ORDER BY cmis:name"; res = query(statement); assertEquals(initialFileCount, res.getNumItems().intValue()); checkValue(PropertyIds.NAME, "testfile1_Title", res.getObjects().get(0)); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, res.getObjects().get(0)); // this should be equivalent to statement = "SELECT cmis:name, nuxeo:isVersion FROM File" + " WHERE nuxeo:isVersion <> true ORDER BY cmis:name"; res = query(statement); assertEquals(initialFileCount, res.getNumItems().intValue()); checkValue(PropertyIds.NAME, "testfile1_Title", res.getObjects().get(0)); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, res.getObjects().get(0)); } @Test public void testQueryLatestsVersions() throws Exception { String statement; ObjectList res; ObjectData first; waitForIndexing(); // check that there is only one version of the document with title // 'testfile1_Title' (for reference) statement = "SELECT * FROM File WHERE cmis:name = 'testfile1_Title'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); // checkin testfile1 as an archived version ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); Holder<String> idHolder = new Holder<>(id); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, null, null, "this is the comment", null, null, null, null); waitForIndexing(); // by default CMISQL queries will return both live documents and // archived versions res = query(statement); assertEquals(2, res.getNumItems().intValue()); // it is however possible to fetch only the last version using the // cmis:isLatestVersion system property statement = "SELECT cmis:isLatestVersion, nuxeo:isVersion FROM File" + " WHERE cmis:isLatestVersion = true AND cmis:name = 'testfile1_Title'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); first = res.getObjects().get(0); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, first); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.TRUE, first); // this should be equivalent to statement = "SELECT cmis:isLatestVersion, nuxeo:isVersion FROM File" + " WHERE cmis:isLatestVersion <> false AND cmis:name = 'testfile1_Title'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); first = res.getObjects().get(0); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, first); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.TRUE, first); // we can check out the last version, edit it and try again: verService.checkOut(repositoryId, idHolder, null, null); waitForIndexing(); ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); Properties props = createProperties("dc:description", "new description"); idHolder = new Holder<>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(ob); objService.updateProperties(repositoryId, idHolder, changeTokenHolder, props, null); assertEquals(ob.getId(), idHolder.getValue()); waitForIndexing(); ob = getObject(ob.getId()); assertEquals("new description", getString(ob, "dc:description")); // the latest major version is still the archived version, not the // checkouted document statement = "SELECT * FROM File WHERE cmis:isLatestVersion = true AND cmis:name = 'testfile1_Title'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); first = res.getObjects().get(0); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, first); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.TRUE, first); checkValue("dc:description", "testfile1_description", first); // is also possible to query for versions that are not the latests, in // this case we only get the checkouted document statement = "SELECT * FROM File WHERE cmis:isLatestVersion = false AND cmis:name = 'testfile1_Title'"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); first = res.getObjects().get(0); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.FALSE, first); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, res.getObjects().get(0)); checkValue("dc:description", "new description", first); } @Test public void testQueryAny() throws Exception { String statement; ObjectList res; waitForIndexing(); // ... = ANY ... statement = "SELECT cmis:name FROM File WHERE 'pete' = ANY dc:contributors"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); statement = "SELECT cmis:name FROM File WHERE 'bob' = ANY dc:contributors"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); // ANY ... IN ... statement = "SELECT cmis:name FROM File WHERE ANY dc:contributors IN ('pete')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); statement = "SELECT cmis:name FROM File WHERE ANY dc:contributors IN ('pete', 'bob')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); // with qualifier statement = "SELECT f.cmis:objectId FROM File f WHERE ANY f.dc:subjects IN ('foo')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); // ANY ... NOT IN ... statement = "SELECT cmis:name FROM File WHERE ANY dc:contributors NOT IN ('pete')"; res = query(statement); assertEquals(emptyListNegativeMatch() ? 2 : 1, res.getNumItems().intValue()); statement = "SELECT cmis:name FROM File WHERE ANY dc:contributors NOT IN ('john')"; res = query(statement); assertEquals(emptyListNegativeMatch() ? 3 : 1, res.getNumItems().intValue()); statement = "SELECT cmis:name FROM File WHERE ANY dc:contributors NOT IN ('pete', 'bob')"; res = query(statement); assertEquals(emptyListNegativeMatch() ? 2 : 0, res.getNumItems().intValue()); } @Test public void testQueryIsNullMuti() throws Exception { String statement; ObjectList res; waitForIndexing(); statement = "SELECT cmis:objectId FROM cmis:document WHERE dc:subjects IS NULL"; res = query(statement); assertEquals(supportsProxies() ? 6 : 5, res.getNumItems().intValue()); // 4 docs, 1 version, 1 proxy // with qualifier statement = "SELECT A.cmis:objectId FROM cmis:document A WHERE A.dc:subjects IS NULL"; res = query(statement); assertEquals(supportsProxies() ? 6 : 5, res.getNumItems().intValue()); // 4 docs, 1 version, 1 proxy } @Test public void testQueryIsNotNullMuti() throws Exception { waitForIndexing(); String statement = "SELECT cmis:objectId FROM cmis:document WHERE dc:subjects IS NOT NULL"; ObjectList res = query(statement); assertEquals(1, res.getNumItems().intValue()); } @SuppressWarnings("boxing") @Test public void testQueryMixinTypes() throws Exception { String statement; ObjectList res; // add some instance facets on 2 documents DocumentModel doc1 = coreSession.getDocument(new PathRef("/testfolder1/testfile1")); assertTrue(doc1.addFacet("CustomFacetWithoutSchema")); coreSession.saveDocument(doc1); DocumentModel doc2 = coreSession.getDocument(new PathRef("/testfolder1/testfile2")); assertTrue(doc2.addFacet("CustomFacetWithMySchema2")); doc2.setPropertyValue("my2:long", 12); coreSession.saveDocument(doc2); coreSession.save(); nextTransaction(); waitForIndexing(); // ... = ANY ... statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE 'Versionable' = ANY nuxeo:secondaryObjectTypeIds"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE 'Downloadable' = ANY nuxeo:secondaryObjectTypeIds"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE 'CustomFacetWithoutSchema' = ANY nuxeo:secondaryObjectTypeIds"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); checkValue(NuxeoTypeHelper.NX_FACETS, Arrays.asList("Commentable", "CustomFacetWithoutSchema", "Downloadable", "HasRelatedText", "Publishable", "Thumbnail", "Versionable"), res.getObjects().get(0)); statement = "SELECT * FROM File WHERE 'CustomFacetWithMySchema2' = ANY nuxeo:secondaryObjectTypeIds"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); // additional test with JOIN in next method // ANY ... IN ... statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds IN ('Versionable')"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds IN ('CustomFacetWithMySchema2')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); checkValue(NuxeoTypeHelper.NX_FACETS, Arrays.asList("Commentable", "CustomFacetWithMySchema2", "Downloadable", "HasRelatedText", "Publishable", "Versionable"), res.getObjects().get(0)); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds IN ('CustomFacetWithoutSchema', 'CustomFacetWithMySchema2')"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds IN ('Versionable', 'CustomFacetWithoutSchema')"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); // ANY ... NOT IN ... statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds NOT IN ('Versionable')"; res = query(statement); assertEquals(0, res.getNumItems().intValue()); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds NOT IN ('CustomFacetWithoutSchema')"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds NOT IN ('CustomFacetWithoutSchema', 'CustomFacetWithMySchema2')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); checkValue(NuxeoTypeHelper.NX_FACETS, Arrays.asList("Commentable", "Downloadable", "HasRelatedText", "Publishable", "Versionable"), res.getObjects().get(0)); statement = "SELECT nuxeo:secondaryObjectTypeIds FROM File WHERE ANY nuxeo:secondaryObjectTypeIds NOT IN ('Versionable', 'CustomFacetWithoutSchema')"; res = query(statement); assertEquals(0, res.getNumItems().intValue()); } @SuppressWarnings("boxing") @Test public void testQueryMixinTypesJoin() throws Exception { assumeSupportsJoins(); String statement; ObjectList res; // add some instance facets on 2 documents DocumentModel doc1 = coreSession.getDocument(new PathRef("/testfolder1/testfile1")); assertTrue(doc1.addFacet("CustomFacetWithoutSchema")); coreSession.saveDocument(doc1); DocumentModel doc2 = coreSession.getDocument(new PathRef("/testfolder1/testfile2")); assertTrue(doc2.addFacet("CustomFacetWithMySchema2")); doc2.setPropertyValue("my2:long", 12); coreSession.saveDocument(doc2); coreSession.save(); nextTransaction(); waitForIndexing(); // ... = ANY ... // with several qualifiers (therefore a JOIN) statement = "SELECT A.cmis:objectId FROM cmis:document A" + " JOIN cmis:folder B ON A.nuxeo:parentId = B.cmis:objectId" + " WHERE 'Versionable' = ANY A.nuxeo:secondaryObjectTypeIds"; res = query(statement); assertEquals(5, res.getNumItems().intValue()); // 5 docs } @Test public void testQueryOrderBy() throws Exception { String statement; ObjectList res; ObjectData data; waitForIndexing(); statement = "SELECT cmis:objectId, cmis:name" // + " FROM File" // + " ORDER BY cmis:name"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); data = res.getObjects().get(0); assertEquals("testfile1_Title", getString(data, PropertyIds.NAME)); // now change order res = query(statement + " DESC"); assertEquals(3, res.getNumItems().intValue()); data = res.getObjects().get(0); assertEquals("testfile4_Title", getString(data, PropertyIds.NAME)); } @Test public void testQueryInFolder() throws Exception { waitForIndexing(); ObjectData f1 = getObjectByPath("/testfolder1"); String statementPattern = "SELECT cmis:name FROM File" // + " WHERE IN_FOLDER('%s')" // + " ORDER BY cmis:name"; String statement = String.format(statementPattern, f1.getId()); ObjectList res = query(statement); assertEquals(2, res.getNumItems().intValue()); assertEquals("testfile1_Title", getString(res.getObjects().get(0), PropertyIds.NAME)); assertEquals("testfile2_Title", getString(res.getObjects().get(1), PropertyIds.NAME)); // missing/illegal ID statement = String.format(statementPattern, "nosuchid"); res = query(statement); assertEquals(0, res.getNumItems().intValue()); } @Test public void testQueryInTree() throws Exception { ObjectList res; String statement; waitForIndexing(); ObjectData f2 = getObjectByPath("/testfolder2"); String statementPattern = "SELECT cmis:name FROM File" // + " WHERE IN_TREE('%s')"; statement = String.format(statementPattern, f2.getId()); res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("testfile4_Title", getString(res.getObjects().get(0), PropertyIds.NAME)); // missing/illegal ID statement = String.format(statementPattern, "nosuchid"); res = query(statement); assertEquals(0, res.getNumItems().intValue()); } @Test public void testQueryInTreeQualifier() throws Exception { ObjectList res; String statement; String statementPattern; // qual is type ObjectData f2 = getObjectByPath("/testfolder2"); waitForIndexing(); statementPattern = "SELECT cmis:name FROM File" // no alias + " WHERE IN_TREE(File, '%s')"; // qual is type statement = String.format(statementPattern, f2.getId()); res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("testfile4_Title", getString(res.getObjects().get(0), PropertyIds.NAME)); statementPattern = "SELECT cmis:name FROM File f" // alias + " WHERE IN_TREE(f, '%s')"; // qual is alias statement = String.format(statementPattern, f2.getId()); res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("testfile4_Title", getString(res.getObjects().get(0), PropertyIds.NAME)); statementPattern = "SELECT cmis:name FROM File f" // alias + " WHERE IN_TREE(File, '%s')"; // qual is type statement = String.format(statementPattern, f2.getId()); res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("testfile4_Title", getString(res.getObjects().get(0), PropertyIds.NAME)); statementPattern = "SELECT cmis:name FROM File f" // alias + " WHERE IN_TREE('%s')"; // no qual statement = String.format(statementPattern, f2.getId()); res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("testfile4_Title", getString(res.getObjects().get(0), PropertyIds.NAME)); try { statement = "SELECT cmis:name FROM File WHERE IN_TREE(g, 'abc')"; // invalid qual query(statement); fail("should fail"); } catch (CmisInvalidArgumentException e) { assertTrue(e.getMessage().contains("g is neither a type query name nor an alias")); } try { statement = "SELECT cmis:name FROM File f WHERE IN_TREE(g, 'abc')"; // invalid qual query(statement); fail("should fail"); } catch (CmisInvalidArgumentException e) { assertTrue(e.getMessage().contains("g is neither a type query name nor an alias")); } } @Test public void testQueryQualifiers() throws Exception { ObjectList res; String statement; waitForIndexing(); statement = "SELECT cmis:name FROM File"; // default res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT File.cmis:name FROM File"; // type qual res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT File.cmis:name, cmis:name FROM File"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT File.cmis:name, cmis:objectTypeId FROM File"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT cmis:name FROM File f"; // no qual res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT f.cmis:name FROM File f"; // alias qual res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT File.cmis:name FROM File f"; // alias qual res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT f.cmis:name, cmis:objectTypeId FROM File f"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT File.cmis:name, f.cmis:objectId FROM File f"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT File.cmis:name, cmis:objectTypeId FROM File f"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); statement = "SELECT File.cmis:name, f.cmis:objectId, cmis:objectTypeId FROM File f"; res = query(statement); assertEquals(3, res.getNumItems().intValue()); } @Test public void testQueryContains() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); PropertyData<?> propTitle = factory.createPropertyStringData("dc:title", "new title1"); PropertyData<?> propDescription = factory.createPropertyStringData("dc:description", "new description1"); Properties properties = factory.createPropertiesData(Arrays.asList(propTitle, propDescription)); Holder<String> objectIdHolder = new Holder<>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(ob); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, properties, null); sleepForFulltext(); waitForIndexing(); ObjectList res; String statement; statement = "SELECT cmis:name FROM File WHERE CONTAINS('title1')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); statement = "SELECT cmis:name FROM File WHERE CONTAINS('description1')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); if (supportsMultipleFulltextIndexes()) { // specific query for title index (the description token do not // match) statement = "SELECT cmis:name FROM File WHERE CONTAINS('nx:title:description1')"; res = query(statement); assertEquals(0, res.getNumItems().intValue()); statement = "SELECT cmis:name FROM File WHERE CONTAINS('nx:title:title1')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); // query for invalid index name try { statement = "SELECT cmis:name FROM File" // + " WHERE CONTAINS('nx:borked:title1')"; res = query(statement); if (!useElasticsearch()) { // ES turns this into the regular fulltext query fail(); } } catch (CmisInvalidArgumentException e) { assertTrue(e.getMessage(), e.getMessage().contains("No such fulltext index: borked")); } } } /** * Test the relax mode having multiple {@code CONTAINS()}s in CMISQL. The relax mode does not follow the CMIS * specification 1.1 where at most one {@code CONTAINS()} function MUST be included in a single query statement * (section 2.1.14.2.4.4). {@code JOIN}s are not supported yet. * * @see https://jira.nuxeo.com/browse/NXP-19858 */ @Test @LocalDeploy("org.nuxeo.ecm.core.opencmis.tests.tests:OSGI-INF/test-relax-cmis-spec.xml") public void testQueryMultiContainsRelaxingSpec() throws Exception { assumeFalse("DBS does not support multiple CONTAINS", coreFeature.getStorageConfiguration().isDBS()); // when using JOINs, we use the CMISQLQueryMaker which hasn't been updated to allow multiple CONTAINs assumeFalse("JOINs are not supported", supportsJoins()); ConfigurationService configService = Framework.getService(ConfigurationService.class); assertTrue(configService.isBooleanPropertyTrue(NuxeoRepository.RELAX_CMIS_SPEC)); ObjectData ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); PropertyData<?> propTitle = factory.createPropertyStringData("dc:title", "new title1"); PropertyData<?> propDescription = factory.createPropertyStringData("dc:description", "new description1"); Properties properties = factory.createPropertiesData(Arrays.asList(propTitle, propDescription)); Holder<String> objectIdHolder = new Holder<>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(ob); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, properties, null); sleepForFulltext(); waitForIndexing(); ObjectList res; res = query("SELECT cmis:name FROM File WHERE CONTAINS('title1') OR CONTAINS('anotherTitle')"); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); res = query("SELECT cmis:name FROM File WHERE CONTAINS('description1') OR CONTAINS('anotherDescription')"); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); } @Test public void testQueryMultiConainsFollowingSpec() throws Exception { ConfigurationService configService = Framework.getService(ConfigurationService.class); assertFalse(configService.isBooleanPropertyTrue(NuxeoRepository.RELAX_CMIS_SPEC)); ObjectData ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); String statement = "SELECT cmis:name FROM File WHERE CONTAINS('testfile1_Title') OR CONTAINS('anotherTitle')"; try { query(statement); fail(); } catch (CmisInvalidArgumentException e) { assertTrue(e.getMessage(), e.getMessage().contains("At most one CONTAINS() is allowed")); } } @Test public void testQueryContainsQualifier() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); PropertyData<?> propTitle = factory.createPropertyStringData("dc:title", "new title1"); PropertyData<?> propDescription = factory.createPropertyStringData("dc:description", "new description1"); Properties properties = factory.createPropertiesData(Arrays.asList(propTitle, propDescription)); Holder<String> objectIdHolder = new Holder<>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(ob); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, properties, null); sleepForFulltext(); waitForIndexing(); // this failed in CMISQL -> SQL mode (NXP-17512) String statement = "SELECT f.* FROM File f WHERE CONTAINS(f, 'title1')"; ObjectList res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); } @Test public void testQueryContainsSyntax() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); assertEquals("testfile1_Title", getString(ob, "dc:title")); PropertyData<?> propTitle = factory.createPropertyStringData("dc:title", "new title1"); PropertyData<?> propDescription = factory.createPropertyStringData("dc:description", "new description1"); Properties properties = factory.createPropertiesData(Arrays.asList(propTitle, propDescription)); Holder<String> objectIdHolder = new Holder<>(ob.getId()); Holder<String> changeTokenHolder = getChangeTokenHolder(ob); objService.updateProperties(repositoryId, objectIdHolder, changeTokenHolder, properties, null); sleepForFulltext(); waitForIndexing(); ObjectList res; String statement; statement = "SELECT cmis:name FROM File WHERE CONTAINS('title1 description1')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); statement = "SELECT cmis:name FROM File WHERE CONTAINS('title1 AND description1')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); statement = "SELECT cmis:name FROM File WHERE CONTAINS('title1 OR blorgzap')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); assertEquals("new title1", getString(res.getObjects().get(0), PropertyIds.NAME)); } @Test public void testQueryScore() throws Exception { sleepForFulltext(); waitForIndexing(); ObjectList res; String statement; ObjectData data; // Oracle cannot match on testfile2_Title, because it gets split // so match on a single word "football" statement = "SELECT cmis:name, SCORE() FROM File" // + " WHERE CONTAINS('football')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); data = res.getObjects().get(0); assertEquals("testfile2_Title", getString(data, PropertyIds.NAME)); assertNotNull(getValue(data, "SEARCH_SCORE")); // name from spec // using an alias for the score statement = "SELECT cmis:name, SCORE() AS priority FROM File" // + " WHERE CONTAINS('football')"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); data = res.getObjects().get(0); assertEquals("testfile2_Title", getString(data, PropertyIds.NAME)); assertNotNull(getValue(data, "priority")); // ORDER BY score statement = "SELECT cmis:name, SCORE() importance FROM File" // + " WHERE CONTAINS('football')" // + " ORDER BY importance DESC"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); data = res.getObjects().get(0); assertEquals("testfile2_Title", getString(data, PropertyIds.NAME)); assertNotNull(getValue(data, "importance")); } @Test public void testQueryJoin() throws Exception { assumeSupportsJoins(); String statement; ObjectList res; ObjectData data; String folder2id = getObjectByPath("/testfolder2").getId(); String folder3id = getObjectByPath("/testfolder2/testfolder3").getId(); String folder4id = getObjectByPath("/testfolder2/testfolder4").getId(); waitForIndexing(); statement = "SELECT A.cmis:objectId, A.dc:title, B.cmis:objectId, B.dc:title" // + " FROM cmis:folder A" // + " JOIN cmis:folder B ON A.cmis:objectId = B.cmis:parentId" // + " WHERE A.cmis:name = 'testfolder2_Title'" // + " ORDER BY B.dc:title"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); data = res.getObjects().get(0); assertEquals(folder2id, getQueryValue(data, "A.cmis:objectId")); assertEquals("testfolder2_Title", getQueryValue(data, "A.dc:title")); assertEquals(folder3id, getQueryValue(data, "B.cmis:objectId")); assertEquals("testfolder3_Title", getQueryValue(data, "B.dc:title")); data = res.getObjects().get(1); assertEquals(folder2id, getQueryValue(data, "A.cmis:objectId")); assertEquals("testfolder2_Title", getQueryValue(data, "A.dc:title")); assertEquals(folder4id, getQueryValue(data, "B.cmis:objectId")); assertEquals("testfolder4_Title", getQueryValue(data, "B.dc:title")); } @Test public void testQueryJoinWithSubQueryMulti() throws Exception { assumeSupportsJoins(); waitForIndexing(); String statement = "SELECT A.cmis:objectId, B.cmis:objectId" // + " FROM cmis:document A" // + " LEFT JOIN File B ON A.cmis:objectId = B.cmis:objectId" // + " WHERE 'foo' = ANY B.dc:subjects"; ObjectList res = query(statement); assertEquals(1, res.getNumItems().intValue()); } @Test public void testQueryJoinWithSubQueryMultiIsNull() throws Exception { assumeSupportsJoins(); waitForIndexing(); String statement = "SELECT A.cmis:objectId, B.cmis:objectId" // + " FROM cmis:document A" // + " LEFT JOIN File B ON A.cmis:objectId = B.cmis:objectId" // + " WHERE B.dc:subjects IS NULL"; ObjectList res = query(statement); assertEquals(5, res.getNumItems().intValue()); // 4 docs, 1 version } @Test public void testQueryJoinWithSecurity() throws Exception { assumeSupportsJoins(); reSetUp("bob"); // only testfile1 and testfile2 are accessible by bob String statement; ObjectList res; waitForIndexing(); // INNER JOIN statement = "SELECT A.cmis:objectId, A.dc:title, B.cmis:objectId, B.dc:title" // + " FROM cmis:folder A" // + " JOIN cmis:folder B ON A.cmis:objectId = B.cmis:parentId" // + " WHERE A.cmis:name = 'testfolder2_Title'" // + " ORDER BY B.dc:title"; res = query(statement); assertEquals(0, res.getNumItems().intValue()); // INNER JOIN statement = "SELECT A.cmis:objectId, B.cmis:objectId" // + " FROM cmis:document A" // + " JOIN File B ON A.cmis:objectId = B.cmis:objectId" // + " WHERE B.cmis:name NOT IN ('testfile3_Title', 'testfile4_Title')"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); // LEFT JOIN statement = "SELECT A.cmis:objectId, B.cmis:objectId" // + " FROM cmis:document A" // + " LEFT JOIN File B ON A.cmis:objectId = B.cmis:objectId" // + " WHERE B.cmis:name NOT IN ('testfile3_Title', 'testfile4_Title')"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); statement = "SELECT A.cmis:objectId, A.cmis:name, B.*, C.note" // + " FROM cmis:document A" // + " LEFT JOIN File B ON A.cmis:objectId = B.cmis:objectId" // + " LEFT JOIN Note C ON A.cmis:objectId = C.cmis:objectId" // + " WHERE (A.cmis:objectTypeId NOT IN ('File')" // + " OR B.cmis:name NOT IN ('testfile3_Title', 'testfile4_Title'))"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); } @Test public void testQueryJoinWithFacets() throws Exception { assumeSupportsJoins(); waitForIndexing(); String statement = "SELECT A.cmis:objectId" // + " FROM cmis:folder A" // + " JOIN cmis:folder B ON A.cmis:objectId = B.cmis:parentId" // + " WHERE ANY A.nuxeo:secondaryObjectTypeIds NOT IN ('Foo')"; ObjectList res = query(statement); assertEquals(4, res.getNumItems().intValue()); // root too } @Test public void testQueryJoinReturnVirtualColumns() throws Exception { assumeSupportsJoins(); waitForIndexing(); String statement = "SELECT A.cmis:objectId, A.nuxeo:contentStreamDigest, B.cmis:path" // + " FROM cmis:document A" // + " JOIN cmis:folder B ON A.nuxeo:parentId = B.cmis:objectId" // + " WHERE A.cmis:name = 'testfile1_Title'"; ObjectList res = query(statement); assertEquals(1, res.getNumItems().intValue()); ObjectData data = res.getObjects().get(0); assertNotNull(getQueryValue(data, "A.nuxeo:contentStreamDigest")); assertEquals("/testfolder1", getQueryValue(data, "B.cmis:path")); } @Test public void testQueryJoinWithMultipleTypes() throws Exception { assumeSupportsJoins(); waitForIndexing(); String statement = "SELECT A.cmis:objectId, A.cmis:name, B.cmis:name, C.note" // + " FROM cmis:document A" // + " LEFT JOIN File B ON A.cmis:objectId = B.cmis:objectId" // + " LEFT JOIN Note C ON A.cmis:objectId = C.cmis:objectId" // + " WHERE ANY A.nuxeo:secondaryObjectTypeIds NOT IN ('Foo')" // + " AND (A.cmis:objectTypeId NOT IN ('File')" // + " OR B.cmis:name NOT IN ('testfile3_Title', 'testfile4_Title'))"; ObjectList res = query(statement); assertEquals(5, res.getNumItems().intValue()); // 4 docs, 1 version } @Test public void testQueryJoinWithMultipleTypes2() throws Exception { assumeSupportsJoins(); waitForIndexing(); String statement = "SELECT A.cmis:objectId, B.cmis:objectId, C.cmis:objectId" // + " FROM cmis:document A" // + " LEFT JOIN File B ON A.cmis:objectId = B.cmis:objectId" // + " LEFT JOIN Note C ON A.cmis:objectId = C.cmis:objectId" // + " WHERE B.cmis:name NOT IN ('testfile3_Title', 'testfile4_Title')"; ObjectList res = query(statement); assertEquals(2, res.getNumItems().intValue()); } @Test public void testQueryJoinSecondaryType() throws Exception { // this is a JOIN with secondary type, always valid even if JOINs are not supported // not implemented for direct CMISQL -> SQL translation assumeFalse("not implemented", supportsJoins()); DocumentModel doc = coreSession.getDocument(new PathRef("/testfolder1/testfile1")); doc.addFacet("CustomFacetWithMySchema2"); doc.setPropertyValue("my2:string", "foo"); coreSession.saveDocument(doc); coreSession.save(); nextTransaction(); waitForIndexing(); String statement = "SELECT *" // + " FROM cmis:document D" // + " JOIN facet:CustomFacetWithMySchema2 F" // + " ON D.cmis:objectId = F.cmis:objectId" // + " WHERE D.cmis:name = 'testfile1_Title'" // + " AND F.my2:string = 'foo'"; ObjectList res = query(statement); assertEquals(1, res.getNumItems().intValue()); statement = "SELECT *" // + " FROM cmis:document D" // + " JOIN facet:CustomFacetWithMySchema2 F" // + " ON D.cmis:objectId = F.cmis:objectId" // + " WHERE F.my2:string = 'notfoo'"; res = query(statement); assertEquals(0, res.getNumItems().intValue()); } @Test public void testQueryBad() throws Exception { try { query("SELECT foo bar baz"); fail(); } catch (CmisInvalidArgumentException e) { assertTrue(e.getMessage().contains("line 1:15 missing FROM at 'baz'")); } try { query("SELECT foo FROM bar"); fail(); } catch (CmisInvalidArgumentException e) { assertTrue(e.getMessage().contains("bar is neither a type query name nor an alias")); } try { query("SELECT foo FROM cmis:folder"); fail(); } catch (CmisInvalidArgumentException e) { assertTrue(e.getMessage().contains("foo is not a property query name in any of the types")); } } @Test public void testQueryBatching() throws Exception { int NUM = 20; for (int i = 0; i < NUM; i++) { String name = String.format("somedoc%03d", Integer.valueOf(i)); objService.createDocument(repositoryId, createBaseDocumentProperties(name, "cmis:document"), rootFolderId, null, VersioningState.CHECKEDOUT, null, null, null, null); } waitForIndexing(); ObjectList res; List<ObjectData> objects; String statement = "SELECT cmis:name FROM cmis:document" + " WHERE cmis:name LIKE 'somedoc%' ORDER BY cmis:name"; res = discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, null, null, null); assertEquals(NUM, res.getNumItems().intValue()); objects = res.getObjects(); assertEquals(NUM, objects.size()); assertEquals("somedoc000", getString(objects.get(0), PropertyIds.NAME)); assertEquals("somedoc019", getString(objects.get(objects.size() - 1), PropertyIds.NAME)); // batch res = discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, BigInteger.valueOf(10), BigInteger.valueOf(5), null); assertEquals(NUM, res.getNumItems().intValue()); objects = res.getObjects(); assertEquals(10, objects.size()); assertEquals("somedoc005", getString(objects.get(0), PropertyIds.NAME)); assertEquals("somedoc014", getString(objects.get(objects.size() - 1), PropertyIds.NAME)); } @Test public void testQueryPWC() throws Exception { waitForIndexing(); // TODO proxies shouldn't be considered checked out boolean expectProxies = supportsProxies() && !useElasticsearch(); ObjectList list = navService.getCheckedOutDocs(repositoryId, null, null, null, null, null, null, null, null, null); assertEquals(expectProxies ? 5 : 4, list.getNumItems().intValue()); // 4 docs, 1 proxy ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); Holder<String> idHolder = new Holder<>(id); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, null, null, "comment", null, null, null, null); waitForIndexing(); list = navService.getCheckedOutDocs(repositoryId, null, null, null, null, null, null, null, null, null); assertEquals(expectProxies ? 4 : 3, list.getNumItems().intValue()); // 3 docs, 1 proxy verService.checkOut(repositoryId, idHolder, null, null); waitForIndexing(); // re-checkout (ecm:isCheckedIn now false instead of null earlier) list = navService.getCheckedOutDocs(repositoryId, null, null, null, null, null, null, null, null, null); assertEquals(expectProxies ? 5 : 4, list.getNumItems().intValue()); // 4 docs, 1 proxy // with folder and filter and order ObjectData f1 = getObjectByPath("/testfolder1"); list = navService.getCheckedOutDocs(repositoryId, f1.getId(), "cmis:name", "cmis:name DESC", null, null, null, null, null, null); assertEquals(3, list.getNumItems().intValue()); List<ObjectData> objects = list.getObjects(); assertEquals("testfile3_Title", getValue(objects.get(0), "cmis:name")); assertEquals("testfile2_Title", getValue(objects.get(1), "cmis:name")); assertEquals("testfile1_Title", getValue(objects.get(2), "cmis:name")); } @Test public void testQueryAllVersions() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); // two versions Holder<String> idHolder = new Holder<>(id); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, null, null, "comment", null, null, null, null); verService.checkOut(repositoryId, idHolder, null, null); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, null, null, "comment", null, null, null, null); waitForIndexing(); ObjectList res; String statement = "SELECT cmis:objectId FROM cmis:document WHERE cmis:name = 'testfile1_Title'"; // search all versions res = discService.query(repositoryId, statement, Boolean.TRUE, null, null, null, null, null, null); assertEquals(3, res.getNumItems().intValue()); // do not search all versions (only latest) res = discService.query(repositoryId, statement, Boolean.FALSE, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); res = discService.query(repositoryId, statement, null, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); } @Test public void testQueryAllVersionsFolders() throws Exception { ObjectList res; Boolean searchAllVersions; waitForIndexing(); String statement = "SELECT cmis:objectId FROM cmis:folder WHERE cmis:name = 'testfolder2_Title'"; searchAllVersions = Boolean.TRUE; res = discService.query(repositoryId, statement, searchAllVersions, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); searchAllVersions = Boolean.FALSE; res = discService.query(repositoryId, statement, searchAllVersions, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); searchAllVersions = null; res = discService.query(repositoryId, statement, searchAllVersions, null, null, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); } @Test // NXP-12776 randomly failing @Ignore public void testVersioning() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); waitForIndexing(); // checked out checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_LABEL, null, ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, id, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, USERNAME, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(PropertyIds.IS_PRIVATE_WORKING_COPY, Boolean.TRUE, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); checkValue(NuxeoTypeHelper.NX_ISCHECKEDIN, Boolean.FALSE, ob); String series = (String) getValue(ob, PropertyIds.VERSION_SERIES_ID); // check in major -> version 1.0 Holder<String> idHolder = new Holder<>(id); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, null, null, "comment", null, null, null, null); waitForIndexing(); String vid = idHolder.getValue(); ObjectData ver = getObject(vid); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ver); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.TRUE, ver); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.TRUE, ver); checkValue(PropertyIds.VERSION_LABEL, "1.0", ver); checkValue(PropertyIds.VERSION_SERIES_ID, series, ver); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ver); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null, ver); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null, ver); checkValue(PropertyIds.CHECKIN_COMMENT, "comment", ver); checkValue(PropertyIds.IS_PRIVATE_WORKING_COPY, Boolean.FALSE, ver); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.TRUE, ver); checkValue(NuxeoTypeHelper.NX_ISCHECKEDIN, Boolean.TRUE, ver); // hm // look at the checked in document to verify // that CMIS views it as a version ObjectData ci = getObject(id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ci); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.TRUE, ci); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.TRUE, ci); checkValue(PropertyIds.VERSION_LABEL, "1.0", ci); checkValue(PropertyIds.VERSION_SERIES_ID, series, ci); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ci); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null, ci); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null, ci); checkValue(PropertyIds.CHECKIN_COMMENT, "comment", ci); // not viewed as a version according to Nuxeo semantics though ob = getObjectByPath("/testfolder1/testfile1"); checkValue(PropertyIds.IS_PRIVATE_WORKING_COPY, Boolean.FALSE, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); checkValue(NuxeoTypeHelper.NX_ISCHECKEDIN, Boolean.TRUE, ob); // check out Holder<Boolean> cchold = new Holder<>(); verService.checkOut(repositoryId, idHolder, null, cchold); waitForIndexing(); String coid = idHolder.getValue(); ObjectData co = getObject(coid); assertEquals(id, coid); // Nuxeo invariant assertEquals(Boolean.TRUE, cchold.getValue()); // copied checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.FALSE, co); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, co); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, co); checkValue(PropertyIds.VERSION_LABEL, null, co); checkValue(PropertyIds.VERSION_SERIES_ID, series, co); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE, co); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, coid, co); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, USERNAME, co); checkValue(PropertyIds.CHECKIN_COMMENT, null, co); checkValue(PropertyIds.IS_PRIVATE_WORKING_COPY, Boolean.TRUE, co); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, co); checkValue(NuxeoTypeHelper.NX_ISCHECKEDIN, Boolean.FALSE, co); // check in minor -> version 1.1 idHolder.setValue(coid); verService.checkIn(repositoryId, idHolder, Boolean.FALSE, null, null, "comment2", null, null, null, null); waitForIndexing(); String v2id = idHolder.getValue(); ObjectData ver2 = getObject(v2id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ver2); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, ver2); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, ver2); checkValue(PropertyIds.VERSION_LABEL, "1.1", ver2); checkValue(PropertyIds.VERSION_SERIES_ID, series, ver2); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ver2); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null, ver2); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null, ver2); checkValue(PropertyIds.CHECKIN_COMMENT, "comment2", ver2); checkValue(PropertyIds.IS_PRIVATE_WORKING_COPY, Boolean.FALSE, ver2); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.TRUE, ver2); checkValue(NuxeoTypeHelper.NX_ISCHECKEDIN, Boolean.TRUE, ver2); // check out again (with no content copied holder) verService.checkOut(repositoryId, idHolder, null, null); coid = idHolder.getValue(); co = getObject(coid); assertEquals(id, coid); // Nuxeo invariant // cancel check out waitForAsyncCompletion(); verService.cancelCheckOut(repositoryId, coid, null); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ver2); ci = getObject(id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ci); // not viewed as a version according to Nuxeo semantics checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ci); // list all versions // TODO check this when no live document exists // have a checked out doc idHolder.setValue(id); verService.checkOut(repositoryId, idHolder, null, null); // atompub passes just object id, soap just version series id List<ObjectData> vers = verService.getAllVersions(repositoryId, id, null, null, null, null); assertEquals(3, vers.size()); assertEquals(id, vers.get(0).getId()); assertEquals(ver2.getId(), vers.get(1).getId()); assertEquals(ver.getId(), vers.get(2).getId()); // get latest version Boolean major = Boolean.FALSE; ObjectData l = verService.getObjectOfLatestVersion(repositoryId, id, null, major, null, null, null, null, null, null, null); assertEquals(ver2.getId(), l.getId()); // also works on a version object l = verService.getObjectOfLatestVersion(repositoryId, ver.getId(), null, major, null, null, null, null, null, null, null); assertEquals(ver2.getId(), l.getId()); // latest major version major = Boolean.TRUE; l = verService.getObjectOfLatestVersion(repositoryId, id, null, major, null, null, null, null, null, null, null); assertEquals(ver.getId(), l.getId()); l = verService.getObjectOfLatestVersion(repositoryId, ver2.getId(), null, major, null, null, null, null, null, null, null); assertEquals(ver.getId(), l.getId()); major = Boolean.FALSE; Properties p = verService.getPropertiesOfLatestVersion(repositoryId, id, null, major, null, null); assertEquals(ver2.getId(), p.getProperties().get(PropertyIds.OBJECT_ID).getFirstValue()); } @Test public void testCancelCheckout() throws Exception { // initial VersioningState.CHECKEDOUT ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); waitForAsyncCompletion(); verService.cancelCheckOut(repositoryId, id, null); try { getObject(id); fail("Document should be deleted"); } catch (CmisObjectNotFoundException e) { // ok } } @Test public void testCancelCheckout2() throws Exception { // VersioningState.CHECKEDOUT after VersioningState.MAJOR ObjectData ob = getObjectByPath("/testfolder1/testfile2"); String id = ob.getId(); Properties props = createProperties("dc:title", "newtitle"); byte[] bytes = "foo-bar".getBytes("UTF-8"); ByteArrayInputStream in = new ByteArrayInputStream(bytes); ContentStream cs = new ContentStreamImpl("test.txt", BigInteger.valueOf(bytes.length), "text/plain", in); Holder<String> idHolder = new Holder<>(id); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, props, cs, "comment", null, null, null, null); String vid = idHolder.getValue(); ObjectData ver = getObject(vid); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ver); checkValue(PropertyIds.VERSION_LABEL, "1.0", ver); checkValue(PropertyIds.CHECKIN_COMMENT, "comment", ver); checkValue("dc:title", "newtitle", ver); verService.checkOut(repositoryId, idHolder, null, null); String pwcId = idHolder.getValue(); ObjectData pwc = getObject(pwcId); checkValue(PropertyIds.VERSION_LABEL, null, pwc); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, pwc); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE, pwc); waitForAsyncCompletion(); verService.cancelCheckOut(repositoryId, pwcId, null); ob = getObject(id); assertEquals(id, ob.getId()); checkValue(PropertyIds.VERSION_LABEL, "1.0", ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ob); } @Test @LocalDeploy("org.nuxeo.ecm.core.opencmis.tests.tests:OSGI-INF/cancelcheckout-error-draft.xml") public void testCancelCheckoutErrorOnDraft() { // initial VersioningState.CHECKEDOUT waitForAsyncCompletion(); ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); try { verService.cancelCheckOut(repositoryId, id, null); fail("should have failed due to configuration"); } catch (CmisVersioningException e) { assertEquals("Cannot cancelCheckOut of draft version due to configuration", e.getMessage()); } ob = getObject(id); assertEquals(id, ob.getId()); checkValue(PropertyIds.VERSION_LABEL, null, ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE, ob); } @Test public void testCheckInWithChanges() throws Exception { ObjectData ob = getObjectByPath("/testfolder1/testfile1"); String id = ob.getId(); // check in with data Properties props = createProperties("dc:title", "newtitle"); byte[] bytes = "foo-bar".getBytes("UTF-8"); ByteArrayInputStream in = new ByteArrayInputStream(bytes); ContentStream cs = new ContentStreamImpl("test.txt", BigInteger.valueOf(bytes.length), "text/plain", in); Holder<String> idHolder = new Holder<>(id); harness.deployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", "OSGI-INF/comment-listener-contrib.xml"); try { CommentListener.clearComments(); verService.checkIn(repositoryId, idHolder, Boolean.TRUE, props, cs, "comment", null, null, null, null); } finally { harness.undeployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", "OSGI-INF/comment-listener-contrib.xml"); } List<String> comments = CommentListener.getComments(); assertEquals(Arrays.asList("documentModified:comment=comment,checkInComment=null", "documentCheckedIn:comment=1.0 comment,checkInComment=comment", "documentCreated:comment=1.0 comment,checkInComment=comment"), comments); String vid = idHolder.getValue(); ObjectData ver = getObject(vid); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ver); checkValue(PropertyIds.VERSION_LABEL, "1.0", ver); checkValue(PropertyIds.CHECKIN_COMMENT, "comment", ver); // check changes applied checkValue("dc:title", "newtitle", ver); ContentStream cs2 = objService.getContentStream(repositoryId, ver.getId(), null, null, null, null); assertEquals("text/plain", cs2.getMimeType()); assertEquals(bytes.length, cs2.getLength()); assertEquals("test.txt", cs2.getFileName()); ByteArrayOutputStream os = new ByteArrayOutputStream(); IOUtils.copy(cs2.getStream(), os); assertEquals("foo-bar", os.toString("UTF-8")); } @Test public void testVersioningInitialState() { // creation as major version (default, per spec) String id = objService.createDocument(repositoryId, createBaseDocumentProperties("newdoc2", "cmis:document"), rootFolderId, null, VersioningState.MAJOR, null, null, null, null); ObjectData ob = getObject(id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.VERSION_LABEL, "1.0", ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); // ... // copy from checked in source as checked out id = objService.createDocumentFromSource(repositoryId, id, null, rootFolderId, VersioningState.CHECKEDOUT, null, null, null, null); ob = getObject(id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_LABEL, null, ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, id, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, USERNAME, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); // creation as minor version id = objService.createDocument(repositoryId, createBaseDocumentProperties("newdoc2", "cmis:document"), rootFolderId, null, VersioningState.MINOR, null, null, null, null); ob = getObject(id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_LABEL, "0.1", ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); // ... // creation checked out id = objService.createDocument(repositoryId, createBaseDocumentProperties("newdoc3", "cmis:document"), rootFolderId, null, VersioningState.CHECKEDOUT, null, null, null, null); ob = getObject(id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_LABEL, null, ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, id, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, USERNAME, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); // copy from checked out source as checked in id = objService.createDocumentFromSource(repositoryId, id, null, rootFolderId, VersioningState.MAJOR, null, null, null, null); ob = getObject(id); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.VERSION_LABEL, "1.0", ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); // ... } @Test public void testProxyVersionProperties() throws Exception { assumeSupportsProxies(); // check proxy to a version ObjectData ob = getObjectByPath("/testfolder2/testfile6"); checkValue("dc:title", "title6", ob); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.TRUE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_LABEL, "0.1", ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); // ... // create live proxy Helper.sleepForAuditGranularity(); DocumentModel proxy = coreSession.createProxy(new PathRef("/testfolder1/testfile1"), new PathRef("/testfolder2")); coreSession.save(); nextTransaction(); waitForIndexing(); // check live proxy ob = getObjectByPath("/testfolder2/testfile1"); checkValue("dc:title", "testfile1_Title", ob); checkValue(PropertyIds.IS_LATEST_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.IS_LATEST_MAJOR_VERSION, Boolean.FALSE, ob); checkValue(PropertyIds.VERSION_LABEL, null, ob); checkValue(PropertyIds.VERSION_SERIES_ID, NOT_NULL, ob); checkValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, Boolean.TRUE, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, NOT_NULL, ob); checkValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, USERNAME, ob); checkValue(PropertyIds.CHECKIN_COMMENT, null, ob); checkValue(NuxeoTypeHelper.NX_ISVERSION, Boolean.FALSE, ob); } @Test public void testGetContentChanges() throws Exception { doTestGetContentChanges(false); } @Test public void testGetContentChangesHiddenType() throws Exception { doTestGetContentChanges(true); } protected void doTestGetContentChanges(boolean addHidden) throws Exception { List<ObjectData> objects; Holder<String> changeLogTokenHolder = new Holder<>(); if (addHidden) { // add a doc whose type is not known to CMIS DocumentModel doc = coreSession.createDocumentModel("/", "hidden", "HiddenFolder"); Helper.sleepForAuditGranularity(); coreSession.createDocument(doc); coreSession.save(); nextTransaction(); } sleepForAudit(); String clt1 = repoService.getRepositoryInfo(repositoryId, null).getLatestChangeLogToken(); assertNotNull(clt1); List<ObjectData> allObjects = readAllContentChanges(changeLogTokenHolder); if (!addHidden) { assertEquals(clt1, changeLogTokenHolder.getValue()); } int n = 13; // last n events assertTrue(allObjects.size() >= n); objects = allObjects.subList(allObjects.size() - n, allObjects.size()); checkChange(objects.get(0), "/testfolder1", // ChangeType.CREATED, "Folder"); checkChange(objects.get(1), "/testfolder1/testfile1", ChangeType.CREATED, "File"); checkChange(objects.get(2), "/testfolder1/testfile2", ChangeType.CREATED, "File"); checkChange(objects.get(3), "/testfolder1/testfile3", ChangeType.CREATED, "Note"); checkChange(objects.get(4), "/testfolder2", // ChangeType.CREATED, "Folder"); checkChange(objects.get(5), "/testfolder2/testfolder3", ChangeType.CREATED, "Folder"); checkChange(objects.get(6), "/testfolder2/testfolder4", ChangeType.CREATED, "Folder"); checkChange(objects.get(7), "/testfolder2/testfolder3/testfile4", ChangeType.CREATED, "File"); checkChange(objects.get(8), file5id, ChangeType.CREATED, "File"); checkChange(objects.get(9), file5id, ChangeType.UPDATED, "File"); checkChange(objects.get(10), "/testfolder2/testfolder3/testfile6", ChangeType.CREATED, "Note"); checkChange(objects.get(11), file6verid, ChangeType.CREATED, "Note"); checkChange(objects.get(12), proxyid, ChangeType.CREATED, "Note"); // remove a doc ObjectData ob1 = getObjectByPath("/testfolder1/testfile1"); objService.deleteObject(repositoryId, ob1.getId(), Boolean.TRUE, null); nextTransaction(); // get latest change log token sleepForAudit(); String clt2 = repoService.getRepositoryInfo(repositoryId, null).getLatestChangeLogToken(); assertNotNull(clt2); assertNotEquals(clt2, clt1); changeLogTokenHolder.setValue(clt2); // just the last ObjectList changes; changes = discService.getContentChanges(repositoryId, changeLogTokenHolder, Boolean.TRUE, null, null, null, BigInteger.valueOf(100), null); objects = changes.getObjects(); assertEquals(1, objects.size()); checkChange(objects.get(0), ob1.getId(), ChangeType.DELETED, "File"); } @Test public void testGetContentChangesBatchHiddenType() throws Exception { Holder<String> changeLogTokenHolder = new Holder<>(); // add docs whose type is not known to CMIS for (int i = 0; i < 15; i++) { DocumentModel doc = coreSession.createDocumentModel("/", "hidden" + i, "HiddenFolder"); Helper.sleepForAuditGranularity(); doc = coreSession.createDocument(doc); coreSession.save(); } // add a regular doc DocumentModel doc = coreSession.createDocumentModel("/", "regular", "File"); Helper.sleepForAuditGranularity(); doc = coreSession.createDocument(doc); coreSession.save(); nextTransaction(); sleepForAudit(); String clt1 = repoService.getRepositoryInfo(repositoryId, null).getLatestChangeLogToken(); assertNotNull(clt1); List<ObjectData> allObjects = readAllContentChanges(changeLogTokenHolder); assertEquals(clt1, changeLogTokenHolder.getValue()); int n = 14; // last n events assertTrue(allObjects.size() >= n); List<ObjectData> objects = allObjects.subList(allObjects.size() - n, allObjects.size()); checkChange(objects.get(0), "/testfolder1", // ChangeType.CREATED, "Folder"); checkChange(objects.get(1), "/testfolder1/testfile1", ChangeType.CREATED, "File"); checkChange(objects.get(2), "/testfolder1/testfile2", ChangeType.CREATED, "File"); checkChange(objects.get(3), "/testfolder1/testfile3", ChangeType.CREATED, "Note"); checkChange(objects.get(4), "/testfolder2", // ChangeType.CREATED, "Folder"); checkChange(objects.get(5), "/testfolder2/testfolder3", ChangeType.CREATED, "Folder"); checkChange(objects.get(6), "/testfolder2/testfolder4", ChangeType.CREATED, "Folder"); checkChange(objects.get(7), "/testfolder2/testfolder3/testfile4", ChangeType.CREATED, "File"); checkChange(objects.get(8), file5id, ChangeType.CREATED, "File"); checkChange(objects.get(9), file5id, ChangeType.UPDATED, "File"); checkChange(objects.get(10), "/testfolder2/testfolder3/testfile6", ChangeType.CREATED, "Note"); checkChange(objects.get(11), file6verid, ChangeType.CREATED, "Note"); checkChange(objects.get(12), proxyid, ChangeType.CREATED, "Note"); checkChange(objects.get(13), doc.getId(), ChangeType.CREATED, "File"); } protected List<ObjectData> readAllContentChanges(Holder<String> changeLogTokenHolder) { List<ObjectData> allObjects = new ArrayList<>(); changeLogTokenHolder.setValue(null); // start at beginning boolean skipFirst = false; ObjectList changes; do { int maxItems = 5; changes = discService.getContentChanges(repositoryId, changeLogTokenHolder, Boolean.TRUE, null, null, null, BigInteger.valueOf(maxItems), null); List<ObjectData> objects = changes.getObjects(); if (skipFirst) { // already got the first one as part of the last batch objects = objects.subList(1, objects.size()); } allObjects.addAll(objects); skipFirst = true; } while (Boolean.TRUE.equals(changes.hasMoreItems())); return allObjects; } protected void sleepForAudit() throws InterruptedException { Thread.sleep(5 * 1000); // wait for audit log to catch up } protected void checkChange(ObjectData data, String id, ChangeType changeType, String type) throws Exception { Map<String, PropertyData<?>> properties; ChangeEventInfo cei; cei = data.getChangeEventInfo(); properties = data.getProperties().getProperties(); String expectedId = id.startsWith("/") ? getObjectByPath(id).getId() : id; assertEquals(expectedId, data.getId()); assertEquals(changeType, cei.getChangeType()); assertEquals(type, properties.get(PropertyIds.OBJECT_TYPE_ID).getFirstValue()); } @Test public void testRelationship() throws Exception { assumeSupportsJoins(); String id1 = getObjectByPath("/testfolder1/testfile1").getId(); String id2 = getObjectByPath("/testfolder1/testfile2").getId(); // create relationship String statement; ObjectList res; List<PropertyData<?>> props = new ArrayList<>(); props.add(factory.createPropertyIdData(PropertyIds.NAME, "rel")); props.add(factory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, "Relation")); props.add(factory.createPropertyIdData(PropertyIds.SOURCE_ID, id1)); props.add(factory.createPropertyIdData(PropertyIds.TARGET_ID, id2)); Properties properties = factory.createPropertiesData(props); String relid = objService.createRelationship(repositoryId, properties, null, null, null, null); waitForIndexing(); // must be superuser... // ObjectData rel = getObject(relid); // assertEquals("rel", getValue(rel, PropertyIds.NAME)); // assertNull(getValue(rel, NuxeoTypeHelper.NX_PARENT_ID)); // objects have relationship info ObjectData od1 = getObject(id1); List<ObjectData> rels1 = od1.getRelationships(); assertNotNull(rels1); assertEquals(1, rels1.size()); // check relation base type id present assertNotNull(getValue(rels1.get(0), PropertyIds.BASE_TYPE_ID)); ObjectData od2 = getObject(id2); List<ObjectData> rels2 = od2.getRelationships(); assertNotNull(rels2); assertEquals(1, rels2.size()); // object from query have relationship info statement = "SELECT cmis:objectId FROM File WHERE cmis:name = 'testfile1_Title'"; res = discService.query(repositoryId, statement, Boolean.TRUE, null, IncludeRelationships.BOTH, null, null, null, null); assertEquals(1, res.getNumItems().intValue()); od1 = res.getObjects().get(0); rels1 = od1.getRelationships(); assertNotNull(rels1); assertEquals(1, rels1.size()); // check relation base type id present assertNotNull(getValue(rels1.get(0), PropertyIds.BASE_TYPE_ID)); // query relationship statement = "SELECT cmis:objectId, cmis:name, cmis:sourceId, cmis:targetId FROM Relation"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); ObjectData od = res.getObjects().get(0); assertEquals(relid, getValue(od, PropertyIds.OBJECT_ID)); assertEquals("rel", getValue(od, PropertyIds.NAME)); assertEquals(id1, getValue(od, PropertyIds.SOURCE_ID)); assertEquals(id2, getValue(od, PropertyIds.TARGET_ID)); // normal user has security applied to its queries reSetUp("john"); statement = "SELECT A.cmis:objectId, B.cmis:objectId FROM cmis:document A" + " JOIN cmis:relationship R ON R.cmis:sourceId = A.cmis:objectId" + " JOIN cmis:document B ON R.cmis:targetId = B.cmis:objectId"; res = query(statement); // no access to testfile1 or testfile2 by john assertEquals(0, res.getNumItems().intValue()); // bob has Browse on testfile1 and testfile2 reSetUp("bob"); // no security check on relationship itself statement = "SELECT A.cmis:objectId, B.cmis:objectId FROM cmis:document A" + " JOIN cmis:relationship R ON R.cmis:sourceId = A.cmis:objectId" + " JOIN cmis:document B ON R.cmis:targetId = B.cmis:objectId"; res = query(statement); assertEquals(1, res.getNumItems().intValue()); // with LEFT JOIN on relation statement = "SELECT A.cmis:objectId, B.cmis:objectId FROM cmis:document A" + " LEFT JOIN cmis:relationship R ON R.cmis:sourceId = A.cmis:objectId" + " LEFT JOIN cmis:document B ON R.cmis:targetId = B.cmis:objectId"; res = query(statement); assertEquals(2, res.getNumItems().intValue()); } @Test public void testQueryWithSecurityPolicy() throws Exception { DocumentModel doc = coreSession.getDocument(new PathRef("/testfolder1/testfile1")); doc.setPropertyValue("dc:title", "SECRET should not be listed"); coreSession.saveDocument(doc); coreSession.save(); nextTransaction(); waitForIndexing(); ObjectList res = query("SELECT cmis:objectId FROM File"); assertEquals(3, res.getNumItems().intValue()); // manually check res = query("SELECT cmis:objectId FROM File WHERE dc:title NOT LIKE 'SECRET%'"); assertEquals(2, res.getNumItems().intValue()); if (!supportsNXQLQueryTransformers()) { // deploy a security policy with a non-trivial query transformer // that has no CMISQL equivalent harness.deployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", "OSGI-INF/security-policy-contrib.xml"); // check that queries now fail try { query("SELECT cmis:objectId FROM File"); fail("Should be denied due to security policy"); } catch (CmisRuntimeException e) { String msg = e.getMessage(); assertTrue(msg, msg.contains("Security policy")); } finally { harness.undeployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", "OSGI-INF/security-policy-contrib.xml"); } // without it it works again res = query("SELECT cmis:objectId FROM File"); assertEquals(3, res.getNumItems().intValue()); } if (!useElasticsearch()) { // deploy a security policy with a transformer String contrib = supportsNXQLQueryTransformers() ? "OSGI-INF/security-policy-contrib3.xml" : "OSGI-INF/security-policy-contrib2.xml"; harness.deployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", contrib); try { res = query("SELECT cmis:objectId FROM File"); assertEquals(2, res.getNumItems().intValue()); res = query("SELECT cmis:objectId FROM File WHERE dc:title <> 'something'"); assertEquals(2, res.getNumItems().intValue()); } finally { harness.undeployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", contrib); } } } /** Get ACL, using * suffix on username to denote non-direct. */ protected static Map<String, Set<String>> getActualAcl(Acl acl) { Map<String, Set<String>> actual = new HashMap<>(); for (Ace ace : acl.getAces()) { actual.put(ace.getPrincipalId() + (ace.isDirect() ? "" : "*"), new HashSet<>(ace.getPermissions())); } return actual; } @Test public void testGetACLBase() throws Exception { String file1Id = getObjectByPath("/testfolder1/testfile1").getId(); Acl acl = aclService.getAcl(repositoryId, file1Id, Boolean.FALSE, null); assertEquals(Boolean.TRUE, acl.isExact()); Map<String, Set<String>> actual = getActualAcl(acl); Map<String, Set<String>> expected = new HashMap<>(); expected.put("bob", set("Browse")); expected.put("members*", set(READ, "Read")); expected.put("Administrator*", set(READ, WRITE, ALL, "Everything")); assertEquals(expected, actual); // with only basic permissions acl = aclService.getAcl(repositoryId, file1Id, Boolean.TRUE, null); assertEquals(Boolean.FALSE, acl.isExact()); actual = getActualAcl(acl); expected = new HashMap<>(); expected.put("members*", set(READ)); expected.put("Administrator*", set(READ, WRITE, ALL)); assertEquals(expected, actual); } @SuppressWarnings("deprecation") @Test public void testGetACL() throws Exception { String folder1Id = getObjectByPath("/testfolder1").getId(); String file1Id = getObjectByPath("/testfolder1/testfile1").getId(); String file4Id = getObjectByPath("/testfolder2/testfolder3/testfile4").getId(); // set more complex ACLs { // file1 ACP acp = new ACPImpl(); ACL acl = new ACLImpl(); acl.add(new ACE("pete", SecurityConstants.READ_WRITE, true)); acl.add(new ACE("john", SecurityConstants.WRITE, true)); acp.addACL(acl); // other ACL acl = new ACLImpl("workflow"); acl.add(new ACE("steve", SecurityConstants.READ, true)); acp.addACL(acl); coreSession.setACP(new IdRef(file1Id), acp, true); // folder1 acp = new ACPImpl(); acl = new ACLImpl(); acl.add(new ACE("mary", SecurityConstants.READ, true)); acp.addACL(acl); coreSession.setACP(new IdRef(folder1Id), acp, true); // block on testfile4 acp = new ACPImpl(); acl = new ACLImpl(); acl.add(new ACE(SecurityConstants.ADMINISTRATOR, SecurityConstants.READ, true)); acl.add(new ACE(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false)); acp.addACL(acl); coreSession.setACP(new IdRef(file4Id), acp, true); coreSession.save(); nextTransaction(); // // process invalidations // ((NuxeoBinding) binding).getCoreSession().save(); } Acl acl = aclService.getAcl(repositoryId, file1Id, Boolean.FALSE, null); assertEquals(Boolean.TRUE, acl.isExact()); Map<String, Set<String>> actual = getActualAcl(acl); Map<String, Set<String>> expected = new HashMap<>(); expected.put("pete", set(READ, WRITE, "ReadWrite")); expected.put("john", set("Write")); // * for inherited or not local acl expected.put("steve*", set(READ, "Read")); expected.put("mary*", set(READ, "Read")); expected.put("members*", set(READ, "Read")); expected.put("Administrator*", set(READ, WRITE, ALL, "Everything")); assertEquals(expected, actual); // direct Object API ObjectData ob = objService.getObjectByPath(repositoryId, "/testfolder1/testfile1", null, null, null, null, null, Boolean.TRUE, null); // includeAcl acl = ob.getAcl(); assertEquals(Boolean.TRUE, acl.isExact()); actual = getActualAcl(acl); assertEquals(expected, actual); // check blocking acl = aclService.getAcl(repositoryId, file4Id, Boolean.FALSE, null); assertEquals(Boolean.TRUE, acl.isExact()); actual = getActualAcl(acl); expected = new HashMap<>(); expected.put("Administrator", set(READ, "Read")); expected.put("Everyone", set("Nothing")); assertEquals(expected, actual); } @Test public void testApplyACL() throws Exception { String file1Id = getObjectByPath("/testfolder1/testfile1").getId(); // file1 already has a bob -> Browse permission from setUp // add Principal p = new AccessControlPrincipalDataImpl("mary"); Ace ace = new AccessControlEntryImpl(p, Arrays.asList(READ)); Acl addAces = new AccessControlListImpl(Arrays.asList(ace)); Acl removeAces = null; Acl acl = aclService.applyAcl(repositoryId, file1Id, addAces, removeAces, AclPropagation.REPOSITORYDETERMINED, null); assertEquals(Boolean.TRUE, acl.isExact()); Map<String, Set<String>> actual = getActualAcl(acl); Map<String, Set<String>> expected = new HashMap<>(); expected.put("bob", set("Browse")); expected.put("mary", set(READ, "Read")); expected.put("members*", set(READ, "Read")); expected.put("Administrator*", set(READ, WRITE, ALL, "Everything")); assertEquals(expected, actual); // remove ace = new AccessControlEntryImpl(p, Arrays.asList(READ)); addAces = null; removeAces = new AccessControlListImpl(Arrays.asList(ace)); acl = aclService.applyAcl(repositoryId, file1Id, addAces, removeAces, AclPropagation.REPOSITORYDETERMINED, null); assertEquals(Boolean.TRUE, acl.isExact()); actual = getActualAcl(acl); expected = new HashMap<>(); expected.put("bob", set("Browse")); expected.put("members*", set(READ, "Read")); expected.put("Administrator*", set(READ, WRITE, ALL, "Everything")); assertEquals(expected, actual); } @Test public void testRecoverableException() throws Exception { // listener that will cause a RecoverableClientException to be thrown // when a doc whose name starts with "throw" is created harness.deployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", "OSGI-INF/recoverable-exc-listener-contrib.xml"); try { createDocument("throw_foo", rootFolderId, "File"); fail("should throw RecoverableClientException"); } catch (CmisRuntimeException e) { Throwable cause = e.getCause(); assertTrue(String.valueOf(cause), cause instanceof RecoverableClientException); } finally { harness.undeployContrib("org.nuxeo.ecm.core.opencmis.tests.tests", "OSGI-INF/recoverable-exc-listener-contrib.xml"); } } @Test public void testQueryProxy() throws Exception { waitForIndexing(); // getChildren DocumentModel folder = coreSession.getDocument(new PathRef("/testfolder2")); ObjectInFolderList children = navService.getChildren(repositoryId, folder.getId(), null, null, null, null, null, null, null, null, null); assertEquals(supportsProxies() ? 3 : 2, children.getNumItems().intValue()); // 2 folders, 1 proxy // query String query = "SELECT cmis:objectId FROM Note WHERE cmis:name = 'title6'"; ObjectList res = query(query); assertEquals(supportsProxies() ? 3 : 2, res.getNumItems().intValue()); // 1 live doc, 1 version, 1 proxy } }