diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml index 8a404d619e..3a7c9d4246 100644 --- a/config/alfresco/model-specific-services-context.xml +++ b/config/alfresco/model-specific-services-context.xml @@ -93,6 +93,13 @@ + + + + + + + diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index 070274b331..ee8d58fb58 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -1166,4 +1166,31 @@ + + + org.alfresco.service.cmr.repository.DocumentLinkService + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 85548af10d..1a42cce612 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -1203,5 +1203,30 @@ - + + + + + + + + + + + + + + + + + + org.alfresco.service.cmr.repository.DocumentLinkService.createDocumentLink=ACL_NODE.0.sys:base.Read,ACL_NODE.1.sys:base.CreateChildren + org.alfresco.service.cmr.repository.DocumentLinkService.getLinkDestination=ACL_NODE.0.sys:base.Read + org.alfresco.service.cmr.repository.DocumentLinkService.deleteLinksToDocument=ACL_NODE.0.sys:base.Read + org.alfresco.service.cmr.repository.DocumentLinkService.*=ACL_DENY + + + + diff --git a/source/java/org/alfresco/repo/doclink/DocumentLinkServiceImpl.java b/source/java/org/alfresco/repo/doclink/DocumentLinkServiceImpl.java new file mode 100644 index 0000000000..b776caf73e --- /dev/null +++ b/source/java/org/alfresco/repo/doclink/DocumentLinkServiceImpl.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.doclink; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; +import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DeleteLinksStatusReport; +import org.alfresco.service.cmr.repository.DocumentLinkService; +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.search.QueryParameterDefinition; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Implementation of the document link service + * + * @author Ana Bozianu + * @since 5.1 + */ +public class DocumentLinkServiceImpl implements DocumentLinkService +{ + private static Log logger = LogFactory.getLog(DocumentLinkServiceImpl.class); + + private NodeService nodeService; + private DictionaryService dictionaryService; + private SearchService searchService; + private NamespaceService namespaceService; + + /** Shallow search for nodes with a name pattern */ + private static final String XPATH_QUERY_NODE_NAME_MATCH = "./*[like(@cm:name, $cm:name, false)]"; + + /** Shallow search for links with a destination pattern */ + private static final String XPATH_QUERY_LINK_DEST_MATCH = ".//*[like(@cm:destination, $cm:destination, false)]"; + + @Override + public NodeRef createDocumentLink(NodeRef source, NodeRef destination) + { + if(logger.isDebugEnabled()) + { + logger.debug("Creating document link. source: " + source + ", destination: " + destination); + } + + /* Validate input */ + PropertyCheck.mandatory(this, "source", source); + PropertyCheck.mandatory(this, "destination", destination); + + // check if source node exists + if (!nodeService.exists(source)) + { + throw new IllegalArgumentException("Source NodeRef '" + source + "' does not exist"); + } + // check if destination node exists + if (!nodeService.exists(destination)) + { + throw new IllegalArgumentException("Destination NodeRef '" + destination + "' does not exist"); + } + // check if destination node is a directory + if (!dictionaryService.isSubClass(nodeService.getType(destination), ContentModel.TYPE_FOLDER)) + { + throw new IllegalArgumentException("Destination node NodeRef '" + source + "' must be of type " + ContentModel.TYPE_FOLDER); + } + + /* Create link */ + String sourceName = (String) nodeService.getProperty(source, ContentModel.PROP_NAME); + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, sourceName); + props.put(ContentModel.PROP_LINK_DESTINATION, source); + QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(sourceName)); + + // check if the link node already exists + if (checkExists(sourceName, destination)) + { + throw new IllegalArgumentException("A file with the name '" + sourceName + "' already exists in the destination folder"); + } + + ChildAssociationRef childRef = null; + if (dictionaryService.isSubClass(nodeService.getType(source), ContentModel.TYPE_CONTENT)) + { + // create File Link node + childRef = nodeService.createNode(destination, ContentModel.ASSOC_CONTAINS, assocQName, ApplicationModel.TYPE_FILELINK, props); + + } + else if (dictionaryService.isSubClass(nodeService.getType(source), ContentModel.TYPE_FOLDER)) + { + // create Folder link node + childRef = nodeService.createNode(destination, ContentModel.ASSOC_CONTAINS, assocQName, ApplicationModel.TYPE_FOLDERLINK, props); + } + else + { + throw new IllegalArgumentException("unsupported source node type : " + nodeService.getType(source)); + } + + return childRef.getChildRef(); + } + + /** + * Check if node with specified name exists within a + * parent folder. + * + * @param name + * @param parent + * @return + */ + private boolean checkExists(String name, NodeRef parent) + { + QueryParameterDefinition[] params = new QueryParameterDefinition[1]; + params[0] = new QueryParameterDefImpl(ContentModel.PROP_NAME, dictionaryService.getDataType(DataTypeDefinition.TEXT), true, name); + + // execute the query + List nodeRefs = searchService.selectNodes(parent, XPATH_QUERY_NODE_NAME_MATCH, params, namespaceService, false); + + return (nodeRefs.size() != 0); + } + + @Override + public NodeRef getLinkDestination(NodeRef linkNodeRef) + { + /* Validate input */ + PropertyCheck.mandatory(this, "linkNodeRef", linkNodeRef); + + /* Check if the node exists */ + if (!nodeService.exists(linkNodeRef)) + { + throw new IllegalArgumentException("The provided node does not exist"); + } + + if (!nodeService.getType(linkNodeRef).equals(ApplicationModel.TYPE_FILELINK) + && !nodeService.getType(linkNodeRef).equals(ApplicationModel.TYPE_FOLDERLINK)) + { + throw new IllegalArgumentException("The provided node is not a document link"); + } + + return (NodeRef) nodeService.getProperty(linkNodeRef, ContentModel.PROP_LINK_DESTINATION); + } + + @Override + public DeleteLinksStatusReport deleteLinksToDocument(NodeRef document) + { + if(logger.isDebugEnabled()) + { + logger.debug("Deleting links of a document. document: " + document); + } + + /* Validate input */ + PropertyCheck.mandatory(this, "document", document); + + /* Get all links of the given document */ + QueryParameterDefinition[] params = new QueryParameterDefinition[1]; + params[0] = new QueryParameterDefImpl(ContentModel.PROP_LINK_DESTINATION, dictionaryService.getDataType(DataTypeDefinition.NODE_REF), true, document.toString()); + + /* Search for links in all stores */ + DeleteLinksStatusReport report = new DeleteLinksStatusReport(); + for(StoreRef store : nodeService.getStores()) + { + /* Get the root node */ + NodeRef rootNodeRef = nodeService.getRootNode(store); + + /* Execute the query, retrieve links to the document*/ + List nodeRefs = searchService.selectNodes(rootNodeRef, XPATH_QUERY_LINK_DEST_MATCH, params, namespaceService, true); + report.addTotalLinksFoundCount(nodeRefs.size()); + + /* Delete the found nodes */ + for(NodeRef linkRef : nodeRefs) + { + try{ + nodeService.deleteNode(linkRef); + + /* if the node was successfully deleted increment the count */ + report.incrementDeletedLinksCount(); + } + catch(AccessDeniedException ex) + { + /* if the node could not be deleted add it to the report */ + report.addErrorDetail(linkRef, ex); + } + } + } + + return report; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } +} diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index 92acaa9919..8af8005d82 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -51,6 +51,7 @@ import org.alfresco.service.cmr.rating.RatingService; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.CopyService; +import org.alfresco.service.cmr.repository.DocumentLinkService; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.ScriptService; @@ -464,4 +465,9 @@ public class ServiceDescriptorRegistry final String beanName = "facet.facetLabelDisplayHandlerRegistry"; return (FacetLabelDisplayHandlerRegistry) beanFactory.getBean(beanName); } + + @Override + public DocumentLinkService getDocumentLinkService() { + return (DocumentLinkService)getService(DOCUMENT_LINK_SERVICE); + } } diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index ff3aa7c27f..b1d368ed18 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -52,6 +52,7 @@ import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.CrossRepositoryCopyService; +import org.alfresco.service.cmr.repository.DocumentLinkService; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.ScriptService; @@ -140,6 +141,7 @@ public interface ServiceRegistry static final QName BLOG_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "BlogService"); static final QName CALENDAR_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CalendarService"); static final QName NOTIFICATION_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "NotificationService"); + static final QName DOCUMENT_LINK_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "DocumentLinkService"); // CMIS static final QName CMIS_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CMISService"); @@ -518,4 +520,11 @@ public interface ServiceRegistry */ @NotAuditable FacetLabelDisplayHandlerRegistry getFacetLabelDisplayHandlerRegistry(); + + /** + * Get the document link service + * @return the document link service + */ + @NotAuditable + DocumentLinkService getDocumentLinkService(); } diff --git a/source/java/org/alfresco/service/cmr/repository/DeleteLinksStatusReport.java b/source/java/org/alfresco/service/cmr/repository/DeleteLinksStatusReport.java new file mode 100644 index 0000000000..bdb7ab297d --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/DeleteLinksStatusReport.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.service.cmr.repository; + +import java.util.HashMap; + +/** + * This class is used by DocumentLinkService to encapsulate the status of + * deleting the links of a document. + * + * @author Ana Bozianu + * @since 5.1 + */ +public class DeleteLinksStatusReport +{ + /* the count of all links to the documents that were found */ + private int totalLinksFoundCount = 0; + /* the count of links that were successfully deleted */ + private int deletedLinksCount = 0; + /* detailed information about the nodes that could not be deleted */ + private HashMap errorDetails = new HashMap(); + + public int getTotalLinksFoundCount() + { + return totalLinksFoundCount; + } + + public int getDeletedLinksCount() + { + return deletedLinksCount; + } + + public HashMap getErrorDetails() + { + return errorDetails; + } + + public void setTotalLinksFoundCount(int totalLinksFoundCount) + { + this.totalLinksFoundCount = totalLinksFoundCount; + } + + public void addTotalLinksFoundCount(int totalLinksFoundCount) + { + this.totalLinksFoundCount += totalLinksFoundCount; + } + + public void setDeletedLinksCount(int deletedLinksCount) + { + this.deletedLinksCount = deletedLinksCount; + } + + public void incrementDeletedLinksCount() + { + this.deletedLinksCount++; + } + + public void addErrorDetail(NodeRef nodeRef, Throwable th) + { + errorDetails.put(nodeRef, th); + } + +} diff --git a/source/java/org/alfresco/service/cmr/repository/DocumentLinkService.java b/source/java/org/alfresco/service/cmr/repository/DocumentLinkService.java new file mode 100644 index 0000000000..0454e2149c --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/DocumentLinkService.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.service.cmr.repository; + +/** + * Provides methods specific to manipulating links of documents + * + * @author Ana Bozianu + * @since 5.1 + */ +public interface DocumentLinkService +{ + + /** + * Creates a link node as child of the destination node + * + * @param source + * Node to create a link for. Can be a file or a folder. + * @param destination + * Destination to create the link in. Must be a folder. + * @return A reference to the created link node + */ + public NodeRef createDocumentLink(NodeRef source, NodeRef destination); + + /** + * Returns the destination node of the provided link + * + * @param linkNodeRef + * The link node. + * @return A reference to the destination of the provided link node + */ + public NodeRef getLinkDestination(NodeRef linkNodeRef); + + /** + * Deletes all links having the provided node as destination. + * + * @param document + * The destination of the links to be deleted. + */ + public DeleteLinksStatusReport deleteLinksToDocument(NodeRef document); + +} diff --git a/source/test-java/org/alfresco/Repository01TestSuite.java b/source/test-java/org/alfresco/Repository01TestSuite.java index 8936c680b9..82ceeecbc4 100644 --- a/source/test-java/org/alfresco/Repository01TestSuite.java +++ b/source/test-java/org/alfresco/Repository01TestSuite.java @@ -419,4 +419,9 @@ public class Repository01TestSuite extends TestSuite { suite.addTest(org.alfresco.repo.search.impl.solr.facet.SolrFacetTestSuite.suite()); } + + static void tests67(TestSuite suite) + { + suite.addTestSuite(org.alfresco.repo.doclink.DocumentLinkServiceImplTest.class); + } } diff --git a/source/test-java/org/alfresco/repo/doclink/DocumentLinkServiceImplTest.java b/source/test-java/org/alfresco/repo/doclink/DocumentLinkServiceImplTest.java new file mode 100644 index 0000000000..ad10b7d60b --- /dev/null +++ b/source/test-java/org/alfresco/repo/doclink/DocumentLinkServiceImplTest.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.doclink; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.DeleteLinksStatusReport; +import org.alfresco.service.cmr.repository.DocumentLinkService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; +import org.springframework.context.ApplicationContext; + +/** + * Test cases for {@link DocumentLinkServiceImpl}. + * + * @author Ana Bozianu + * @since 5.1 + */ + +public class DocumentLinkServiceImplTest extends TestCase +{ + + private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + private static final String TEST_USER = DocumentLinkServiceImplTest.class.getSimpleName() + "_testuser"; + + private UserTransaction txn; + + private TransactionService transactionService; + private DocumentLinkService documentLinkService; + private PermissionService permissionService; + private PersonService personService; + private SiteService siteService; + private FileFolderService fileFolderService; + private NodeService nodeService; + + // nodes the test user has read/write permission to + private NodeRef site1File1; + private NodeRef site1File2; // do not create links of this file + private NodeRef site1Folder1; + private NodeRef site1Folder2; + private NodeRef site1Folder3; + + // nodes the test user has no permission to + private NodeRef site2File; + private NodeRef site2Folder1; + private NodeRef site2Folder2; + private NodeRef linkOfFile1Site2; + + private String site1File1Name = GUID.generate(); + private String site1Folder1Name = GUID.generate();; + + @Override + public void setUp() throws Exception + { + // Set up the services + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); + transactionService = serviceRegistry.getTransactionService(); + documentLinkService = serviceRegistry.getDocumentLinkService(); + permissionService = serviceRegistry.getPermissionService(); + personService = serviceRegistry.getPersonService(); + siteService = serviceRegistry.getSiteService(); + fileFolderService = serviceRegistry.getFileFolderService(); + nodeService = serviceRegistry.getNodeService(); + + // Start the transaction + txn = transactionService.getUserTransaction(); + txn.begin(); + + // Authenticate + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + /* Create the test user */ + Map props = new HashMap(); + props.put(ContentModel.PROP_USERNAME, TEST_USER); + personService.createPerson(props); + + /* + * Create the working test root 1 to which the user has read/write + * permission + */ + NodeRef site1 = siteService.createSite("site1", GUID.generate(), "myTitle", "myDescription", SiteVisibility.PUBLIC).getNodeRef(); + permissionService.setPermission(site1, TEST_USER, PermissionService.ALL_PERMISSIONS, true); + site1Folder1 = fileFolderService.create(site1, site1Folder1Name, ContentModel.TYPE_FOLDER).getNodeRef(); + site1File1 = fileFolderService.create(site1Folder1, site1File1Name, ContentModel.TYPE_CONTENT).getNodeRef(); + site1File2 = fileFolderService.create(site1Folder1, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef(); + site1Folder2 = fileFolderService.create(site1, GUID.generate(), ContentModel.TYPE_FOLDER).getNodeRef(); + site1Folder3 = fileFolderService.create(site1, GUID.generate(), ContentModel.TYPE_FOLDER).getNodeRef(); + // create a link of site1File1 in site1Folder3 to test regular deletion + documentLinkService.createDocumentLink(site1File2, site1Folder3); + + /* Create the working test root 2 to which the user has no permission */ + NodeRef site2 = siteService.createSite("site2", GUID.generate(), "myTitle", "myDescription", SiteVisibility.PRIVATE).getNodeRef(); + permissionService.setPermission(site2, TEST_USER, PermissionService.ALL_PERMISSIONS, false); + site2File = fileFolderService.create(site2, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef(); + site2Folder1 = fileFolderService.create(site2, GUID.generate(), ContentModel.TYPE_FOLDER).getNodeRef(); + site2Folder2 = fileFolderService.create(site2, GUID.generate(), ContentModel.TYPE_FOLDER).getNodeRef(); + // Create a link of site1File1 in site2Folder2 to test the deletion + // without permission + linkOfFile1Site2 = documentLinkService.createDocumentLink(site1File2, site2Folder2); + } + + @Override + protected void tearDown() throws Exception + { + try + { + if (txn.getStatus() != Status.STATUS_ROLLEDBACK && txn.getStatus() != Status.STATUS_COMMITTED) + { + txn.rollback(); + } + } + catch (Throwable e) + { + e.printStackTrace(); + } + } + + /** + * Tests the creation of a file link + * + * @throws Exception + */ + public void testCreateFileLink() throws Exception + { + // create a link of file site1File1_1 in folder site1Folder2 + NodeRef linkNodeRef = documentLinkService.createDocumentLink(site1File1, site1Folder2); + assertNotNull(linkNodeRef); + + // test if the link node is listed as a child of site1Folder2 + NodeRef linkNodeRef2 = fileFolderService.searchSimple(site1Folder2, site1File1Name); + assertNotNull(linkNodeRef2); + assertEquals(linkNodeRef, linkNodeRef2); + + // check the type of the link + assertEquals(nodeService.getType(linkNodeRef), ApplicationModel.TYPE_FILELINK); + + // check if the link destination is site1File1_1 + NodeRef linkDestination = documentLinkService.getLinkDestination(linkNodeRef); + assertNotNull(linkDestination); + assertEquals(linkDestination, site1File1); + } + + /** + * Tests the creation of a folder link + * + * @throws Exception + */ + public void testCreateFolderLink() throws Exception + { + // create a link of file site1File1_1 in folder site1Folder2 + NodeRef linkNodeRef = documentLinkService.createDocumentLink(site1Folder1, site1Folder2); + assertNotNull(linkNodeRef); + + // test if the link node is listed as a child of site1Folder2 + NodeRef linkNodeRef2 = fileFolderService.searchSimple(site1Folder2, site1Folder1Name); + assertNotNull(linkNodeRef2); + assertEquals(linkNodeRef, linkNodeRef2); + + // check the type of the link + assertEquals(nodeService.getType(linkNodeRef), ApplicationModel.TYPE_FOLDERLINK); + + // check if the link destination is site1File1_1 + NodeRef linkDestination = documentLinkService.getLinkDestination(linkNodeRef); + assertNotNull(linkDestination); + assertEquals(linkDestination, site1Folder1); + } + + /** + * Tests the behavior of createDocumentLink when providing null arguments + * + * @throws Exception + */ + public void testCreateDocumentWithNullArguments() throws Exception + { + try + { + documentLinkService.createDocumentLink(null, null); + fail("null arguments must generate AlfrescoRuntimeException."); + } + catch (AlfrescoRuntimeException e) + { + // Expected + } + } + + /** + * Test the behavior of createDocumentLink when provided a file as a + * destination + * + * @throws Exception + */ + public void testInvalidDestination() throws Exception + { + try + { + documentLinkService.createDocumentLink(site1Folder2, site1File1); + fail("invalid destination argument must generate IllegalArgumentException."); + } + catch (IllegalArgumentException e) + { + // Expected + } + } + + /** + * Tests the creation of a document link when the user doesn't have read + * permission on the source node + * + * @throws Exception + */ + public void testCreateDocLinkWithoutReadPermissionOnSource() throws Exception + { + try + { + AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + documentLinkService.createDocumentLink(site2File, site1Folder2); + return null; + } + }, TEST_USER); + + fail("no read permission on the source node must generate AccessDeniedException."); + } + catch (AccessDeniedException ex) + { + // Expected + } + } + + /** + * Tests the creation of a document link when the user doesn't have write + * permission on the destination node + * + * @throws Exception + */ + public void testCreateDocLinkWithoutWritePermissionOnDestination() throws Exception + { + try + { + AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + documentLinkService.createDocumentLink(site1File1, site2Folder1); + return null; + } + }, TEST_USER); + + fail("no write permission on the destination node must generate AccessDeniedException."); + } + catch (AccessDeniedException ex) + { + // Expected + } + } + + /** + * Tests the deletion of a document's links, with and without write + * permissions + * + * @throws Exception + */ + public void testDeleteLinks() throws Exception + { + DeleteLinksStatusReport report = AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public DeleteLinksStatusReport doWork() throws Exception + { + return documentLinkService.deleteLinksToDocument(site1File2); + } + }, TEST_USER); + + // check if the service found 2 links of the document + assertEquals(report.getTotalLinksFoundCount(), 2); + + // check if the service successfully deleted one + assertEquals(report.getDeletedLinksCount(), 1); + + // check if the second one failed with access denied + Throwable ex = report.getErrorDetails().get(linkOfFile1Site2); + assertNotNull(ex); + assertEquals(ex.getClass(), AccessDeniedException.class); + } + +} diff --git a/source/test-java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java b/source/test-java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java index 283241be5d..c67efdbf29 100644 --- a/source/test-java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java +++ b/source/test-java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java @@ -54,6 +54,7 @@ import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.CrossRepositoryCopyService; +import org.alfresco.service.cmr.repository.DocumentLinkService; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.ScriptService; @@ -485,5 +486,11 @@ public class MockedTestServiceRegistry implements ServiceRegistry { // TODO Auto-generated method stub return null; + } + + @Override + public DocumentLinkService getDocumentLinkService() { + // A mock response + return null; } }