/* * Copyright (C) 2005-2007 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.repo.content; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.util.Locale; import java.util.Map; import javax.transaction.RollbackException; import javax.transaction.UserTransaction; import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NoTransformerException; 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.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.alfresco.util.PropertyMap; import org.alfresco.util.TempFileProvider; import org.springframework.context.ApplicationContext; /** * @see org.alfresco.repo.content.RoutingContentService * * @author Derek Hulley */ public class RoutingContentServiceTest extends TestCase { private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private static final String SOME_CONTENT = "ABC"; private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/RoutingContentServiceTest"; private TransactionService transactionService; private ContentService contentService; private PolicyComponent policyComponent; private NodeService nodeService; private AuthenticationComponent authenticationComponent; private UserTransaction txn; private NodeRef rootNodeRef; private NodeRef contentNodeRef; public RoutingContentServiceTest() { } @Override public void setUp() throws Exception { transactionService = (TransactionService) ctx.getBean("transactionComponent"); nodeService = (NodeService) ctx.getBean("dbNodeService"); contentService = (ContentService) ctx.getBean(ServiceRegistry.CONTENT_SERVICE.getLocalName()); this.policyComponent = (PolicyComponent) ctx.getBean("policyComponent"); this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); // authenticate this.authenticationComponent.setSystemUserAsCurrentUser(); // start the transaction txn = getUserTransaction(); txn.begin(); // create a store and get the root node StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, getName()); if (!nodeService.exists(storeRef)) { storeRef = nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier()); } rootNodeRef = nodeService.getRootNode(storeRef); // create a content node ContentData contentData = new ContentData(null, "text/plain", 0L, "UTF-16", Locale.CHINESE); PropertyMap properties = new PropertyMap(); properties.put(ContentModel.PROP_CONTENT, contentData); ChildAssociationRef assocRef = nodeService.createNode( rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, GUID.generate()), ContentModel.TYPE_CONTENT, properties); contentNodeRef = assocRef.getChildRef(); } @Override public void tearDown() throws Exception { try { authenticationComponent.clearCurrentSecurityContext(); } catch (Throwable e) { // ignore } try { if (txn != null) { txn.rollback(); } } catch (Throwable e) { // ignore } } private UserTransaction getUserTransaction() { return (UserTransaction) transactionService.getUserTransaction(); } public void testSetUp() throws Exception { assertNotNull(contentService); assertNotNull(nodeService); assertNotNull(rootNodeRef); assertNotNull(contentNodeRef); assertNotNull(getUserTransaction()); assertFalse(getUserTransaction() == getUserTransaction()); // ensure txn instances aren't shared } /** * Check that a valid writer into the content store can be retrieved and used. */ public void testSimpleNonTempWriter() throws Exception { ContentWriter writer = contentService.getWriter(null, null, false); assertNotNull("Writer should not be null", writer); assertNotNull("Content URL should not be null", writer.getContentUrl()); // write some content writer.putContent(SOME_CONTENT); writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); writer.setEncoding("UTF-16"); writer.setLocale(Locale.CHINESE); // set the content property manually nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, writer.getContentData()); // get the reader ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNotNull("Reader should not be null", reader); assertNotNull("Content URL should not be null", reader.getContentUrl()); assertEquals("Content Encoding was not set", "UTF-16", reader.getEncoding()); assertEquals("Content Locale was not set", Locale.CHINESE, reader.getLocale()); } /** * Checks that the URL, mimetype and encoding are automatically set on the readers * and writers */ public void testAutoSettingOfProperties() throws Exception { // get a writer onto the node ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); assertNotNull("Writer should not be null", writer); assertNotNull("Content URL should not be null", writer.getContentUrl()); assertNotNull("Content mimetype should not be null", writer.getMimetype()); assertNotNull("Content encoding should not be null", writer.getEncoding()); assertNotNull("Content locale should not be null", writer.getLocale()); // write some content writer.putContent(SOME_CONTENT); // get the reader ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNotNull("Reader should not be null", reader); assertNotNull("Content URL should not be null", reader.getContentUrl()); assertNotNull("Content mimetype should not be null", reader.getMimetype()); assertNotNull("Content encoding should not be null", reader.getEncoding()); assertNotNull("Content locale should not be null", reader.getLocale()); // check that the content length is correct // - note encoding is important as we get the byte length long length = SOME_CONTENT.getBytes(reader.getEncoding()).length; // ensures correct decoding long checkLength = reader.getSize(); assertEquals("Content length incorrect", length, checkLength); // check the content - the encoding will come into effect here String contentCheck = reader.getContentString(); assertEquals("Content incorrect", SOME_CONTENT, contentCheck); } public void testWriteToNodeWithoutAnyContentProperties() throws Exception { // previously, the node was populated with the mimetype, etc // check that the write has these ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, writer.getMimetype()); assertEquals("UTF-16", writer.getEncoding()); assertEquals(Locale.CHINESE, writer.getLocale()); // now remove the content property from the node nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, null); writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); assertNull(writer.getMimetype()); assertEquals("UTF-8", writer.getEncoding()); assertEquals(Locale.getDefault(), writer.getLocale()); // now set it on the writer writer.setMimetype("text/plain"); writer.setEncoding("UTF-16"); writer.setLocale(Locale.FRENCH); String content = "The quick brown fox ..."; writer.putContent(content); // the properties should have found their way onto the node ContentData contentData = (ContentData) nodeService.getProperty(contentNodeRef, ContentModel.PROP_CONTENT); assertEquals("metadata didn't get onto node", writer.getContentData(), contentData); // check that the reader's metadata is set ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertEquals("Metadata didn't get set on reader", writer.getContentData(), reader.getContentData()); } public void testNullReaderForNullUrl() throws Exception { // set the property, but with a null URL ContentData contentData = new ContentData(null, null, 0L, null); nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, contentData); // get the reader ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNull("Reader must be null if the content URL is null", reader); } public void testGetRawReader() throws Exception { ContentReader reader = contentService.getRawReader("test://non-existence"); assertNotNull("A reader is expected with content URL referencing no content", reader); assertFalse("Reader should not have any content", reader.exists()); // Now write something ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false); writer.putContent("ABC from " + getName()); // Try again String contentUrl = writer.getContentUrl(); reader = contentService.getRawReader(contentUrl); assertNotNull("Expected reader for live, raw content", reader); assertEquals("Content sizes don't match", writer.getSize(), reader.getSize()); } /** * Checks what happens when the physical content disappears */ public void testMissingContent() throws Exception { File tempFile = TempFileProvider.createTempFile(getName(), ".txt"); ContentWriter writer = new FileContentWriter(tempFile); writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); writer.setEncoding("UTF-8"); writer.putContent("What about the others? Buckwheats!"); // check assertTrue("File does not exist", tempFile.exists()); assertTrue("File not written to", tempFile.length() > 0); // update the node with this new info ContentData contentData = writer.getContentData(); nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, contentData); // delete the content tempFile.delete(); assertFalse("File not deleted", tempFile.exists()); // now attempt to get the reader for the node ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertFalse("Reader should indicate that content is missing", reader.exists()); // check the indexing doesn't spank everthing txn.commit(); txn = null; // cleanup txn = getUserTransaction(); txn.begin(); nodeService.deleteNode(contentNodeRef); txn.commit(); txn = null; } /** * Tests simple writes that don't automatically update the node content URL */ public void testSimpleWrite() throws Exception { // get a writer to an arbitrary node ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false); // no updating of URL assertNotNull("Writer should not be null", writer); // put some content writer.putContent(SOME_CONTENT); // get the reader for the node ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNull("No reader should yet be available for the node", reader); } private boolean policyFired = false; private boolean readPolicyFired = false; private boolean newContent = true; /** * Tests that the content update policy firs correctly */ public void testOnContentUpdatePolicy() { // Register interest in the content update event for a versionable node this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onContentUpdate"), ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "onContentUpdateBehaviourTest")); // First check that the policy is not fired when the versionable aspect is not present ContentWriter contentWriter = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); contentWriter.putContent("content update one"); assertFalse(this.policyFired); this.newContent = false; // Now check that the policy is fired when the versionable aspect is present this.nodeService.addAspect(this.contentNodeRef, ContentModel.ASPECT_VERSIONABLE, null); ContentWriter contentWriter2 = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); contentWriter2.putContent("content update two"); assertTrue(this.policyFired); this.policyFired = false; // Check that the policy is not fired when using a non updating content writer ContentWriter contentWriter3 = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false); contentWriter3.putContent("content update three"); assertFalse(this.policyFired); } public void onContentUpdateBehaviourTest(NodeRef nodeRef, boolean newContent) { assertEquals(this.contentNodeRef, nodeRef); assertEquals(this.newContent, newContent); assertTrue(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)); this.policyFired = true; } public void testOnContentReadPolicy() { // Register interest in the content read event for a versionable node this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onContentRead"), ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "onContentReadBehaviourTest")); // First check that the policy is not fired when the versionable aspect is not present this.contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertFalse(this.readPolicyFired); // Write some content and check that the policy is still not fired ContentWriter contentWriter2 = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); contentWriter2.putContent("content update two"); this.contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertFalse(this.readPolicyFired); // Now check that the policy is fired when the versionable aspect is present this.nodeService.addAspect(this.contentNodeRef, ContentModel.ASPECT_VERSIONABLE, null); this.contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertTrue(this.readPolicyFired); } public void onContentReadBehaviourTest(NodeRef nodeRef) { this.readPolicyFired = true; } public void testTempWrite() throws Exception { // get a temporary writer ContentWriter writer1 = contentService.getTempWriter(); // and another ContentWriter writer2 = contentService.getTempWriter(); // check assertNotSame("Temp URLs must be different", writer1.getContentUrl(), writer2.getContentUrl()); } /** * Tests the automatic updating of nodes' content URLs */ public void testUpdatingWrite() throws Exception { // check that the content URL property has not been set ContentData contentData = (ContentData) nodeService.getProperty( contentNodeRef, ContentModel.PROP_CONTENT); assertNull("Content URL should be null", contentData.getContentUrl()); // before the content is written, there should not be any reader available ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNull("No reader should be available for new node", reader); // get the writer ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); assertNotNull("No writer received", writer); // write some content directly writer.putContent(SOME_CONTENT); // make sure that we can't reuse the writer try { writer.putContent("DEF"); fail("Failed to prevent repeated use of the content writer"); } catch (ContentIOException e) { // expected } // check that there is a reader available reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNotNull("No reader available for node", reader); String contentCheck = reader.getContentString(); assertEquals("Content fetched doesn't match that written", SOME_CONTENT, contentCheck); // check that the content data was set contentData = (ContentData) nodeService.getProperty( contentNodeRef, ContentModel.PROP_CONTENT); assertNotNull("Content data not set", contentData); assertEquals("Mismatched URL between writer and node", writer.getContentUrl(), contentData.getContentUrl()); // check that the content size was set assertEquals("Reader content length and node content length different", reader.getSize(), contentData.getSize()); // check that the mimetype was set assertEquals("Mimetype not set on content data", "text/plain", contentData.getMimetype()); // check encoding assertEquals("Encoding not set", "UTF-16", contentData.getEncoding()); } /** * Checks that multiple writes can occur to the same node outside of any transactions. *

* It is only when the streams are closed that the node is updated. */ public void testConcurrentWritesNoTxn() throws Exception { // ensure that the transaction is ended - ofcourse, we need to force a commit txn.commit(); txn = null; ContentWriter writer1 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); ContentWriter writer2 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); ContentWriter writer3 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); writer1.putContent("writer1 wrote this"); writer2.putContent("writer2 wrote this"); writer3.putContent("writer3 wrote this"); // get the content ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); String contentCheck = reader.getContentString(); assertEquals("Content check failed", "writer3 wrote this", contentCheck); } public void testConcurrentWritesWithSingleTxn() throws Exception { // want to operate in a user transaction txn.commit(); txn = null; UserTransaction txn = getUserTransaction(); txn.begin(); txn.setRollbackOnly(); ContentWriter writer1 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); ContentWriter writer2 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); ContentWriter writer3 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); writer1.putContent("writer1 wrote this"); writer2.putContent("writer2 wrote this"); writer3.putContent("writer3 wrote this"); // get the content ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); String contentCheck = reader.getContentString(); assertEquals("Content check failed", "writer3 wrote this", contentCheck); try { txn.commit(); fail("Transaction has been marked for rollback"); } catch (RollbackException e) { // expected } // rollback and check that the content has 'disappeared' txn.rollback(); // need a new transaction txn = getUserTransaction(); txn.begin(); txn.setRollbackOnly(); reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNull("Transaction was rolled back - no content should be visible", reader); txn.rollback(); } /** * Create several threads that will attempt to write to the same node property. * The ContentWriter is handed to the thread, so this checks that the stream closure * uses the transaction that called close and not the transaction that * fetched the ContentWriter. */ public synchronized void testConcurrentWritesWithMultipleTxns() throws Exception { // ensure that there is no content to read on the node ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNull("Reader should not be available", reader); // commit node so that threads can see node txn.commit(); txn = null; String threadContent = "Thread content"; WriteThread[] writeThreads = new WriteThread[5]; for (int i = 0; i < writeThreads.length; i++) { ContentWriter threadWriter = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); writeThreads[i] = new WriteThread(threadWriter, threadContent); // Kick each thread off writeThreads[i].start(); } // Wait for all threads to be waiting outer: while (true) { // Wait for each thread to be in a transaction for (int i = 0; i < writeThreads.length; i++) { if (!writeThreads[i].isWaiting()) { wait(10); continue outer; } } // All threads were waiting break outer; } // Kick each thread into the stream close phase for (int i = 0; i < writeThreads.length; i++) { synchronized(writeThreads[i]) { writeThreads[i].notifyAll(); } } // Wait for the threads to complete (one way or another) for (int i = 0; i < writeThreads.length; i++) { while (!writeThreads[i].isDone()) { wait(10); } } // check content has taken on thread's content reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNotNull("Reader should now be available", reader); String checkContent = reader.getContentString(); assertEquals("Content check failed", threadContent, checkContent); } public void testTransformation() throws Exception { // commit node so that threads can see node txn.commit(); txn = null; UserTransaction txn = getUserTransaction(); txn.begin(); txn.setRollbackOnly(); // get a regular writer ContentWriter writer = contentService.getTempWriter(); writer.setMimetype("text/xml"); // write some stuff String content = ""; writer.putContent(content); // get a reader onto the content ContentReader reader = writer.getReader(); // get a new writer for the transformation writer = contentService.getTempWriter(); writer.setMimetype("audio/x-wav"); // no such conversion possible try { contentService.transform(reader, writer); fail("Transformation attempted with invalid mimetype"); } catch (NoTransformerException e) { // expected } // at this point, the transaction is unusable txn.rollback(); txn = getUserTransaction(); txn.begin(); txn.setRollbackOnly(); writer.setMimetype("text/plain"); ContentTransformer transformer = contentService.getTransformer(reader.getMimetype(), writer.getMimetype()); assertNotNull("Expected a valid transformer", transformer); contentService.transform(reader, writer); // get the content from the writer reader = writer.getReader(); assertEquals("Mimetype of target reader incorrect", writer.getMimetype(), reader.getMimetype()); String contentCheck = reader.getContentString(); assertEquals("Content check failed", content, contentCheck); txn.rollback(); } /** * Writes some content to the writer's output stream and then aquires * a lock on the writer, waits until notified and then closes the * output stream before terminating. *

* When firing thread up, be sure to call notify on the * Thread instance in order to let the thread run to completion. */ private class WriteThread extends Thread { private ContentWriter writer; private String content; private volatile boolean isWaiting; private volatile boolean isDone; private volatile Throwable error; public WriteThread(ContentWriter writer, String content) { this.writer = writer; this.content = content; isWaiting = false; isDone = false; error = null; } public boolean isWaiting() { return isWaiting; } public boolean isDone() { return isDone; } public Throwable getError() { return error; } public void run() { authenticationComponent.setSystemUserAsCurrentUser(); synchronized (this) { isWaiting = true; try { this.wait(); } catch (InterruptedException e) {}; // wait until notified } final OutputStream os = writer.getContentOutputStream(); // Callback to write to the content in a new transaction RetryingTransactionCallback callback = new RetryingTransactionCallback() { public Void execute() throws Throwable { try { // put the content if (writer.getEncoding() == null) { os.write(content.getBytes()); } else { os.write(content.getBytes(writer.getEncoding())); } os.close(); } finally { if (os != null) { try { os.close(); } catch (IOException e) {} } } return null; } }; try { transactionService.getRetryingTransactionHelper().doInTransaction(callback); } catch (Throwable e) { e.printStackTrace(); error = e; } finally { isDone = true; } } } /** * Check that the system is able to handle the uploading of content with an unknown mimetype. * The unknown mimetype should be preserved, but treated just like an octet stream. */ public void testUnknownMimetype() throws Exception { String bogusMimetype = "text/bamboozle"; // get a writer onto the node ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); writer.setMimetype(bogusMimetype); // write something in writer.putContent(SOME_CONTENT); // commit the transaction to ensure that it goes in OK txn.commit(); // so far, so good ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); assertNotNull("Should be able to get reader", reader); assertEquals("Unknown mimetype was changed", bogusMimetype, reader.getMimetype()); } /** * Checks that node copy and delete behaviour behaves correctly w.r.t. cleanup and shared URLs */ public void testPostCopyContentRetrieval() throws Exception { ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); writer.putContent("Some content"); ContentData writerContentData = writer.getContentData(); ContentData nodeContentData = (ContentData) nodeService.getProperty(contentNodeRef, ContentModel.PROP_CONTENT); assertNotNull(nodeContentData); assertEquals("ContentData not the same from NodeService and from ContentWriter", writerContentData, nodeContentData); Map copyProperties = nodeService.getProperties(contentNodeRef); copyProperties.remove(ContentModel.PROP_NODE_UUID); // Copy the node NodeRef contentCopyNodeRef = nodeService.createNode( rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, GUID.generate()), ContentModel.TYPE_CONTENT, copyProperties).getChildRef(); // Now get and check the ContentData for the copy ContentData copyNodeContentData = (ContentData) nodeService.getProperty(contentCopyNodeRef, ContentModel.PROP_CONTENT); assertNotNull(copyNodeContentData); // The copy should share the same URL even assertEquals("Copied node's cm:content ContentData was different", writerContentData, copyNodeContentData); // Delete the first node and ensure that the second valud remains good and the content is editable nodeService.deleteNode(contentNodeRef); copyNodeContentData = (ContentData) nodeService.getProperty(contentCopyNodeRef, ContentModel.PROP_CONTENT); assertNotNull(copyNodeContentData); assertEquals("Post-delete value didn't remain the same", writerContentData, copyNodeContentData); ContentReader copyNodeContentReader = contentService.getReader(contentCopyNodeRef, ContentModel.PROP_CONTENT); assertTrue("Physical content was removed", copyNodeContentReader.exists()); txn.commit(); txn = null; } }