/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . * #L% */ package org.alfresco.repo.dictionary; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; /** * * @author sglover * */ public class ModelValidatorTest { private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private String testNamespace; private String modelName; private ModelValidator modelValidator; private DictionaryDAO dictionaryDAO; private QNameDAO qnameDAO; private NamespaceDAO namespaceDAO; private NodeService nodeService; private FileFolderService fileFolderService; private ContentService contentService; private VersionService versionService; private TransactionService transactionService; private NodeArchiveService nodeArchiveService; private M2Model model; private QName modelQName; private QName typeQName; private M2Type type; private QName propertyQName; private M2Property property; @Before public void setUp() throws Exception { this.modelValidator = (ModelValidator)ctx.getBean("modelValidator"); this.dictionaryDAO = (DictionaryDAO)ctx.getBean("dictionaryDAO"); this.qnameDAO = (QNameDAO)ctx.getBean("qnameDAO"); this.namespaceDAO = (NamespaceDAO)ctx.getBean("namespaceDAO"); this.nodeService = (NodeService)ctx.getBean("NodeService"); this.fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); this.contentService = (ContentService)ctx.getBean("contentService"); this.versionService = (VersionService)ctx.getBean("VersionService"); this.transactionService = (TransactionService)ctx.getBean("TransactionService"); this.nodeArchiveService = (NodeArchiveService)ctx.getBean("nodeArchiveService"); this.modelName = "modelvalidatortest" + System.currentTimeMillis(); addModel(); } @AfterClass public static void cleanUp() { AuthenticationUtil.clearCurrentSecurityContext(); } private QName addModel() { this.testNamespace = "http://www.alfresco.org/test/" + modelName; this.modelQName = QName.createQName(testNamespace, modelName); // Create a model this.model = M2Model.createModel(modelName + ":" + modelName); model.createNamespace(testNamespace, modelName); model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, "d"); model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX); this.typeQName = QName.createQName(testNamespace, "type1"); this.type = model.createType(modelName + ":" + typeQName.getLocalName()); type.setParentName("cm:folder"); this.propertyQName = QName.createQName(testNamespace, "prop1"); this.property = type.createProperty(modelName + ":" + propertyQName.getLocalName()); property.setType("d:text"); dictionaryDAO.putModel(model); return modelQName; } private NodeRef getParentNodeRef() { NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); // create temporary folder NodeRef parentNodeRef = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.ALFRESCO_URI, "working root" + GUID.generate()), ContentModel.TYPE_FOLDER).getChildRef(); return parentNodeRef; } /** * Test that a model cannot be deleted if nodes and properties exist that reference it. * * @throws Exception */ @Test public void testInvalidModelDelete() throws Exception { // authenticate AuthenticationUtil.pushAuthentication(); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); final QName modelQName = QName.createQName(testNamespace, modelName); // Create a node that uses the new type (type is created in setUp) RetryingTransactionCallback createNodeCallback = new RetryingTransactionCallback() { public NodeRef execute() throws Throwable { // Create a node that uses it NodeRef parentNodeRef = getParentNodeRef(); FileInfo folder = fileFolderService.create(parentNodeRef, GUID.generate(), ContentModel.TYPE_FOLDER); assertNotNull(folder); NodeRef folderNodeRef = folder.getNodeRef(); FileInfo node = fileFolderService.create(folderNodeRef, GUID.generate(), typeQName); assertNotNull(node); NodeRef nodeRef = node.getNodeRef(); ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); writer.putContent("Test"); Map properties = new HashMap<>(); properties.put(propertyQName, "Test"); nodeService.setProperties(nodeRef, properties); versionService.createVersion(nodeRef, null); return nodeRef; } }; transactionService.getRetryingTransactionHelper().doInTransaction(createNodeCallback, false, true); // try to delete the model RetryingTransactionCallback deleteModelCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { if(modelValidator.canDeleteModel(modelQName)) { fail("Model delete should have failed"); } return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteModelCallback, false, true); } /** * Tests that a model cannot be deleted/made inactive when there are any archived nodes * still in the repository that use it. * * Test case for MNT-13820 * * @throws Exception */ @Test public void testInvalidModelDeleteArchivedNode() throws Exception { // authenticate AuthenticationUtil.pushAuthentication(); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); final QName modelQName = QName.createQName(testNamespace, modelName); // Create a node that uses the new type (type is created in setUp) RetryingTransactionCallback createNodeCallback = new RetryingTransactionCallback() { public NodeRef execute() throws Throwable { // Create a node that uses it NodeRef parentNodeRef = getParentNodeRef(); FileInfo folder = fileFolderService.create(parentNodeRef, GUID.generate(), ContentModel.TYPE_FOLDER); assertNotNull(folder); NodeRef folderNodeRef = folder.getNodeRef(); FileInfo node = fileFolderService.create(folderNodeRef, GUID.generate(), typeQName); assertNotNull(node); NodeRef nodeRef = node.getNodeRef(); ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); writer.putContent("Test"); Map properties = new HashMap<>(); properties.put(propertyQName, "Test"); nodeService.setProperties(nodeRef, properties); versionService.createVersion(nodeRef, null); return nodeRef; } }; final NodeRef nodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(createNodeCallback, false, true); RetryingTransactionCallback deleteNodeCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { nodeService.deleteNode(nodeRef); return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteNodeCallback, false, true); // try to delete the model RetryingTransactionCallback deleteModelCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { if(modelValidator.canDeleteModel(modelQName)) { fail("Model delete should have failed"); } return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteModelCallback, false, true); } /** * Tests that a model can be deleted when nodes are deleted. * * Test case for MNT-13820 * * @throws Exception */ @Test public void testModelDelete() throws Exception { // authenticate AuthenticationUtil.pushAuthentication(); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); final QName modelQName = QName.createQName(testNamespace, modelName); // Create a node that uses the new type (type is created in setUp) RetryingTransactionCallback createNodeCallback = new RetryingTransactionCallback() { public NodeRef execute() throws Throwable { // Create a node that uses it NodeRef parentNodeRef = getParentNodeRef(); FileInfo folder = fileFolderService.create(parentNodeRef, GUID.generate(), ContentModel.TYPE_FOLDER); assertNotNull(folder); NodeRef folderNodeRef = folder.getNodeRef(); FileInfo node = fileFolderService.create(folderNodeRef, GUID.generate(), typeQName); assertNotNull(node); NodeRef nodeRef = node.getNodeRef(); ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); writer.putContent("Test"); Map properties = new HashMap<>(); properties.put(propertyQName, "Test"); nodeService.setProperties(nodeRef, properties); versionService.createVersion(nodeRef, null); return nodeRef; } }; final NodeRef nodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(createNodeCallback, false, true); RetryingTransactionCallback deleteNodeCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); nodeService.deleteNode(nodeRef); return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteNodeCallback, false, true); // try to delete the model RetryingTransactionCallback deleteModelCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { if(!modelValidator.canDeleteModel(modelQName)) { fail("Model delete should have succeeded"); } return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteModelCallback, false, true); // Check that the qnames are still there // try to delete the model RetryingTransactionCallback checkQNamesCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { assertNotNull(qnameDAO.getQName(propertyQName)); assertNotNull(qnameDAO.getQName(typeQName)); return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(checkQNamesCallback, false, true); } /** * Test that a model cannot be deleted if there are node properties referencing it. * * @throws Exception */ @Test public void testInvalidPropertyDelete() throws Exception { // authenticate AuthenticationUtil.pushAuthentication(); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // Create a node that uses the new type RetryingTransactionCallback createNodeCallback = new RetryingTransactionCallback() { public NodeRef execute() throws Throwable { // Create a node that uses it NodeRef parentNodeRef = getParentNodeRef(); FileInfo folder = fileFolderService.create(parentNodeRef, GUID.generate(), ContentModel.TYPE_FOLDER); assertNotNull(folder); NodeRef folderNodeRef = folder.getNodeRef(); FileInfo node = fileFolderService.create(folderNodeRef, GUID.generate(), typeQName); assertNotNull(node); NodeRef nodeRef = node.getNodeRef(); ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); writer.putContent("Test"); Map properties = new HashMap<>(); properties.put(propertyQName, "Test"); nodeService.setProperties(nodeRef, properties); versionService.createVersion(nodeRef, null); return nodeRef; } }; final NodeRef nodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(createNodeCallback, false, true); RetryingTransactionCallback deleteModelCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { type.removeProperty(modelName + ":" + propertyQName.getLocalName()); CompiledModel compiledModel = model.compile(dictionaryDAO, namespaceDAO, true); try { modelValidator.validateModel(compiledModel); fail("Property delete should have failed"); } catch(ModelInUseException e) { System.out.println("help"); // ok } return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteModelCallback, false, true); // delete the node that is using properties in the model RetryingTransactionCallback deleteNodeCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { nodeService.deleteNode(nodeRef); return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteNodeCallback, false, true); // make sure that the archive store is purged RetryingTransactionCallback purgeArchiveCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { nodeArchiveService.purgeAllArchivedNodes(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(purgeArchiveCallback, false, true); // try to delete model again - should work now RetryingTransactionCallback deleteModelAgainCallback = new RetryingTransactionCallback() { public Void execute() throws Throwable { type.removeProperty(modelName + ":" + propertyQName.getLocalName()); CompiledModel compiledModel = model.compile(dictionaryDAO, namespaceDAO, true); modelValidator.validateModel(compiledModel); return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(deleteModelAgainCallback, false, true); } /** * Tests that an unused imported namespace can be deleted. * * @throws Exception */ @Test public void testDeleteNamespace() throws Exception { // authenticate AuthenticationUtil.pushAuthentication(); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // Remove the only property (created in setup method) this.type.removeProperty(this.property.getName()); // We don't have any property that references the imported dictionary namespace, so remove it. this.model.removeImport(NamespaceService.DICTIONARY_MODEL_1_0_URI); // Check that it compiles CompiledModel compiledModel = model.compile(dictionaryDAO, namespaceDAO, true); modelValidator.validateModel(compiledModel); // Remove the imported content model namespace this.model.removeImport(NamespaceService.CONTENT_MODEL_1_0_URI); try { model.compile(dictionaryDAO, namespaceDAO, true); fail("Should have failed as the model's type references the content model (cm:folder)."); } catch (DictionaryException dx) { //expected } // Add the content model namespace back model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX); model.compile(dictionaryDAO, namespaceDAO, true); // Remove the defined namespace this.model.removeNamespace(testNamespace); try { model.compile(dictionaryDAO, namespaceDAO, true); fail("Should have failed as the type's name references the namespace."); } catch (DictionaryException dx) { //expected } } }