From 75d5201af1de0d2eed34265323f5125a1d717ec0 Mon Sep 17 00:00:00 2001 From: Gerard Olenski <31597546+gerardolenski@users.noreply.github.com> Date: Mon, 17 Feb 2025 08:58:04 +0100 Subject: [PATCH] ACS 9256 improve async tests stability (#3191) * ACS-9256 Improved stability in DynamicallySizedThreadPoolExecutorTest * ACS-9256 Removed unused unstable test in SpringAwareUserTransactionTest * ACS-9256 Improved stability in DynamicallySizedThreadPoolExecutorTest * ACS-9256 Improved stability in ActionServiceImplTest and RuleServiceCoverageTest * ACS-9256 Improved stability in ActionTrackingServiceImplTest * ACS-9256 Improved performance in ComparePropertyValueEvaluatorTest * ACS-9256 Improved performance in LockServiceImplTest * ACS-9256 Improved stability in LockBehaviourImplTest * ACS-9256 Improved stability in ContentMetadataExtracterTest * ACS-9256 Removed unstable and unused tests * ACS-9256 Improve stability in CachedContentCleanupJobTest * ACS-9256 Pre-commit fixes --- .secrets.baseline | 22 +- core/pom.xml | 6 + ...ynamicallySizedThreadPoolExecutorTest.java | 201 +- .../SpringAwareUserTransactionTest.java | 119 +- .../repo/action/ActionServiceImpl2Test.java | 1020 +++-- .../repo/action/ActionServiceImplTest.java | 2879 ++++++------ .../action/ActionTrackingServiceImplTest.java | 943 ++-- .../ComparePropertyValueEvaluatorTest.java | 1245 ++--- .../ContentMetadataExtracterTest.java | 717 ++- .../cleanup/CachedContentCleanupJobTest.java | 953 ++-- .../repo/lock/LockBehaviourImplTest.java | 1316 +++--- .../repo/lock/LockServiceImplTest.java | 2440 +++++----- .../repo/rule/RuleServiceCoverageTest.java | 3999 ++++++++--------- .../org/alfresco/util/BaseSpringTest.java | 114 +- 14 files changed, 7654 insertions(+), 8320 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 0b3be8cdb8..64703dba5e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1431,26 +1431,6 @@ "is_secret": false } ], - "repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java": [ - { - "type": "Secret Keyword", - "filename": "repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java", - "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", - "is_verified": false, - "line_number": 112, - "is_secret": false - } - ], - "repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java": [ - { - "type": "Secret Keyword", - "filename": "repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java", - "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", - "is_verified": false, - "line_number": 103, - "is_secret": false - } - ], "repository/src/test/java/org/alfresco/repo/management/JmxDumpUtilTest.java": [ { "type": "Secret Keyword", @@ -1888,5 +1868,5 @@ } ] }, - "generated_at": "2024-12-19T08:58:42Z" + "generated_at": "2025-02-11T13:28:51Z" } diff --git a/core/pom.xml b/core/pom.xml index 6a1b7e3c45..ba80ee60e8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -145,6 +145,12 @@ org.apache.httpcomponents httpclient + + org.awaitility + awaitility + ${dependency.awaitility.version} + test + diff --git a/core/src/test/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java b/core/src/test/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java index c5ace65cdf..07cc1e3b4e 100644 --- a/core/src/test/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java +++ b/core/src/test/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. + * Copyright (C) 2005-2025 Alfresco Software Limited. * * This file is part of Alfresco * @@ -18,6 +18,9 @@ */ package org.alfresco.util; +import static org.awaitility.Awaitility.await; + +import java.time.Duration; import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; @@ -26,20 +29,20 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import junit.framework.TestCase; - /** * Tests for our instance of {@link java.util.concurrent.ThreadPoolExecutor} - * + * * @author Nick Burch */ public class DynamicallySizedThreadPoolExecutorTest extends TestCase { - private static Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutorTest.class); + private static final Duration MAX_WAIT_TIMEOUT = Duration.ofSeconds(1); + private static final Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutorTest.class); private static final int DEFAULT_KEEP_ALIVE_TIME = 90; @Override @@ -48,9 +51,9 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase SleepUntilAllWake.reset(); } - public void testUpToCore() throws Exception + public void testUpToCore() { - DynamicallySizedThreadPoolExecutor exec = createInstance(5,10, DEFAULT_KEEP_ALIVE_TIME); + DynamicallySizedThreadPoolExecutor exec = createInstance(5, 10, DEFAULT_KEEP_ALIVE_TIME); assertEquals(0, exec.getPoolSize()); exec.execute(new SleepUntilAllWake()); @@ -61,15 +64,15 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase assertEquals(4, exec.getPoolSize()); exec.execute(new SleepUntilAllWake()); assertEquals(5, exec.getPoolSize()); - + SleepUntilAllWake.wakeAll(); - Thread.sleep(100); + waitForPoolSizeEquals(exec, 5); assertEquals(5, exec.getPoolSize()); } - public void testPastCoreButNotHugeQueue() throws Exception + public void testPastCoreButNotHugeQueue() { - DynamicallySizedThreadPoolExecutor exec = createInstance(5,10, DEFAULT_KEEP_ALIVE_TIME); + DynamicallySizedThreadPoolExecutor exec = createInstance(5, 10, DEFAULT_KEEP_ALIVE_TIME); assertEquals(0, exec.getPoolSize()); assertEquals(0, exec.getQueue().size()); @@ -80,7 +83,7 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase exec.execute(new SleepUntilAllWake()); assertEquals(5, exec.getPoolSize()); assertEquals(0, exec.getQueue().size()); - + // Need to hit max pool size before it adds more exec.execute(new SleepUntilAllWake()); exec.execute(new SleepUntilAllWake()); @@ -89,20 +92,20 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase exec.execute(new SleepUntilAllWake()); assertEquals(5, exec.getPoolSize()); assertEquals(5, exec.getQueue().size()); - + exec.execute(new SleepUntilAllWake()); exec.execute(new SleepUntilAllWake()); assertEquals(5, exec.getPoolSize()); assertEquals(7, exec.getQueue().size()); - + SleepUntilAllWake.wakeAll(); - Thread.sleep(100); + waitForPoolSizeEquals(exec, 5); assertEquals(5, exec.getPoolSize()); } - + public void testToExpandQueue() throws Exception { - DynamicallySizedThreadPoolExecutor exec = createInstance(2,4,1); + DynamicallySizedThreadPoolExecutor exec = createInstance(2, 4, 5); assertEquals(0, exec.getPoolSize()); assertEquals(0, exec.getQueue().size()); @@ -110,166 +113,37 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase exec.execute(new SleepUntilAllWake()); assertEquals(2, exec.getPoolSize()); assertEquals(0, exec.getQueue().size()); - + exec.execute(new SleepUntilAllWake()); exec.execute(new SleepUntilAllWake()); exec.execute(new SleepUntilAllWake()); assertEquals(2, exec.getPoolSize()); assertEquals(3, exec.getQueue().size()); - + // Next should add one exec.execute(new SleepUntilAllWake()); - Thread.sleep(20); // Let the new thread spin up + waitForPoolSizeEquals(exec, 3); // Let the new thread spin up assertEquals(3, exec.getPoolSize()); assertEquals(3, exec.getQueue().size()); // And again exec.execute(new SleepUntilAllWake()); - Thread.sleep(20); // Let the new thread spin up + waitForPoolSizeEquals(exec, 4); // Let the new thread spin up assertEquals(4, exec.getPoolSize()); assertEquals(3, exec.getQueue().size()); - + // But no more will be added, as we're at max exec.execute(new SleepUntilAllWake()); exec.execute(new SleepUntilAllWake()); exec.execute(new SleepUntilAllWake()); assertEquals(4, exec.getPoolSize()); assertEquals(6, exec.getQueue().size()); - - SleepUntilAllWake.wakeAll(); - Thread.sleep(100); - - // All threads still running, as 1 second timeout - assertEquals(4, exec.getPoolSize()); - } - public void offTestToExpandThenContract() throws Exception - { - DynamicallySizedThreadPoolExecutor exec = createInstance(2,4,1); - exec.setKeepAliveTime(30, TimeUnit.MILLISECONDS); - - assertEquals(0, exec.getPoolSize()); - assertEquals(0, exec.getQueue().size()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - assertEquals(2, exec.getPoolSize()); - assertEquals(0, exec.getQueue().size()); - - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - assertEquals(2, exec.getPoolSize()); - assertEquals(3, exec.getQueue().size()); - - // Next should add one - exec.execute(new SleepUntilAllWake()); - Thread.sleep(20); // Let the new thread spin up - assertEquals(3, exec.getPoolSize()); - assertEquals(3, exec.getQueue().size()); + SleepUntilAllWake.wakeAll(); + Thread.sleep(100); - // And again - exec.execute(new SleepUntilAllWake()); - Thread.sleep(20); // Let the new thread spin up + // All threads still running, as 5 second timeout assertEquals(4, exec.getPoolSize()); - assertEquals(3, exec.getQueue().size()); - - // But no more will be added, as we're at max - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - assertEquals(4, exec.getPoolSize()); - assertEquals(6, exec.getQueue().size()); - - SleepUntilAllWake.wakeAll(); - Thread.sleep(100); - - // Wait longer than the timeout without any work, which should - // let all the extra threads go away - // (Depending on how closely your JVM follows the specification, - // we may fall back to the core size which is correct, or we - // may go to zero which is wrong, but hey, it's the JVM...) - logger.debug("Core pool size is " + exec.getCorePoolSize()); - logger.debug("Current pool size is " + exec.getPoolSize()); - logger.debug("Queue size is " + exec.getQueue().size()); - assertTrue( - "Pool size should be 0-2 as everything is idle, was " + exec.getPoolSize(), - exec.getPoolSize() >= 0 - ); - assertTrue( - "Pool size should be 0-2 as everything is idle, was " + exec.getPoolSize(), - exec.getPoolSize() <= 2 - ); - - SleepUntilAllWake.reset(); - - // Add 2 new jobs, will stay/ go to at 2 threads - assertEquals(0, exec.getQueue().size()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - - // Let the idle threads grab them, then check - Thread.sleep(20); - assertEquals(2, exec.getPoolSize()); - assertEquals(0, exec.getQueue().size()); - - // 3 more, still at 2 threads - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - assertEquals(2, exec.getPoolSize()); - assertEquals(3, exec.getQueue().size()); - - // And again wait for it all - SleepUntilAllWake.wakeAll(); - Thread.sleep(100); - assertEquals(2, exec.getPoolSize()); - - - // Now decrease the overall pool size - // Will rise and fall to there now - exec.setCorePoolSize(1); - - // Run a quick job, to ensure that the - // "can I kill one yet" logic is applied - SleepUntilAllWake.reset(); - exec.execute(new SleepUntilAllWake()); - SleepUntilAllWake.wakeAll(); - - Thread.sleep(100); - assertEquals(1, exec.getPoolSize()); - assertEquals(0, exec.getQueue().size()); - - SleepUntilAllWake.reset(); - - - // Push enough on to go up to 4 active threads - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - exec.execute(new SleepUntilAllWake()); - - Thread.sleep(20); // Let the new threads spin up - assertEquals(4, exec.getPoolSize()); - assertEquals(6, exec.getQueue().size()); - - // Wait for them all to finish, should drop back to 1 now - // (Or zero, if your JVM can't read the specification...) - SleepUntilAllWake.wakeAll(); - Thread.sleep(100); - assertTrue( - "Pool size should be 0 or 1 as everything is idle, was " + exec.getPoolSize(), - exec.getPoolSize() >= 0 - ); - assertTrue( - "Pool size should be 0 or 1 as everything is idle, was " + exec.getPoolSize(), - exec.getPoolSize() <= 1 - ); } private DynamicallySizedThreadPoolExecutor createInstance(int corePoolSize, int maximumPoolSize, int keepAliveTime) @@ -291,6 +165,11 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase new ThreadPoolExecutor.CallerRunsPolicy()); } + private void waitForPoolSizeEquals(DynamicallySizedThreadPoolExecutor exec, int expectedSize) + { + await().atMost(MAX_WAIT_TIMEOUT).until(() -> exec.getPoolSize() == expectedSize); + } + public static class SleepUntilAllWake implements Runnable { private static ConcurrentMap sleeping = new ConcurrentHashMap(); @@ -299,31 +178,33 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase @Override public void run() { - if(allAwake) return; - + if (allAwake) + return; + // Track us, and wait for the bang logger.debug("Adding thread: " + Thread.currentThread().getName()); sleeping.put(Thread.currentThread().getName(), Thread.currentThread()); try { - Thread.sleep(30*1000); + Thread.sleep(30 * 1000); System.err.println("Warning - Thread finished sleeping without wake!"); } - catch(InterruptedException e) + catch (InterruptedException e) { logger.debug("Interrupted thread: " + Thread.currentThread().getName()); } } - + public static void wakeAll() { allAwake = true; - for(Entry t : sleeping.entrySet()) + for (Entry t : sleeping.entrySet()) { logger.debug("Interrupting thread: " + t.getKey()); t.getValue().interrupt(); } } + public static void reset() { logger.debug("Resetting."); diff --git a/core/src/test/java/org/alfresco/util/transaction/SpringAwareUserTransactionTest.java b/core/src/test/java/org/alfresco/util/transaction/SpringAwareUserTransactionTest.java index 00855942f8..be3c08ba23 100644 --- a/core/src/test/java/org/alfresco/util/transaction/SpringAwareUserTransactionTest.java +++ b/core/src/test/java/org/alfresco/util/transaction/SpringAwareUserTransactionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2023 Alfresco Software Limited. + * Copyright (C) 2005-2025 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,13 +20,11 @@ package org.alfresco.util.transaction; import java.util.NoSuchElementException; import java.util.Objects; - import jakarta.transaction.RollbackException; import jakarta.transaction.Status; import jakarta.transaction.UserTransaction; import junit.framework.TestCase; - import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.NoTransactionException; import org.springframework.transaction.TransactionDefinition; @@ -35,21 +33,20 @@ import org.springframework.transaction.support.AbstractPlatformTransactionManage import org.springframework.transaction.support.DefaultTransactionStatus; /** - * @see org.alfresco.util.transaction.SpringAwareUserTransaction - * * @author Derek Hulley + * @see org.alfresco.util.transaction.SpringAwareUserTransaction */ public class SpringAwareUserTransactionTest extends TestCase { private DummyTransactionManager transactionManager; private FailingTransactionManager failingTransactionManager; private UserTransaction txn; - + public SpringAwareUserTransactionTest() { super(); } - + @Override protected void setUp() throws Exception { @@ -57,7 +54,7 @@ public class SpringAwareUserTransactionTest extends TestCase failingTransactionManager = new FailingTransactionManager(); txn = getTxn(); } - + private UserTransaction getTxn() { return new SpringAwareUserTransaction( @@ -67,13 +64,13 @@ public class SpringAwareUserTransactionTest extends TestCase TransactionDefinition.PROPAGATION_REQUIRED, TransactionDefinition.TIMEOUT_DEFAULT); } - + public void testSetUp() throws Exception { assertNotNull(transactionManager); assertNotNull(txn); } - + private void checkNoStatusOnThread() { try @@ -86,7 +83,7 @@ public class SpringAwareUserTransactionTest extends TestCase // expected } } - + public void testNoTxnStatus() throws Exception { checkNoStatusOnThread(); @@ -134,7 +131,7 @@ public class SpringAwareUserTransactionTest extends TestCase } checkNoStatusOnThread(); } - + public void testSimpleTxnWithRollback() throws Exception { testNoTxnStatus(); @@ -156,7 +153,7 @@ public class SpringAwareUserTransactionTest extends TestCase transactionManager.getStatus()); checkNoStatusOnThread(); } - + public void testNoBeginCommit() throws Exception { testNoTxnStatus(); @@ -171,7 +168,7 @@ public class SpringAwareUserTransactionTest extends TestCase } checkNoStatusOnThread(); } - + public void testPostRollbackCommitDetection() throws Exception { testNoTxnStatus(); @@ -189,7 +186,7 @@ public class SpringAwareUserTransactionTest extends TestCase } checkNoStatusOnThread(); } - + public void testPostSetRollbackOnlyCommitDetection() throws Exception { testNoTxnStatus(); @@ -208,7 +205,7 @@ public class SpringAwareUserTransactionTest extends TestCase } checkNoStatusOnThread(); } - + public void testMismatchedBeginCommit() throws Exception { UserTransaction txn1 = getTxn(); @@ -218,18 +215,18 @@ public class SpringAwareUserTransactionTest extends TestCase txn1.begin(); txn2.begin(); - + txn2.commit(); txn1.commit(); - + checkNoStatusOnThread(); - + txn1 = getTxn(); txn2 = getTxn(); - + txn1.begin(); txn2.begin(); - + try { txn1.commit(); @@ -245,58 +242,6 @@ public class SpringAwareUserTransactionTest extends TestCase checkNoStatusOnThread(); } - /** - * Test for leaked transactions (no guarantee it will succeed due to reliance - * on garbage collector), so disabled by default. - * - * Also, if it succeeds, transaction call stack tracing will be enabled - * potentially hitting the performance of all subsequent tests. - * - * @throws Exception - */ - public void xtestLeakedTransactionLogging() throws Exception - { - assertFalse(SpringAwareUserTransaction.isCallStackTraced()); - - TrxThread t1 = new TrxThread(); - t1.start(); - System.gc(); - Thread.sleep(1000); - - TrxThread t2 = new TrxThread(); - t2.start(); - System.gc(); - Thread.sleep(1000); - - assertTrue(SpringAwareUserTransaction.isCallStackTraced()); - - TrxThread t3 = new TrxThread(); - t3.start(); - System.gc(); - Thread.sleep(3000); - System.gc(); - Thread.sleep(3000); - } - - private class TrxThread extends Thread - { - public void run() - { - try - { - getTrx(); - } - catch (Exception e) {} - } - - public void getTrx() throws Exception - { - UserTransaction txn = getTxn(); - txn.begin(); - txn = null; - } - } - public void testConnectionPoolException() throws Exception { testNoTxnStatus(); @@ -311,7 +256,7 @@ public class SpringAwareUserTransactionTest extends TestCase // Expected fail } } - + private UserTransaction getFailingTxn() { return new SpringAwareUserTransaction( @@ -321,7 +266,7 @@ public class SpringAwareUserTransactionTest extends TestCase TransactionDefinition.PROPAGATION_REQUIRED, TransactionDefinition.TIMEOUT_DEFAULT); } - + public void testTransactionListenerOrder() throws Throwable { testNoTxnStatus(); @@ -360,12 +305,12 @@ public class SpringAwareUserTransactionTest extends TestCase } checkNoStatusOnThread(); } - + private static class TestTransactionListener extends TransactionListenerAdapter { private final String name; private final StringBuffer buffer; - + public TestTransactionListener(String name, StringBuffer buffer) { Objects.requireNonNull(name); @@ -373,18 +318,18 @@ public class SpringAwareUserTransactionTest extends TestCase this.name = name; this.buffer = buffer; } - + @Override public void beforeCommit(boolean readOnly) { buffer.append(name); } - + public String getName() { return name; } - + @Override public boolean equals(Object obj) { @@ -394,17 +339,17 @@ public class SpringAwareUserTransactionTest extends TestCase } return false; } - + @Override public int hashCode() { return name.hashCode(); } } - + /** * Used to check that the transaction manager is being called correctly - * + * * @author Derek Hulley */ @SuppressWarnings("serial") @@ -412,7 +357,7 @@ public class SpringAwareUserTransactionTest extends TestCase { private int status = Status.STATUS_NO_TRANSACTION; private Object txn = new Object(); - + /** * @return Returns one of the {@link Status Status.STATUS_XXX} constants */ @@ -441,10 +386,10 @@ public class SpringAwareUserTransactionTest extends TestCase status = Status.STATUS_ROLLEDBACK; } } - + /** * Throws {@link NoSuchElementException} on begin() - * + * * @author alex.mukha */ private static class FailingTransactionManager extends AbstractPlatformTransactionManager @@ -452,7 +397,7 @@ public class SpringAwareUserTransactionTest extends TestCase private static final long serialVersionUID = 1L; private int status = Status.STATUS_NO_TRANSACTION; private Object txn = new Object(); - + /** * @return Returns one of the {@link Status Status.STATUS_XXX} constants */ diff --git a/repository/src/test/java/org/alfresco/repo/action/ActionServiceImpl2Test.java b/repository/src/test/java/org/alfresco/repo/action/ActionServiceImpl2Test.java index 56076b1c05..bc27390f90 100644 --- a/repository/src/test/java/org/alfresco/repo/action/ActionServiceImpl2Test.java +++ b/repository/src/test/java/org/alfresco/repo/action/ActionServiceImpl2Test.java @@ -1,514 +1,506 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2020 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.action; - -import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.ActionExecuter; -import org.alfresco.repo.action.executer.ContentMetadataExtracter; -import org.alfresco.repo.action.executer.CounterIncrementActionExecuter; -import org.alfresco.repo.action.executer.ScriptActionExecuter; -import org.alfresco.repo.action.executer.TransformActionExecuter; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.transform.AbstractContentTransformerTest; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.action.ParameterConstraint; -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.site.SiteVisibility; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.test.junitrules.ApplicationContextInit; -import org.alfresco.util.test.junitrules.TemporaryNodes; -import org.alfresco.util.test.junitrules.TemporarySites; -import org.alfresco.util.test.junitrules.TemporarySites.TestSiteAndMemberInfo; -import org.alfresco.util.test.junitrules.WellKnownNodes; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; - -/** - * @author Jamal Kaabi-Mofrad - * @since Odin - */ -public class ActionServiceImpl2Test -{ - // Rule to initialise the default Alfresco spring configuration - @ClassRule - public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); - - /** - * This JUnit rule will allow us to create Share sites and users and have - * them automatically cleaned up for us. - */ - @Rule - public TemporarySites temporarySites = new TemporarySites(APP_CONTEXT_INIT); - - @Rule - public WellKnownNodes wellKnownNodes = new WellKnownNodes(APP_CONTEXT_INIT); - - @Rule - public TemporaryNodes tempNodes = new TemporaryNodes(APP_CONTEXT_INIT); - - // Various services - private static NodeService nodeService; - - private static ActionService actionService; - - private static ContentService contentService; - - private static RetryingTransactionHelper transactionHelper; - - private TestSiteAndMemberInfo testSiteAndMemberInfo; - - private NodeRef testNode; - - - - @BeforeClass - public static void initStaticData() throws Exception - { - nodeService = (NodeService) APP_CONTEXT_INIT.getApplicationContext().getBean("nodeService"); - actionService = (ActionService) APP_CONTEXT_INIT.getApplicationContext().getBean("actionService"); - contentService = (ContentService) APP_CONTEXT_INIT.getApplicationContext().getBean("contentService"); - transactionHelper = (RetryingTransactionHelper) APP_CONTEXT_INIT.getApplicationContext().getBean( - "retryingTransactionHelper"); - } - - @Before - public void initTestSiteAndUsersAndSomeContent() - { - final String siteShortName = ActionServiceImpl2Test.class.getSimpleName() + "TestSite" - + System.currentTimeMillis(); - - // This will create a public Share site whose creator is the admin user. - // It will create 4 users (one for each of the Share roles, and add them - // to the site. - testSiteAndMemberInfo = temporarySites.createTestSiteWithUserPerRole(siteShortName, "sitePreset", - SiteVisibility.PUBLIC, AuthenticationUtil.getAdminUserName()); - - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - testNode = transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Throwable - { - // get the Document Library NodeRef - final NodeRef docLibNodeRef = testSiteAndMemberInfo.doclib; - - // Create a test node. It doesn't need content. - return nodeService.createNode(docLibNodeRef, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS, - ContentModel.TYPE_CONTENT).getChildRef(); - } - }); - } - - // MNT-15365 - @Test - public void testIncrementCounterOnDeletedNode() throws Exception - { - final NodeRef deletedNode = transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Throwable - { - // get the Document Library NodeRef - final NodeRef docLibNodeRef = testSiteAndMemberInfo.doclib; - - NodeRef result = nodeService.createNode(docLibNodeRef, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS, - ContentModel.TYPE_CONTENT).getChildRef(); - nodeService.deleteNode(result); - return result; - } - }); - - // before the fix that would thrown an error - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - Action incrementAction = actionService.createAction(CounterIncrementActionExecuter.NAME); - - actionService.executeAction(incrementAction, deletedNode); - return null; - } - }); - } - - @Test - public void testIncrementCounter() throws Exception - { - // Set authentication to SiteManager. - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - // add the cm:countable aspect and set the value to 1 - Map props = new HashMap(1); - props.put(ContentModel.PROP_COUNTER, 1); - nodeService.addAspect(testNode, ContentModel.ASPECT_COUNTABLE, props); - - return null; - } - }); - // check that the default counter value is set to 1 - int beforeIncrement = (Integer) nodeService.getProperty(testNode, ContentModel.PROP_COUNTER); - assertEquals("Counter value incorrect", 1, beforeIncrement); - - // Set authentication to SiteConsumer. - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteConsumer); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - Action incrementAction = actionService.createAction(CounterIncrementActionExecuter.NAME); - - actionService.executeAction(incrementAction, testNode); - return null; - } - }); - - int afterIncrement = (Integer) nodeService.getProperty(testNode, ContentModel.PROP_COUNTER); - // CounterIncrementActionExecuter is a sample action, therefore, the - // permission is no checked. - assertEquals(2, afterIncrement); - } - - @Test//(expected = AccessDeniedException.class) - public void testTransform() throws Exception - { - final File file = loadAndAddQuickFileAsManager(testNode, "quick.txt", MimetypeMap.MIMETYPE_TEXT_PLAIN); - assertNotNull("Failed to load required test file.", file); - - // Set authentication to SiteConsumer. - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - Action action = actionService.createAction(TransformActionExecuter.NAME); - Map map = new HashMap(); - map.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_HTML); - map.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, nodeService.getPrimaryParent(testNode) - .getParentRef()); - action.setParameterValues(map); - - actionService.executeAction(action, testNode); - - return null; - } - }); - } - - @Test - public void testParameterConstraints() throws Exception - { - List constraints = actionService.getParameterConstraints(); - assertNotNull(constraints); - if (constraints.size() > 0) - { - ParameterConstraint parameterConstraint = constraints.get(0); - ParameterConstraint pConstraintAgain = actionService.getParameterConstraint(parameterConstraint.getName()); - Assert.assertEquals(parameterConstraint, pConstraintAgain); - } - } - - @Test - public void testExecuteScript() throws Exception - { - final NodeRef scriptToBeExecuted = addTempScript("changeFileNameTest.js", - "document.properties.name = \"Changed_\" + document.properties.name;\ndocument.save();"); - assertNotNull("Failed to add the test script.", scriptToBeExecuted); - - // add a test file to the Site in order to change its name - final File file = loadAndAddQuickFileAsManager(testNode, "quick.pdf", MimetypeMap.MIMETYPE_PDF); - assertNotNull("Failed to load required test file.", file); - - // Set authentication to SiteConsumer - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteConsumer); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - // Create the action - Action action = actionService.createAction(ScriptActionExecuter.NAME); - action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, scriptToBeExecuted); - - try - { - // Execute the action - actionService.executeAction(action, testNode); - } - catch (Throwable th) - { - // do nothing - } - assertTrue("The consumer shouldn't be able to change the name of the file.", - ("quick.pdf".equals(nodeService.getProperty(testNode, ContentModel.PROP_NAME)))); - - return null; - } - }); - - // Set authentication to SiteManager - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - // Create the action - Action action = actionService.createAction(ScriptActionExecuter.NAME); - action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, scriptToBeExecuted); - - // Execute the action - actionService.executeAction(action, testNode); - - assertEquals("Changed_quick.pdf", nodeService.getProperty(testNode, ContentModel.PROP_NAME)); - - return null; - } - }); - - //Execute script not in Data Dictionary > Scripts - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); - NodeRef companyHomeRef = wellKnownNodes.getCompanyHome(); - NodeRef sharedFolderRef = nodeService.getChildByName(companyHomeRef, ContentModel.ASSOC_CONTAINS, - "Shared"); - final NodeRef invalidScriptRef = addTempScript("changeFileNameTest.js", - "document.properties.name = \"Invalid_Change.pdf\";\ndocument.save();",sharedFolderRef); - assertNotNull("Failed to add the test script.", scriptToBeExecuted); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - // Create the action - Action action = actionService.createAction(ScriptActionExecuter.NAME); - action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, invalidScriptRef); - - try - { - // Execute the action - actionService.executeAction(action, testNode); - } - catch (Throwable th) - { - // do nothing - } - assertFalse("Scripts outside of Data Dictionary Scripts folder should not be executed", - ("Invalid_Change.pdf".equals(nodeService.getProperty(testNode, ContentModel.PROP_NAME)))); - - return null; - } - }); - } - - @Test - public void testActionResult() throws Exception - { - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - try - { - // Create the script node reference - NodeRef script = addTempScript("test-action-result-script.js", "\"VALUE\";"); - - // Create the action - Action action = actionService.createAction(ScriptActionExecuter.NAME); - action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, script); - - // Execute the action - actionService.executeAction(action, testNode); - - // Get the result - String result = (String) action.getParameterValue(ActionExecuter.PARAM_RESULT); - assertNotNull(result); - assertEquals("VALUE", result); - } - finally - { - AuthenticationUtil.clearCurrentSecurityContext(); - } - - return null; - } - }); - } - - @Test - public void testExtractMetedata() throws Exception - { - // add a test file to the Site in order to change its name - final File file = loadAndAddQuickFileAsManager(testNode, "quick.pdf", MimetypeMap.MIMETYPE_PDF); - assertNotNull("Failed to load required test file.", file); - - // Set authentication to SiteConsumer - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteConsumer); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - // Create the action - Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME); - try - { - actionService.executeAction(action, testNode); - } - catch (Throwable th) - { - // do nothing - } - assertTrue("The consumer shouldn't be able to perform Extract Metadata.", - (nodeService.getProperty(testNode, ContentModel.PROP_DESCRIPTION) == null)); - - return null; - } - }); - - // Set authentication to SiteCollaborator - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteCollaborator); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - // Create the action - Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME); - // Execute the action - actionService.executeAction(action, testNode); - return null; - } - }); - - Thread.sleep(3000); // Need to wait for the async extract - - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - assertEquals("Pangram, fox, dog, Gym class featuring a brown fox and lazy dog", - nodeService.getProperty(testNode, ContentModel.PROP_DESCRIPTION)); - return null; - } - }); - } - - private NodeRef addTempScript(final String scriptFileName, final String javaScript, final NodeRef parentRef) - { - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - return transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Throwable - { - - // Create the script node reference - NodeRef script = nodeService.createNode(parentRef, ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, scriptFileName), - ContentModel.TYPE_CONTENT).getChildRef(); - - nodeService.setProperty(script, ContentModel.PROP_NAME, scriptFileName); - - ContentWriter contentWriter = contentService.getWriter(script, ContentModel.PROP_CONTENT, true); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_JAVASCRIPT); - contentWriter.setEncoding("UTF-8"); - contentWriter.putContent(javaScript); - - tempNodes.addNodeRef(script); - return script; - } - }); - } - - private NodeRef addTempScript(final String scriptFileName, final String javaScript) - { - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - return transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Throwable - { - - // get the company_home - NodeRef companyHomeRef = wellKnownNodes.getCompanyHome(); - // get the Data Dictionary - NodeRef dataDictionaryRef = nodeService.getChildByName(companyHomeRef, ContentModel.ASSOC_CONTAINS, - "Data Dictionary"); - // get the Scripts - NodeRef scriptsRef = nodeService.getChildByName(dataDictionaryRef, ContentModel.ASSOC_CONTAINS, - "Scripts"); - - return addTempScript(scriptFileName, javaScript, scriptsRef); - } - }); - } - - private File loadAndAddQuickFileAsManager(final NodeRef nodeRef, final String quickFileName, final String mimeType) - throws IOException - { - final File file = AbstractContentTransformerTest.loadNamedQuickTestFile(quickFileName); - - if (file == null) { return null; } - - // Set authentication to SiteManager and add a file - AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); - transactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, quickFileName); - - ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - writer.setMimetype(mimeType); - writer.setEncoding("UTF-8"); - writer.putContent(file); - - return null; - } - }); - - return file; - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.action; + +import static java.time.Duration.ofSeconds; +import static java.util.Objects.nonNull; + +import static junit.framework.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.repo.action.executer.ContentMetadataExtracter; +import org.alfresco.repo.action.executer.CounterIncrementActionExecuter; +import org.alfresco.repo.action.executer.ScriptActionExecuter; +import org.alfresco.repo.action.executer.TransformActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.AbstractContentTransformerTest; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ParameterConstraint; +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.site.SiteVisibility; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.test.junitrules.ApplicationContextInit; +import org.alfresco.util.test.junitrules.TemporaryNodes; +import org.alfresco.util.test.junitrules.TemporarySites; +import org.alfresco.util.test.junitrules.TemporarySites.TestSiteAndMemberInfo; +import org.alfresco.util.test.junitrules.WellKnownNodes; + +/** + * @author Jamal Kaabi-Mofrad + * @since Odin + */ +public class ActionServiceImpl2Test +{ + private static final int MAX_WAIT_TIMEOUT = 10; + // Rule to initialise the default Alfresco spring configuration + @ClassRule + public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); + + /** + * This JUnit rule will allow us to create Share sites and users and have them automatically cleaned up for us. + */ + @Rule + public TemporarySites temporarySites = new TemporarySites(APP_CONTEXT_INIT); + + @Rule + public WellKnownNodes wellKnownNodes = new WellKnownNodes(APP_CONTEXT_INIT); + + @Rule + public TemporaryNodes tempNodes = new TemporaryNodes(APP_CONTEXT_INIT); + + // Various services + private static NodeService nodeService; + + private static ActionService actionService; + + private static ContentService contentService; + + private static RetryingTransactionHelper transactionHelper; + + private TestSiteAndMemberInfo testSiteAndMemberInfo; + + private NodeRef testNode; + + @BeforeClass + public static void initStaticData() throws Exception + { + nodeService = (NodeService) APP_CONTEXT_INIT.getApplicationContext().getBean("nodeService"); + actionService = (ActionService) APP_CONTEXT_INIT.getApplicationContext().getBean("actionService"); + contentService = (ContentService) APP_CONTEXT_INIT.getApplicationContext().getBean("contentService"); + transactionHelper = (RetryingTransactionHelper) APP_CONTEXT_INIT.getApplicationContext().getBean( + "retryingTransactionHelper"); + } + + @Before + public void initTestSiteAndUsersAndSomeContent() + { + final String siteShortName = ActionServiceImpl2Test.class.getSimpleName() + "TestSite" + + System.currentTimeMillis(); + + // This will create a public Share site whose creator is the admin user. + // It will create 4 users (one for each of the Share roles, and add them + // to the site. + testSiteAndMemberInfo = temporarySites.createTestSiteWithUserPerRole(siteShortName, "sitePreset", + SiteVisibility.PUBLIC, AuthenticationUtil.getAdminUserName()); + + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + testNode = transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public NodeRef execute() throws Throwable + { + // get the Document Library NodeRef + final NodeRef docLibNodeRef = testSiteAndMemberInfo.doclib; + + // Create a test node. It doesn't need content. + return nodeService.createNode(docLibNodeRef, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS, + ContentModel.TYPE_CONTENT).getChildRef(); + } + }); + } + + // MNT-15365 + @Test + public void testIncrementCounterOnDeletedNode() throws Exception + { + final NodeRef deletedNode = transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public NodeRef execute() throws Throwable + { + // get the Document Library NodeRef + final NodeRef docLibNodeRef = testSiteAndMemberInfo.doclib; + + NodeRef result = nodeService.createNode(docLibNodeRef, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS, + ContentModel.TYPE_CONTENT).getChildRef(); + nodeService.deleteNode(result); + return result; + } + }); + + // before the fix that would thrown an error + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + Action incrementAction = actionService.createAction(CounterIncrementActionExecuter.NAME); + + actionService.executeAction(incrementAction, deletedNode); + return null; + } + }); + } + + @Test + public void testIncrementCounter() throws Exception + { + // Set authentication to SiteManager. + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + // add the cm:countable aspect and set the value to 1 + Map props = new HashMap(1); + props.put(ContentModel.PROP_COUNTER, 1); + nodeService.addAspect(testNode, ContentModel.ASPECT_COUNTABLE, props); + + return null; + } + }); + // check that the default counter value is set to 1 + int beforeIncrement = (Integer) nodeService.getProperty(testNode, ContentModel.PROP_COUNTER); + assertEquals("Counter value incorrect", 1, beforeIncrement); + + // Set authentication to SiteConsumer. + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteConsumer); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + Action incrementAction = actionService.createAction(CounterIncrementActionExecuter.NAME); + + actionService.executeAction(incrementAction, testNode); + return null; + } + }); + + int afterIncrement = (Integer) nodeService.getProperty(testNode, ContentModel.PROP_COUNTER); + // CounterIncrementActionExecuter is a sample action, therefore, the + // permission is no checked. + assertEquals(2, afterIncrement); + } + + @Test // (expected = AccessDeniedException.class) + public void testTransform() throws Exception + { + final File file = loadAndAddQuickFileAsManager(testNode, "quick.txt", MimetypeMap.MIMETYPE_TEXT_PLAIN); + assertNotNull("Failed to load required test file.", file); + + // Set authentication to SiteConsumer. + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + Action action = actionService.createAction(TransformActionExecuter.NAME); + Map map = new HashMap(); + map.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_HTML); + map.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, nodeService.getPrimaryParent(testNode) + .getParentRef()); + action.setParameterValues(map); + + actionService.executeAction(action, testNode); + + return null; + } + }); + } + + @Test + public void testParameterConstraints() throws Exception + { + List constraints = actionService.getParameterConstraints(); + assertNotNull(constraints); + if (constraints.size() > 0) + { + ParameterConstraint parameterConstraint = constraints.get(0); + ParameterConstraint pConstraintAgain = actionService.getParameterConstraint(parameterConstraint.getName()); + Assert.assertEquals(parameterConstraint, pConstraintAgain); + } + } + + @Test + public void testExecuteScript() throws Exception + { + final NodeRef scriptToBeExecuted = addTempScript("changeFileNameTest.js", + "document.properties.name = \"Changed_\" + document.properties.name;\ndocument.save();"); + assertNotNull("Failed to add the test script.", scriptToBeExecuted); + + // add a test file to the Site in order to change its name + final File file = loadAndAddQuickFileAsManager(testNode, "quick.pdf", MimetypeMap.MIMETYPE_PDF); + assertNotNull("Failed to load required test file.", file); + + // Set authentication to SiteConsumer + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteConsumer); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + // Create the action + Action action = actionService.createAction(ScriptActionExecuter.NAME); + action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, scriptToBeExecuted); + + try + { + // Execute the action + actionService.executeAction(action, testNode); + } + catch (Throwable th) + { + // do nothing + } + assertTrue("The consumer shouldn't be able to change the name of the file.", + ("quick.pdf".equals(nodeService.getProperty(testNode, ContentModel.PROP_NAME)))); + + return null; + } + }); + + // Set authentication to SiteManager + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + // Create the action + Action action = actionService.createAction(ScriptActionExecuter.NAME); + action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, scriptToBeExecuted); + + // Execute the action + actionService.executeAction(action, testNode); + + assertEquals("Changed_quick.pdf", nodeService.getProperty(testNode, ContentModel.PROP_NAME)); + + return null; + } + }); + + // Execute script not in Data Dictionary > Scripts + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); + NodeRef companyHomeRef = wellKnownNodes.getCompanyHome(); + NodeRef sharedFolderRef = nodeService.getChildByName(companyHomeRef, ContentModel.ASSOC_CONTAINS, + "Shared"); + final NodeRef invalidScriptRef = addTempScript("changeFileNameTest.js", + "document.properties.name = \"Invalid_Change.pdf\";\ndocument.save();", sharedFolderRef); + assertNotNull("Failed to add the test script.", scriptToBeExecuted); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + // Create the action + Action action = actionService.createAction(ScriptActionExecuter.NAME); + action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, invalidScriptRef); + + try + { + // Execute the action + actionService.executeAction(action, testNode); + } + catch (Throwable th) + { + // do nothing + } + assertFalse("Scripts outside of Data Dictionary Scripts folder should not be executed", + ("Invalid_Change.pdf".equals(nodeService.getProperty(testNode, ContentModel.PROP_NAME)))); + + return null; + } + }); + } + + @Test + public void testActionResult() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + try + { + // Create the script node reference + NodeRef script = addTempScript("test-action-result-script.js", "\"VALUE\";"); + + // Create the action + Action action = actionService.createAction(ScriptActionExecuter.NAME); + action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, script); + + // Execute the action + actionService.executeAction(action, testNode); + + // Get the result + String result = (String) action.getParameterValue(ActionExecuter.PARAM_RESULT); + assertNotNull(result); + assertEquals("VALUE", result); + } + finally + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + + return null; + } + }); + } + + @Test + public void testExtractMetedata() throws Exception + { + // add a test file to the Site in order to change its name + final File file = loadAndAddQuickFileAsManager(testNode, "quick.pdf", MimetypeMap.MIMETYPE_PDF); + assertNotNull("Failed to load required test file.", file); + + // Set authentication to SiteConsumer + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteConsumer); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + // Create the action + Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME); + try + { + actionService.executeAction(action, testNode); + } + catch (Throwable th) + { + // do nothing + } + assertTrue("The consumer shouldn't be able to perform Extract Metadata.", + (nodeService.getProperty(testNode, ContentModel.PROP_DESCRIPTION) == null)); + + return null; + } + }); + + // Set authentication to SiteCollaborator + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteCollaborator); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + // Create the action + Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME); + // Execute the action + actionService.executeAction(action, testNode); + return null; + } + }); + + // Need to wait for the async extract + await().atMost(ofSeconds(MAX_WAIT_TIMEOUT)) + .until(() -> nonNull(getProperty(testNode, ContentModel.PROP_DESCRIPTION))); + + assertThat(getProperty(testNode, ContentModel.PROP_DESCRIPTION)) + .isEqualTo("Pangram, fox, dog, Gym class featuring a brown fox and lazy dog"); + } + + private Serializable getProperty(NodeRef nodeRef, QName propertyName) + { + return transactionHelper.doInTransaction(() -> nodeService.getProperty(nodeRef, propertyName)); + } + + private NodeRef addTempScript(final String scriptFileName, final String javaScript, final NodeRef parentRef) + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + return transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public NodeRef execute() throws Throwable + { + + // Create the script node reference + NodeRef script = nodeService.createNode(parentRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, scriptFileName), + ContentModel.TYPE_CONTENT).getChildRef(); + + nodeService.setProperty(script, ContentModel.PROP_NAME, scriptFileName); + + ContentWriter contentWriter = contentService.getWriter(script, ContentModel.PROP_CONTENT, true); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_JAVASCRIPT); + contentWriter.setEncoding("UTF-8"); + contentWriter.putContent(javaScript); + + tempNodes.addNodeRef(script); + return script; + } + }); + } + + private NodeRef addTempScript(final String scriptFileName, final String javaScript) + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + return transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public NodeRef execute() throws Throwable + { + + // get the company_home + NodeRef companyHomeRef = wellKnownNodes.getCompanyHome(); + // get the Data Dictionary + NodeRef dataDictionaryRef = nodeService.getChildByName(companyHomeRef, ContentModel.ASSOC_CONTAINS, + "Data Dictionary"); + // get the Scripts + NodeRef scriptsRef = nodeService.getChildByName(dataDictionaryRef, ContentModel.ASSOC_CONTAINS, + "Scripts"); + + return addTempScript(scriptFileName, javaScript, scriptsRef); + } + }); + } + + private File loadAndAddQuickFileAsManager(final NodeRef nodeRef, final String quickFileName, final String mimeType) + throws IOException + { + final File file = AbstractContentTransformerTest.loadNamedQuickTestFile(quickFileName); + + if (file == null) + { + return null; + } + + // Set authentication to SiteManager and add a file + AuthenticationUtil.setFullyAuthenticatedUser(testSiteAndMemberInfo.siteManager); + transactionHelper.doInTransaction(new RetryingTransactionCallback() { + public Void execute() throws Throwable + { + nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, quickFileName); + + ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(mimeType); + writer.setEncoding("UTF-8"); + writer.putContent(file); + + return null; + } + }); + + return file; + } +} diff --git a/repository/src/test/java/org/alfresco/repo/action/ActionServiceImplTest.java b/repository/src/test/java/org/alfresco/repo/action/ActionServiceImplTest.java index f227199b14..be77d11e9e 100644 --- a/repository/src/test/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/repository/src/test/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -1,1531 +1,1348 @@ -/* - * #%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.action; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; -import org.alfresco.repo.action.evaluator.InCategoryEvaluator; -import org.alfresco.repo.action.evaluator.NoConditionEvaluator; -import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; -import org.alfresco.repo.action.executer.ActionExecuter; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; -import org.alfresco.repo.action.executer.CheckInActionExecuter; -import org.alfresco.repo.action.executer.CheckOutActionExecuter; -import org.alfresco.repo.action.executer.CompositeActionExecuter; -import org.alfresco.repo.action.executer.MoveActionExecuter; -import org.alfresco.repo.action.executer.ScriptActionExecuter; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ActionConditionDefinition; -import org.alfresco.service.cmr.action.ActionDefinition; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.action.ActionServiceTransientException; -import org.alfresco.service.cmr.action.ActionStatus; -import org.alfresco.service.cmr.action.ActionTrackingService; -import org.alfresco.service.cmr.action.CancellableAction; -import org.alfresco.service.cmr.action.CompositeAction; -import org.alfresco.service.cmr.action.CompositeActionCondition; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.repository.ContentData; -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.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.test_category.BaseSpringTestsCategory; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.BaseAlfrescoSpringTest; -import org.alfresco.util.GUID; -import org.alfresco.util.PropertyMap; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.transaction.TestTransaction; -import org.springframework.transaction.annotation.Transactional; - -/** - * Action service test - * - * @author Roy Wetherall - */ -@Category(BaseSpringTestsCategory.class) -@Transactional -@ContextConfiguration({"classpath:alfresco/application-context.xml", - "classpath:org/alfresco/repo/action/test-action-services-context.xml"}) -public class ActionServiceImplTest extends BaseAlfrescoSpringTest -{ - private static final String BAD_NAME = "badName"; - - private NodeRef nodeRef; - private NodeRef folder; - private RetryingTransactionHelper transactionHelper; - - @Before - public void before() throws Exception - { - super.before(); - - this.transactionHelper = (RetryingTransactionHelper)this.applicationContext.getBean("retryingTransactionHelper"); - - // Create the node used for tests - this.nodeRef = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testnode"), - ContentModel.TYPE_CONTENT).getChildRef(); - this.nodeService.setProperty( - this.nodeRef, - ContentModel.PROP_CONTENT, - new ContentData(null, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, null)); - this.folder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testFolder"), - ContentModel.TYPE_FOLDER).getChildRef(); - - // Register the test executor, if needed - SleepActionExecuter.registerIfNeeded(applicationContext); - } - - /** - * Test getActionDefinition - */ - @Test - public void testGetActionDefinition() - { - ActionDefinition action = actionService.getActionDefinition(AddFeaturesActionExecuter.NAME); - assertNotNull(action); - assertEquals(AddFeaturesActionExecuter.NAME, action.getName()); - - ActionConditionDefinition nullCondition = this.actionService.getActionConditionDefinition(BAD_NAME); - assertNull(nullCondition); - } - - /** - * Test getActionDefintions - */ - @Test - public void testGetActionDefinitions() - { - List defintions = this.actionService.getActionDefinitions(); - assertNotNull(defintions); - assertFalse(defintions.isEmpty()); - int totalCount = defintions.size(); - - for (ActionDefinition definition : defintions) - { - System.out.println(definition.getTitle()); - } - - // Get the action defintions for a folder type (there should be less than the total available) - List definitions = this.actionService.getActionDefinitions(this.folder); - assertNotNull(definitions); - assertTrue(totalCount > definitions.size()); - } - - /** - * Test getActionConditionDefinition - */ - @Test - public void testGetActionConditionDefinition() - { - ActionConditionDefinition condition = this.actionService.getActionConditionDefinition(NoConditionEvaluator.NAME); - assertNotNull(condition); - assertEquals(NoConditionEvaluator.NAME, condition.getName()); - - ActionConditionDefinition nullCondition = this.actionService.getActionConditionDefinition(BAD_NAME); - assertNull(nullCondition); - } - - /** - * Test getActionConditionDefinitions - * - */ - @Test - public void testGetActionConditionDefinitions() - { - List defintions = this.actionService.getActionConditionDefinitions(); - assertNotNull(defintions); - assertFalse(defintions.isEmpty()); - - for (ActionConditionDefinition definition : defintions) - { - System.out.println(definition.getTitle()); - } - } - - /** - * Test create action condition - */ - @Test - public void testCreateActionCondition() - { - ActionCondition condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - assertNotNull(condition); - assertEquals(NoConditionEvaluator.NAME, condition.getActionConditionDefinitionName()); - - Map params = new HashMap<>(0); - condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME, params); - assertNotNull(condition); - assertEquals(NoConditionEvaluator.NAME, condition.getActionConditionDefinitionName()); - - } - - /** - * Test createCompositeAction - */ - @Test - public void testCreateCompositeActionCondition() - { - CompositeActionCondition action = this.actionService.createCompositeActionCondition(); - assertNotNull(action); - assertEquals(CompositeActionCondition.COMPOSITE_CONDITION, action.getActionConditionDefinitionName()); - } - - /** - * Test createAction - */ - @Test - public void testCreateAction() - { - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - assertNotNull(action); - assertEquals(AddFeaturesActionExecuter.NAME, action.getActionDefinitionName()); - - Map params = new HashMap<>(0); - action = this.actionService.createAction(AddFeaturesActionExecuter.NAME, params); - assertNotNull(action); - assertEquals(AddFeaturesActionExecuter.NAME, action.getActionDefinitionName()); - } - - /** - * Test createCompositeAction - */ - @Test - public void testCreateCompositeAction() - { - CompositeAction action = this.actionService.createCompositeAction(); - assertNotNull(action); - assertEquals(CompositeActionExecuter.NAME, action.getActionDefinitionName()); - } - - /** - * Evaluate action - */ - @Test - public void testEvaluateAction() - { - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - assertTrue(this.actionService.evaluateAction(action, this.nodeRef)); - - ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); - action.addActionCondition(condition); - - assertFalse(this.actionService.evaluateAction(action, this.nodeRef)); - this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); - assertTrue(this.actionService.evaluateAction(action, this.nodeRef)); - - ActionCondition condition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - condition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "my"); - action.addActionCondition(condition2); - assertTrue(this.actionService.evaluateAction(action, this.nodeRef)); - - this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "document.doc"); - assertFalse(this.actionService.evaluateAction(action, this.nodeRef)); - } - - /** - * Test evaluate action condition - */ - @Test - public void testEvaluateActionCondition() - { - ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); - - assertFalse(this.actionService.evaluateActionCondition(condition, this.nodeRef)); - this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); - assertTrue(this.actionService.evaluateActionCondition(condition, this.nodeRef)); - - // Check that inverting the condition has the correct effect - condition.setInvertCondition(true); - assertFalse(this.actionService.evaluateActionCondition(condition, this.nodeRef)); - } - - /** - * Test evaluate action condition - */ - @Test - public void testEvaluateCompositeActionConditionWith1SubCondition() - { - CompositeActionCondition compositeCondition = this.actionService.createCompositeActionCondition(); - - ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); - - compositeCondition.addActionCondition(condition); - - this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); - assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); - - // Check that inverting the condition has the correct effect - compositeCondition.setInvertCondition(true); - assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); - } - - /** - * Test evaluate action condition - */ - @Test - public void testEvaluateCompositeActionConditionWith2SubConditions() - { - CompositeActionCondition compositeCondition = this.actionService.createCompositeActionCondition(); - - ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); - - compositeCondition.addActionCondition(condition); - - this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); - assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); - - ActionCondition conditionTwo = this.actionService.createActionCondition("compare-text-property"); - conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Doc"); - conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, QName.createQName(null, "name")); - conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); - - compositeCondition.addActionCondition(conditionTwo); - assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); - - compositeCondition.removeAllActionConditions(); - assertFalse(compositeCondition.hasActionConditions()); - - compositeCondition.addActionCondition(condition); - conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "NotFound"); - assertFalse(this.actionService.evaluateActionCondition(conditionTwo, this.nodeRef)); - - compositeCondition.addActionCondition(conditionTwo); - compositeCondition.setORCondition(true); - assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); - - compositeCondition.setORCondition(false); - assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); - - // Check that inverting the condition has the correct effect - compositeCondition.setInvertCondition(true); - assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); - } - - /** - * Test execute action - */ - @Test - public void testExecuteAction() - { - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - - this.actionService.executeAction(action, this.nodeRef); - assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); - action.addActionCondition(condition); - - this.actionService.executeAction(action, this.nodeRef); - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.actionService.executeAction(action, this.nodeRef, true); - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.actionService.executeAction(action, this.nodeRef, false); - assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); - this.actionService.executeAction(action, this.nodeRef); - assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Create the composite action - Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE); - Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - CompositeAction compAction = this.actionService.createCompositeAction(); - compAction.setTitle("title"); - compAction.setDescription("description"); - compAction.addAction(action1); - compAction.addAction(action2); - - // Execute the composite action - this.actionService.executeAction(compAction, this.nodeRef); - - assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE)); - assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - } - - @Test - public void testGetAndGetAllWithNoActions() - { - assertNull(this.actionService.getAction(this.nodeRef, AddFeaturesActionExecuter.NAME)); - List actions = this.actionService.getActions(this.nodeRef); - assertNotNull(actions); - assertEquals(0, actions.size()); - } - - @Test - public void testExecuteActionWithNoParameterDef() - { - Action action = this.actionService.createAction("empty-action"); - this.actionService.executeAction(action, this.nodeRef); - assertTrue("If we got here then the test is successful", true); - } - - /** - * Test saving an action with no conditions. Includes testing storage and retrieval - * of compensating actions. - */ - @Test - public void testSaveActionNoCondition() - { - // Create the action - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - String actionId = action.getId(); - - // Set the parameters of the action - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - - // Set the title and description of the action - action.setTitle("title"); - action.setDescription("description"); - action.setExecuteAsynchronously(true); - - // Save the action - this.actionService.saveAction(this.nodeRef, action); - - // Get the action - Action savedAction = this.actionService.getAction(this.nodeRef, actionId); - - // Check the action - assertEquals(action.getId(), savedAction.getId()); - assertEquals(action.getActionDefinitionName(), savedAction.getActionDefinitionName()); - - // Check the properties - assertEquals("title", savedAction.getTitle()); - assertEquals("description", savedAction.getDescription()); - assertTrue(savedAction.getExecuteAsychronously()); - - // Check that the compensating action has not been set - assertNull(savedAction.getCompensatingAction()); - - // Check the properties - assertEquals(1, savedAction.getParameterValues().size()); - assertEquals(ContentModel.ASPECT_VERSIONABLE, savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); - - // Check the conditions - assertNotNull(savedAction.getActionConditions()); - assertEquals(0, savedAction.getActionConditions().size()); - - // Edit the properties of the action - Map properties = new HashMap(1); - properties.put(ContentModel.PROP_NAME, "testName"); - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_AUDITABLE); - - // Set the compensating action - Action compensatingAction = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - action.setCompensatingAction(compensatingAction); - - this.actionService.saveAction(this.nodeRef, action); - Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId); - - // Check the updated properties - assertEquals(1, savedAction2.getParameterValues().size()); - assertEquals(ContentModel.ASPECT_AUDITABLE, savedAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); - - // Check the compensating action - Action savedCompensatingAction = savedAction2.getCompensatingAction(); - assertNotNull(savedCompensatingAction); - assertEquals(compensatingAction, savedCompensatingAction); - assertEquals(AddFeaturesActionExecuter.NAME, savedCompensatingAction.getActionDefinitionName()); - assertEquals(ContentModel.ASPECT_VERSIONABLE, savedCompensatingAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); - - // Change the details of the compensating action (edit and remove) - compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); - this.actionService.saveAction(this.nodeRef, action); - Action savedAction3 = this.actionService.getAction(this.nodeRef, actionId); - Action savedCompensatingAction2 = savedAction3.getCompensatingAction(); - assertNotNull(savedCompensatingAction2); - assertEquals(compensatingAction, savedCompensatingAction2); - assertEquals(AddFeaturesActionExecuter.NAME, savedCompensatingAction2.getActionDefinitionName()); - assertEquals(ContentModel.ASPECT_CLASSIFIABLE, savedCompensatingAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); - action.setCompensatingAction(null); - this.actionService.saveAction(this.nodeRef, action); - Action savedAction4 = this.actionService.getAction(this.nodeRef, actionId); - assertNull(savedAction4.getCompensatingAction()); - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - - @Test - public void testOwningNodeRef() - { - // Create the action - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - String actionId = action.getId(); - - // Set the parameters of the action - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - - // Set the title and description of the action - action.setTitle("title"); - action.setDescription("description"); - action.setExecuteAsynchronously(true); - - // Check the owning node ref - //assertNull(action.getOwningNodeRef()); - - // Save the action - this.actionService.saveAction(this.nodeRef, action); - - // Get the action - this.actionService.getAction(this.nodeRef, actionId); - } - - /** - * Test saving an action with conditions - */ - @Test - public void testSaveActionWithConditions() - { - // Create the action - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - String actionId = action.getId(); - - // Set the parameters of the action - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - - // Set the conditions of the action - ActionCondition actionCondition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - actionCondition.setInvertCondition(true); - ActionCondition actionCondition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); - action.addActionCondition(actionCondition); - action.addActionCondition(actionCondition2); - - // Save the action - this.actionService.saveAction(this.nodeRef, action); - - // Get the action - Action savedAction = this.actionService.getAction(this.nodeRef, actionId); - - // Check the action - assertEquals(action.getId(), savedAction.getId()); - assertEquals(action.getActionDefinitionName(), savedAction.getActionDefinitionName()); - - // Check the properties - assertEquals(action.getParameterValues().size(), savedAction.getParameterValues().size()); - assertEquals(ContentModel.ASPECT_VERSIONABLE, savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); - - // Check the conditions - assertNotNull(savedAction.getActionConditions()); - assertEquals(2, savedAction.getActionConditions().size()); - for (ActionCondition savedCondition : savedAction.getActionConditions()) - { - if (savedCondition.getActionConditionDefinitionName().equals(NoConditionEvaluator.NAME) == true) - { - assertEquals(0, savedCondition.getParameterValues().size()); - assertTrue(savedCondition.getInvertCondition()); - } - else if (savedCondition.getActionConditionDefinitionName().equals(ComparePropertyValueEvaluator.NAME) == true) - { - assertEquals(1, savedCondition.getParameterValues().size()); - assertEquals("*.doc", savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE)); - assertFalse(savedCondition.getInvertCondition()); - } - else - { - fail("There is a condition here that we are not expecting."); - } - } - - // Modify the conditions of the action - ActionCondition actionCondition3 = this.actionService.createActionCondition(InCategoryEvaluator.NAME); - actionCondition3.setParameterValue(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, ContentModel.ASPECT_OWNABLE); - action.addActionCondition(actionCondition3); - action.removeActionCondition(actionCondition); - actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.exe"); - actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS); - - this.actionService.saveAction(this.nodeRef, action); - Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId); - - // Check that the conditions have been updated correctly - assertNotNull(savedAction2.getActionConditions()); - assertEquals(2, savedAction2.getActionConditions().size()); - for (ActionCondition savedCondition : savedAction2.getActionConditions()) - { - if (savedCondition.getActionConditionDefinitionName().equals(InCategoryEvaluator.NAME) == true) - { - assertEquals(1, savedCondition.getParameterValues().size()); - assertEquals(ContentModel.ASPECT_OWNABLE, savedCondition.getParameterValue(InCategoryEvaluator.PARAM_CATEGORY_ASPECT)); - } - else if (savedCondition.getActionConditionDefinitionName().equals(ComparePropertyValueEvaluator.NAME) == true) - { - assertEquals(2, savedCondition.getParameterValues().size()); - assertEquals("*.exe", savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE)); - assertEquals(ComparePropertyValueOperation.EQUALS, savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION)); - } - else - { - fail("There is a condition here that we are not expecting."); - } - } - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - - /** - * Test saving a composite action - */ - @Test - public void testSaveCompositeAction() - { - Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - Action action2 = this.actionService.createAction(CheckInActionExecuter.NAME); - - CompositeAction compositeAction = this.actionService.createCompositeAction(); - String actionId = compositeAction.getId(); - compositeAction.addAction(action1); - compositeAction.addAction(action2); - - this.actionService.saveAction(this.nodeRef, compositeAction); - assertEquals(1, this.actionService.getActions(this.nodeRef).size()); - CompositeAction savedCompositeAction = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId); - - // Check the saved composite action - assertEquals(2, savedCompositeAction.getActions().size()); - for (Action action : savedCompositeAction.getActions()) - { - if (action.getActionDefinitionName().equals(AddFeaturesActionExecuter.NAME) == true) - { - assertEquals(action, action1); - } - else if (action.getActionDefinitionName().equals(CheckInActionExecuter.NAME) == true) - { - assertEquals(action, action2); - } - else - { - fail("We have an action here we are not expecting."); - } - } - - // Change the actions and re-save - compositeAction.removeAction(action1); - Action action3 = this.actionService.createAction(CheckOutActionExecuter.NAME); - compositeAction.addAction(action3); - action2.setParameterValue(CheckInActionExecuter.PARAM_DESCRIPTION, "description"); - - this.actionService.saveAction(this.nodeRef, compositeAction); - assertEquals(1, this.actionService.getActions(this.nodeRef).size()); - CompositeAction savedCompositeAction2 = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId); - - assertEquals(2, savedCompositeAction2.getActions().size()); - for (Action action : savedCompositeAction2.getActions()) - { - if (action.getActionDefinitionName().equals(CheckOutActionExecuter.NAME) == true) - { - assertEquals(action, action3); - } - else if (action.getActionDefinitionName().equals(CheckInActionExecuter.NAME) == true) - { - assertEquals(action, action2); - assertEquals("description", action2.getParameterValue(CheckInActionExecuter.PARAM_DESCRIPTION)); - } - else - { - fail("We have an action here we are not expecting."); - } - } - } - - /** - * Test remove action - */ - @Test - public void testRemove() - { - assertEquals(0, this.actionService.getActions(this.nodeRef).size()); - - Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - this.actionService.saveAction(this.nodeRef, action1); - Action action2 = this.actionService.createAction(CheckInActionExecuter.NAME); - this.actionService.saveAction(this.nodeRef, action2); - assertEquals(2, this.actionService.getActions(this.nodeRef).size()); - - this.actionService.removeAction(this.nodeRef, action1); - assertEquals(1, this.actionService.getActions(this.nodeRef).size()); - - this.actionService.removeAllActions(this.nodeRef); - assertEquals(0, this.actionService.getActions(this.nodeRef).size()); - } - - @Test - public void testConditionOrder() - { - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - String actionId = action.getId(); - - ActionCondition condition1 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - ActionCondition condition2 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - - action.addActionCondition(condition1); - action.addActionCondition(condition2); - - this.actionService.saveAction(this.nodeRef, action); - Action savedAction = this.actionService.getAction(this.nodeRef, actionId); - - // Check that the conditions have been retrieved in the correct order - assertNotNull(savedAction); - assertEquals(condition1, savedAction.getActionCondition(0)); - assertEquals(condition2, savedAction.getActionCondition(1)); - - ActionCondition condition3 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - ActionCondition condition4 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - - // Update the conditions on the action - savedAction.removeActionCondition(condition1); - savedAction.addActionCondition(condition3); - savedAction.addActionCondition(condition4); - - this.actionService.saveAction(this.nodeRef, savedAction); - Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId); - - // Check that the conditions are still in the correct order - assertNotNull(savedAction2); - assertEquals(condition2, savedAction2.getActionCondition(0)); - assertEquals(condition3, savedAction2.getActionCondition(1)); - assertEquals(condition4, savedAction2.getActionCondition(2)); - } - - @Test - public void testActionOrder() - { - CompositeAction action = this.actionService.createCompositeAction(); - String actionId = action.getId(); - - Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - - action.addAction(action1); - action.addAction(action2); - - this.actionService.saveAction(this.nodeRef, action); - CompositeAction savedAction = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId); - - // Check that the conditions have been retrieved in the correct order - assertNotNull(savedAction); - assertEquals(action1, savedAction.getAction(0)); - assertEquals(action2, savedAction.getAction(1)); - - Action action3 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - Action action4 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - - // Update the conditions on the action - savedAction.removeAction(action1); - savedAction.addAction(action3); - savedAction.addAction(action4); - - this.actionService.saveAction(this.nodeRef, savedAction); - CompositeAction savedAction2 = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId); - - // Check that the conditions are still in the correct order - assertNotNull(savedAction2); - assertEquals(action2, savedAction2.getAction(0)); - assertEquals(action3, savedAction2.getAction(1)); - assertEquals(action4, savedAction2.getAction(2)); - } - - - /** =================================================================================== - * Test asynchronous actions - */ - - - /** - * This test checks that a series of "equivalent" actions submitted for asynchronous execution - * will be correctly filtered so that no 2 equivalent actions are executed at the same time. - */ - public void offtestAsyncLongRunningActionsFilter() - { - TestTransaction.flagForCommit(); - TestTransaction.end(); - - final SleepActionExecuter sleepAction = (SleepActionExecuter)applicationContext.getBean("sleep-action"); - assertNotNull(sleepAction); - sleepAction.setSleepMs(10); - - final int actionSubmissonCount = 4; // Rather arbitrary count. - for (int i = 0; i < actionSubmissonCount; i ++) - { - transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - Action action = actionService.createAction(SleepActionExecuter.NAME); - action.setExecuteAsynchronously(true); - - actionService.executeAction(action, nodeRef); - - return null; - } - }); - - } - - // Wait long enough for previous action(s) to have executed and then submit another - try - { - Thread.sleep(sleepAction.getSleepMs() * actionSubmissonCount + 1000); // Enough time for all actions and an extra second for luck. - } - catch (InterruptedException ignored) - { - // intentionally empty - } - transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - Action action = actionService.createAction(SleepActionExecuter.NAME); - action.setExecuteAsynchronously(true); - - actionService.executeAction(action, nodeRef); - - return null; - } - }); - try - { - Thread.sleep(sleepAction.getSleepMs() + 2000); // Enough time for latest action and an extra 2 seconds for luck. - } - catch (InterruptedException ignored) - { - // intentionally empty - } - - - int sleepTime = 0; // Do not sleep during execution as the Action itself sleeps. - int maxTries = 1; - postAsyncActionTest( - this.transactionService, - sleepTime, - maxTries, - new AsyncTest() - { - public String executeTest() - { - final int expectedResult = 2; - int actualResult = sleepAction.getTimesExecuted(); - return actualResult == expectedResult ? null : "Expected timesExecuted " + expectedResult + " was " + actualResult; - }; - }); - } - - /** - * Test asynchronous execute action - */ - @Test - public void testAsyncExecuteAction() - { - assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); - - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); - action.setExecuteAsynchronously(true); - - this.actionService.executeAction(action, this.nodeRef); - - TestTransaction.flagForCommit(); - TestTransaction.end(); - - final NodeService finalNodeService = this.nodeService; - final NodeRef finalNodeRef = this.nodeRef; - - postAsyncActionTest( - this.transactionService, - 1000l, - 10, - new AsyncTest() - { - public String executeTest() - { - boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE); - return result ? null : "Expected aspect Classifiable"; - }; - }); - } - - - - /** - * Test async composite action execution - */ - @Test - public void testAsyncCompositeActionExecute() - { - // Create the composite action - Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE); - Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - CompositeAction compAction = this.actionService.createCompositeAction(); - compAction.setTitle("title"); - compAction.setDescription("description"); - compAction.addAction(action1); - compAction.addAction(action2); - compAction.setExecuteAsynchronously(true); - - // Execute the composite action - this.actionService.executeAction(compAction, this.nodeRef); - - TestTransaction.flagForCommit(); - TestTransaction.end(); - - final NodeService finalNodeService = this.nodeService; - final NodeRef finalNodeRef = this.nodeRef; - - postAsyncActionTest( - this.transactionService, - 1000, - 10, - new AsyncTest() - { - public String executeTest() - { - boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE) && - finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE); - return result ? null : "Expected aspects Versionable & Lockable"; - }; - }); - } - - public void xtestAsyncLoadTest() - { - // TODO this is very weak .. how do we improve this ??? - - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - action.setExecuteAsynchronously(true); - - for (int i = 0; i < 1000; i++) - { - this.actionService.executeAction(action, this.nodeRef); - } - - TestTransaction.flagForCommit(); - TestTransaction.end(); - - // TODO how do we assess whether the large number of actions stacked cause a problem ?? - } - - /** - * - * @param sleepTime - * @param maxTries - * @param test - */ - public static void postAsyncActionTest( - TransactionService transactionService, - final long sleepTime, - final int maxTries, - final AsyncTest test) - { - try - { - int tries = 0; - String errorMsg = null; - while (errorMsg == null && tries < maxTries) - { - try - { - // Increment the tries counter - tries++; - - // Sleep for a bit - Thread.sleep(sleepTime); - - errorMsg = (transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback() - { - public String execute() - { - // See if the action has been performed - String done = test.executeTest(); - return done; - } - })); - } - catch (InterruptedException e) - { - // Do nothing - e.printStackTrace(); - } - } - - if (errorMsg != null) - { - throw new RuntimeException("Asynchronous action was not executed. " + errorMsg); - } - } - catch (Throwable exception) - { - exception.printStackTrace(); - fail("An exception was encountered whilst checking the async action was executed: " + exception.getMessage()); - } - } - - /** - * Async test interface - */ - public interface AsyncTest - { - /** - * - * @return null if the test succeeded, else an error message for use in JUnit report. - */ - String executeTest(); - } - - /** =================================================================================== - * Test failure behaviour - */ - - /** - * Test sync failure behaviour - */ - @Test - public void testSyncFailureBehaviour() - { - // Create an action that is going to fail - Action action = createFailingMoveAction(true); - - try - { - this.actionService.executeAction(action, this.nodeRef); - - // Fail if we get there since the exception should have been raised - fail("An exception should have been raised."); - } - catch (RuntimeException exception) - { - // Good! The exception was raised correctly - } - - // Test what happens when a element of a composite action fails (should raise and bubble up to parent bahviour) - // Create the composite action - Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE); - Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, QName.createQName("{test}badDogAspect")); - CompositeAction compAction = this.actionService.createCompositeAction(); - compAction.setTitle("title"); - compAction.setDescription("description"); - compAction.addAction(action1); - compAction.addAction(action2); - - try - { - // Execute the composite action - this.actionService.executeAction(compAction, this.nodeRef); - - fail("An exception should have been raised here !!"); - } - catch (RuntimeException runtimeException) - { - // Good! The exception was raised - } - } - - /** - * Test the compensating action - */ - @Test - public void testCompensatingAction() - { - // Create actions that are going to fail - final Action fatalAction = createFailingMoveAction(true); - final Action nonfatalAction = createFailingMoveAction(false); - fatalAction.setTitle("fatal title"); - nonfatalAction.setTitle("non-fatal title"); - - // Create the compensating actions - Action compensatingAction = actionService.createAction(AddFeaturesActionExecuter.NAME); - compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); - compensatingAction.setTitle("title"); - fatalAction.setCompensatingAction(compensatingAction); - - Action compensatingAction2 = actionService.createAction(AddFeaturesActionExecuter.NAME); - compensatingAction2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPORARY); - compensatingAction2.setTitle("title"); - nonfatalAction.setCompensatingAction(compensatingAction2); - - - // Set the actions to execute asynchronously - fatalAction.setExecuteAsynchronously(true); - nonfatalAction.setExecuteAsynchronously(true); - - this.actionService.executeAction(fatalAction, this.nodeRef); - this.actionService.executeAction(nonfatalAction, this.nodeRef); - - TestTransaction.flagForCommit(); - TestTransaction.end(); - - postAsyncActionTest( - this.transactionService, - 1000, - 10, - new AsyncTest() - { - public String executeTest() - { - boolean fatalCompensatingActionRun = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE); - - boolean nonFatalCompensatingActionRun = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY); - - StringBuilder result = new StringBuilder(); - if (!fatalCompensatingActionRun) - { - result.append("Expected aspect Classifiable."); - } - if (nonFatalCompensatingActionRun) - { - result.append(" Did not expect aspect Temporary"); - } - - return ( !fatalCompensatingActionRun || nonFatalCompensatingActionRun ? result.toString() : null); - }; - }); - - // Modify the compensating action so that it will also fail - compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, QName.createQName("{test}badAspect")); - - this.transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback() - { - public Object execute() - { - try - { - ActionServiceImplTest.this.actionService.executeAction(fatalAction, ActionServiceImplTest.this.nodeRef); - } - catch (RuntimeException exception) - { - // The exception should have been ignored and execution continued - exception.printStackTrace(); - fail("An exception should not have been raised here."); - } - return null; - } - - }); - - } - - /** - * http://issues.alfresco.com/jira/browse/ALF-5027 - */ - @Test - public void testALF5027() throws Exception - { - String userName = "bob" + GUID.generate(); - createUser(userName); - PermissionService permissionService = (PermissionService)applicationContext.getBean("PermissionService"); - permissionService.setPermission(rootNodeRef, userName, PermissionService.COORDINATOR, true); - - AuthenticationUtil.setRunAsUser(userName); - - NodeRef myNodeRef = nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}myTestNode" + GUID.generate()), - ContentModel.TYPE_CONTENT).getChildRef(); - - CheckOutCheckInService coci = (CheckOutCheckInService)applicationContext.getBean("CheckoutCheckinService"); - NodeRef workingcopy = coci.checkout(myNodeRef); - assertNotNull(workingcopy); - - assertFalse(nodeService.hasAspect(myNodeRef, ContentModel.ASPECT_DUBLINCORE)); - - Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_DUBLINCORE); - actionService.executeAction(action1, myNodeRef); - - // The action should have been ignored since the node is locked - assertFalse(nodeService.hasAspect(myNodeRef, ContentModel.ASPECT_DUBLINCORE)); - - coci.checkin(workingcopy, null); - actionService.executeAction(action1, myNodeRef); - - assertTrue(nodeService.hasAspect(myNodeRef, ContentModel.ASPECT_DUBLINCORE)); - } - - /** - * Tests that we can read, save, load etc the various - * execution related details such as started at, - * ended at, status and exception - */ - @Test - public void testExecutionTrackingDetails() { - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); - String actionId = action.getId(); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - - // Save and load, details shouldn't have changed - this.actionService.saveAction(this.nodeRef, action); - action = (Action)this.actionService.getAction(this.nodeRef, actionId); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - - // Set some details, ensure they survive a save/load - ((ActionImpl)action).setExecutionStatus(ActionStatus.Running); - ((ActionImpl)action).setExecutionStartDate(new Date(12345)); - - this.actionService.saveAction(this.nodeRef, action); - action = (Action)this.actionService.getAction(this.nodeRef, actionId); - - assertEquals(ActionStatus.Running, action.getExecutionStatus()); - assertEquals(12345, action.getExecutionStartDate().getTime()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - - - // Set the rest, and change some, ensure they survive a save/load - ((ActionImpl)action).setExecutionStatus(ActionStatus.Failed); - ((ActionImpl)action).setExecutionStartDate(new Date(123450)); - ((ActionImpl)action).setExecutionEndDate(new Date(123455)); - ((ActionImpl)action).setExecutionFailureMessage("Testing"); - - this.actionService.saveAction(this.nodeRef, action); - action = (Action)this.actionService.getAction(this.nodeRef, actionId); - - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - assertEquals(123450, action.getExecutionStartDate().getTime()); - assertEquals(123455, action.getExecutionEndDate().getTime()); - assertEquals("Testing", action.getExecutionFailureMessage()); - - - // Unset a few, ensure they survive a save/load - ((ActionImpl)action).setExecutionStatus(null); - ((ActionImpl)action).setExecutionStartDate(new Date(123450)); - ((ActionImpl)action).setExecutionFailureMessage(null); - - this.actionService.saveAction(this.nodeRef, action); - action = (Action)this.actionService.getAction(this.nodeRef, actionId); - - assertEquals(ActionStatus.New, action.getExecutionStatus()); // Default - assertEquals(123450, action.getExecutionStartDate().getTime()); - assertEquals(123455, action.getExecutionEndDate().getTime()); - assertEquals(null, action.getExecutionFailureMessage()); - } - - /** - * This method returns an {@link Action} which will fail when executed. - * - * @param isFatal if false this will give an action which throws - * a {@link ActionServiceTransientException non-fatal action exception}. - */ - protected Action createFailingMoveAction(boolean isFatal) { - Action failingAction; - if (isFatal) - { - failingAction = this.actionService.createAction(MoveActionExecuter.NAME); - - // Create a bad node ref - NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); - failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); - } - else - { - failingAction = this.actionService.createAction(TransientFailActionExecuter.NAME); - } - - return failingAction; - } - - protected Action createFailingSleepAction(String id, boolean isFatal) throws Exception { - return createFailingSleepAction(id, isFatal, this.actionService); - } - protected static Action createFailingSleepAction(String id, boolean isFatal, ActionService actionService) throws Exception { - Action failingAction = createWorkingSleepAction(id, actionService); - failingAction.setParameterValue(SleepActionExecuter.GO_BANG, Boolean.TRUE); - failingAction.setParameterValue(SleepActionExecuter.FAIL_FATALLY, Boolean.valueOf(isFatal)); - return failingAction; - } - - protected Action createWorkingSleepAction() throws Exception - { - return createWorkingSleepAction(null); - } - protected Action createWorkingSleepAction(String id) throws Exception - { - return createWorkingSleepAction(id, this.actionService); - } - protected static Action createWorkingSleepAction(String id, ActionService actionService) throws Exception - { - Action workingAction = new CancellableSleepAction(actionService.createAction(SleepActionExecuter.NAME)); - workingAction.setTrackStatus(Boolean.TRUE); - if(id != null) - { - Field idF = ParameterizedItemImpl.class.getDeclaredField("id"); - idF.setAccessible(true); - idF.set(workingAction, id); - } - return workingAction; - } - - /** - * This class is only used during JUnit testing. - * - * @author Neil Mc Erlean - */ - public static class SleepActionFilter extends AbstractAsynchronousActionFilter - { - public int compare(OngoingAsyncAction sae1, OngoingAsyncAction sae2) - { - // Sleep actions are always equivalent. - return 0; - } - } - - /** - * This class is only intended for use in JUnit tests. - * - * @author Neil McErlean. - */ - public static class SleepActionExecuter extends ActionExecuterAbstractBase - { - public static final String NAME = "sleep-action"; - public static final String GO_BANG = "GoBang"; - public static final String FAIL_FATALLY = "failFatally"; - private int sleepMs; - - private Thread executingThread; - - private int timesExecuted = 0; - private void incrementTimesExecutedCount() {timesExecuted++;} - public int getTimesExecuted() {return timesExecuted;} - public void resetTimesExecuted() {timesExecuted=0;} - - private ActionTrackingService actionTrackingService; - - /** - * Loads this executor into the ApplicationContext, if it isn't already there - */ - public static void registerIfNeeded(ApplicationContext ctx) - { - if (!ctx.containsBean(SleepActionExecuter.NAME)) - { - // Create, and do dependencies - SleepActionExecuter executor = new SleepActionExecuter(); - executor.setTrackStatus(true); - executor.actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService"); - // Register - ((ConfigurableApplicationContext) ctx).getBeanFactory().registerSingleton(SleepActionExecuter.NAME, executor); - } - } - - public Thread getExecutingThread() - { - return executingThread; - } - - public int getSleepMs() - { - return sleepMs; - } - - public void setSleepMs(int sleepMs) - { - this.sleepMs = sleepMs; - } - - /** - * Add parameter definitions - */ - @Override - protected void addParameterDefinitions(List paramList) - { - // Intentionally empty - } - - @Override - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { - executingThread = Thread.currentThread(); - //System.err.println("Sleeping for " + sleepMs + " for " + action); - - try - { - Thread.sleep(sleepMs); - } - catch (InterruptedException ignored) - { - // Intentionally empty - } - finally - { - incrementTimesExecutedCount(); - } - - Boolean fail = (Boolean)action.getParameterValue(GO_BANG); - Boolean failFatally = (Boolean)action.getParameterValue(FAIL_FATALLY); - if (fail != null && fail) - { - // this should fail - if (failFatally != null && failFatally) - { - // this should fail fatally - throw new RuntimeException("Bang!"); - } - else - { - // this should fail non-fatally - throw new ActionServiceTransientException("Pop!"); - } - } - - if(action instanceof CancellableSleepAction) - { - CancellableSleepAction ca = (CancellableSleepAction)action; - boolean cancelled = actionTrackingService.isCancellationRequested(ca); - if(cancelled) - throw new ActionCancelledException(ca); - } - } - } - - /** - * This class is only intended for use in JUnit tests. - * - * @author Neil McErlean. - * @since 4.0.1 - */ - public static class TransientFailActionExecuter extends ActionExecuterAbstractBase - { - public static final String NAME = "transient-fail-action"; - - private ActionTrackingService actionTrackingService; - - /** - * Loads this executor into the ApplicationContext, if it isn't already there - */ - public static void registerIfNeeded(ConfigurableApplicationContext ctx) - { - if (!ctx.containsBean(TransientFailActionExecuter.NAME)) - { - // Create, and do dependencies - TransientFailActionExecuter executor = new TransientFailActionExecuter(); - executor.setTrackStatus(true); - executor.actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService"); - // Register - ctx.getBeanFactory().registerSingleton(TransientFailActionExecuter.NAME, executor); - } - } - - @Override protected void addParameterDefinitions(List paramList) - { - // Intentionally empty - } - - @Override protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { - // this action always fails with a non-fatal exception. - throw new ActionServiceTransientException("action failed intentionally in " + TransientFailActionExecuter.class.getSimpleName()); - } - } - - - - protected static class CancellableSleepAction extends ActionImpl implements CancellableAction - { - public CancellableSleepAction(Action action) - { - super(action); - this.setTrackStatus(Boolean.TRUE); - } - } - - public static void assertBefore(Date before, Date after) - { - assertTrue( - before.toString() + " not before " + after.toString(), - before.getTime() <= after.getTime() - ); - } -} \ No newline at end of file +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.action; + +import static java.time.Duration.ofMillis; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.annotation.Transactional; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.evaluator.InCategoryEvaluator; +import org.alfresco.repo.action.evaluator.NoConditionEvaluator; +import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; +import org.alfresco.repo.action.executer.CheckInActionExecuter; +import org.alfresco.repo.action.executer.CheckOutActionExecuter; +import org.alfresco.repo.action.executer.CompositeActionExecuter; +import org.alfresco.repo.action.executer.MoveActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionConditionDefinition; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionServiceTransientException; +import org.alfresco.service.cmr.action.ActionStatus; +import org.alfresco.service.cmr.action.ActionTrackingService; +import org.alfresco.service.cmr.action.CancellableAction; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.action.CompositeActionCondition; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.test_category.BaseSpringTestsCategory; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.GUID; + +/** + * Action service test + * + * @author Roy Wetherall + */ +@Category(BaseSpringTestsCategory.class) +@Transactional +@ContextConfiguration({"classpath:alfresco/application-context.xml", + "classpath:org/alfresco/repo/action/test-action-services-context.xml"}) +public class ActionServiceImplTest extends BaseAlfrescoSpringTest +{ + private static final String BAD_NAME = "badName"; + + private NodeRef nodeRef; + private NodeRef folder; + private RetryingTransactionHelper transactionHelper; + + @Before + public void before() throws Exception + { + super.before(); + + this.transactionHelper = (RetryingTransactionHelper) this.applicationContext.getBean("retryingTransactionHelper"); + + // Create the node used for tests + this.nodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}testnode"), + ContentModel.TYPE_CONTENT).getChildRef(); + this.nodeService.setProperty( + this.nodeRef, + ContentModel.PROP_CONTENT, + new ContentData(null, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, null)); + this.folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}testFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // Register the test executor, if needed + SleepActionExecuter.registerIfNeeded(applicationContext); + } + + /** + * Test getActionDefinition + */ + @Test + public void testGetActionDefinition() + { + ActionDefinition action = actionService.getActionDefinition(AddFeaturesActionExecuter.NAME); + assertNotNull(action); + assertEquals(AddFeaturesActionExecuter.NAME, action.getName()); + + ActionConditionDefinition nullCondition = this.actionService.getActionConditionDefinition(BAD_NAME); + assertNull(nullCondition); + } + + /** + * Test getActionDefintions + */ + @Test + public void testGetActionDefinitions() + { + List defintions = this.actionService.getActionDefinitions(); + assertNotNull(defintions); + assertFalse(defintions.isEmpty()); + int totalCount = defintions.size(); + + for (ActionDefinition definition : defintions) + { + System.out.println(definition.getTitle()); + } + + // Get the action defintions for a folder type (there should be less than the total available) + List definitions = this.actionService.getActionDefinitions(this.folder); + assertNotNull(definitions); + assertTrue(totalCount > definitions.size()); + } + + /** + * Test getActionConditionDefinition + */ + @Test + public void testGetActionConditionDefinition() + { + ActionConditionDefinition condition = this.actionService.getActionConditionDefinition(NoConditionEvaluator.NAME); + assertNotNull(condition); + assertEquals(NoConditionEvaluator.NAME, condition.getName()); + + ActionConditionDefinition nullCondition = this.actionService.getActionConditionDefinition(BAD_NAME); + assertNull(nullCondition); + } + + /** + * Test getActionConditionDefinitions + */ + @Test + public void testGetActionConditionDefinitions() + { + List defintions = this.actionService.getActionConditionDefinitions(); + assertNotNull(defintions); + assertFalse(defintions.isEmpty()); + + for (ActionConditionDefinition definition : defintions) + { + System.out.println(definition.getTitle()); + } + } + + /** + * Test create action condition + */ + @Test + public void testCreateActionCondition() + { + ActionCondition condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + assertNotNull(condition); + assertEquals(NoConditionEvaluator.NAME, condition.getActionConditionDefinitionName()); + + Map params = new HashMap<>(0); + condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME, params); + assertNotNull(condition); + assertEquals(NoConditionEvaluator.NAME, condition.getActionConditionDefinitionName()); + + } + + /** + * Test createCompositeAction + */ + @Test + public void testCreateCompositeActionCondition() + { + CompositeActionCondition action = this.actionService.createCompositeActionCondition(); + assertNotNull(action); + assertEquals(CompositeActionCondition.COMPOSITE_CONDITION, action.getActionConditionDefinitionName()); + } + + /** + * Test createAction + */ + @Test + public void testCreateAction() + { + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + assertNotNull(action); + assertEquals(AddFeaturesActionExecuter.NAME, action.getActionDefinitionName()); + + Map params = new HashMap<>(0); + action = this.actionService.createAction(AddFeaturesActionExecuter.NAME, params); + assertNotNull(action); + assertEquals(AddFeaturesActionExecuter.NAME, action.getActionDefinitionName()); + } + + /** + * Test createCompositeAction + */ + @Test + public void testCreateCompositeAction() + { + CompositeAction action = this.actionService.createCompositeAction(); + assertNotNull(action); + assertEquals(CompositeActionExecuter.NAME, action.getActionDefinitionName()); + } + + /** + * Evaluate action + */ + @Test + public void testEvaluateAction() + { + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + assertTrue(this.actionService.evaluateAction(action, this.nodeRef)); + + ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); + action.addActionCondition(condition); + + assertFalse(this.actionService.evaluateAction(action, this.nodeRef)); + this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); + assertTrue(this.actionService.evaluateAction(action, this.nodeRef)); + + ActionCondition condition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + condition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "my"); + action.addActionCondition(condition2); + assertTrue(this.actionService.evaluateAction(action, this.nodeRef)); + + this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "document.doc"); + assertFalse(this.actionService.evaluateAction(action, this.nodeRef)); + } + + /** + * Test evaluate action condition + */ + @Test + public void testEvaluateActionCondition() + { + ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); + + assertFalse(this.actionService.evaluateActionCondition(condition, this.nodeRef)); + this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); + assertTrue(this.actionService.evaluateActionCondition(condition, this.nodeRef)); + + // Check that inverting the condition has the correct effect + condition.setInvertCondition(true); + assertFalse(this.actionService.evaluateActionCondition(condition, this.nodeRef)); + } + + /** + * Test evaluate action condition + */ + @Test + public void testEvaluateCompositeActionConditionWith1SubCondition() + { + CompositeActionCondition compositeCondition = this.actionService.createCompositeActionCondition(); + + ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); + + compositeCondition.addActionCondition(condition); + + this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); + assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); + + // Check that inverting the condition has the correct effect + compositeCondition.setInvertCondition(true); + assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); + } + + /** + * Test evaluate action condition + */ + @Test + public void testEvaluateCompositeActionConditionWith2SubConditions() + { + CompositeActionCondition compositeCondition = this.actionService.createCompositeActionCondition(); + + ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); + + compositeCondition.addActionCondition(condition); + + this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); + assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); + + ActionCondition conditionTwo = this.actionService.createActionCondition("compare-text-property"); + conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Doc"); + conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, QName.createQName(null, "name")); + conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); + + compositeCondition.addActionCondition(conditionTwo); + assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); + + compositeCondition.removeAllActionConditions(); + assertFalse(compositeCondition.hasActionConditions()); + + compositeCondition.addActionCondition(condition); + conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "NotFound"); + assertFalse(this.actionService.evaluateActionCondition(conditionTwo, this.nodeRef)); + + compositeCondition.addActionCondition(conditionTwo); + compositeCondition.setORCondition(true); + assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); + + compositeCondition.setORCondition(false); + assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); + + // Check that inverting the condition has the correct effect + compositeCondition.setInvertCondition(true); + assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef)); + } + + /** + * Test execute action + */ + @Test + public void testExecuteAction() + { + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + this.actionService.executeAction(action, this.nodeRef); + assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); + action.addActionCondition(condition); + + this.actionService.executeAction(action, this.nodeRef); + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.actionService.executeAction(action, this.nodeRef, true); + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.actionService.executeAction(action, this.nodeRef, false); + assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc"); + this.actionService.executeAction(action, this.nodeRef); + assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create the composite action + Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE); + Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + CompositeAction compAction = this.actionService.createCompositeAction(); + compAction.setTitle("title"); + compAction.setDescription("description"); + compAction.addAction(action1); + compAction.addAction(action2); + + // Execute the composite action + this.actionService.executeAction(compAction, this.nodeRef); + + assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE)); + assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + @Test + public void testGetAndGetAllWithNoActions() + { + assertNull(this.actionService.getAction(this.nodeRef, AddFeaturesActionExecuter.NAME)); + List actions = this.actionService.getActions(this.nodeRef); + assertNotNull(actions); + assertEquals(0, actions.size()); + } + + @Test + public void testExecuteActionWithNoParameterDef() + { + Action action = this.actionService.createAction("empty-action"); + this.actionService.executeAction(action, this.nodeRef); + assertTrue("If we got here then the test is successful", true); + } + + /** + * Test saving an action with no conditions. Includes testing storage and retrieval of compensating actions. + */ + @Test + public void testSaveActionNoCondition() + { + // Create the action + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + String actionId = action.getId(); + + // Set the parameters of the action + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + // Set the title and description of the action + action.setTitle("title"); + action.setDescription("description"); + action.setExecuteAsynchronously(true); + + // Save the action + this.actionService.saveAction(this.nodeRef, action); + + // Get the action + Action savedAction = this.actionService.getAction(this.nodeRef, actionId); + + // Check the action + assertEquals(action.getId(), savedAction.getId()); + assertEquals(action.getActionDefinitionName(), savedAction.getActionDefinitionName()); + + // Check the properties + assertEquals("title", savedAction.getTitle()); + assertEquals("description", savedAction.getDescription()); + assertTrue(savedAction.getExecuteAsychronously()); + + // Check that the compensating action has not been set + assertNull(savedAction.getCompensatingAction()); + + // Check the properties + assertEquals(1, savedAction.getParameterValues().size()); + assertEquals(ContentModel.ASPECT_VERSIONABLE, savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); + + // Check the conditions + assertNotNull(savedAction.getActionConditions()); + assertEquals(0, savedAction.getActionConditions().size()); + + // Edit the properties of the action + Map properties = new HashMap(1); + properties.put(ContentModel.PROP_NAME, "testName"); + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_AUDITABLE); + + // Set the compensating action + Action compensatingAction = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + action.setCompensatingAction(compensatingAction); + + this.actionService.saveAction(this.nodeRef, action); + Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId); + + // Check the updated properties + assertEquals(1, savedAction2.getParameterValues().size()); + assertEquals(ContentModel.ASPECT_AUDITABLE, savedAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); + + // Check the compensating action + Action savedCompensatingAction = savedAction2.getCompensatingAction(); + assertNotNull(savedCompensatingAction); + assertEquals(compensatingAction, savedCompensatingAction); + assertEquals(AddFeaturesActionExecuter.NAME, savedCompensatingAction.getActionDefinitionName()); + assertEquals(ContentModel.ASPECT_VERSIONABLE, savedCompensatingAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); + + // Change the details of the compensating action (edit and remove) + compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); + this.actionService.saveAction(this.nodeRef, action); + Action savedAction3 = this.actionService.getAction(this.nodeRef, actionId); + Action savedCompensatingAction2 = savedAction3.getCompensatingAction(); + assertNotNull(savedCompensatingAction2); + assertEquals(compensatingAction, savedCompensatingAction2); + assertEquals(AddFeaturesActionExecuter.NAME, savedCompensatingAction2.getActionDefinitionName()); + assertEquals(ContentModel.ASPECT_CLASSIFIABLE, savedCompensatingAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); + action.setCompensatingAction(null); + this.actionService.saveAction(this.nodeRef, action); + Action savedAction4 = this.actionService.getAction(this.nodeRef, actionId); + assertNull(savedAction4.getCompensatingAction()); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + + @Test + public void testOwningNodeRef() + { + // Create the action + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + String actionId = action.getId(); + + // Set the parameters of the action + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + // Set the title and description of the action + action.setTitle("title"); + action.setDescription("description"); + action.setExecuteAsynchronously(true); + + // Check the owning node ref + // assertNull(action.getOwningNodeRef()); + + // Save the action + this.actionService.saveAction(this.nodeRef, action); + + // Get the action + this.actionService.getAction(this.nodeRef, actionId); + } + + /** + * Test saving an action with conditions + */ + @Test + public void testSaveActionWithConditions() + { + // Create the action + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + String actionId = action.getId(); + + // Set the parameters of the action + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + // Set the conditions of the action + ActionCondition actionCondition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + actionCondition.setInvertCondition(true); + ActionCondition actionCondition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); + action.addActionCondition(actionCondition); + action.addActionCondition(actionCondition2); + + // Save the action + this.actionService.saveAction(this.nodeRef, action); + + // Get the action + Action savedAction = this.actionService.getAction(this.nodeRef, actionId); + + // Check the action + assertEquals(action.getId(), savedAction.getId()); + assertEquals(action.getActionDefinitionName(), savedAction.getActionDefinitionName()); + + // Check the properties + assertEquals(action.getParameterValues().size(), savedAction.getParameterValues().size()); + assertEquals(ContentModel.ASPECT_VERSIONABLE, savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); + + // Check the conditions + assertNotNull(savedAction.getActionConditions()); + assertEquals(2, savedAction.getActionConditions().size()); + for (ActionCondition savedCondition : savedAction.getActionConditions()) + { + if (savedCondition.getActionConditionDefinitionName().equals(NoConditionEvaluator.NAME) == true) + { + assertEquals(0, savedCondition.getParameterValues().size()); + assertTrue(savedCondition.getInvertCondition()); + } + else if (savedCondition.getActionConditionDefinitionName().equals(ComparePropertyValueEvaluator.NAME) == true) + { + assertEquals(1, savedCondition.getParameterValues().size()); + assertEquals("*.doc", savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE)); + assertFalse(savedCondition.getInvertCondition()); + } + else + { + fail("There is a condition here that we are not expecting."); + } + } + + // Modify the conditions of the action + ActionCondition actionCondition3 = this.actionService.createActionCondition(InCategoryEvaluator.NAME); + actionCondition3.setParameterValue(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, ContentModel.ASPECT_OWNABLE); + action.addActionCondition(actionCondition3); + action.removeActionCondition(actionCondition); + actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.exe"); + actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS); + + this.actionService.saveAction(this.nodeRef, action); + Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId); + + // Check that the conditions have been updated correctly + assertNotNull(savedAction2.getActionConditions()); + assertEquals(2, savedAction2.getActionConditions().size()); + for (ActionCondition savedCondition : savedAction2.getActionConditions()) + { + if (savedCondition.getActionConditionDefinitionName().equals(InCategoryEvaluator.NAME) == true) + { + assertEquals(1, savedCondition.getParameterValues().size()); + assertEquals(ContentModel.ASPECT_OWNABLE, savedCondition.getParameterValue(InCategoryEvaluator.PARAM_CATEGORY_ASPECT)); + } + else if (savedCondition.getActionConditionDefinitionName().equals(ComparePropertyValueEvaluator.NAME) == true) + { + assertEquals(2, savedCondition.getParameterValues().size()); + assertEquals("*.exe", savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE)); + assertEquals(ComparePropertyValueOperation.EQUALS, savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION)); + } + else + { + fail("There is a condition here that we are not expecting."); + } + } + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + + /** + * Test saving a composite action + */ + @Test + public void testSaveCompositeAction() + { + Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + Action action2 = this.actionService.createAction(CheckInActionExecuter.NAME); + + CompositeAction compositeAction = this.actionService.createCompositeAction(); + String actionId = compositeAction.getId(); + compositeAction.addAction(action1); + compositeAction.addAction(action2); + + this.actionService.saveAction(this.nodeRef, compositeAction); + assertEquals(1, this.actionService.getActions(this.nodeRef).size()); + CompositeAction savedCompositeAction = (CompositeAction) this.actionService.getAction(this.nodeRef, actionId); + + // Check the saved composite action + assertEquals(2, savedCompositeAction.getActions().size()); + for (Action action : savedCompositeAction.getActions()) + { + if (action.getActionDefinitionName().equals(AddFeaturesActionExecuter.NAME) == true) + { + assertEquals(action, action1); + } + else if (action.getActionDefinitionName().equals(CheckInActionExecuter.NAME) == true) + { + assertEquals(action, action2); + } + else + { + fail("We have an action here we are not expecting."); + } + } + + // Change the actions and re-save + compositeAction.removeAction(action1); + Action action3 = this.actionService.createAction(CheckOutActionExecuter.NAME); + compositeAction.addAction(action3); + action2.setParameterValue(CheckInActionExecuter.PARAM_DESCRIPTION, "description"); + + this.actionService.saveAction(this.nodeRef, compositeAction); + assertEquals(1, this.actionService.getActions(this.nodeRef).size()); + CompositeAction savedCompositeAction2 = (CompositeAction) this.actionService.getAction(this.nodeRef, actionId); + + assertEquals(2, savedCompositeAction2.getActions().size()); + for (Action action : savedCompositeAction2.getActions()) + { + if (action.getActionDefinitionName().equals(CheckOutActionExecuter.NAME) == true) + { + assertEquals(action, action3); + } + else if (action.getActionDefinitionName().equals(CheckInActionExecuter.NAME) == true) + { + assertEquals(action, action2); + assertEquals("description", action2.getParameterValue(CheckInActionExecuter.PARAM_DESCRIPTION)); + } + else + { + fail("We have an action here we are not expecting."); + } + } + } + + /** + * Test remove action + */ + @Test + public void testRemove() + { + assertEquals(0, this.actionService.getActions(this.nodeRef).size()); + + Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + this.actionService.saveAction(this.nodeRef, action1); + Action action2 = this.actionService.createAction(CheckInActionExecuter.NAME); + this.actionService.saveAction(this.nodeRef, action2); + assertEquals(2, this.actionService.getActions(this.nodeRef).size()); + + this.actionService.removeAction(this.nodeRef, action1); + assertEquals(1, this.actionService.getActions(this.nodeRef).size()); + + this.actionService.removeAllActions(this.nodeRef); + assertEquals(0, this.actionService.getActions(this.nodeRef).size()); + } + + @Test + public void testConditionOrder() + { + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + String actionId = action.getId(); + + ActionCondition condition1 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + ActionCondition condition2 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + + action.addActionCondition(condition1); + action.addActionCondition(condition2); + + this.actionService.saveAction(this.nodeRef, action); + Action savedAction = this.actionService.getAction(this.nodeRef, actionId); + + // Check that the conditions have been retrieved in the correct order + assertNotNull(savedAction); + assertEquals(condition1, savedAction.getActionCondition(0)); + assertEquals(condition2, savedAction.getActionCondition(1)); + + ActionCondition condition3 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + ActionCondition condition4 = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + + // Update the conditions on the action + savedAction.removeActionCondition(condition1); + savedAction.addActionCondition(condition3); + savedAction.addActionCondition(condition4); + + this.actionService.saveAction(this.nodeRef, savedAction); + Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId); + + // Check that the conditions are still in the correct order + assertNotNull(savedAction2); + assertEquals(condition2, savedAction2.getActionCondition(0)); + assertEquals(condition3, savedAction2.getActionCondition(1)); + assertEquals(condition4, savedAction2.getActionCondition(2)); + } + + @Test + public void testActionOrder() + { + CompositeAction action = this.actionService.createCompositeAction(); + String actionId = action.getId(); + + Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + + action.addAction(action1); + action.addAction(action2); + + this.actionService.saveAction(this.nodeRef, action); + CompositeAction savedAction = (CompositeAction) this.actionService.getAction(this.nodeRef, actionId); + + // Check that the conditions have been retrieved in the correct order + assertNotNull(savedAction); + assertEquals(action1, savedAction.getAction(0)); + assertEquals(action2, savedAction.getAction(1)); + + Action action3 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + Action action4 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + + // Update the conditions on the action + savedAction.removeAction(action1); + savedAction.addAction(action3); + savedAction.addAction(action4); + + this.actionService.saveAction(this.nodeRef, savedAction); + CompositeAction savedAction2 = (CompositeAction) this.actionService.getAction(this.nodeRef, actionId); + + // Check that the conditions are still in the correct order + assertNotNull(savedAction2); + assertEquals(action2, savedAction2.getAction(0)); + assertEquals(action3, savedAction2.getAction(1)); + assertEquals(action4, savedAction2.getAction(2)); + } + + /** + * =================================================================================== Test asynchronous actions + */ + + /** + * Test asynchronous execute action + */ + @Test + public void testAsyncExecuteAction() + { + assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE)); + + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); + action.setExecuteAsynchronously(true); + + this.actionService.executeAction(action, this.nodeRef); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + + final NodeRef finalNodeRef = this.nodeRef; + + await().atMost(MAX_ASYNC_TIMEOUT).until(() -> nodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE)); + + assertThat(nodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE)) + .as("Expected aspect classifiable") + .isTrue(); + } + + /** + * Test async composite action execution + */ + @Test + public void testAsyncCompositeActionExecute() + { + // Create the composite action + Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE); + Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + CompositeAction compAction = this.actionService.createCompositeAction(); + compAction.setTitle("title"); + compAction.setDescription("description"); + compAction.addAction(action1); + compAction.addAction(action2); + compAction.setExecuteAsynchronously(true); + + // Execute the composite action + this.actionService.executeAction(compAction, this.nodeRef); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + + final NodeRef finalNodeRef = this.nodeRef; + + await().atMost(MAX_ASYNC_TIMEOUT).until(() -> nodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE)); + await().atMost(MAX_ASYNC_TIMEOUT).until(() -> nodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE)); + + assertThat(nodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE)) + .as("Expected aspect versionable") + .isTrue(); + assertThat(nodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE)) + .as("Expected aspect lockable") + .isTrue(); + } + + /** + * Async test interface + */ + public interface AsyncTest + { + /** + * @return null if the test succeeded, else an error message for use in JUnit report. + */ + String executeTest(); + } + + /** + * =================================================================================== Test failure behaviour + */ + + /** + * Test sync failure behaviour + */ + @Test + public void testSyncFailureBehaviour() + { + // Create an action that is going to fail + Action action = createFailingMoveAction(true); + + try + { + this.actionService.executeAction(action, this.nodeRef); + + // Fail if we get there since the exception should have been raised + fail("An exception should have been raised."); + } + catch (RuntimeException exception) + { + // Good! The exception was raised correctly + } + + // Test what happens when a element of a composite action fails (should raise and bubble up to parent bahviour) + // Create the composite action + Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE); + Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, QName.createQName("{test}badDogAspect")); + CompositeAction compAction = this.actionService.createCompositeAction(); + compAction.setTitle("title"); + compAction.setDescription("description"); + compAction.addAction(action1); + compAction.addAction(action2); + + try + { + // Execute the composite action + this.actionService.executeAction(compAction, this.nodeRef); + + fail("An exception should have been raised here !!"); + } + catch (RuntimeException runtimeException) + { + // Good! The exception was raised + } + } + + /** + * Test the compensating action + */ + @Test + public void testCompensatingAction() + { + // Create actions that are going to fail + final Action fatalAction = createFailingMoveAction(true); + final Action nonfatalAction = createFailingMoveAction(false); + fatalAction.setTitle("fatal title"); + nonfatalAction.setTitle("non-fatal title"); + + // Create the compensating actions + Action compensatingAction = actionService.createAction(AddFeaturesActionExecuter.NAME); + compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); + compensatingAction.setTitle("title"); + fatalAction.setCompensatingAction(compensatingAction); + + Action compensatingAction2 = actionService.createAction(AddFeaturesActionExecuter.NAME); + compensatingAction2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPORARY); + compensatingAction2.setTitle("title"); + nonfatalAction.setCompensatingAction(compensatingAction2); + + // Set the actions to execute asynchronously + fatalAction.setExecuteAsynchronously(true); + nonfatalAction.setExecuteAsynchronously(true); + + this.actionService.executeAction(fatalAction, this.nodeRef); + this.actionService.executeAction(nonfatalAction, this.nodeRef); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + + await().atMost(MAX_ASYNC_TIMEOUT).until(() -> nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE)); + await().pollDelay(ofMillis(500)).atMost(MAX_ASYNC_TIMEOUT).until(() -> !nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY)); + + assertThat(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE)) + .as("Expected aspect Classifiable") + .isTrue(); + assertThat(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY)) + .as("Did not expect aspect Temporary") + .isFalse(); + + // Modify the compensating action so that it will also fail + compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, QName.createQName("{test}badAspect")); + + this.transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Object execute() + { + try + { + ActionServiceImplTest.this.actionService.executeAction(fatalAction, ActionServiceImplTest.this.nodeRef); + } + catch (RuntimeException exception) + { + // The exception should have been ignored and execution continued + exception.printStackTrace(); + fail("An exception should not have been raised here."); + } + return null; + } + + }); + + } + + /** + * http://issues.alfresco.com/jira/browse/ALF-5027 + */ + @Test + public void testALF5027() throws Exception + { + String userName = "bob" + GUID.generate(); + createUser(userName); + PermissionService permissionService = (PermissionService) applicationContext.getBean("PermissionService"); + permissionService.setPermission(rootNodeRef, userName, PermissionService.COORDINATOR, true); + + AuthenticationUtil.setRunAsUser(userName); + + NodeRef myNodeRef = nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}myTestNode" + GUID.generate()), + ContentModel.TYPE_CONTENT).getChildRef(); + + CheckOutCheckInService coci = (CheckOutCheckInService) applicationContext.getBean("CheckoutCheckinService"); + NodeRef workingcopy = coci.checkout(myNodeRef); + assertNotNull(workingcopy); + + assertFalse(nodeService.hasAspect(myNodeRef, ContentModel.ASPECT_DUBLINCORE)); + + Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_DUBLINCORE); + actionService.executeAction(action1, myNodeRef); + + // The action should have been ignored since the node is locked + assertFalse(nodeService.hasAspect(myNodeRef, ContentModel.ASPECT_DUBLINCORE)); + + coci.checkin(workingcopy, null); + actionService.executeAction(action1, myNodeRef); + + assertTrue(nodeService.hasAspect(myNodeRef, ContentModel.ASPECT_DUBLINCORE)); + } + + /** + * Tests that we can read, save, load etc the various execution related details such as started at, ended at, status and exception + */ + @Test + public void testExecutionTrackingDetails() + { + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + String actionId = action.getId(); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + // Save and load, details shouldn't have changed + this.actionService.saveAction(this.nodeRef, action); + action = (Action) this.actionService.getAction(this.nodeRef, actionId); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + // Set some details, ensure they survive a save/load + ((ActionImpl) action).setExecutionStatus(ActionStatus.Running); + ((ActionImpl) action).setExecutionStartDate(new Date(12345)); + + this.actionService.saveAction(this.nodeRef, action); + action = (Action) this.actionService.getAction(this.nodeRef, actionId); + + assertEquals(ActionStatus.Running, action.getExecutionStatus()); + assertEquals(12345, action.getExecutionStartDate().getTime()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + + // Set the rest, and change some, ensure they survive a save/load + ((ActionImpl) action).setExecutionStatus(ActionStatus.Failed); + ((ActionImpl) action).setExecutionStartDate(new Date(123450)); + ((ActionImpl) action).setExecutionEndDate(new Date(123455)); + ((ActionImpl) action).setExecutionFailureMessage("Testing"); + + this.actionService.saveAction(this.nodeRef, action); + action = (Action) this.actionService.getAction(this.nodeRef, actionId); + + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + assertEquals(123450, action.getExecutionStartDate().getTime()); + assertEquals(123455, action.getExecutionEndDate().getTime()); + assertEquals("Testing", action.getExecutionFailureMessage()); + + // Unset a few, ensure they survive a save/load + ((ActionImpl) action).setExecutionStatus(null); + ((ActionImpl) action).setExecutionStartDate(new Date(123450)); + ((ActionImpl) action).setExecutionFailureMessage(null); + + this.actionService.saveAction(this.nodeRef, action); + action = (Action) this.actionService.getAction(this.nodeRef, actionId); + + assertEquals(ActionStatus.New, action.getExecutionStatus()); // Default + assertEquals(123450, action.getExecutionStartDate().getTime()); + assertEquals(123455, action.getExecutionEndDate().getTime()); + assertEquals(null, action.getExecutionFailureMessage()); + } + + /** + * This method returns an {@link Action} which will fail when executed. + * + * @param isFatal + * if false this will give an action which throws a {@link ActionServiceTransientException non-fatal action exception}. + */ + protected Action createFailingMoveAction(boolean isFatal) + { + Action failingAction; + if (isFatal) + { + failingAction = this.actionService.createAction(MoveActionExecuter.NAME); + + // Create a bad node ref + NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); + failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); + } + else + { + failingAction = this.actionService.createAction(TransientFailActionExecuter.NAME); + } + + return failingAction; + } + + protected Action createFailingSleepAction(String id, boolean isFatal) throws Exception + { + return createFailingSleepAction(id, isFatal, this.actionService); + } + + protected static Action createFailingSleepAction(String id, boolean isFatal, ActionService actionService) throws Exception + { + Action failingAction = createWorkingSleepAction(id, actionService); + failingAction.setParameterValue(SleepActionExecuter.GO_BANG, Boolean.TRUE); + failingAction.setParameterValue(SleepActionExecuter.FAIL_FATALLY, Boolean.valueOf(isFatal)); + return failingAction; + } + + protected Action createWorkingSleepAction() throws Exception + { + return createWorkingSleepAction(null); + } + + protected Action createWorkingSleepAction(String id) throws Exception + { + return createWorkingSleepAction(id, this.actionService); + } + + protected static Action createWorkingSleepAction(String id, ActionService actionService) throws Exception + { + Action workingAction = new CancellableSleepAction(actionService.createAction(SleepActionExecuter.NAME)); + workingAction.setTrackStatus(Boolean.TRUE); + if (id != null) + { + Field idF = ParameterizedItemImpl.class.getDeclaredField("id"); + idF.setAccessible(true); + idF.set(workingAction, id); + } + return workingAction; + } + + /** + * This class is only used during JUnit testing. + * + * @author Neil Mc Erlean + */ + public static class SleepActionFilter extends AbstractAsynchronousActionFilter + { + public int compare(OngoingAsyncAction sae1, OngoingAsyncAction sae2) + { + // Sleep actions are always equivalent. + return 0; + } + } + + /** + * This class is only intended for use in JUnit tests. + * + * @author Neil McErlean. + */ + public static class SleepActionExecuter extends ActionExecuterAbstractBase + { + public static final String NAME = "sleep-action"; + public static final String GO_BANG = "GoBang"; + public static final String FAIL_FATALLY = "failFatally"; + private int sleepMs; + + private Thread executingThread; + + private int timesExecuted = 0; + + private void incrementTimesExecutedCount() + { + timesExecuted++; + } + + public int getTimesExecuted() + { + return timesExecuted; + } + + public void resetTimesExecuted() + { + timesExecuted = 0; + } + + private ActionTrackingService actionTrackingService; + + /** + * Loads this executor into the ApplicationContext, if it isn't already there + */ + public static void registerIfNeeded(ApplicationContext ctx) + { + if (!ctx.containsBean(SleepActionExecuter.NAME)) + { + // Create, and do dependencies + SleepActionExecuter executor = new SleepActionExecuter(); + executor.setTrackStatus(true); + executor.actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService"); + // Register + ((ConfigurableApplicationContext) ctx).getBeanFactory().registerSingleton(SleepActionExecuter.NAME, executor); + } + } + + public Thread getExecutingThread() + { + return executingThread; + } + + public int getSleepMs() + { + return sleepMs; + } + + public void setSleepMs(int sleepMs) + { + this.sleepMs = sleepMs; + } + + /** + * Add parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + // Intentionally empty + } + + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + executingThread = Thread.currentThread(); + // System.err.println("Sleeping for " + sleepMs + " for " + action); + + try + { + Thread.sleep(sleepMs); + } + catch (InterruptedException ignored) + { + // Intentionally empty + } + finally + { + incrementTimesExecutedCount(); + } + + Boolean fail = (Boolean) action.getParameterValue(GO_BANG); + Boolean failFatally = (Boolean) action.getParameterValue(FAIL_FATALLY); + if (fail != null && fail) + { + // this should fail + if (failFatally != null && failFatally) + { + // this should fail fatally + throw new RuntimeException("Bang!"); + } + else + { + // this should fail non-fatally + throw new ActionServiceTransientException("Pop!"); + } + } + + if (action instanceof CancellableSleepAction) + { + CancellableSleepAction ca = (CancellableSleepAction) action; + boolean cancelled = actionTrackingService.isCancellationRequested(ca); + if (cancelled) + throw new ActionCancelledException(ca); + } + } + } + + /** + * This class is only intended for use in JUnit tests. + * + * @author Neil McErlean. + * @since 4.0.1 + */ + public static class TransientFailActionExecuter extends ActionExecuterAbstractBase + { + public static final String NAME = "transient-fail-action"; + + private ActionTrackingService actionTrackingService; + + /** + * Loads this executor into the ApplicationContext, if it isn't already there + */ + public static void registerIfNeeded(ConfigurableApplicationContext ctx) + { + if (!ctx.containsBean(TransientFailActionExecuter.NAME)) + { + // Create, and do dependencies + TransientFailActionExecuter executor = new TransientFailActionExecuter(); + executor.setTrackStatus(true); + executor.actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService"); + // Register + ctx.getBeanFactory().registerSingleton(TransientFailActionExecuter.NAME, executor); + } + } + + @Override + protected void addParameterDefinitions(List paramList) + { + // Intentionally empty + } + + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + // this action always fails with a non-fatal exception. + throw new ActionServiceTransientException("action failed intentionally in " + TransientFailActionExecuter.class.getSimpleName()); + } + } + + protected static class CancellableSleepAction extends ActionImpl implements CancellableAction + { + public CancellableSleepAction(Action action) + { + super(action); + this.setTrackStatus(Boolean.TRUE); + } + } + + public static void assertBefore(Date before, Date after) + { + assertTrue( + before.toString() + " not before " + after.toString(), + before.getTime() <= after.getTime()); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/repository/src/test/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java index a7c0d22d13..a26dc4e70e 100644 --- a/repository/src/test/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java +++ b/repository/src/test/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java @@ -2,38 +2,47 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2023 Alfresco Software Limited + * Copyright (C) 2005 - 2025 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 + * 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.action; -import static org.alfresco.repo.action.ActionServiceImplTest.assertBefore; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + +import static org.awaitility.Awaitility.await; import java.net.InetAddress; +import java.time.Duration; import java.util.Date; import java.util.HashMap; import java.util.Map; - +import java.util.Objects; import jakarta.transaction.UserTransaction; +import junit.framework.TestCase; +import org.apache.commons.collections4.CollectionUtils; +import org.junit.experimental.categories.Category; +import org.springframework.context.ConfigurableApplicationContext; + import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ActionServiceImplTest.CancellableSleepAction; import org.alfresco.repo.action.ActionServiceImplTest.SleepActionExecuter; @@ -65,26 +74,21 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.test_category.OwnJVMTestsCategory; import org.alfresco.util.ApplicationContextHelper; -import org.junit.experimental.categories.Category; -import org.springframework.context.ConfigurableApplicationContext; - -import junit.framework.TestCase; /** - * Action tracking service tests. These mostly need - * careful control over the transactions they use. - * + * Action tracking service tests. These mostly need careful control over the transactions they use. + * * @author Nick Burch */ @Category(OwnJVMTestsCategory.class) public class ActionTrackingServiceImplTest extends TestCase { - private static ConfigurableApplicationContext ctx = - (ConfigurableApplicationContext)ApplicationContextHelper.getApplicationContext(); - + private static final Duration MAX_WAIT_TIMEOUT = Duration.ofSeconds(10); + private static ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext(); + private StoreRef storeRef; private NodeRef rootNodeRef; - + private NodeRef nodeRef; @SuppressWarnings("unused") private NodeRef folder; @@ -95,9 +99,9 @@ public class ActionTrackingServiceImplTest extends TestCase private RuntimeActionService runtimeActionService; private ActionTrackingService actionTrackingService; private SimpleCache executingActionsCache; - + private AsyncOccurs asyncOccurs; - + @Override @SuppressWarnings("unchecked") protected void setUp() throws Exception @@ -105,30 +109,30 @@ public class ActionTrackingServiceImplTest extends TestCase // Detect any dangling transactions as there is a lot of direct UserTransaction manipulation if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) { - throw new IllegalStateException( - "There should not be any transactions when starting test: " + - AlfrescoTransactionSupport.getTransactionId() + " started at " + - new Date(AlfrescoTransactionSupport.getTransactionStartTime())); + throw new IllegalStateException( + "There should not be any transactions when starting test: " + + AlfrescoTransactionSupport.getTransactionId() + " started at " + + new Date(AlfrescoTransactionSupport.getTransactionStartTime())); } - + // Grab our beans - this.nodeService = (NodeService)ctx.getBean("nodeService"); - this.scriptService = (ScriptService)ctx.getBean("scriptService"); - this.actionService = (ActionService)ctx.getBean("actionService"); - this.runtimeActionService = (RuntimeActionService)ctx.getBean("actionService"); - this.actionTrackingService = (ActionTrackingService)ctx.getBean("actionTrackingService"); - this.transactionService = (TransactionService)ctx.getBean("transactionService"); - this.executingActionsCache = (SimpleCache)ctx.getBean("executingActionsCache"); + this.nodeService = (NodeService) ctx.getBean("nodeService"); + this.scriptService = (ScriptService) ctx.getBean("scriptService"); + this.actionService = (ActionService) ctx.getBean("actionService"); + this.runtimeActionService = (RuntimeActionService) ctx.getBean("actionService"); + this.actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService"); + this.transactionService = (TransactionService) ctx.getBean("transactionService"); + this.executingActionsCache = (SimpleCache) ctx.getBean("executingActionsCache"); AuthenticationUtil.setRunAsUserSystem(); - + UserTransaction txn = transactionService.getUserTransaction(); txn.begin(); - + // Where to put things this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.rootNodeRef = this.nodeService.getRootNode(this.storeRef); - + // Create the node used for tests this.nodeRef = this.nodeService.createNode( this.rootNodeRef, @@ -144,278 +148,266 @@ public class ActionTrackingServiceImplTest extends TestCase ContentModel.ASSOC_CHILDREN, QName.createQName("{test}testFolder"), ContentModel.TYPE_FOLDER).getChildRef(); - + txn.commit(); - + // Cache should start empty each time executingActionsCache.clear(); - + // Reset the execution instance IDs, so we - // can predict what they'll be - ((ActionTrackingServiceImpl)actionTrackingService).resetNextExecutionId(); - + // can predict what they'll be + ((ActionTrackingServiceImpl) actionTrackingService).resetNextExecutionId(); + // Register the test executor, if needed SleepActionExecuter.registerIfNeeded(ctx); - + // We want to know when async actions occur asyncOccurs = new AsyncOccurs(); - ((PolicyComponent)ctx.getBean("policyComponent")).bindClassBehaviour( - AsynchronousActionExecutionQueuePolicies.OnAsyncActionExecute.QNAME, - ActionModel.TYPE_ACTION, - new JavaBehaviour(asyncOccurs, "onAsyncActionExecute", NotificationFrequency.EVERY_EVENT) - ); + ((PolicyComponent) ctx.getBean("policyComponent")).bindClassBehaviour( + AsynchronousActionExecutionQueuePolicies.OnAsyncActionExecute.QNAME, + ActionModel.TYPE_ACTION, + new JavaBehaviour(asyncOccurs, "onAsyncActionExecute", NotificationFrequency.EVERY_EVENT)); } /** Creating cache keys */ public void testCreateCacheKeys() throws Exception { - ActionImpl action = (ActionImpl)createWorkingSleepAction("1234"); - assertEquals("sleep-action", action.getActionDefinitionName()); - assertEquals("1234", action.getId()); - assertEquals(-1, action.getExecutionInstance()); - - // Give it a predictable execution instance - action.setExecutionInstance(1); - - // From an action - String key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals("sleep-action=1234=1", key); - - // From an ExecutionSummary - ExecutionSummary s = new ExecutionSummary("sleep-action", "1234", 1); - key = ActionTrackingServiceImpl.generateCacheKey(s); - assertEquals("sleep-action=1234=1", key); + ActionImpl action = (ActionImpl) createWorkingSleepAction("1234"); + assertEquals("sleep-action", action.getActionDefinitionName()); + assertEquals("1234", action.getId()); + assertEquals(-1, action.getExecutionInstance()); + + // Give it a predictable execution instance + action.setExecutionInstance(1); + + // From an action + String key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals("sleep-action=1234=1", key); + + // From an ExecutionSummary + ExecutionSummary s = new ExecutionSummary("sleep-action", "1234", 1); + key = ActionTrackingServiceImpl.generateCacheKey(s); + assertEquals("sleep-action=1234=1", key); } - + /** Creating ExecutionDetails and ExecutionSummary */ public void testExecutionDetailsSummary() throws Exception { - // Create an action with a known execution instance - Action action = createWorkingSleepAction("1234"); - ((ActionImpl)action).setExecutionInstance(1); - - // Create the ExecutionSummary from an action - String key = ActionTrackingServiceImpl.generateCacheKey(action); - - ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); - assertEquals("sleep-action", s.getActionType()); - assertEquals("1234", s.getActionId()); - assertEquals(1, s.getExecutionInstance()); - - // Create the ExecutionSummery from a key - s = ActionTrackingServiceImpl.buildExecutionSummary(key); - assertEquals("sleep-action", s.getActionType()); - assertEquals("1234", s.getActionId()); - assertEquals(1, s.getExecutionInstance()); - - // Now create ExecutionDetails - ExecutionDetails d = ActionTrackingServiceImpl.buildExecutionDetails(action); - assertNotNull(d.getExecutionSummary()); - assertEquals("sleep-action", d.getActionType()); - assertEquals("1234", d.getActionId()); - assertEquals(1, d.getExecutionInstance()); - assertEquals(null, d.getPersistedActionRef()); - assertEquals(null, d.getStartedAt()); - - // Check the machine details - // Should be "IP : Name" - InetAddress localhost = InetAddress.getLocalHost(); - String machineName = localhost.getHostAddress() + " : " + + // Create an action with a known execution instance + Action action = createWorkingSleepAction("1234"); + ((ActionImpl) action).setExecutionInstance(1); + + // Create the ExecutionSummary from an action + String key = ActionTrackingServiceImpl.generateCacheKey(action); + + ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); + assertEquals("sleep-action", s.getActionType()); + assertEquals("1234", s.getActionId()); + assertEquals(1, s.getExecutionInstance()); + + // Create the ExecutionSummery from a key + s = ActionTrackingServiceImpl.buildExecutionSummary(key); + assertEquals("sleep-action", s.getActionType()); + assertEquals("1234", s.getActionId()); + assertEquals(1, s.getExecutionInstance()); + + // Now create ExecutionDetails + ExecutionDetails d = ActionTrackingServiceImpl.buildExecutionDetails(action); + assertNotNull(d.getExecutionSummary()); + assertEquals("sleep-action", d.getActionType()); + assertEquals("1234", d.getActionId()); + assertEquals(1, d.getExecutionInstance()); + assertEquals(null, d.getPersistedActionRef()); + assertEquals(null, d.getStartedAt()); + + // Check the machine details + // Should be "IP : Name" + InetAddress localhost = InetAddress.getLocalHost(); + String machineName = localhost.getHostAddress() + " : " + localhost.getHostName(); - assertEquals(machineName, d.getRunningOn()); + assertEquals(machineName, d.getRunningOn()); } - + /** Running an action gives it an execution ID */ public void testExecutionInstanceAssignment() throws Exception { - ActionImpl action = (ActionImpl)createWorkingSleepAction("1234"); - assertEquals(-1, action.getExecutionInstance()); - - // Have it run, will get the ID of 1 - actionTrackingService.recordActionExecuting(action); - assertEquals(1, action.getExecutionInstance()); - - // And again, gets 2 - actionTrackingService.recordActionExecuting(action); - assertEquals(2, action.getExecutionInstance()); - - // And again, gets 3 - actionTrackingService.recordActionExecuting(action); - assertEquals(3, action.getExecutionInstance()); + ActionImpl action = (ActionImpl) createWorkingSleepAction("1234"); + assertEquals(-1, action.getExecutionInstance()); + + // Have it run, will get the ID of 1 + actionTrackingService.recordActionExecuting(action); + assertEquals(1, action.getExecutionInstance()); + + // And again, gets 2 + actionTrackingService.recordActionExecuting(action); + assertEquals(2, action.getExecutionInstance()); + + // And again, gets 3 + actionTrackingService.recordActionExecuting(action); + assertEquals(3, action.getExecutionInstance()); } - - /** - * The correct things happen with the cache - * when you mark things as working / failed / etc + + /** + * The correct things happen with the cache when you mark things as working / failed / etc */ public void testInOutCache() throws Exception { - Action action = createWorkingSleepAction("1234"); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - String key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(null, executingActionsCache.get(key)); - - - // Can complete or fail, won't be there - actionTrackingService.recordActionComplete(action); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - assertEquals(null, executingActionsCache.get(key)); - - actionTrackingService.recordActionFailure(action, new Exception("Testing")); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - assertEquals("Testing", action.getExecutionFailureMessage()); - assertEquals(null, executingActionsCache.get(key)); - - - // Pending will add it, but with no start date - actionTrackingService.recordActionPending(action); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - assertNotNull(null, executingActionsCache.get(key)); - - ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); - ExecutionDetails d = actionTrackingService.getExecutionDetails(s); - assertNotNull(d.getExecutionSummary()); - assertEquals("sleep-action", d.getActionType()); - assertEquals("1234", d.getActionId()); - assertEquals(1, d.getExecutionInstance()); - assertEquals(null, d.getPersistedActionRef()); - assertNull(null, d.getStartedAt()); - - - // Run it, will be updated in the cache - actionTrackingService.recordActionExecuting(action); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(ActionStatus.Running, action.getExecutionStatus()); - assertNotNull(null, executingActionsCache.get(key)); - - s = ActionTrackingServiceImpl.buildExecutionSummary(action); - d = actionTrackingService.getExecutionDetails(s); - assertNotNull(d.getExecutionSummary()); - assertEquals("sleep-action", d.getActionType()); - assertEquals("1234", d.getActionId()); - assertEquals(1, d.getExecutionInstance()); - assertEquals(null, d.getPersistedActionRef()); - assertNotNull(null, d.getStartedAt()); - - - // Completion removes it - actionTrackingService.recordActionComplete(action); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - assertEquals(null, executingActionsCache.get(key)); - - // Failure removes it - actionTrackingService.recordActionExecuting(action); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertNotNull(null, executingActionsCache.get(key)); - - actionTrackingService.recordActionFailure(action, new Exception("Testing")); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - assertEquals("Testing", action.getExecutionFailureMessage()); - assertEquals(null, executingActionsCache.get(key)); - - - // If run from new, i.e. not via pending, goes into the cache - ((ActionImpl)action).setExecutionStatus(ActionStatus.New); - ((ActionImpl)action).setExecutionStartDate(null); - ((ActionImpl)action).setExecutionEndDate(null); - ((ActionImpl)action).setExecutionFailureMessage(null); - ((ActionImpl)action).setExecutionInstance(-1); - - actionTrackingService.recordActionExecuting(action); - assertEquals(ActionStatus.Running, action.getExecutionStatus()); - assertTrue( ((ActionImpl)action).getExecutionInstance() != -1 ); - - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertNotNull(null, executingActionsCache.get(key)); - - actionTrackingService.recordActionComplete(action); - key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - assertEquals(null, executingActionsCache.get(key)); + Action action = createWorkingSleepAction("1234"); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + String key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(null, executingActionsCache.get(key)); + + // Can complete or fail, won't be there + actionTrackingService.recordActionComplete(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + assertEquals(null, executingActionsCache.get(key)); + + actionTrackingService.recordActionFailure(action, new Exception("Testing")); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + assertEquals("Testing", action.getExecutionFailureMessage()); + assertEquals(null, executingActionsCache.get(key)); + + // Pending will add it, but with no start date + actionTrackingService.recordActionPending(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(ActionStatus.Pending, action.getExecutionStatus()); + assertNotNull(null, executingActionsCache.get(key)); + + ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); + ExecutionDetails d = actionTrackingService.getExecutionDetails(s); + assertNotNull(d.getExecutionSummary()); + assertEquals("sleep-action", d.getActionType()); + assertEquals("1234", d.getActionId()); + assertEquals(1, d.getExecutionInstance()); + assertEquals(null, d.getPersistedActionRef()); + assertNull(null, d.getStartedAt()); + + // Run it, will be updated in the cache + actionTrackingService.recordActionExecuting(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(ActionStatus.Running, action.getExecutionStatus()); + assertNotNull(null, executingActionsCache.get(key)); + + s = ActionTrackingServiceImpl.buildExecutionSummary(action); + d = actionTrackingService.getExecutionDetails(s); + assertNotNull(d.getExecutionSummary()); + assertEquals("sleep-action", d.getActionType()); + assertEquals("1234", d.getActionId()); + assertEquals(1, d.getExecutionInstance()); + assertEquals(null, d.getPersistedActionRef()); + assertNotNull(null, d.getStartedAt()); + + // Completion removes it + actionTrackingService.recordActionComplete(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + assertEquals(null, executingActionsCache.get(key)); + + // Failure removes it + actionTrackingService.recordActionExecuting(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertNotNull(null, executingActionsCache.get(key)); + + actionTrackingService.recordActionFailure(action, new Exception("Testing")); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + assertEquals("Testing", action.getExecutionFailureMessage()); + assertEquals(null, executingActionsCache.get(key)); + + // If run from new, i.e. not via pending, goes into the cache + ((ActionImpl) action).setExecutionStatus(ActionStatus.New); + ((ActionImpl) action).setExecutionStartDate(null); + ((ActionImpl) action).setExecutionEndDate(null); + ((ActionImpl) action).setExecutionFailureMessage(null); + ((ActionImpl) action).setExecutionInstance(-1); + + actionTrackingService.recordActionExecuting(action); + assertEquals(ActionStatus.Running, action.getExecutionStatus()); + assertTrue(((ActionImpl) action).getExecutionInstance() != -1); + + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertNotNull(null, executingActionsCache.get(key)); + + actionTrackingService.recordActionComplete(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + assertEquals(null, executingActionsCache.get(key)); } - + /** Working actions go into the cache, then out */ - public void testWorkingActions() throws Exception + public void testWorkingActions() throws Exception { - final SleepActionExecuter sleepActionExec = - (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME); - sleepActionExec.resetTimesExecuted(); - sleepActionExec.setSleepMs(10000); + final SleepActionExecuter sleepActionExec = (SleepActionExecuter) ctx.getBean(SleepActionExecuter.NAME); + sleepActionExec.resetTimesExecuted(); + sleepActionExec.setSleepMs(10000); - // Have it run asynchronously - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - Action action = createWorkingSleepAction("54321"); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - String key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(null, executingActionsCache.get(key)); - - this.actionService.executeAction(action, this.nodeRef, false, true); - - - // End the transaction. Should allow the async action - // to start up, and begin sleeping - txn.commit(); - Thread.sleep(150); - - // The action should now be running - // It will have got an execution instance id, so a new key - key = ActionTrackingServiceImpl.generateCacheKey(action); - - - // Check it's in the cache - System.out.println("Checking the cache for " + key); - assertNotNull(executingActionsCache.get(key)); - - ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); - ExecutionDetails d = actionTrackingService.getExecutionDetails(s); - assertNotNull(d.getExecutionSummary()); - assertEquals("sleep-action", d.getActionType()); - assertEquals("54321", d.getActionId()); - assertEquals(1, d.getExecutionInstance()); - assertEquals(null, d.getPersistedActionRef()); - assertNotNull(null, d.getStartedAt()); + // Have it run asynchronously + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + Action action = createWorkingSleepAction("54321"); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); - - // Tell it to stop sleeping - // Then wait for it to finish - asyncOccurs.awaitExecution(null, sleepActionExec.getExecutingThread(), action.getActionDefinitionName()); - - - // Ensure it went away again - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - assertEquals(null, executingActionsCache.get(key)); - - d = actionTrackingService.getExecutionDetails(s); - assertEquals(null, d); + String key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(null, executingActionsCache.get(key)); + + this.actionService.executeAction(action, this.nodeRef, false, true); + + // End the transaction. Should allow the async action + // to start up, and begin sleeping + txn.commit(); + waitUntilActionHasStarted(action); + + // The action should now be running + // It will have got an execution instance id, so a new key + key = ActionTrackingServiceImpl.generateCacheKey(action); + + // Check it's in the cache + System.out.println("Checking the cache for " + key); + assertNotNull(executingActionsCache.get(key)); + + ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); + ExecutionDetails d = actionTrackingService.getExecutionDetails(s); + assertNotNull(d.getExecutionSummary()); + assertEquals("sleep-action", d.getActionType()); + assertEquals("54321", d.getActionId()); + assertEquals(1, d.getExecutionInstance()); + assertEquals(null, d.getPersistedActionRef()); + assertNotNull(null, d.getStartedAt()); + + // Tell it to stop sleeping + // Then wait for it to finish + asyncOccurs.awaitExecution(null, sleepActionExec.getExecutingThread(), action.getActionDefinitionName()); + + // Ensure it went away again + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + assertEquals(null, executingActionsCache.get(key)); + + d = actionTrackingService.getExecutionDetails(s); + assertEquals(null, d); } - + /** Failing actions go into the cache, then out */ public void testFatallyFailingActions() throws Exception { - Action failedAction = performFailingActionImpl(true, "54321"); - - assertEquals(ActionStatus.Failed, failedAction.getExecutionStatus()); - assertEquals("Bang!", failedAction.getExecutionFailureMessage()); + Action failedAction = performFailingActionImpl(true, "54321"); + + assertEquals(ActionStatus.Failed, failedAction.getExecutionStatus()); + assertEquals("Bang!", failedAction.getExecutionFailureMessage()); } - + /** Failing actions go into the cache, then out */ public void testTransientlyFailingActions() throws Exception { - Action failedAction = performFailingActionImpl(false, "654321"); - - assertEquals(ActionStatus.Declined, failedAction.getExecutionStatus()); - assertTrue(failedAction.getExecutionFailureMessage().endsWith("Pop!")); + Action failedAction = performFailingActionImpl(false, "654321"); + + assertEquals(ActionStatus.Declined, failedAction.getExecutionStatus()); + assertTrue(failedAction.getExecutionFailureMessage().endsWith("Pop!")); } private Action performFailingActionImpl(boolean fatalFailure, String actionId) throws Exception @@ -432,18 +424,18 @@ public class ActionTrackingServiceImplTest extends TestCase assertNull(action.getExecutionFailureMessage()); assertEquals(ActionStatus.New, action.getExecutionStatus()); - String key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(null, executingActionsCache.get(key)); + var beforeExecKey = ActionTrackingServiceImpl.generateCacheKey(action); + assertNull(executingActionsCache.get(beforeExecKey)); this.actionService.executeAction(action, this.nodeRef, false, true); // End the transaction. Should allow the async action // to be started, and move into its sleeping phase txn.commit(); - Thread.sleep(150); + waitUntilActionHasStarted(action); // Will get an execution instance id, so a new key - key = ActionTrackingServiceImpl.generateCacheKey(action); + var key = ActionTrackingServiceImpl.generateCacheKey(action); // Check it's in the cache System.out.println("Checking the cache for " + key); @@ -455,77 +447,57 @@ public class ActionTrackingServiceImplTest extends TestCase assertEquals("sleep-action", d.getActionType()); assertEquals(actionId, d.getActionId()); assertEquals(1, d.getExecutionInstance()); - assertEquals(null, d.getPersistedActionRef()); + assertNull(d.getPersistedActionRef()); // let's be more resilient and try a number of times with a delay - long start = System.currentTimeMillis(); - int sleepTime = 1000; // 1s - for (int i = 0; i < 10; i++) - { - if (d.getStartedAt() == null) - { - Thread.sleep(sleepTime); - sleepTime += 100; // increase by 100ms - continue; - } - else - { - break; - } - } - long end = System.currentTimeMillis(); - assertNotNull("Started at time is null, the action has not yet started after " + (end - start) + "ms", - d.getStartedAt()); + await().atMost(MAX_WAIT_TIMEOUT).until(() -> nonNull(d.getStartedAt())); + assertNotNull("Started at time is null, the action has not yet started after 10s", d.getStartedAt()); // Tell it to stop sleeping // Then wait for it to finish and go bang // (Need to do it by hand, as it won't fire the complete policy // as the action has failed) sleepActionExec.getExecutingThread().interrupt(); - Thread.sleep(150); + await().atMost(MAX_WAIT_TIMEOUT).until(() -> isNull(executingActionsCache.get(key))); // Ensure it went away again - assertEquals(null, executingActionsCache.get(key)); - - d = actionTrackingService.getExecutionDetails(s); - assertEquals(null, d); + assertNull(executingActionsCache.get(key)); + assertNull(actionTrackingService.getExecutionDetails(s)); return action; } - + /** Ensure that pending actions behave properly */ public void testPendingActions() throws Exception { // New ones won't be in the cache Action action = createWorkingSleepAction("1234"); assertEquals(ActionStatus.New, action.getExecutionStatus()); - + String key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(null, executingActionsCache.get(key)); - - + assertNull(executingActionsCache.get(key)); + // Ask for it to be pending, will go in actionTrackingService.recordActionPending(action); key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Pending, action.getExecutionStatus()); assertNotNull(null, executingActionsCache.get(key)); - + ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); ExecutionDetails d = actionTrackingService.getExecutionDetails(s); assertNotNull(d.getExecutionSummary()); assertEquals("sleep-action", d.getActionType()); assertEquals("1234", d.getActionId()); assertEquals(1, d.getExecutionInstance()); - assertEquals(null, d.getPersistedActionRef()); - assertNull(null, d.getStartedAt()); + assertNull(d.getPersistedActionRef()); + assertNull(d.getStartedAt()); - // Run it, will stay actionTrackingService.recordActionExecuting(action); key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Running, action.getExecutionStatus()); assertNotNull(null, executingActionsCache.get(key)); - + s = ActionTrackingServiceImpl.buildExecutionSummary(action); d = actionTrackingService.getExecutionDetails(s); assertNotNull(d.getExecutionSummary()); @@ -534,38 +506,33 @@ public class ActionTrackingServiceImplTest extends TestCase assertEquals(1, d.getExecutionInstance()); assertEquals(null, d.getPersistedActionRef()); assertNotNull(d.getStartedAt()); - - + // Finish, goes actionTrackingService.recordActionComplete(action); key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Completed, action.getExecutionStatus()); assertEquals(null, executingActionsCache.get(key)); - - + // Put another pending one in action = createWorkingSleepAction("1234"); actionTrackingService.recordActionPending(action); key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Pending, action.getExecutionStatus()); assertNotNull(null, executingActionsCache.get(key)); - - + // Remove it by hand executingActionsCache.remove(key); assertNull(null, executingActionsCache.get(key)); - int instanceId = ((ActionImpl)action).getExecutionInstance(); - - + int instanceId = ((ActionImpl) action).getExecutionInstance(); + // Run it, will go back in again, ID unchanged actionTrackingService.recordActionExecuting(action); assertEquals(key, ActionTrackingServiceImpl.generateCacheKey(action)); - assertEquals(instanceId, ((ActionImpl)action).getExecutionInstance()); - + assertEquals(instanceId, ((ActionImpl) action).getExecutionInstance()); + assertEquals(ActionStatus.Running, action.getExecutionStatus()); assertNotNull(null, executingActionsCache.get(key)); - - + // Finish, will go again actionTrackingService.recordActionComplete(action); key = ActionTrackingServiceImpl.generateCacheKey(action); @@ -663,7 +630,7 @@ public class ActionTrackingServiceImplTest extends TestCase actionTrackingService.recordActionComplete(moveAction); txn.commit(); - Thread.sleep(50); + await().atMost(MAX_WAIT_TIMEOUT).until(() -> actionTrackingService.getAllExecutingActions(), n -> CollectionUtils.size(n) == 2); // Check assertEquals(2, actionTrackingService.getAllExecutingActions().size()); @@ -728,7 +695,7 @@ public class ActionTrackingServiceImplTest extends TestCase // End the transaction. Should allow the async action // to be started txn.commit(); - Thread.sleep(150); + waitUntilActionHasStarted(sleepAction3); // Get the updated key, and check key3 = ActionTrackingServiceImpl.generateCacheKey(sleepAction3); @@ -747,7 +714,7 @@ public class ActionTrackingServiceImplTest extends TestCase // Have it finish sleeping, will have been cancelled // (Can't use the policy, as cancel is counted as a failure) sleepActionExec.getExecutingThread().interrupt(); - Thread.sleep(150); + await().atMost(MAX_WAIT_TIMEOUT).until(sleepAction3::getExecutionStatus, ActionStatus.Cancelled::equals); // Ensure the proper cancelled tracking assertEquals(ActionStatus.Cancelled, sleepAction3.getExecutionStatus()); @@ -756,321 +723,6 @@ public class ActionTrackingServiceImplTest extends TestCase // =================================================================== // - /** - * Tests that when we run an action, either synchronously or asynchronously, with it working or failing, that the - * action execution service correctly sets the flags - */ - public void xtestExecutionTrackingOnExecution() throws Exception - { - // FIXME: This test fails intermittently for no apparent reason. - // Removed until a reason/resolution can be found - final SleepActionExecuter sleepActionExec = (SleepActionExecuter) ctx.getBean(SleepActionExecuter.NAME); - sleepActionExec.setSleepMs(10); - Action action; - NodeRef actionNode; - - // We need real transactions - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - - // =========================================================== - // Execute a transient Action that works, synchronously - // =========================================================== - action = createWorkingSleepAction(null); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef); - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // =========================================================== - // Execute a transient Action that fails, synchronously - // =========================================================== - action = createFailingMoveAction(); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - try - { - this.actionService.executeAction(action, this.nodeRef); - fail("Action should have failed, and the error been thrown"); - } - catch (Exception e) - { - } - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Tidy up from the action failure - txn.rollback(); - txn = transactionService.getUserTransaction(); - txn.begin(); - - // =========================================================== - // Execute a stored Action that works, synchronously - // =========================================================== - action = createWorkingSleepAction(null); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef); - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // Let the update change the stored node - // (Can't use policy as the action has already finished!) - txn.commit(); - Thread.sleep(150); - txn = transactionService.getUserTransaction(); - txn.begin(); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // =========================================================== - // Execute a stored Action that fails, synchronously - // =========================================================== - action = createFailingMoveAction(); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - String actionId = action.getId(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - // Save this - txn.commit(); - txn = transactionService.getUserTransaction(); - txn.begin(); - - // Run the action - will fail and trigger a rollback - try - { - this.actionService.executeAction(action, this.nodeRef); - fail("Action should have failed, and the error been thrown"); - } - catch (Exception e) - { - } - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Wait for the post-rollback update to complete - // (The stored one gets updated asynchronously) - txn.rollback(); - Thread.sleep(150); - txn = transactionService.getUserTransaction(); - txn.begin(); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - assertEquals(actionId, action.getId()); - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Tidy up from the action failure - txn.commit(); - txn = transactionService.getUserTransaction(); - txn.begin(); - - // =========================================================== - // Execute a transient Action that works, asynchronously - // =========================================================== - action = createWorkingSleepAction(null); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - - // End the transaction. Should allow the async action - // to be executed - asyncOccurs.awaitExecution(txn, null, action.getActionDefinitionName()); - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // Put things back ready for the next check - txn = transactionService.getUserTransaction(); - txn.begin(); - - // =========================================================== - // Execute a transient Action that fails, asynchronously - // =========================================================== - action = createFailingMoveAction(); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - - // End the transaction, and await the failure - // (Can't use the policy as fails not suceeds) - txn.commit(); - Thread.sleep(150); - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Put things back ready for the next check - txn = transactionService.getUserTransaction(); - txn.begin(); - - // =========================================================== - // Execute a stored Action that works, asynchronously - // =========================================================== - action = createWorkingSleepAction(null); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - - // End the transaction. Should allow the async action - // to be executed - asyncOccurs.awaitExecution(txn, null, action.getActionDefinitionName()); - Thread.sleep(250); // Need to allow the post-commit update to the stored node - txn = transactionService.getUserTransaction(); - txn.begin(); - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // =========================================================== - // Execute a stored Action that fails, asynchronously - // =========================================================== - action = createFailingMoveAction(); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - - // End the transaction, and await the failure - // (Can't use the policy as fails not suceeds) - txn.commit(); - // Now also wait for the on-rollback to kick in and update - // the persisted copy of the action node too - Thread.sleep(400); - txn = transactionService.getUserTransaction(); - txn.begin(); - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - txn.commit(); - } - public void testJavascriptAPI() throws Exception { // We need a background action to sleep for long enough for @@ -1168,8 +820,7 @@ public class ActionTrackingServiceImplTest extends TestCase } } catch (InterruptedException e) - { - } + {} } } } @@ -1188,8 +839,7 @@ public class ActionTrackingServiceImplTest extends TestCase /** * @param isFatal - * true means the sleep action will fail with a RuntimeException, false means it will - * fail with a {@link ActionServiceTransientException}. + * true means the sleep action will fail with a RuntimeException, false means it will fail with a {@link ActionServiceTransientException}. */ private Action createFailingSleepAction(String id, boolean isFatal) throws Exception { @@ -1200,4 +850,9 @@ public class ActionTrackingServiceImplTest extends TestCase { return ActionServiceImplTest.createWorkingSleepAction(id, actionService); } -} \ No newline at end of file + + private void waitUntilActionHasStarted(Action action) + { + await().atMost(MAX_WAIT_TIMEOUT).until(action::getExecutionStartDate, Objects::nonNull); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluatorTest.java b/repository/src/test/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluatorTest.java index 9dc65eef11..74219d69c2 100644 --- a/repository/src/test/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluatorTest.java +++ b/repository/src/test/java/org/alfresco/repo/action/evaluator/ComparePropertyValueEvaluatorTest.java @@ -1,559 +1,686 @@ -/* - * #%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.action.evaluator; - -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ActionConditionImpl; -import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; -import org.alfresco.repo.action.evaluator.compare.ContentPropertyName; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.dictionary.DictionaryDAO; -import org.alfresco.repo.dictionary.M2Model; -import org.alfresco.repo.dictionary.M2Property; -import org.alfresco.repo.dictionary.M2Type; -import org.alfresco.service.cmr.action.ActionConditionDefinition; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.action.ParameterConstraint; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -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.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.BaseSpringTest; -import org.alfresco.util.GUID; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -/** - * Compare property value evaluator test - * - * @author Roy Wetherall - */ -@Transactional -public class ComparePropertyValueEvaluatorTest extends BaseSpringTest -{ - private static final String TEST_TYPE_NAMESPACE = "testNamespace"; - private static final QName TEST_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testType"); - private static final QName PROP_TEXT = QName.createQName(TEST_TYPE_NAMESPACE, "propText"); - private static final QName PROP_INT = QName.createQName(TEST_TYPE_NAMESPACE, "propInt"); - private static final QName PROP_DATETIME = QName.createQName(TEST_TYPE_NAMESPACE, "propDatetime"); - private static final QName PROP_NODEREF = QName.createQName(TEST_TYPE_NAMESPACE, "propNodeRef"); - private static final QName PROP_MULTI_VALUE = QName.createQName(TEST_TYPE_NAMESPACE, "propMultiValue"); - - private static final String TEXT_VALUE = "myDocument.doc"; - private static final int INT_VALUE = 100; - - private Date beforeDateValue; - private Date dateValue; - private Date afterDateValue; - private NodeRef nodeValue; - - @Autowired - private DictionaryDAO dictionaryDAO; - private NodeService nodeService; - private ContentService contentService; - private ActionService actionService; - private StoreRef testStoreRef; - private NodeRef rootNodeRef; - private NodeRef nodeRef; - private ComparePropertyValueEvaluator evaluator; - - @Before - public void before() throws Exception - { - // Need to create model to contain our custom type - createTestModel(); - - this.nodeService = (NodeService)this.applicationContext.getBean("nodeService"); - this.contentService = (ContentService)this.applicationContext.getBean("contentService"); - actionService = (ActionService)applicationContext.getBean("actionService"); - - // Create the store and get the root node - this.testStoreRef = this.nodeService.createStore( - StoreRef.PROTOCOL_WORKSPACE, "Test_" - + System.currentTimeMillis()); - this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); - - this.nodeValue = this.rootNodeRef; - - this.beforeDateValue = new Date(); - Thread.sleep(2000); - this.dateValue = new Date(); - Thread.sleep(2000); - this.afterDateValue = new Date(); - - Map props = new HashMap(); - props.put(PROP_TEXT, TEXT_VALUE); - props.put(PROP_INT, INT_VALUE); - props.put(PROP_DATETIME, this.dateValue); - props.put(PROP_NODEREF, this.nodeValue); - props.put(PROP_MULTI_VALUE, TEXT_VALUE); - - // Create the node used for tests - this.nodeRef = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testnode"), - TEST_TYPE_QNAME, - props).getChildRef(); - - this.evaluator = (ComparePropertyValueEvaluator)this.applicationContext.getBean(ComparePropertyValueEvaluator.NAME); - } - - @Test - public void testCheckParamDefintionWithConstraint() - { - ActionConditionDefinition def = evaluator.getActionConditionDefintion(); - assertEquals(ComparePropertyValueEvaluator.NAME, def.getName()); - ParameterDefinition paramDef = def.getParameterDefintion(ComparePropertyValueEvaluator.PARAM_OPERATION); - assertNotNull(paramDef); - assertEquals(ComparePropertyValueEvaluator.PARAM_OPERATION, paramDef.getName()); - String constraintName = paramDef.getParameterConstraintName(); - assertNotNull(constraintName); - ParameterConstraint paramConstraint = actionService.getParameterConstraint(constraintName); - assertNotNull(paramConstraint); - assertEquals("ac-compare-operations", paramConstraint.getName()); - } - - /** - * Test numeric comparisions - */ - @Test - public void testNumericComparison() - { - ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_INT); - - // Test the default operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, INT_VALUE); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, INT_VALUE); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals greater than operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals greater than operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 100); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals less than operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals less than equals operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 100); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Ensure other operators are invalid - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {exception.printStackTrace();}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - } - - /** - * Test date comparison - */ - @Test - public void testDateComparison() - { - ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_DATETIME); - - // Test the default operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, new Date()); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test the equals operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, new Date()); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals greater than operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals greater than operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals less than operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals less than equals operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Ensure other operators are invalid - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {exception.printStackTrace();}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - } - - /** - * Test text comparison - */ - @Test - public void testTextComparison() - { - ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_TEXT); - - // Test default operations implied by presence and position of * - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.xls"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "my*"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bad*"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Document"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals operator - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, TEXT_VALUE); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test contains operator - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Document"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test begins operator - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "my"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test ends operator - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "doc"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Ensure other operators are invalid - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {exception.printStackTrace();}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - } - - /** - * Test some combinations of test file names that had been failing - */ - @Test - public void testTempFileNames() - { - ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_TEXT); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "~*.doc"); - this.nodeService.setProperty(this.nodeRef, PROP_TEXT, "~1234.doc"); - - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - } - - /** - * Test comparison of properties that do not have a registered comparitor - */ - @Test - public void testOtherComparison() - { - NodeRef badNodeRef = new NodeRef(this.testStoreRef, "badId"); - - ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_NODEREF); - - // Test default operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.nodeValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, badNodeRef); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "this isn't even the correct type!"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test equals operation - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.nodeValue); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, badNodeRef); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Ensure other operators are invalid - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) { exception.printStackTrace();}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); - try { this.evaluator.evaluate(condition, this.nodeRef); fail("An exception should have been raised here."); } catch (ActionServiceException exception) {}; - - } - - @Test - public void testContentPropertyComparisons() - { - ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, ContentModel.PROP_CONTENT); - - // What happens if you do this and the node has no content set yet !! - - // Add some content to the node reference - ContentWriter contentWriter = this.contentService.getWriter(this.nodeRef, ContentModel.PROP_CONTENT, true); - contentWriter.setEncoding("UTF-8"); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - contentWriter.putContent("This is some test content."); - - // Test matching the mimetype - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.MIME_TYPE.toString()); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, MimetypeMap.MIMETYPE_TEXT_PLAIN); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, MimetypeMap.MIMETYPE_HTML); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test matching the encoding - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.ENCODING.toString()); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "UTF-8"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "UTF-16"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - // Test comparision to the size of the content - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.SIZE.toString()); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 50); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 2); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - - } - - @Test - public void testMultiValuedPropertyComparisons() - { - ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_MULTI_VALUE); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Document"); - assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); - - condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); - assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); - - } - - private void createTestModel() - { - M2Model model = M2Model.createModel("test:comparepropertyvalueevaluatortest"); - model.createNamespace(TEST_TYPE_NAMESPACE, "test"); - model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, NamespaceService.DICTIONARY_MODEL_PREFIX); - model.createImport(NamespaceService.SYSTEM_MODEL_1_0_URI, NamespaceService.SYSTEM_MODEL_PREFIX); - model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX); - - M2Type testType = model.createType("test:" + TEST_TYPE_QNAME.getLocalName()); - testType.setParentName("cm:" + ContentModel.TYPE_CONTENT.getLocalName()); - - M2Property prop1 = testType.createProperty("test:" + PROP_TEXT.getLocalName()); - prop1.setMandatory(false); - prop1.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); - prop1.setMultiValued(false); - - M2Property prop2 = testType.createProperty("test:" + PROP_INT.getLocalName()); - prop2.setMandatory(false); - prop2.setType("d:" + DataTypeDefinition.INT.getLocalName()); - prop2.setMultiValued(false); - - M2Property prop3 = testType.createProperty("test:" + PROP_DATETIME.getLocalName()); - prop3.setMandatory(false); - prop3.setType("d:" + DataTypeDefinition.DATETIME.getLocalName()); - prop3.setMultiValued(false); - - M2Property prop4 = testType.createProperty("test:" + PROP_NODEREF.getLocalName()); - prop4.setMandatory(false); - prop4.setType("d:" + DataTypeDefinition.NODE_REF.getLocalName()); - prop4.setMultiValued(false); - - M2Property prop5 = testType.createProperty("test:" + PROP_MULTI_VALUE.getLocalName()); - prop5.setMandatory(false); - prop5.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); - prop5.setMultiValued(true); - - dictionaryDAO.putModel(model); - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.action.evaluator; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionConditionImpl; +import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation; +import org.alfresco.repo.action.evaluator.compare.ContentPropertyName; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.dictionary.M2Property; +import org.alfresco.repo.dictionary.M2Type; +import org.alfresco.service.cmr.action.ActionConditionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.action.ParameterConstraint; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +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.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; + +/** + * Compare property value evaluator test + * + * @author Roy Wetherall + */ +@Transactional +public class ComparePropertyValueEvaluatorTest extends BaseSpringTest +{ + private static final String TEST_TYPE_NAMESPACE = "testNamespace"; + private static final QName TEST_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testType"); + private static final QName PROP_TEXT = QName.createQName(TEST_TYPE_NAMESPACE, "propText"); + private static final QName PROP_INT = QName.createQName(TEST_TYPE_NAMESPACE, "propInt"); + private static final QName PROP_DATETIME = QName.createQName(TEST_TYPE_NAMESPACE, "propDatetime"); + private static final QName PROP_NODEREF = QName.createQName(TEST_TYPE_NAMESPACE, "propNodeRef"); + private static final QName PROP_MULTI_VALUE = QName.createQName(TEST_TYPE_NAMESPACE, "propMultiValue"); + + private static final String TEXT_VALUE = "myDocument.doc"; + private static final int INT_VALUE = 100; + + private Date beforeDateValue; + private Date dateValue; + private Date afterDateValue; + private NodeRef nodeValue; + + @Autowired + private DictionaryDAO dictionaryDAO; + private NodeService nodeService; + private ContentService contentService; + private ActionService actionService; + private StoreRef testStoreRef; + private NodeRef rootNodeRef; + private NodeRef nodeRef; + private ComparePropertyValueEvaluator evaluator; + + @Before + public void before() throws Exception + { + // Need to create model to contain our custom type + createTestModel(); + + this.nodeService = (NodeService) this.applicationContext.getBean("nodeService"); + this.contentService = (ContentService) this.applicationContext.getBean("contentService"); + actionService = (ActionService) applicationContext.getBean("actionService"); + + // Create the store and get the root node + this.testStoreRef = this.nodeService.createStore( + StoreRef.PROTOCOL_WORKSPACE, "Test_" + + System.currentTimeMillis()); + this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); + + this.nodeValue = this.rootNodeRef; + + var now = Instant.now(); + this.beforeDateValue = Date.from(now.minusSeconds(4)); + this.dateValue = Date.from(now.minusSeconds(2)); + this.afterDateValue = Date.from(now); + + Map props = new HashMap(); + props.put(PROP_TEXT, TEXT_VALUE); + props.put(PROP_INT, INT_VALUE); + props.put(PROP_DATETIME, this.dateValue); + props.put(PROP_NODEREF, this.nodeValue); + props.put(PROP_MULTI_VALUE, TEXT_VALUE); + + // Create the node used for tests + this.nodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}testnode"), + TEST_TYPE_QNAME, + props).getChildRef(); + + this.evaluator = (ComparePropertyValueEvaluator) this.applicationContext.getBean(ComparePropertyValueEvaluator.NAME); + } + + @Test + public void testCheckParamDefintionWithConstraint() + { + ActionConditionDefinition def = evaluator.getActionConditionDefintion(); + assertEquals(ComparePropertyValueEvaluator.NAME, def.getName()); + ParameterDefinition paramDef = def.getParameterDefintion(ComparePropertyValueEvaluator.PARAM_OPERATION); + assertNotNull(paramDef); + assertEquals(ComparePropertyValueEvaluator.PARAM_OPERATION, paramDef.getName()); + String constraintName = paramDef.getParameterConstraintName(); + assertNotNull(constraintName); + ParameterConstraint paramConstraint = actionService.getParameterConstraint(constraintName); + assertNotNull(paramConstraint); + assertEquals("ac-compare-operations", paramConstraint.getName()); + } + + /** + * Test numeric comparisions + */ + @Test + public void testNumericComparison() + { + ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_INT); + + // Test the default operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, INT_VALUE); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, INT_VALUE); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals greater than operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals greater than operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 100); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals less than operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals less than equals operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 101); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 100); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 99); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Ensure other operators are invalid + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + { + exception.printStackTrace(); + } + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + } + + /** + * Test date comparison + */ + @Test + public void testDateComparison() + { + ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_DATETIME); + + // Test the default operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, new Date()); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test the equals operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, new Date()); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals greater than operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals greater than operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals less than operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals less than equals operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.afterDateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.dateValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.beforeDateValue); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Ensure other operators are invalid + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + { + exception.printStackTrace(); + } + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + } + + /** + * Test text comparison + */ + @Test + public void testTextComparison() + { + ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_TEXT); + + // Test default operations implied by presence and position of * + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.xls"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "my*"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bad*"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Document"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals operator + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, TEXT_VALUE); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test contains operator + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Document"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test begins operator + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "my"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test ends operator + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "doc"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Ensure other operators are invalid + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + { + exception.printStackTrace(); + } + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + } + + /** + * Test some combinations of test file names that had been failing + */ + @Test + public void testTempFileNames() + { + ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_TEXT); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "~*.doc"); + this.nodeService.setProperty(this.nodeRef, PROP_TEXT, "~1234.doc"); + + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + } + + /** + * Test comparison of properties that do not have a registered comparitor + */ + @Test + public void testOtherComparison() + { + NodeRef badNodeRef = new NodeRef(this.testStoreRef, "badId"); + + ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_NODEREF); + + // Test default operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.nodeValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, badNodeRef); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "this isn't even the correct type!"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test equals operation + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, this.nodeValue); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, badNodeRef); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Ensure other operators are invalid + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.BEGINS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + { + exception.printStackTrace(); + } + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.ENDS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.GREATER_THAN_EQUAL.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN_EQUAL.toString()); + try + { + this.evaluator.evaluate(condition, this.nodeRef); + fail("An exception should have been raised here."); + } + catch (ActionServiceException exception) + {} + ; + + } + + @Test + public void testContentPropertyComparisons() + { + ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, ContentModel.PROP_CONTENT); + + // What happens if you do this and the node has no content set yet !! + + // Add some content to the node reference + ContentWriter contentWriter = this.contentService.getWriter(this.nodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.setEncoding("UTF-8"); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter.putContent("This is some test content."); + + // Test matching the mimetype + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.MIME_TYPE.toString()); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, MimetypeMap.MIMETYPE_TEXT_PLAIN); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, MimetypeMap.MIMETYPE_HTML); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test matching the encoding + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.ENCODING.toString()); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "UTF-8"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "UTF-16"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + // Test comparision to the size of the content + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, ContentPropertyName.SIZE.toString()); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.LESS_THAN.toString()); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 50); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, 2); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + } + + @Test + public void testMultiValuedPropertyComparisons() + { + ActionConditionImpl condition = new ActionConditionImpl(GUID.generate(), ComparePropertyValueEvaluator.NAME); + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, PROP_MULTI_VALUE); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString()); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Document"); + assertTrue(this.evaluator.evaluate(condition, this.nodeRef)); + + condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "bobbins"); + assertFalse(this.evaluator.evaluate(condition, this.nodeRef)); + + } + + private void createTestModel() + { + M2Model model = M2Model.createModel("test:comparepropertyvalueevaluatortest"); + model.createNamespace(TEST_TYPE_NAMESPACE, "test"); + model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, NamespaceService.DICTIONARY_MODEL_PREFIX); + model.createImport(NamespaceService.SYSTEM_MODEL_1_0_URI, NamespaceService.SYSTEM_MODEL_PREFIX); + model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX); + + M2Type testType = model.createType("test:" + TEST_TYPE_QNAME.getLocalName()); + testType.setParentName("cm:" + ContentModel.TYPE_CONTENT.getLocalName()); + + M2Property prop1 = testType.createProperty("test:" + PROP_TEXT.getLocalName()); + prop1.setMandatory(false); + prop1.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); + prop1.setMultiValued(false); + + M2Property prop2 = testType.createProperty("test:" + PROP_INT.getLocalName()); + prop2.setMandatory(false); + prop2.setType("d:" + DataTypeDefinition.INT.getLocalName()); + prop2.setMultiValued(false); + + M2Property prop3 = testType.createProperty("test:" + PROP_DATETIME.getLocalName()); + prop3.setMandatory(false); + prop3.setType("d:" + DataTypeDefinition.DATETIME.getLocalName()); + prop3.setMultiValued(false); + + M2Property prop4 = testType.createProperty("test:" + PROP_NODEREF.getLocalName()); + prop4.setMandatory(false); + prop4.setType("d:" + DataTypeDefinition.NODE_REF.getLocalName()); + prop4.setMultiValued(false); + + M2Property prop5 = testType.createProperty("test:" + PROP_MULTI_VALUE.getLocalName()); + prop5.setMandatory(false); + prop5.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); + prop5.setMultiValued(true); + + dictionaryDAO.putModel(model); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java b/repository/src/test/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java index 1abb38449a..95fad349c6 100644 --- a/repository/src/test/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java +++ b/repository/src/test/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java @@ -1,363 +1,354 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2023 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% - */ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * 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.action.executer; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ActionImpl; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter; -import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; -import org.alfresco.repo.content.transform.AbstractContentTransformerTest; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -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.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.test_category.BaseSpringTestsCategory; -import org.alfresco.util.BaseSpringTest; -import org.alfresco.util.GUID; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.springframework.test.context.transaction.TestTransaction; -import org.springframework.transaction.annotation.Transactional; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -/** - * Test of the ActionExecuter for extracting metadata. - * - * @author Jesper Steen Møller - */ -@Category(BaseSpringTestsCategory.class) -@Transactional -public class ContentMetadataExtracterTest extends BaseSpringTest -{ - protected static final String QUICK_TITLE = "The quick brown fox jumps over the lazy dog"; - protected static final String QUICK_DESCRIPTION = "Pangram, fox, dog, Gym class featuring a brown fox and lazy dog"; - protected static final String QUICK_CREATOR = "Nevin Nollop"; - - private NodeService nodeService; - private ContentService contentService; - private MetadataExtracterRegistry registry; - private TransactionService transactionService; - private StoreRef testStoreRef; - private NodeRef rootNodeRef; - private NodeRef nodeRef; - - private ContentMetadataExtracter executer; - - private final static String ID = GUID.generate(); - - @Before - public void before() throws Exception - { - this.nodeService = (NodeService) this.applicationContext.getBean("nodeService"); - this.contentService = (ContentService) this.applicationContext.getBean("contentService"); - registry = (MetadataExtracterRegistry) applicationContext.getBean("metadataExtracterRegistry"); - transactionService = (TransactionService) this.applicationContext.getBean("transactionService"); - - AuthenticationComponent authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); - authenticationComponent.setSystemUserAsCurrentUser(); - - // Create the store and get the root node - this.testStoreRef = this.nodeService.createStore( - StoreRef.PROTOCOL_WORKSPACE, - "Test_" + System.currentTimeMillis()); - this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); - - // Create the node used for tests - this.nodeRef = this.nodeService.createNode( - this.rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testnode"), - ContentModel.TYPE_CONTENT).getChildRef(); - - // Setup the content from the PDF test data - ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - cw.setMimetype(MimetypeMap.MIMETYPE_PDF); - cw.putContent(AbstractContentTransformerTest.loadQuickTestFile("pdf")); - - // Get the executer instance - this.executer = (ContentMetadataExtracter) this.applicationContext.getBean("extract-metadata"); - } - - /** - * Test execution of the extraction itself - */ - @Test - public void testFromBlanks() throws Exception - { - // Test that the action writes properties when they don't exist or are - // unset - - // Get the old props - Map props = this.nodeService.getProperties(this.nodeRef); - props.remove(ContentModel.PROP_AUTHOR); - props.put(ContentModel.PROP_TITLE, ""); - props.put(ContentModel.PROP_DESCRIPTION, null); // Wonder how this will - // be handled - this.nodeService.setProperties(this.nodeRef, props); - - // Make the nodeRef visible to other transactions as it will need to be in async requests - TestTransaction.flagForCommit(); - TestTransaction.end(); - - // Execute the action - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); - executer.execute(action, nodeRef); - return null; - } - }); - - Thread.sleep(3000); // Need to wait for the async extract - - // Check that the properties have been set - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - assertEquals(QUICK_TITLE, nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE)); - assertEquals(QUICK_DESCRIPTION, nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION)); - assertEquals(QUICK_CREATOR, nodeService.getProperty(nodeRef, ContentModel.PROP_AUTHOR)); - return null; - } - }); - } - - private static final QName PROP_UNKNOWN_1 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "unkown1"); - private static final QName PROP_UNKNOWN_2 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "unkown2"); - private static class TestUnknownMetadataExtracter extends AbstractMappingMetadataExtracter - { - public TestUnknownMetadataExtracter() - { - Properties mappingProperties = new Properties(); - mappingProperties.put("unknown1", PROP_UNKNOWN_1.toString()); - mappingProperties.put("unknown2", PROP_UNKNOWN_2.toString()); - setMappingProperties(mappingProperties); - } - @Override - protected Map> getDefaultMapping() - { - // No need to give anything back as we have explicitly set the mapping already - return new HashMap>(0); - } - @Override - public boolean isSupported(String sourceMimetype) - { - return sourceMimetype.equals(MimetypeMap.MIMETYPE_BINARY); - } - - public Map extractRaw(ContentReader reader) throws Throwable - { - Map rawMap = newRawMap(); - rawMap.put("unknown1", Integer.valueOf(1)); - rawMap.put("unknown2", "TWO"); - return rawMap; - } - } - - @Test - public void testUnknownProperties() - { - TestUnknownMetadataExtracter extracterUnknown = new TestUnknownMetadataExtracter(); - extracterUnknown.setRegistry(registry); - extracterUnknown.register(); - // Now add some content with a binary mimetype - ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - cw.setMimetype(MimetypeMap.MIMETYPE_BINARY); - cw.putContent("Content for " + getName()); - - ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); - executer.execute(action, this.nodeRef); - - // The unkown properties should be present - Serializable prop1 = nodeService.getProperty(nodeRef, PROP_UNKNOWN_1); - Serializable prop2 = nodeService.getProperty(nodeRef, PROP_UNKNOWN_2); - - assertNotNull("Unknown property is null", prop1); - assertNotNull("Unknown property is null", prop2); - } - - private static class TestNullPropMetadataExtracter extends AbstractMappingMetadataExtracter - { - public TestNullPropMetadataExtracter() - { - Properties mappingProperties = new Properties(); - mappingProperties.put("title", ContentModel.PROP_TITLE.toString()); - mappingProperties.put("description", ContentModel.PROP_DESCRIPTION.toString()); - setMappingProperties(mappingProperties); - } - @Override - protected Map> getDefaultMapping() - { - // No need to give anything back as we have explicitly set the mapping already - return new HashMap>(0); - } - @Override - public boolean isSupported(String sourceMimetype) - { - return sourceMimetype.equals(MimetypeMap.MIMETYPE_BINARY); - } - - public Map extractRaw(ContentReader reader) throws Throwable - { - Map rawMap = newRawMap(); - putRawValue("title", null, rawMap); - putRawValue("description", "", rawMap); - return rawMap; - } - } - - /** - * Ensure that missing raw values result in node properties being removed - * when running with {@link ContentMetadataExtracter#setCarryAspectProperties(boolean)} - * set to false. - */ - @Test - public void testNullExtractedValues_ALF1823() - { - TestNullPropMetadataExtracter extractor = new TestNullPropMetadataExtracter(); - extractor.setRegistry(registry); - extractor.register(); - // Now set the title and description - nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, "TITLE"); - nodeService.setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, "DESCRIPTION"); - // Now add some content with a binary mimetype - ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - cw.setMimetype(MimetypeMap.MIMETYPE_BINARY); - cw.putContent("Content for " + getName()); - - ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); - executer.execute(action, this.nodeRef); - - // cm:titled properties should be present - Serializable title = nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - Serializable descr = nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION); - - assertNotNull("cm:title property is null", title); - assertNotNull("cm:description property is null", descr); - - try - { - // Now change the setting to remove unset aspect properties - executer.setCarryAspectProperties(false); - // Extract again - executer.execute(action, this.nodeRef); - - // cm:titled properties should *NOT* be present - title = nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - descr = nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION); - - assertNull("cm:title property is not null", title); - assertNull("cm:description property is not null", descr); - } - finally - { - executer.setCarryAspectProperties(true); - } - } - - /** - * Test execution of the pragmatic approach - */ - @Test - public void testFromPartial() throws Exception - { - // Test that the action does not overwrite properties that are already - // set - String myCreator = "Null-op"; - String myTitle = "The hot dog is eaten by the city fox"; - - // Get the old props - Map props = this.nodeService.getProperties(this.nodeRef); - props.put(ContentModel.PROP_AUTHOR, myCreator); - props.put(ContentModel.PROP_TITLE, myTitle); - props.remove(ContentModel.PROP_DESCRIPTION); // Allow this baby - this.nodeService.setProperties(this.nodeRef, props); - - // Make the nodeRef visible to other transactions as it will need to be in async requests - TestTransaction.flagForCommit(); - TestTransaction.end(); - - // Execute the action - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); - executer.execute(action, nodeRef); - return null; - } - }); - - Thread.sleep(3000); // Need to wait for the async extract - - // Check that the properties have been preserved, but that description has been set - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - assertEquals(myTitle, nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE)); - assertEquals(myCreator, nodeService.getProperty(nodeRef, ContentModel.PROP_AUTHOR)); - - assertEquals(QUICK_DESCRIPTION, nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION)); - return null; - } - }); - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.action.executer; + +import static org.awaitility.Awaitility.await; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.annotation.Transactional; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionImpl; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter; +import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; +import org.alfresco.repo.content.transform.AbstractContentTransformerTest; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +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.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.test_category.BaseSpringTestsCategory; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; + +/** + * Test of the ActionExecuter for extracting metadata. + * + * @author Jesper Steen Møller + */ +@Category(BaseSpringTestsCategory.class) +@Transactional +public class ContentMetadataExtracterTest extends BaseSpringTest +{ + protected static final String QUICK_TITLE = "The quick brown fox jumps over the lazy dog"; + protected static final String QUICK_DESCRIPTION = "Pangram, fox, dog, Gym class featuring a brown fox and lazy dog"; + protected static final String QUICK_CREATOR = "Nevin Nollop"; + + private NodeService nodeService; + private ContentService contentService; + private MetadataExtracterRegistry registry; + private TransactionService transactionService; + private StoreRef testStoreRef; + private NodeRef rootNodeRef; + private NodeRef nodeRef; + + private ContentMetadataExtracter executer; + + private final static String ID = GUID.generate(); + + @Before + public void before() throws Exception + { + this.nodeService = (NodeService) this.applicationContext.getBean("nodeService"); + this.contentService = (ContentService) this.applicationContext.getBean("contentService"); + registry = (MetadataExtracterRegistry) applicationContext.getBean("metadataExtracterRegistry"); + transactionService = (TransactionService) this.applicationContext.getBean("transactionService"); + + AuthenticationComponent authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); + authenticationComponent.setSystemUserAsCurrentUser(); + + // Create the store and get the root node + this.testStoreRef = this.nodeService.createStore( + StoreRef.PROTOCOL_WORKSPACE, + "Test_" + System.currentTimeMillis()); + this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); + + // Create the node used for tests + this.nodeRef = this.nodeService.createNode( + this.rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}testnode"), + ContentModel.TYPE_CONTENT).getChildRef(); + + // Setup the content from the PDF test data + ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + cw.setMimetype(MimetypeMap.MIMETYPE_PDF); + cw.putContent(AbstractContentTransformerTest.loadQuickTestFile("pdf")); + + // Get the executer instance + this.executer = (ContentMetadataExtracter) this.applicationContext.getBean("extract-metadata"); + } + + /** + * Test execution of the extraction itself + */ + @Test + public void testFromBlanks() throws Exception + { + // Test that the action writes properties when they don't exist or are + // unset + + // Get the old props + Map props = this.nodeService.getProperties(this.nodeRef); + props.remove(ContentModel.PROP_AUTHOR); + props.put(ContentModel.PROP_TITLE, ""); + props.put(ContentModel.PROP_DESCRIPTION, null); // Wonder how this will + // be handled + this.nodeService.setProperties(this.nodeRef, props); + + // Make the nodeRef visible to other transactions as it will need to be in async requests + TestTransaction.flagForCommit(); + TestTransaction.end(); + + // Execute the action + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { + public Void execute() throws Throwable + { + ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); + executer.execute(action, nodeRef); + return null; + } + }); + + // Need to wait for the async extract + await().pollInSameThread() + .atMost(MAX_ASYNC_TIMEOUT) + .until(() -> nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION), Objects::nonNull); + + // Check that the properties have been set + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { + public Void execute() throws Throwable + { + assertEquals(QUICK_TITLE, nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE)); + assertEquals(QUICK_DESCRIPTION, nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION)); + assertEquals(QUICK_CREATOR, nodeService.getProperty(nodeRef, ContentModel.PROP_AUTHOR)); + return null; + } + }); + } + + private static final QName PROP_UNKNOWN_1 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "unkown1"); + private static final QName PROP_UNKNOWN_2 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "unkown2"); + + private static class TestUnknownMetadataExtracter extends AbstractMappingMetadataExtracter + { + public TestUnknownMetadataExtracter() + { + Properties mappingProperties = new Properties(); + mappingProperties.put("unknown1", PROP_UNKNOWN_1.toString()); + mappingProperties.put("unknown2", PROP_UNKNOWN_2.toString()); + setMappingProperties(mappingProperties); + } + + @Override + protected Map> getDefaultMapping() + { + // No need to give anything back as we have explicitly set the mapping already + return new HashMap>(0); + } + + @Override + public boolean isSupported(String sourceMimetype) + { + return sourceMimetype.equals(MimetypeMap.MIMETYPE_BINARY); + } + + public Map extractRaw(ContentReader reader) throws Throwable + { + Map rawMap = newRawMap(); + rawMap.put("unknown1", Integer.valueOf(1)); + rawMap.put("unknown2", "TWO"); + return rawMap; + } + } + + @Test + public void testUnknownProperties() + { + TestUnknownMetadataExtracter extracterUnknown = new TestUnknownMetadataExtracter(); + extracterUnknown.setRegistry(registry); + extracterUnknown.register(); + // Now add some content with a binary mimetype + ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + cw.setMimetype(MimetypeMap.MIMETYPE_BINARY); + cw.putContent("Content for " + getName()); + + ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); + executer.execute(action, this.nodeRef); + + // The unkown properties should be present + Serializable prop1 = nodeService.getProperty(nodeRef, PROP_UNKNOWN_1); + Serializable prop2 = nodeService.getProperty(nodeRef, PROP_UNKNOWN_2); + + assertNotNull("Unknown property is null", prop1); + assertNotNull("Unknown property is null", prop2); + } + + private static class TestNullPropMetadataExtracter extends AbstractMappingMetadataExtracter + { + public TestNullPropMetadataExtracter() + { + Properties mappingProperties = new Properties(); + mappingProperties.put("title", ContentModel.PROP_TITLE.toString()); + mappingProperties.put("description", ContentModel.PROP_DESCRIPTION.toString()); + setMappingProperties(mappingProperties); + } + + @Override + protected Map> getDefaultMapping() + { + // No need to give anything back as we have explicitly set the mapping already + return new HashMap>(0); + } + + @Override + public boolean isSupported(String sourceMimetype) + { + return sourceMimetype.equals(MimetypeMap.MIMETYPE_BINARY); + } + + public Map extractRaw(ContentReader reader) throws Throwable + { + Map rawMap = newRawMap(); + putRawValue("title", null, rawMap); + putRawValue("description", "", rawMap); + return rawMap; + } + } + + /** + * Ensure that missing raw values result in node properties being removed when running with {@link ContentMetadataExtracter#setCarryAspectProperties(boolean)} set to false. + */ + @Test + public void testNullExtractedValues_ALF1823() + { + TestNullPropMetadataExtracter extractor = new TestNullPropMetadataExtracter(); + extractor.setRegistry(registry); + extractor.register(); + // Now set the title and description + nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, "TITLE"); + nodeService.setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, "DESCRIPTION"); + // Now add some content with a binary mimetype + ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + cw.setMimetype(MimetypeMap.MIMETYPE_BINARY); + cw.putContent("Content for " + getName()); + + ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); + executer.execute(action, this.nodeRef); + + // cm:titled properties should be present + Serializable title = nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + Serializable descr = nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION); + + assertNotNull("cm:title property is null", title); + assertNotNull("cm:description property is null", descr); + + try + { + // Now change the setting to remove unset aspect properties + executer.setCarryAspectProperties(false); + // Extract again + executer.execute(action, this.nodeRef); + + // cm:titled properties should *NOT* be present + title = nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + descr = nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION); + + assertNull("cm:title property is not null", title); + assertNull("cm:description property is not null", descr); + } + finally + { + executer.setCarryAspectProperties(true); + } + } + + /** + * Test execution of the pragmatic approach + */ + @Test + public void testFromPartial() throws Exception + { + // Test that the action does not overwrite properties that are already + // set + String myCreator = "Null-op"; + String myTitle = "The hot dog is eaten by the city fox"; + + // Get the old props + Map props = this.nodeService.getProperties(this.nodeRef); + props.put(ContentModel.PROP_AUTHOR, myCreator); + props.put(ContentModel.PROP_TITLE, myTitle); + props.remove(ContentModel.PROP_DESCRIPTION); // Allow this baby + this.nodeService.setProperties(this.nodeRef, props); + + // Make the nodeRef visible to other transactions as it will need to be in async requests + TestTransaction.flagForCommit(); + TestTransaction.end(); + + // Execute the action + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { + public Void execute() throws Throwable + { + ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); + executer.execute(action, nodeRef); + return null; + } + }); + + // Need to wait for the async extract + await().pollInSameThread() + .atMost(MAX_ASYNC_TIMEOUT) + .until(() -> nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION), Objects::nonNull); + + // Check that the properties have been preserved, but that description has been set + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { + public Void execute() throws Throwable + { + assertEquals(myTitle, nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE)); + assertEquals(myCreator, nodeService.getProperty(nodeRef, ContentModel.PROP_AUTHOR)); + + assertEquals(QUICK_DESCRIPTION, nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION)); + return null; + } + }); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java b/repository/src/test/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java index c029ad02f9..135176876b 100644 --- a/repository/src/test/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java @@ -1,478 +1,475 @@ -/* - * #%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.content.caching.cleanup; - - -import static org.junit.Assert.*; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Calendar; -import java.util.GregorianCalendar; - -import org.alfresco.repo.content.caching.CacheFileProps; -import org.alfresco.repo.content.caching.CachingContentStore; -import org.alfresco.repo.content.caching.ContentCacheImpl; -import org.alfresco.repo.content.caching.Key; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.GUID; -import org.alfresco.util.testing.category.LuceneTests; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.springframework.context.ApplicationContext; - -/** - * Tests for the CachedContentCleanupJob - * - * @author Matt Ward - */ -@Category(LuceneTests.class) -public class CachedContentCleanupJobTest -{ - private enum UrlSource { PROPS_FILE, REVERSE_CACHE_LOOKUP, NOT_PRESENT }; - private static ApplicationContext ctx; - private CachingContentStore cachingStore; - private ContentCacheImpl cache; - private File cacheRoot; - private CachedContentCleaner cleaner; - - - @BeforeClass - public static void beforeClass() - { - String cleanerConf = "classpath:cachingstore/test-cleaner-context.xml"; - ctx = ApplicationContextHelper.getApplicationContext(new String[] { cleanerConf }); - } - - - @Before - public void setUp() throws IOException - { - cachingStore = (CachingContentStore) ctx.getBean("cachingContentStore"); - cache = (ContentCacheImpl) ctx.getBean("contentCache"); - cacheRoot = cache.getCacheRoot(); - cleaner = (CachedContentCleaner) ctx.getBean("cachedContentCleaner"); - cleaner.setMinFileAgeMillis(0); - cleaner.setMaxDeleteWatchCount(0); - - // Clear the cache from disk and memory - cache.removeAll(); - FileUtils.cleanDirectory(cacheRoot); - } - - - @Test - public void filesNotInCacheAreDeleted() throws InterruptedException - { - cleaner.setMaxDeleteWatchCount(0); - int numFiles = 300; // Must be a multiple of number of UrlSource types being tested - long totalSize = 0; // what is the total size of the sample files? - File[] files = new File[numFiles]; - for (int i = 0; i < numFiles; i++) - { - // Testing with a number of files. The cached file cleaner will be able to determine the 'original' - // content URL for each file by either retrieving from the companion properties file, or performing - // a 'reverse lookup' in the cache (i.e. cache.contains(Key.forCacheFile(...))), or there will be no - // URL determinable for the file. - UrlSource urlSource = UrlSource.values()[i % UrlSource.values().length]; - File cacheFile = createCacheFile(urlSource, false); - files[i] = cacheFile; - totalSize += cacheFile.length(); - } - - // Run cleaner - cleaner.execute(); - - Thread.sleep(400); - while (cleaner.isRunning()) - { - Thread.sleep(200); - } - - // check all files deleted - for (File file : files) - { - assertFalse("File should have been deleted: " + file, file.exists()); - } - - assertEquals("Incorrect number of deleted files", numFiles, cleaner.getNumFilesDeleted()); - assertEquals("Incorrect total size of files deleted", totalSize, cleaner.getSizeFilesDeleted()); - } - - - @Test - public void filesNewerThanMinFileAgeMillisAreNotDeleted() throws InterruptedException - { - final long minFileAge = 5000; - cleaner.setMinFileAgeMillis(minFileAge); - cleaner.setMaxDeleteWatchCount(0); - int numFiles = 10; - - File[] oldFiles = new File[numFiles]; - for (int i = 0; i < numFiles; i++) - { - oldFiles[i] = createCacheFile(UrlSource.REVERSE_CACHE_LOOKUP, false); - } - - // Sleep to make sure 'old' files really are older than minFileAgeMillis - Thread.sleep(minFileAge); - - File[] newFiles = new File[numFiles]; - long newFilesTotalSize = 0; - for (int i = 0; i < numFiles; i++) - { - newFiles[i] = createCacheFile(UrlSource.REVERSE_CACHE_LOOKUP, false); - newFilesTotalSize += newFiles[i].length(); - } - - - // The cleaner must finish before any of the newFiles are older than minFileAge. If the files are too - // old the test will fail and it will be necessary to rethink how to test this. - cleaner.execute(); - - Thread.sleep(400); - while (cleaner.isRunning()) - { - Thread.sleep(200); - } - - if (cleaner.getDurationMillis() > minFileAge) - { - fail("Test unable to complete, since cleaner took " + cleaner.getDurationMillis() + "ms" + - " which is longer than minFileAge [" + minFileAge + "ms]"); - } - - // check all 'old' files deleted - for (File file : oldFiles) - { - assertFalse("File should have been deleted: " + file, file.exists()); - } - // check all 'new' files still present - for (File file : newFiles) - { - assertTrue("File should not have been deleted: " + file, file.exists()); - } - - assertEquals("Incorrect number of deleted files", newFiles.length, cleaner.getNumFilesDeleted()); - assertEquals("Incorrect total size of files deleted", newFilesTotalSize, cleaner.getSizeFilesDeleted()); - } - - @Test - public void aggressiveCleanReclaimsTargetSpace() throws InterruptedException - { - int numFiles = 30; - File[] files = new File[numFiles]; - for (int i = 0; i < numFiles; i++) - { - // Make sure it's in the cache - all the files will be in the cache, so the - // cleaner won't clean any up once it has finished aggressively reclaiming space. - files[i] = createCacheFile(UrlSource.REVERSE_CACHE_LOOKUP, true); - } - - // How much space to reclaim - seven files worth (all files are same size) - long fileSize = files[0].length(); - long sevenFilesSize = 7 * fileSize; - - // We'll get it to clean seven files worth aggressively and then it will continue non-aggressively. - // It will delete the older files aggressively (i.e. the ones prior to the two second sleep) and - // then will examine the new files for potential deletion. - // Since some of the newer files are not in the cache, it will delete those. - cleaner.executeAggressive("aggressiveCleanReclaimsTargetSpace()", sevenFilesSize); - - Thread.sleep(400); - while (cleaner.isRunning()) - { - Thread.sleep(200); - } - - int numDeleted = 0; - - for (File f : files) - { - if (!f.exists()) - { - numDeleted++; - } - } - // How many were definitely deleted? - assertEquals("Wrong number of files deleted", 7 , numDeleted); - - // The cleaner should have recorded the correct number of deletions - assertEquals("Incorrect number of deleted files", 7, cleaner.getNumFilesDeleted()); - assertEquals("Incorrect total size of files deleted", sevenFilesSize, cleaner.getSizeFilesDeleted()); - } - - - @Test - public void standardCleanAfterAggressiveFinished() throws InterruptedException - { - // Don't use numFiles > 59! as we're using this for the minute element in the cache file path. - final int numFiles = 30; - File[] files = new File[numFiles]; - - - for (int i = 0; i < numFiles; i++) - { - Calendar calendar = new GregorianCalendar(2010, 11, 2, 17, i); - - if (i >= 21 && i <= 24) - { - // 21 to 24 will be deleted after the aggressive deletions (once the cleaner has returned - // to normal cleaning), because they are not in the cache. - files[i] = createCacheFile(calendar, UrlSource.NOT_PRESENT, false); - } - else - { - // All other files will be in the cache - files[i] = createCacheFile(calendar, UrlSource.REVERSE_CACHE_LOOKUP, true); - } - } - - // How much space to reclaim - seven files worth (all files are same size) - long fileSize = files[0].length(); - long sevenFilesSize = 7 * fileSize; - - // We'll get it to clean seven files worth aggressively and then it will continue non-aggressively. - // It will delete the older files aggressively (i.e. even if they are actively in the cache) and - // then will examine the new files for potential deletion. - // Since some of the newer files are not in the cache, it will delete those too. - cleaner.executeAggressive("standardCleanAfterAggressiveFinished()", sevenFilesSize); - - Thread.sleep(400); - while (cleaner.isRunning()) - { - Thread.sleep(200); - } - - for (int i = 0; i < numFiles; i++) - { - if (i < 7) - { - assertFalse("First 7 files should have been aggressively cleaned", files[i].exists()); - } - - if (i >= 21 && i <= 24) - { - assertFalse("Files with indexes 21-24 should have been deleted", files[i].exists()); - } - } - assertEquals("Incorrect number of deleted files", 11, cleaner.getNumFilesDeleted()); - assertEquals("Incorrect total size of files deleted", (11*fileSize), cleaner.getSizeFilesDeleted()); - } - - @Test - public void emptyParentDirectoriesAreDeleted() throws FileNotFoundException - { - cleaner.setMaxDeleteWatchCount(0); - File file = new File(cacheRoot, "243235984/a/b/c/d.bin"); - file.getParentFile().mkdirs(); - PrintWriter writer = new PrintWriter(file); - writer.println("Content for emptyParentDirectoriesAreDeleted"); - writer.close(); - assertTrue("Directory should exist", new File(cacheRoot, "243235984/a/b/c").exists()); - - cleaner.handle(file); - - assertFalse("Directory should have been deleted", new File(cacheRoot, "243235984").exists()); - } - - @Test - public void markedFilesHaveDeletionDeferredUntilCorrectPassOfCleaner() - { - // A non-advisable setting but useful for testing, maxDeleteWatchCount of zero - // which should result in immediate deletion upon discovery of content no longer in the cache. - cleaner.setMaxDeleteWatchCount(0); - File file = createCacheFile(UrlSource.NOT_PRESENT, false); - - cleaner.handle(file); - checkFilesDeleted(file); - - // Anticipated to be the most common setting: maxDeleteWatchCount of 1. - cleaner.setMaxDeleteWatchCount(1); - file = createCacheFile(UrlSource.NOT_PRESENT, false); - - cleaner.handle(file); - checkWatchCountForCacheFile(file, 1); - - cleaner.handle(file); - checkFilesDeleted(file); - - // Check that some other arbitrary figure for maxDeleteWatchCount works correctly. - cleaner.setMaxDeleteWatchCount(3); - file = createCacheFile(UrlSource.NOT_PRESENT, false); - - cleaner.handle(file); - checkWatchCountForCacheFile(file, 1); - - cleaner.handle(file); - checkWatchCountForCacheFile(file, 2); - - cleaner.handle(file); - checkWatchCountForCacheFile(file, 3); - - cleaner.handle(file); - checkFilesDeleted(file); - } - - - private void checkFilesDeleted(File file) - { - assertFalse("File should have been deleted: " + file, file.exists()); - CacheFileProps props = new CacheFileProps(file); - assertFalse("Properties file should have been deleted, cache file: " + file, props.exists()); - } - - - private void checkWatchCountForCacheFile(File file, Integer expectedWatchCount) - { - assertTrue("File should still exist: " + file, file.exists()); - CacheFileProps props = new CacheFileProps(file); - props.load(); - assertEquals("File should contain correct deleteWatchCount", expectedWatchCount, props.getDeleteWatchCount()); - } - - - @Test - public void filesInCacheAreNotDeleted() throws InterruptedException - { - cleaner.setMaxDeleteWatchCount(0); - - // The SlowContentStore will always give out content when asked, - // so asking for any content will cause something to be cached. - String url = makeContentUrl(); - int numFiles = 50; - for (int i = 0; i < numFiles; i++) - { - ContentReader reader = cachingStore.getReader(url); - reader.getContentString(); - } - - cleaner.execute(); - - Thread.sleep(400); - while (cleaner.isRunning()) - { - Thread.sleep(200); - } - - for (int i = 0; i < numFiles; i++) - { - File cacheFile = new File(cache.getCacheFilePath(url)); - assertTrue("File should exist", cacheFile.exists()); - } - } - - private File createCacheFile(UrlSource urlSource, boolean putInCache) - { - Calendar calendar = new GregorianCalendar(); - return createCacheFile(calendar, urlSource, putInCache); - } - - private File createCacheFile(Calendar calendar, /*int year, int month, int day, int hour, int minute,*/ - UrlSource urlSource, boolean putInCache) - { - File file = new File(cacheRoot, createNewCacheFilePath(calendar)); - file.getParentFile().mkdirs(); - writeSampleContent(file); - String contentUrl = makeContentUrl(); - - if (putInCache) - { - cache.putIntoLookup(Key.forUrl(contentUrl), file.getAbsolutePath()); - } - - switch(urlSource) - { - case NOT_PRESENT: - // cache won't be able to determine original content URL for the file - break; - case PROPS_FILE: - // file with content URL in properties file - CacheFileProps props = new CacheFileProps(file); - props.setContentUrl(contentUrl); - props.store(); - break; - case REVERSE_CACHE_LOOKUP: - // file with content URL in reverse lookup cache - but not 'in the cache' (forward lookup). - cache.putIntoLookup(Key.forCacheFile(file), contentUrl); - } - assertTrue("File should exist", file.exists()); - return file; - } - - - /** - * Mimick functionality of ContentCacheImpl.createNewCacheFilePath() - * but allowing a specific date (rather than 'now') to be used. - * - * @param calendar Calendar - * @return Path to use for cache file. - */ - private String createNewCacheFilePath(Calendar calendar) - { - int year = calendar.get(Calendar.YEAR); - int month = calendar.get(Calendar.MONTH) + 1; // 0-based - int day = calendar.get(Calendar.DAY_OF_MONTH); - int hour = calendar.get(Calendar.HOUR_OF_DAY); - int minute = calendar.get(Calendar.MINUTE); - // create the URL - StringBuilder sb = new StringBuilder(20); - sb.append(year).append('/') - .append(month).append('/') - .append(day).append('/') - .append(hour).append('/') - .append(minute).append('/') - .append(GUID.generate()).append(".bin"); - return sb.toString(); - } - - - private String makeContentUrl() - { - return "protocol://some/made/up/url/" + GUID.generate(); - } - - - private void writeSampleContent(File file) - { - try - { - PrintWriter writer = new PrintWriter(file); - writer.println("Content for sample file in " + getClass().getName()); - writer.close(); - } - catch (Throwable e) - { - throw new RuntimeException("Couldn't write file: " + file, e); - } - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.content.caching.cleanup; + +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.time.Duration; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.context.ApplicationContext; + +import org.alfresco.repo.content.caching.CacheFileProps; +import org.alfresco.repo.content.caching.CachingContentStore; +import org.alfresco.repo.content.caching.ContentCacheImpl; +import org.alfresco.repo.content.caching.Key; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; +import org.alfresco.util.testing.category.LuceneTests; + +/** + * Tests for the CachedContentCleanupJob + * + * @author Matt Ward + */ +@Category(LuceneTests.class) +public class CachedContentCleanupJobTest +{ + + private static final Duration MAX_WAIT_TIMEOUT = Duration.ofSeconds(10); + + private enum UrlSource + { + PROPS_FILE, REVERSE_CACHE_LOOKUP, NOT_PRESENT + } + + ; + + private static ApplicationContext ctx; + private CachingContentStore cachingStore; + private ContentCacheImpl cache; + private File cacheRoot; + private CachedContentCleaner cleaner; + + @BeforeClass + public static void beforeClass() + { + String cleanerConf = "classpath:cachingstore/test-cleaner-context.xml"; + ctx = ApplicationContextHelper.getApplicationContext(new String[]{cleanerConf}); + } + + @Before + public void setUp() throws IOException + { + cachingStore = (CachingContentStore) ctx.getBean("cachingContentStore"); + cache = (ContentCacheImpl) ctx.getBean("contentCache"); + cacheRoot = cache.getCacheRoot(); + cleaner = (CachedContentCleaner) ctx.getBean("cachedContentCleaner"); + cleaner.setMinFileAgeMillis(0); + cleaner.setMaxDeleteWatchCount(0); + + // Clear the cache from disk and memory + cache.removeAll(); + FileUtils.cleanDirectory(cacheRoot); + } + + @Test + public void filesNotInCacheAreDeleted() throws InterruptedException + { + cleaner.setMaxDeleteWatchCount(0); + int numFiles = 300; // Must be a multiple of number of UrlSource types being tested + long totalSize = 0; // what is the total size of the sample files? + File[] files = new File[numFiles]; + for (int i = 0; i < numFiles; i++) + { + // Testing with a number of files. The cached file cleaner will be able to determine the 'original' + // content URL for each file by either retrieving from the companion properties file, or performing + // a 'reverse lookup' in the cache (i.e. cache.contains(Key.forCacheFile(...))), or there will be no + // URL determinable for the file. + UrlSource urlSource = UrlSource.values()[i % UrlSource.values().length]; + File cacheFile = createCacheFile(urlSource, false); + files[i] = cacheFile; + totalSize += cacheFile.length(); + } + + // Run cleaner + cleaner.execute(); + + await().pollDelay(Duration.ofMillis(100)) + .atMost(MAX_WAIT_TIMEOUT) + .until(() -> !cleaner.isRunning()); + + // check all files deleted + for (File file : files) + { + assertFalse("File should have been deleted: " + file, file.exists()); + } + + assertEquals("Incorrect number of deleted files", numFiles, cleaner.getNumFilesDeleted()); + assertEquals("Incorrect total size of files deleted", totalSize, cleaner.getSizeFilesDeleted()); + } + + @Test + public void filesNewerThanMinFileAgeMillisAreNotDeleted() throws InterruptedException + { + final long minFileAge = 5000; + cleaner.setMinFileAgeMillis(minFileAge); + cleaner.setMaxDeleteWatchCount(0); + int numFiles = 10; + + File[] oldFiles = new File[numFiles]; + for (int i = 0; i < numFiles; i++) + { + oldFiles[i] = createCacheFile(UrlSource.REVERSE_CACHE_LOOKUP, false); + } + + // Sleep to make sure 'old' files really are older than minFileAgeMillis + Thread.sleep(minFileAge); + + File[] newFiles = new File[numFiles]; + long newFilesTotalSize = 0; + for (int i = 0; i < numFiles; i++) + { + newFiles[i] = createCacheFile(UrlSource.REVERSE_CACHE_LOOKUP, false); + newFilesTotalSize += newFiles[i].length(); + } + + // The cleaner must finish before any of the newFiles are older than minFileAge. If the files are too + // old the test will fail and it will be necessary to rethink how to test this. + cleaner.execute(); + + await().pollDelay(Duration.ofMillis(100)) + .atMost(MAX_WAIT_TIMEOUT) + .until(() -> !cleaner.isRunning()); + + if (cleaner.getDurationMillis() > minFileAge) + { + fail("Test unable to complete, since cleaner took " + cleaner.getDurationMillis() + "ms" + + " which is longer than minFileAge [" + minFileAge + "ms]"); + } + + // check all 'old' files deleted + for (File file : oldFiles) + { + assertFalse("File should have been deleted: " + file, file.exists()); + } + // check all 'new' files still present + for (File file : newFiles) + { + assertTrue("File should not have been deleted: " + file, file.exists()); + } + + assertEquals("Incorrect number of deleted files", newFiles.length, cleaner.getNumFilesDeleted()); + assertEquals("Incorrect total size of files deleted", newFilesTotalSize, cleaner.getSizeFilesDeleted()); + } + + @Test + public void aggressiveCleanReclaimsTargetSpace() throws InterruptedException + { + int numFiles = 30; + File[] files = new File[numFiles]; + for (int i = 0; i < numFiles; i++) + { + // Make sure it's in the cache - all the files will be in the cache, so the + // cleaner won't clean any up once it has finished aggressively reclaiming space. + files[i] = createCacheFile(UrlSource.REVERSE_CACHE_LOOKUP, true); + } + + // How much space to reclaim - seven files worth (all files are same size) + long fileSize = files[0].length(); + long sevenFilesSize = 7 * fileSize; + + // We'll get it to clean seven files worth aggressively and then it will continue non-aggressively. + // It will delete the older files aggressively (i.e. the ones prior to the two second sleep) and + // then will examine the new files for potential deletion. + // Since some of the newer files are not in the cache, it will delete those. + cleaner.executeAggressive("aggressiveCleanReclaimsTargetSpace()", sevenFilesSize); + + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + + int numDeleted = 0; + + for (File f : files) + { + if (!f.exists()) + { + numDeleted++; + } + } + // How many were definitely deleted? + assertEquals("Wrong number of files deleted", 7, numDeleted); + + // The cleaner should have recorded the correct number of deletions + assertEquals("Incorrect number of deleted files", 7, cleaner.getNumFilesDeleted()); + assertEquals("Incorrect total size of files deleted", sevenFilesSize, cleaner.getSizeFilesDeleted()); + } + + @Test + public void standardCleanAfterAggressiveFinished() throws InterruptedException + { + // Don't use numFiles > 59! as we're using this for the minute element in the cache file path. + final int numFiles = 30; + File[] files = new File[numFiles]; + + for (int i = 0; i < numFiles; i++) + { + Calendar calendar = new GregorianCalendar(2010, 11, 2, 17, i); + + if (i >= 21 && i <= 24) + { + // 21 to 24 will be deleted after the aggressive deletions (once the cleaner has returned + // to normal cleaning), because they are not in the cache. + files[i] = createCacheFile(calendar, UrlSource.NOT_PRESENT, false); + } + else + { + // All other files will be in the cache + files[i] = createCacheFile(calendar, UrlSource.REVERSE_CACHE_LOOKUP, true); + } + } + + // How much space to reclaim - seven files worth (all files are same size) + long fileSize = files[0].length(); + long sevenFilesSize = 7 * fileSize; + + // We'll get it to clean seven files worth aggressively and then it will continue non-aggressively. + // It will delete the older files aggressively (i.e. even if they are actively in the cache) and + // then will examine the new files for potential deletion. + // Since some of the newer files are not in the cache, it will delete those too. + cleaner.executeAggressive("standardCleanAfterAggressiveFinished()", sevenFilesSize); + + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + + for (int i = 0; i < numFiles; i++) + { + if (i < 7) + { + assertFalse("First 7 files should have been aggressively cleaned", files[i].exists()); + } + + if (i >= 21 && i <= 24) + { + assertFalse("Files with indexes 21-24 should have been deleted", files[i].exists()); + } + } + assertEquals("Incorrect number of deleted files", 11, cleaner.getNumFilesDeleted()); + assertEquals("Incorrect total size of files deleted", (11 * fileSize), cleaner.getSizeFilesDeleted()); + } + + @Test + public void emptyParentDirectoriesAreDeleted() throws FileNotFoundException + { + cleaner.setMaxDeleteWatchCount(0); + File file = new File(cacheRoot, "243235984/a/b/c/d.bin"); + file.getParentFile().mkdirs(); + PrintWriter writer = new PrintWriter(file); + writer.println("Content for emptyParentDirectoriesAreDeleted"); + writer.close(); + assertTrue("Directory should exist", new File(cacheRoot, "243235984/a/b/c").exists()); + + cleaner.handle(file); + + assertFalse("Directory should have been deleted", new File(cacheRoot, "243235984").exists()); + } + + @Test + public void markedFilesHaveDeletionDeferredUntilCorrectPassOfCleaner() + { + // A non-advisable setting but useful for testing, maxDeleteWatchCount of zero + // which should result in immediate deletion upon discovery of content no longer in the cache. + cleaner.setMaxDeleteWatchCount(0); + File file = createCacheFile(UrlSource.NOT_PRESENT, false); + + cleaner.handle(file); + checkFilesDeleted(file); + + // Anticipated to be the most common setting: maxDeleteWatchCount of 1. + cleaner.setMaxDeleteWatchCount(1); + file = createCacheFile(UrlSource.NOT_PRESENT, false); + + cleaner.handle(file); + checkWatchCountForCacheFile(file, 1); + + cleaner.handle(file); + checkFilesDeleted(file); + + // Check that some other arbitrary figure for maxDeleteWatchCount works correctly. + cleaner.setMaxDeleteWatchCount(3); + file = createCacheFile(UrlSource.NOT_PRESENT, false); + + cleaner.handle(file); + checkWatchCountForCacheFile(file, 1); + + cleaner.handle(file); + checkWatchCountForCacheFile(file, 2); + + cleaner.handle(file); + checkWatchCountForCacheFile(file, 3); + + cleaner.handle(file); + checkFilesDeleted(file); + } + + private void checkFilesDeleted(File file) + { + assertFalse("File should have been deleted: " + file, file.exists()); + CacheFileProps props = new CacheFileProps(file); + assertFalse("Properties file should have been deleted, cache file: " + file, props.exists()); + } + + private void checkWatchCountForCacheFile(File file, Integer expectedWatchCount) + { + assertTrue("File should still exist: " + file, file.exists()); + CacheFileProps props = new CacheFileProps(file); + props.load(); + assertEquals("File should contain correct deleteWatchCount", expectedWatchCount, props.getDeleteWatchCount()); + } + + @Test + public void filesInCacheAreNotDeleted() throws InterruptedException + { + cleaner.setMaxDeleteWatchCount(0); + + // The SlowContentStore will always give out content when asked, + // so asking for any content will cause something to be cached. + String url = makeContentUrl(); + int numFiles = 50; + for (int i = 0; i < numFiles; i++) + { + ContentReader reader = cachingStore.getReader(url); + reader.getContentString(); + } + + cleaner.execute(); + + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + + for (int i = 0; i < numFiles; i++) + { + File cacheFile = new File(cache.getCacheFilePath(url)); + assertTrue("File should exist", cacheFile.exists()); + } + } + + private File createCacheFile(UrlSource urlSource, boolean putInCache) + { + Calendar calendar = new GregorianCalendar(); + return createCacheFile(calendar, urlSource, putInCache); + } + + private File createCacheFile(Calendar calendar, /* int year, int month, int day, int hour, int minute, */ + UrlSource urlSource, boolean putInCache) + { + File file = new File(cacheRoot, createNewCacheFilePath(calendar)); + file.getParentFile().mkdirs(); + writeSampleContent(file); + String contentUrl = makeContentUrl(); + + if (putInCache) + { + cache.putIntoLookup(Key.forUrl(contentUrl), file.getAbsolutePath()); + } + + switch (urlSource) + { + case NOT_PRESENT: + // cache won't be able to determine original content URL for the file + break; + case PROPS_FILE: + // file with content URL in properties file + CacheFileProps props = new CacheFileProps(file); + props.setContentUrl(contentUrl); + props.store(); + break; + case REVERSE_CACHE_LOOKUP: + // file with content URL in reverse lookup cache - but not 'in the cache' (forward lookup). + cache.putIntoLookup(Key.forCacheFile(file), contentUrl); + } + assertTrue("File should exist", file.exists()); + return file; + } + + /** + * Mimick functionality of ContentCacheImpl.createNewCacheFilePath() but allowing a specific date (rather than 'now') to be used. + * + * @param calendar + * Calendar + * @return Path to use for cache file. + */ + private String createNewCacheFilePath(Calendar calendar) + { + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH) + 1; // 0-based + int day = calendar.get(Calendar.DAY_OF_MONTH); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + // create the URL + StringBuilder sb = new StringBuilder(20); + sb.append(year).append('/') + .append(month).append('/') + .append(day).append('/') + .append(hour).append('/') + .append(minute).append('/') + .append(GUID.generate()).append(".bin"); + return sb.toString(); + } + + private String makeContentUrl() + { + return "protocol://some/made/up/url/" + GUID.generate(); + } + + private void writeSampleContent(File file) + { + try + { + PrintWriter writer = new PrintWriter(file); + writer.println("Content for sample file in " + getClass().getName()); + writer.close(); + } + catch (Throwable e) + { + throw new RuntimeException("Couldn't write file: " + file, e); + } + } +} diff --git a/repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java b/repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java index 015d04ce2f..a9fa2f4cb5 100644 --- a/repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java +++ b/repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java @@ -1,655 +1,661 @@ -/* - * #%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.lock; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.node.archive.NodeArchiveService; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.lock.NodeLockedException; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.CopyService; -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.security.MutableAuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.version.VersionService; -import org.alfresco.service.namespace.QName; -import org.alfresco.test_category.OwnJVMTestsCategory; -import org.alfresco.util.BaseSpringTest; -import org.alfresco.util.TestWithUserUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.springframework.transaction.annotation.Transactional; - -/** - * LockBehaviourImpl Unit Test. - * - * @author Roy Wetherall - */ -@Category(OwnJVMTestsCategory.class) -@Transactional -public class LockBehaviourImplTest extends BaseSpringTest -{ - /** - * The lock service - */ - private LockService lockService; - - /** - * The version service - */ - private VersionService versionService; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * The Copy service - */ - private CopyService copyService; - - /** - * The NodeArchiveService service - */ - private NodeArchiveService nodeArchiveService; - - /** - * The authentication service - */ - private MutableAuthenticationService authenticationService; - - private PermissionService permissionService; - - /** - * Node references used in the tests - */ - private NodeRef nodeRef; - private NodeRef noAspectNode; - private NodeRef inSpaceStoreNode; - - /** - * Store reference - */ - private StoreRef storeRef; - - /** - * User details - */ - private static final String PWD = "password"; - private static final String GOOD_USER_NAME = "goodUser"; - private static final String BAD_USER_NAME = "badUser"; - private static final String BAD_USER_WITH_ALL_PERMS_NAME = "badUserOwns"; - - NodeRef rootNodeRef; - - @Before - public void before() throws Exception - { - this.nodeService = (NodeService)applicationContext.getBean("dbNodeService"); - this.lockService = (LockService)applicationContext.getBean("lockService"); - this.versionService = (VersionService)applicationContext.getBean("versionService"); - this.authenticationService = (MutableAuthenticationService)applicationContext.getBean("authenticationService"); - this.permissionService = (PermissionService)applicationContext.getBean("permissionService"); - this.copyService = (CopyService)applicationContext.getBean("copyService"); - this.nodeArchiveService = (NodeArchiveService)applicationContext.getBean("nodeArchiveService"); - - // Set the authentication - AuthenticationComponent authComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); - authComponent.setSystemUserAsCurrentUser(); - - // Create the node properties - HashMap nodeProperties = new HashMap(); - nodeProperties.put(QName.createQName("{test}property1"), "value1"); - - // Create a workspace that contains the 'live' nodes - this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - - // Get a reference to the root node - rootNodeRef = this.nodeService.getRootNode(this.storeRef); - - // Create node - this.nodeRef = this.nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}ParentNode"), - ContentModel.TYPE_FOLDER, - nodeProperties).getChildRef(); - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, new HashMap()); - assertNotNull(this.nodeRef); - - this.inSpaceStoreNode = this.nodeService.createNode( - nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}ParentNode"), - ContentModel.TYPE_FOLDER, - nodeProperties).getChildRef(); - this.nodeService.addAspect(this.inSpaceStoreNode, ContentModel.ASPECT_LOCKABLE, new HashMap()); - assertNotNull(this.inSpaceStoreNode); - - // Create a node with no lockAspect - this.noAspectNode = this.nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}noAspectNode"), - ContentModel.TYPE_CONTAINER, - nodeProperties).getChildRef(); - assertNotNull(this.noAspectNode); - - // Create the users - TestWithUserUtils.createUser(GOOD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); - TestWithUserUtils.createUser(BAD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); - TestWithUserUtils.createUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); - - // Stash the user node ref's for later use - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); - - permissionService.setPermission(rootNodeRef, GOOD_USER_NAME, PermissionService.ALL_PERMISSIONS, true); - permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.READ, true); - permissionService.setPermission(rootNodeRef, BAD_USER_WITH_ALL_PERMS_NAME, PermissionService.ALL_PERMISSIONS, true); - - permissionService.setPermission(inSpaceStoreNode, GOOD_USER_NAME, PermissionService.ALL_PERMISSIONS, true); - permissionService.setPermission(inSpaceStoreNode, BAD_USER_WITH_ALL_PERMS_NAME, PermissionService.ALL_PERMISSIONS, true); - } - - /** - * Test checkForLock (no user specified) - */ - @Test - public void testCheckForLockNoUser() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - this.lockService.checkForLock(this.nodeRef); - assertFalse(lockService.isLocked(nodeRef)); - this.lockService.checkForLock(this.noAspectNode); - assertFalse(lockService.isLocked(noAspectNode)); - - // Give the node a write lock (as the good user) - this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); - - this.lockService.checkForLock(this.nodeRef); - assertTrue(lockService.isLocked(nodeRef)); - assertFalse(lockService.isLockedAndReadOnly(nodeRef)); - - // Unlock - this.lockService.unlock(this.nodeRef); - - assertFalse(lockService.isLocked(nodeRef)); - assertFalse(lockService.isLockedAndReadOnly(nodeRef)); - - // Give the node a read only lock (as the good user) - this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK); - try - { - this.lockService.checkForLock(this.nodeRef); - fail("The node locked exception should have been raised"); - } - catch (NodeLockedException exception) - { - // Correct behaviour - } - assertTrue(lockService.isLocked(nodeRef)); - assertTrue(lockService.isLockedAndReadOnly(nodeRef)); - - // Unlock - this.lockService.unlock(this.nodeRef); - assertFalse(lockService.isLocked(nodeRef)); - assertFalse(lockService.isLockedAndReadOnly(nodeRef)); - - // Give the node a write lock (as the bad user) - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - try - { - this.lockService.checkForLock(this.nodeRef); - fail("The node locked exception should have been raised"); - } - catch (NodeLockedException exception) - { - // Correct behaviour - } - - assertTrue(lockService.isLocked(nodeRef)); - assertTrue(lockService.isLockedAndReadOnly(nodeRef)); - - // Give the node a read only lock (as the bad user) - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.lockService.unlock(this.nodeRef); - assertFalse(lockService.isLocked(nodeRef)); - assertFalse(lockService.isLockedAndReadOnly(nodeRef)); - - this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - try - { - this.lockService.checkForLock(this.nodeRef); - fail("The node locked exception should have been raised"); - } - catch (NodeLockedException exception) - { - // Correct behaviour - } - - assertTrue(lockService.isLocked(nodeRef)); - assertTrue(lockService.isLockedAndReadOnly(nodeRef)); - } - - @Test - public void testCheckForLockWhenExpired() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK, 1); - try - { - this.lockService.checkForLock(this.nodeRef); - fail("Should be locked."); - } - catch (NodeLockedException e) - { - // Expected - } - - assertTrue(lockService.isLocked(nodeRef)); - assertTrue(lockService.isLockedAndReadOnly(nodeRef)); - - try {Thread.sleep(2*1000); } catch (Exception e) {}; - - // Should now have expired so the node should no longer appear to be locked - this.lockService.checkForLock(this.nodeRef); - - assertFalse(lockService.isLocked(nodeRef)); - assertFalse(lockService.isLockedAndReadOnly(nodeRef)); - } - - /** - * Test version service lock checking - */ - @Test - public void testVersionServiceLockBehaviour01() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Add the version aspect to the node - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE, null); - - try - { - this.versionService.createVersion(this.nodeRef, new HashMap()); - } - catch (NodeLockedException exception) - { - fail("There is no lock so this should have worked."); - } - - // Lock the node as the good user with a write lock - this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); - try - { - this.versionService.createVersion(this.nodeRef, new HashMap()); - } - catch (NodeLockedException exception) - { - fail("Tried to version as the lock owner so should work."); - } - this.lockService.unlock(this.nodeRef); - - // Lock the node as the good user with a read only lock - this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK); - try - { - this.versionService.createVersion(this.nodeRef, new HashMap()); - } - catch (NodeLockedException exception) - { - fail("Should have passed, as we should be able to create a version. See ALF-16540"); - } - this.lockService.unlock(this.nodeRef); - } - - /** - * Test version service lock checking - */ - @Test - public void testVersionServiceLockBehaviour02() - { - // Add the version aspect to the node - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE, null); - - // Lock the node as the bad user with a write lock - this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); - try - { - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - this.versionService.createVersion(this.nodeRef, new HashMap()); - fail("Should have failed since this node has been locked by another user with a write lock."); - } - catch (AccessDeniedException exception) - { - // Exception occurs when the properties are updated for a node - } - } - - /** - * Test that the node service lock behaviour is as we expect - * - */ - @SuppressWarnings("unused") - public void testNodeServiceLockBehaviour() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check that we can create a new node and set of it properties when no lock is present - ChildAssociationRef childAssocRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTAINER); - NodeRef nodeRef = childAssocRef.getChildRef(); - - // Lets lock the parent node and check that whether we can still create a new node - this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); - ChildAssociationRef childAssocRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTAINER); - NodeRef nodeRef2 = childAssocRef.getChildRef(); - - // Lets check that we can do other stuff with the node since we have it locked - this.nodeService.setProperty(this.nodeRef, QName.createQName("{test}prop1"), "value1"); - Map propMap = new HashMap(); - propMap.put(QName.createQName("{test}prop2"), "value2"); - this.nodeService.setProperties(this.nodeRef, propMap); - this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); - // TODO there are various other calls that could be more vigirously checked - - // Lock the node as the 'bad' user - this.lockService.unlock(this.nodeRef); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Lets check that we can't create a new child - try - { - this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTAINER); - fail("The parent is locked so a new child should not have been created."); - } - catch(NodeLockedException exception) - { - } - - // TODO various other tests along these lines ... - - // TODO check that delete is also working - } - - /** - * ALF-5680: It is possible to cut/paste a locked file - */ - @Test - public void testCannotMoveNodeWhenLocked() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Create the node that we'll try to move - NodeRef parentNode = this.nodeRef; - ChildAssociationRef childAssocRef = nodeService.createNode( - parentNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTENT); - - NodeRef nodeRef = childAssocRef.getChildRef(); - // Lock it - so that it can't be moved. - this.lockService.lock(nodeRef, LockType.WRITE_LOCK); - - // Create the new container that we'll move the node to. - NodeRef newParentRef = nodeService.createNode( - parentNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTAINER).getChildRef(); - - // Now the bad user will try to move the node. - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - try - { - nodeService.moveNode( - nodeRef, - newParentRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest")); - fail("Shouldn't have been able to move locked node."); - } - catch (NodeLockedException e) - { - // Good, we can't move it - as expected. - } - } - - /** - * MNT-9475: Moving locked content breaks edit online - */ - @Test - public void testCanMoveCopyDeleteWithLockOwner() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Create the node that we'll try to move, copy & delete - NodeRef parentNode = this.nodeRef; - ChildAssociationRef childAssocRef = nodeService.createNode( - parentNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTENT); - - NodeRef nodeRef = childAssocRef.getChildRef(); - // Lock it - this.lockService.lock(nodeRef, LockType.WRITE_LOCK); - - // Create the node that we'll try to archive and restore - NodeRef archivingBehaviorNodeRef = nodeService.createNode( - this.inSpaceStoreNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTENT).getChildRef(); - // Lock it - this.lockService.lock(archivingBehaviorNodeRef, LockType.WRITE_LOCK); - - // Create the new container that we'll move the node to. - NodeRef newParentRefToMove = nodeService.createNode( - parentNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTAINER).getChildRef(); - - // Create the new container that we'll copy the node to. - NodeRef newParentRefToCopy = nodeService.createNode( - parentNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTAINER).getChildRef(); - - try - { - // user should be able to move node - nodeService.moveNode(nodeRef, newParentRefToMove, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); - - // copy it - copyService.copy(nodeRef, newParentRefToCopy, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); - - // and delete node - nodeService.deleteNode(nodeRef); - } - catch (NodeLockedException e) - { - fail("Should be moved, copied an deleted."); - } - - childAssocRef = nodeService.createNode( - parentNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTENT); - - nodeRef = childAssocRef.getChildRef(); - - // Create the new container that we'll copy the node to. - newParentRefToCopy = nodeService.createNode( - parentNode, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}nodeServiceLockTest"), - ContentModel.TYPE_CONTAINER).getChildRef(); - - this.lockService.lock(nodeRef, LockType.WRITE_LOCK); - - TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); - - try - { - // Node Can be Copied by Not LockOwner - copyService.copy(nodeRef, newParentRefToCopy, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); - } - catch (NodeLockedException e) - { - fail("Should be copied."); - } - - try - { - nodeService.deleteNode(newParentRefToCopy); - } - catch (NodeLockedException e) - { - fail("Should not have any locks."); - } - try - { - nodeService.deleteNode(nodeRef); - fail("Should not be deleted."); - } - catch (NodeLockedException e) - { - // Only LockOwner can Delete Node - } - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - nodeService.deleteNode(archivingBehaviorNodeRef); - NodeRef archivedNode = nodeArchiveService.getArchivedNode(archivingBehaviorNodeRef); - - // check for lock for archived node - checkForLockForBadAndGoodUsers(archivedNode); - - TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); - try - { - // Try to restore archived node by Not Lock Owner - archivingBehaviorNodeRef = nodeService.restoreNode(archivedNode, - this.inSpaceStoreNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); - } - catch (Exception e) - { - fail("Should not be any Exceptons."); - } - - // check for lock for restored node by bad user - checkForLockForBadAndGoodUsers(archivingBehaviorNodeRef); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - nodeService.deleteNode(archivingBehaviorNodeRef); - try - { - archivingBehaviorNodeRef = nodeService.restoreNode(archivedNode, - this.inSpaceStoreNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); - } - catch (Exception e) - { - fail("Should not be any Exceptons."); - } - - // check for lock for restored node by good user - checkForLockForBadAndGoodUsers(archivingBehaviorNodeRef); - } - - private void checkForLockForBadAndGoodUsers(NodeRef nodeToCheck) - { - String currentUserName = TestWithUserUtils.getCurrentUser(this.authenticationService); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - try - { - lockService.checkForLock(nodeToCheck); - } - catch (NodeLockedException e) - { - fail("Should not be locked for GoodUser : " + nodeToCheck); - } - assertTrue(lockService.isLocked(nodeToCheck)); - assertFalse(lockService.isLockedAndReadOnly(nodeToCheck)); - - TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); - try - { - lockService.checkForLock(nodeToCheck); - fail("Should be locked for BadUser : " + nodeToCheck); - } - catch (NodeLockedException e) - { - // It's Ok - } - assertTrue(lockService.isLocked(nodeToCheck)); - assertTrue(lockService.isLockedAndReadOnly(nodeToCheck)); - - TestWithUserUtils.authenticateUser(currentUserName, PWD, rootNodeRef, this.authenticationService); - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.lock; + +import static org.apache.commons.lang3.RandomStringUtils.secure; +import static org.awaitility.Awaitility.await; + +import java.io.Serializable; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.transaction.annotation.Transactional; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.archive.NodeArchiveService; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.lock.NodeLockedException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.CopyService; +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.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.test_category.OwnJVMTestsCategory; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.TestWithUserUtils; + +/** + * LockBehaviourImpl Unit Test. + * + * @author Roy Wetherall + */ +@Category(OwnJVMTestsCategory.class) +@Transactional +public class LockBehaviourImplTest extends BaseSpringTest +{ + /** + * The lock service + */ + private LockService lockService; + + /** + * The version service + */ + private VersionService versionService; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * The Copy service + */ + private CopyService copyService; + + /** + * The NodeArchiveService service + */ + private NodeArchiveService nodeArchiveService; + + /** + * The authentication service + */ + private MutableAuthenticationService authenticationService; + + private PermissionService permissionService; + + /** + * Node references used in the tests + */ + private NodeRef nodeRef; + private NodeRef noAspectNode; + private NodeRef inSpaceStoreNode; + + /** + * Store reference + */ + private StoreRef storeRef; + + /** + * User details + */ + private static final String PWD = secure().nextAlphabetic(10); + private static final String GOOD_USER_NAME = "goodUser"; + private static final String BAD_USER_NAME = "badUser"; + private static final String BAD_USER_WITH_ALL_PERMS_NAME = "badUserOwns"; + + NodeRef rootNodeRef; + + @Before + public void before() throws Exception + { + this.nodeService = (NodeService) applicationContext.getBean("dbNodeService"); + this.lockService = (LockService) applicationContext.getBean("lockService"); + this.versionService = (VersionService) applicationContext.getBean("versionService"); + this.authenticationService = (MutableAuthenticationService) applicationContext.getBean("authenticationService"); + this.permissionService = (PermissionService) applicationContext.getBean("permissionService"); + this.copyService = (CopyService) applicationContext.getBean("copyService"); + this.nodeArchiveService = (NodeArchiveService) applicationContext.getBean("nodeArchiveService"); + + // Set the authentication + AuthenticationComponent authComponent = (AuthenticationComponent) this.applicationContext.getBean("authenticationComponent"); + authComponent.setSystemUserAsCurrentUser(); + + // Create the node properties + HashMap nodeProperties = new HashMap(); + nodeProperties.put(QName.createQName("{test}property1"), "value1"); + + // Create a workspace that contains the 'live' nodes + this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + + // Get a reference to the root node + rootNodeRef = this.nodeService.getRootNode(this.storeRef); + + // Create node + this.nodeRef = this.nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}ParentNode"), + ContentModel.TYPE_FOLDER, + nodeProperties).getChildRef(); + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, new HashMap()); + assertNotNull(this.nodeRef); + + this.inSpaceStoreNode = this.nodeService.createNode( + nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}ParentNode"), + ContentModel.TYPE_FOLDER, + nodeProperties).getChildRef(); + this.nodeService.addAspect(this.inSpaceStoreNode, ContentModel.ASPECT_LOCKABLE, new HashMap()); + assertNotNull(this.inSpaceStoreNode); + + // Create a node with no lockAspect + this.noAspectNode = this.nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}noAspectNode"), + ContentModel.TYPE_CONTAINER, + nodeProperties).getChildRef(); + assertNotNull(this.noAspectNode); + + // Create the users + TestWithUserUtils.createUser(GOOD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); + TestWithUserUtils.createUser(BAD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); + TestWithUserUtils.createUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); + + // Stash the user node ref's for later use + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); + + permissionService.setPermission(rootNodeRef, GOOD_USER_NAME, PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.READ, true); + permissionService.setPermission(rootNodeRef, BAD_USER_WITH_ALL_PERMS_NAME, PermissionService.ALL_PERMISSIONS, true); + + permissionService.setPermission(inSpaceStoreNode, GOOD_USER_NAME, PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(inSpaceStoreNode, BAD_USER_WITH_ALL_PERMS_NAME, PermissionService.ALL_PERMISSIONS, true); + } + + /** + * Test checkForLock (no user specified) + */ + @Test + public void testCheckForLockNoUser() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + this.lockService.checkForLock(this.nodeRef); + assertFalse(lockService.isLocked(nodeRef)); + this.lockService.checkForLock(this.noAspectNode); + assertFalse(lockService.isLocked(noAspectNode)); + + // Give the node a write lock (as the good user) + this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); + + this.lockService.checkForLock(this.nodeRef); + assertTrue(lockService.isLocked(nodeRef)); + assertFalse(lockService.isLockedAndReadOnly(nodeRef)); + + // Unlock + this.lockService.unlock(this.nodeRef); + + assertFalse(lockService.isLocked(nodeRef)); + assertFalse(lockService.isLockedAndReadOnly(nodeRef)); + + // Give the node a read only lock (as the good user) + this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK); + try + { + this.lockService.checkForLock(this.nodeRef); + fail("The node locked exception should have been raised"); + } + catch (NodeLockedException exception) + { + // Correct behaviour + } + assertTrue(lockService.isLocked(nodeRef)); + assertTrue(lockService.isLockedAndReadOnly(nodeRef)); + + // Unlock + this.lockService.unlock(this.nodeRef); + assertFalse(lockService.isLocked(nodeRef)); + assertFalse(lockService.isLockedAndReadOnly(nodeRef)); + + // Give the node a write lock (as the bad user) + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + try + { + this.lockService.checkForLock(this.nodeRef); + fail("The node locked exception should have been raised"); + } + catch (NodeLockedException exception) + { + // Correct behaviour + } + + assertTrue(lockService.isLocked(nodeRef)); + assertTrue(lockService.isLockedAndReadOnly(nodeRef)); + + // Give the node a read only lock (as the bad user) + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.lockService.unlock(this.nodeRef); + assertFalse(lockService.isLocked(nodeRef)); + assertFalse(lockService.isLockedAndReadOnly(nodeRef)); + + this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + try + { + this.lockService.checkForLock(this.nodeRef); + fail("The node locked exception should have been raised"); + } + catch (NodeLockedException exception) + { + // Correct behaviour + } + + assertTrue(lockService.isLocked(nodeRef)); + assertTrue(lockService.isLockedAndReadOnly(nodeRef)); + } + + @Test + public void testCheckForLockWhenExpired() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK, 1); + try + { + this.lockService.checkForLock(this.nodeRef); + fail("Should be locked."); + } + catch (NodeLockedException e) + { + // Expected + } + + assertTrue(lockService.isLocked(nodeRef)); + assertTrue(lockService.isLockedAndReadOnly(nodeRef)); + + await().pollInSameThread() + .pollDelay(Duration.ofMillis(500)) + .atMost(MAX_ASYNC_TIMEOUT) + .until(() -> !lockService.isLocked(nodeRef)); + + // Should now have expired so the node should no longer appear to be locked + this.lockService.checkForLock(this.nodeRef); + + assertFalse(lockService.isLocked(nodeRef)); + assertFalse(lockService.isLockedAndReadOnly(nodeRef)); + } + + /** + * Test version service lock checking + */ + @Test + public void testVersionServiceLockBehaviour01() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Add the version aspect to the node + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + try + { + this.versionService.createVersion(this.nodeRef, new HashMap()); + } + catch (NodeLockedException exception) + { + fail("There is no lock so this should have worked."); + } + + // Lock the node as the good user with a write lock + this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); + try + { + this.versionService.createVersion(this.nodeRef, new HashMap()); + } + catch (NodeLockedException exception) + { + fail("Tried to version as the lock owner so should work."); + } + this.lockService.unlock(this.nodeRef); + + // Lock the node as the good user with a read only lock + this.lockService.lock(this.nodeRef, LockType.READ_ONLY_LOCK); + try + { + this.versionService.createVersion(this.nodeRef, new HashMap()); + } + catch (NodeLockedException exception) + { + fail("Should have passed, as we should be able to create a version. See ALF-16540"); + } + this.lockService.unlock(this.nodeRef); + } + + /** + * Test version service lock checking + */ + @Test + public void testVersionServiceLockBehaviour02() + { + // Add the version aspect to the node + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + // Lock the node as the bad user with a write lock + this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); + try + { + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + this.versionService.createVersion(this.nodeRef, new HashMap()); + fail("Should have failed since this node has been locked by another user with a write lock."); + } + catch (AccessDeniedException exception) + { + // Exception occurs when the properties are updated for a node + } + } + + /** + * Test that the node service lock behaviour is as we expect + */ + @SuppressWarnings("unused") + public void testNodeServiceLockBehaviour() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check that we can create a new node and set of it properties when no lock is present + ChildAssociationRef childAssocRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER); + NodeRef nodeRef = childAssocRef.getChildRef(); + + // Lets lock the parent node and check that whether we can still create a new node + this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); + ChildAssociationRef childAssocRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER); + NodeRef nodeRef2 = childAssocRef.getChildRef(); + + // Lets check that we can do other stuff with the node since we have it locked + this.nodeService.setProperty(this.nodeRef, QName.createQName("{test}prop1"), "value1"); + Map propMap = new HashMap(); + propMap.put(QName.createQName("{test}prop2"), "value2"); + this.nodeService.setProperties(this.nodeRef, propMap); + this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE); + // TODO there are various other calls that could be more vigirously checked + + // Lock the node as the 'bad' user + this.lockService.unlock(this.nodeRef); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.lockService.lock(this.nodeRef, LockType.WRITE_LOCK); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Lets check that we can't create a new child + try + { + this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER); + fail("The parent is locked so a new child should not have been created."); + } + catch (NodeLockedException exception) + {} + + // TODO various other tests along these lines ... + + // TODO check that delete is also working + } + + /** + * ALF-5680: It is possible to cut/paste a locked file + */ + @Test + public void testCannotMoveNodeWhenLocked() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Create the node that we'll try to move + NodeRef parentNode = this.nodeRef; + ChildAssociationRef childAssocRef = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTENT); + + NodeRef nodeRef = childAssocRef.getChildRef(); + // Lock it - so that it can't be moved. + this.lockService.lock(nodeRef, LockType.WRITE_LOCK); + + // Create the new container that we'll move the node to. + NodeRef newParentRef = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER).getChildRef(); + + // Now the bad user will try to move the node. + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + try + { + nodeService.moveNode( + nodeRef, + newParentRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest")); + fail("Shouldn't have been able to move locked node."); + } + catch (NodeLockedException e) + { + // Good, we can't move it - as expected. + } + } + + /** + * MNT-9475: Moving locked content breaks edit online + */ + @Test + public void testCanMoveCopyDeleteWithLockOwner() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Create the node that we'll try to move, copy & delete + NodeRef parentNode = this.nodeRef; + ChildAssociationRef childAssocRef = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTENT); + + NodeRef nodeRef = childAssocRef.getChildRef(); + // Lock it + this.lockService.lock(nodeRef, LockType.WRITE_LOCK); + + // Create the node that we'll try to archive and restore + NodeRef archivingBehaviorNodeRef = nodeService.createNode( + this.inSpaceStoreNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTENT).getChildRef(); + // Lock it + this.lockService.lock(archivingBehaviorNodeRef, LockType.WRITE_LOCK); + + // Create the new container that we'll move the node to. + NodeRef newParentRefToMove = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER).getChildRef(); + + // Create the new container that we'll copy the node to. + NodeRef newParentRefToCopy = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER).getChildRef(); + + try + { + // user should be able to move node + nodeService.moveNode(nodeRef, newParentRefToMove, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); + + // copy it + copyService.copy(nodeRef, newParentRefToCopy, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); + + // and delete node + nodeService.deleteNode(nodeRef); + } + catch (NodeLockedException e) + { + fail("Should be moved, copied an deleted."); + } + + childAssocRef = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTENT); + + nodeRef = childAssocRef.getChildRef(); + + // Create the new container that we'll copy the node to. + newParentRefToCopy = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}nodeServiceLockTest"), + ContentModel.TYPE_CONTAINER).getChildRef(); + + this.lockService.lock(nodeRef, LockType.WRITE_LOCK); + + TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); + + try + { + // Node Can be Copied by Not LockOwner + copyService.copy(nodeRef, newParentRefToCopy, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); + } + catch (NodeLockedException e) + { + fail("Should be copied."); + } + + try + { + nodeService.deleteNode(newParentRefToCopy); + } + catch (NodeLockedException e) + { + fail("Should not have any locks."); + } + try + { + nodeService.deleteNode(nodeRef); + fail("Should not be deleted."); + } + catch (NodeLockedException e) + { + // Only LockOwner can Delete Node + } + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + nodeService.deleteNode(archivingBehaviorNodeRef); + NodeRef archivedNode = nodeArchiveService.getArchivedNode(archivingBehaviorNodeRef); + + // check for lock for archived node + checkForLockForBadAndGoodUsers(archivedNode); + + TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); + try + { + // Try to restore archived node by Not Lock Owner + archivingBehaviorNodeRef = nodeService.restoreNode(archivedNode, + this.inSpaceStoreNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); + } + catch (Exception e) + { + fail("Should not be any Exceptons."); + } + + // check for lock for restored node by bad user + checkForLockForBadAndGoodUsers(archivingBehaviorNodeRef); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + nodeService.deleteNode(archivingBehaviorNodeRef); + try + { + archivingBehaviorNodeRef = nodeService.restoreNode(archivedNode, + this.inSpaceStoreNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}nodeServiceLockTest")); + } + catch (Exception e) + { + fail("Should not be any Exceptons."); + } + + // check for lock for restored node by good user + checkForLockForBadAndGoodUsers(archivingBehaviorNodeRef); + } + + private void checkForLockForBadAndGoodUsers(NodeRef nodeToCheck) + { + String currentUserName = TestWithUserUtils.getCurrentUser(this.authenticationService); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + try + { + lockService.checkForLock(nodeToCheck); + } + catch (NodeLockedException e) + { + fail("Should not be locked for GoodUser : " + nodeToCheck); + } + assertTrue(lockService.isLocked(nodeToCheck)); + assertFalse(lockService.isLockedAndReadOnly(nodeToCheck)); + + TestWithUserUtils.authenticateUser(BAD_USER_WITH_ALL_PERMS_NAME, PWD, rootNodeRef, this.authenticationService); + try + { + lockService.checkForLock(nodeToCheck); + fail("Should be locked for BadUser : " + nodeToCheck); + } + catch (NodeLockedException e) + { + // It's Ok + } + assertTrue(lockService.isLocked(nodeToCheck)); + assertTrue(lockService.isLockedAndReadOnly(nodeToCheck)); + + TestWithUserUtils.authenticateUser(currentUserName, PWD, rootNodeRef, this.authenticationService); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java b/repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java index dac8f82146..642d2bcaee 100644 --- a/repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java +++ b/repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java @@ -1,1222 +1,1218 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2023 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.lock; - -import static org.junit.Assert.assertNotEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.lock.mem.Lifetime; -import org.alfresco.repo.lock.mem.LockState; -import org.alfresco.repo.lock.mem.LockStore; -import org.alfresco.repo.policy.BehaviourDefinition; -import org.alfresco.repo.policy.ClassBehaviourBinding; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.search.IndexerAndSearcher; -import org.alfresco.repo.search.SearcherComponent; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockStatus; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.lock.NodeLockedException; -import org.alfresco.service.cmr.lock.UnableToAquireLockException; -import org.alfresco.service.cmr.lock.UnableToReleaseLockException; -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.ResultSet; -import org.alfresco.service.cmr.security.MutableAuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; -import org.alfresco.test_category.BaseSpringTestsCategory; -import org.alfresco.util.BaseSpringTest; -import org.alfresco.util.TestWithUserUtils; -import org.alfresco.util.testing.category.RedundantTests; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.springframework.test.context.transaction.TestTransaction; -import org.springframework.transaction.annotation.Transactional; - -/** - * Simple lock service test - * - * @author Roy Wetherall - */ -@Category({BaseSpringTestsCategory.class}) -@Transactional -public class LockServiceImplTest extends BaseSpringTest -{ - /** - * Services used in tests - */ - private NodeService nodeService; - private LockService lockService; - private MutableAuthenticationService authenticationService; - - private LockService securedLockService; - /** - * Data used in tests - */ - private NodeRef parentNode; - private NodeRef childNode1; - private NodeRef childNode2; - private NodeRef noAspectNode; - private NodeRef checkedOutNode; - - private static final String GOOD_USER_NAME = "goodUser"; - private static final String BAD_USER_NAME = "badUser"; - private static final String PWD = "password"; - - NodeRef rootNodeRef; - private StoreRef storeRef; - - private PolicyComponent policyComponent; - - - public class LockServicePoliciesImpl implements LockServicePolicies.BeforeLock, - LockServicePolicies.BeforeUnlock - { - @Override - public void beforeLock(NodeRef nodeRef, LockType lockType) - { - if (logger.isDebugEnabled()) - { - logger.debug("Invoked beforeLock() for nodeRef: " + nodeRef + - " and lockType: " + lockType); - } - } - - @Override - public void beforeUnlock(NodeRef nodeRef) - { - if (logger.isDebugEnabled()) - { - logger.debug("Invoked beforeUnlock() for nodeRef: " + nodeRef); - } - } - } - - - @Before - public void before() throws Exception - { - this.nodeService = (NodeService)applicationContext.getBean("dbNodeService"); - this.lockService = (LockService)applicationContext.getBean("lockService"); - - this.securedLockService = (LockService)applicationContext.getBean("LockService"); - PermissionService permissionService = (PermissionService) applicationContext.getBean("PermissionService"); - - this.authenticationService = (MutableAuthenticationService)applicationContext.getBean("authenticationService"); - CheckOutCheckInService cociService = (CheckOutCheckInService) applicationContext.getBean( - "checkOutCheckInService"); - - this.policyComponent = (PolicyComponent)applicationContext.getBean("policyComponent"); - - // Set the authentication - AuthenticationComponent authComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); - authComponent.setSystemUserAsCurrentUser(); - - // Create the node properties - HashMap nodeProperties = new HashMap<>(); - nodeProperties.put(QName.createQName("{test}property1"), "value1"); - - // Create a workspace that contains the 'live' nodes - storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - - // Get a reference to the root node - rootNodeRef = this.nodeService.getRootNode(storeRef); - - // Create node - this.parentNode = this.nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}ParentNode"), - ContentModel.TYPE_CONTAINER, - nodeProperties).getChildRef(); - this.nodeService.addAspect(this.parentNode, ContentModel.ASPECT_LOCKABLE, new HashMap<>()); - HashMap audProps = new HashMap<>(); - audProps.put(ContentModel.PROP_CREATOR, "Monkey"); - this.nodeService.addAspect(this.parentNode, ContentModel.ASPECT_AUDITABLE, audProps); - assertNotNull("parentNode should not be null", this.parentNode); - - // Add some children to the node - this.childNode1 = this.nodeService.createNode( - this.parentNode, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}ChildNode1"), - ContentModel.TYPE_CONTAINER, - nodeProperties).getChildRef(); - this.nodeService.addAspect(this.childNode1, ContentModel.ASPECT_LOCKABLE, new HashMap<>()); - assertNotNull("childNode1 should not be null", this.childNode1); - this.childNode2 = this.nodeService.createNode( - this.parentNode, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}ChildNode2"), - ContentModel.TYPE_CONTAINER, - nodeProperties).getChildRef(); - this.nodeService.addAspect(this.childNode2, ContentModel.ASPECT_LOCKABLE, new HashMap<>()); - assertNotNull("childNode2 should not be null", this.childNode2); - - // Create a node with no lockAspect - this.noAspectNode = this.nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}noAspectNode"), - ContentModel.TYPE_CONTAINER, - nodeProperties).getChildRef(); - assertNotNull("noAspectNode should not be null", this.noAspectNode); - - // Create node with checkedOut - this.checkedOutNode = this.nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{}checkedOutNode"), - ContentModel.TYPE_CONTAINER, - nodeProperties).getChildRef(); - assertNotNull("checkedOutNode node should not be null", this.checkedOutNode); - - // Check out test file - NodeRef fileWorkingCopyNodeRef = cociService.checkout(checkedOutNode); - assertNotNull("node file working copy should not be null", fileWorkingCopyNodeRef); - assertTrue("checkedOutNode should have checked out aspect", nodeService.hasAspect(checkedOutNode, ContentModel.ASPECT_CHECKED_OUT)); - assertTrue("checkedOutNode should have lockable aspect", nodeService.hasAspect(checkedOutNode, ContentModel.ASPECT_LOCKABLE)); - - - // Create the users - TestWithUserUtils.createUser(GOOD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); - TestWithUserUtils.createUser(BAD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); - - permissionService.setPermission(rootNodeRef, GOOD_USER_NAME, PermissionService.ALL_PERMISSIONS, true); - permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.CHECK_OUT, true); - permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.WRITE, true); - permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.READ, true); - - // Stash the user node ref's for later use - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - } - - @Test - public void testLockServicePolicies() - { - LockServicePoliciesImpl mockedLockServicePoliciesImpl = mock(LockServicePoliciesImpl.class); - - BehaviourDefinition lockDef = - this.policyComponent.bindClassBehaviour(LockServicePolicies.BeforeLock.QNAME, ContentModel.TYPE_BASE, - new JavaBehaviour(mockedLockServicePoliciesImpl, "beforeLock")); - - BehaviourDefinition unlockDef = - this.policyComponent.bindClassBehaviour(LockServicePolicies.BeforeUnlock.QNAME, ContentModel.TYPE_BASE, - new JavaBehaviour(mockedLockServicePoliciesImpl, "beforeUnlock")); - - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - - verify(mockedLockServicePoliciesImpl, times(1)).beforeLock(this.parentNode, LockType.WRITE_LOCK); - - this.lockService.unlock(this.parentNode); - - verify(mockedLockServicePoliciesImpl, times(1)).beforeUnlock(this.parentNode); - - // cleanup: - this.policyComponent.removeClassDefinition(lockDef); - this.policyComponent.removeClassDefinition(unlockDef); - } - - /** - * Test lock - */ - private void testLock() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check that the node is not currently locked - assertEquals("parent node should not be locked", LockStatus.NO_LOCK, this.lockService.getLockStatus(this.parentNode)); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - - // Test valid lock - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - assertEquals("parent node should be locked", LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode)); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - - // Check that we can retrieve LockState - LockState lockState = lockService.getLockState(parentNode); - assertEquals("retrieved node should be the same", parentNode, lockState.getNodeRef()); - assertEquals("retrieved lock type should be write type", LockType.WRITE_LOCK, lockState.getLockType()); - assertEquals("retrieved lock owner should be the same", GOOD_USER_NAME, lockState.getOwner()); - assertEquals("retrieved lock lifetime should be persistent", Lifetime.PERSISTENT, lockState.getLifetime()); - assertNull("retrieved expire date should be null", lockState.getExpires()); - assertNull("retrieved additional info should be null", lockState.getAdditionalInfo()); - - // Check the correct properties have been set - Map props = nodeService.getProperties(parentNode); - assertEquals("node owner should be the same", GOOD_USER_NAME, props.get(ContentModel.PROP_LOCK_OWNER)); - assertEquals("lock should be write type", LockType.WRITE_LOCK.toString(), props.get(ContentModel.PROP_LOCK_TYPE)); - assertEquals("lifetime should be persistent", Lifetime.PERSISTENT.toString(), props.get(ContentModel.PROP_LOCK_LIFETIME)); - assertNull("expire date should be null", props.get(ContentModel.PROP_EXPIRY_DATE)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - assertEquals( - "parent node should be locked", - LockStatus.LOCKED, - this.lockService.getLockStatus(this.parentNode)); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - - // Test lock when already locked - try - { - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - fail("The user should not be able to lock the node since it is already locked by another user."); - } - catch (UnableToAquireLockException exception) - { - if(logger.isDebugEnabled()) - { - logger.debug(exception.getMessage()); - } - } - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Test already locked by this user - try - { - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - } - catch (Exception exception) - { - fail("No error should be thrown when a node is re-locked by the current lock owner."); - } - - // Test with no aspect node - this.lockService.lock(this.noAspectNode, LockType.WRITE_LOCK); - assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); - } - - @Test - public void testPersistentLockMayStoreAdditionalInfo() - { - lockService.lock(noAspectNode, LockType.NODE_LOCK, 0, Lifetime.PERSISTENT, "additional info"); - - LockState lockState = lockService.getLockState(noAspectNode); - assertEquals("retrieved additional info should be the same", "additional info", lockState.getAdditionalInfo()); - } - - @Test - public void testEphemeralLock() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check that the node is not currently locked - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); - - // Check that there really is no lockable aspect - assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); - - // Lock the node - lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL, "some extra data"); - - // Check additionalInfo has been stored - assertEquals("retrieved additional info should be the same", "some extra data", lockService.getAdditionalInfo(noAspectNode)); - - // Check that we can retrieve LockState - LockState lockState = lockService.getLockState(noAspectNode); - assertEquals("retrieved node should be the same", noAspectNode, lockState.getNodeRef()); - assertEquals("retrieved lock type should be write type", LockType.WRITE_LOCK, lockState.getLockType()); - assertEquals("retrieved lock owner should be the same", GOOD_USER_NAME, lockState.getOwner()); - assertEquals("retrieved lock lifetime should be ephemeral", Lifetime.EPHEMERAL, lockState.getLifetime()); - assertNotNull("retrieved expire date should not be null", lockState.getExpires()); - assertEquals("retrieved additional info should be the same", "some extra data", lockState.getAdditionalInfo()); - // The node should be locked - assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); - assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); - - // The node must still not have the lockable aspect applied - assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); - // ...though the full node service should report that it is present - NodeService fullNodeService = (NodeService) applicationContext.getBean("nodeService"); - assertTrue("noAspectNode should have aspect", fullNodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - assertEquals("noAspectNode should be locked", LockStatus.LOCKED, lockService.getLockStatus(noAspectNode)); - assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); - - // Test lock when already locked - try - { - lockService.lock(noAspectNode, LockType.WRITE_LOCK); - fail("The user should not be able to lock the node since it is already locked by another user."); - } - catch (UnableToAquireLockException exception) - { - if (logger.isDebugEnabled()) - { - logger.debug(exception.getMessage()); - } - } - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); - assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); - - // Test already locked by this user - relock - try - { - lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); - } - catch (Exception exception) - { - fail("No error should be thrown when a node is re-locked by the current lock owner."); - } - - // The node should be locked - assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); - assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); - // If we remove the lock info directly from the memory store then the node should no longer - // be reported as locked (as it is an ephemeral lock) - LockStore lockStore = (LockStore) applicationContext.getBean("lockStore"); - lockStore.clear(); - // The node must no longer be reported as locked - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); - - // Lock again, ready to test unlocking an ephemeral lock. - try - { - lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); - } - catch (Exception exception) - { - fail("No error should be thrown when a node is re-locked by the current lock owner."); - } - - assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); - assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); - - lockService.unlock(noAspectNode); - - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); - } - - @Test - @Category(RedundantTests.class) - public void testEphemeralLockIndexing() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, authenticationService); - - IndexerAndSearcher indexerAndSearcher = (IndexerAndSearcher) - applicationContext.getBean("indexerAndSearcherFactory"); - SearcherComponent searcher = new SearcherComponent(); - searcher.setIndexerAndSearcherFactory(indexerAndSearcher); - - // Create a lock (owned by the current user) - lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL); - - // Query for the user's locks - final String query = String.format("+@cm\\:lockOwner:\"%s\" +@cm\\:lockType:\"WRITE_LOCK\"", GOOD_USER_NAME); - ResultSet rs = searcher.query(storeRef, "lucene", query); - assertTrue("lock should be found", rs.getNodeRefs().contains(noAspectNode)); - - // Unlock the node - lockService.unlock(noAspectNode); - - // Perform a new search, the index should reflect that it is not locked. - rs = searcher.query(storeRef, "lucene", query); - assertFalse("lock should not be found", rs.getNodeRefs().contains(noAspectNode)); - } - - /* MNT-10477 related test */ - @Test - public void testEphemeralLockModifyNode() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check that the node is not currently locked - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); - - // Check that there really is no lockable aspect - assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); - - // Lock the node - lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL, "some extra data"); - - // get bad user - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - assertEquals("noAspectNode should be locked", LockStatus.LOCKED, lockService.getLockStatus(noAspectNode)); - assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); - - NodeService fullNodeService = (NodeService) applicationContext.getBean("nodeService"); - - /* addProperties test */ - try - { - Map props = new HashMap<>(); - props.put(ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis()); - props.put(ContentModel.PROP_TITLE, "title" + System.currentTimeMillis()); - fullNodeService.addProperties(noAspectNode, props); - - fail("node should be locked"); - } - catch(NodeLockedException e) - { - // it's ok - node supposed to be locked - } - - /* setProperty test */ - try - { - fullNodeService.setProperty(noAspectNode, ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis()); - - fail("node should be locked"); - } - catch(NodeLockedException e) - { - // it's ok - node supposed to be locked - } - - /* setProperties test */ - try - { - Map props = new HashMap<>(); - props.put(ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis()); - props.put(ContentModel.PROP_TITLE, "title" + System.currentTimeMillis()); - fullNodeService.setProperties(noAspectNode, props); - - fail("node should be locked"); - } - catch(NodeLockedException e) - { - // it's ok - node supposed to be locked - } - - /* removeProperty test */ - try - { - fullNodeService.removeProperty(noAspectNode, ContentModel.PROP_DESCRIPTION); - - fail("node should be locked"); - } - catch(NodeLockedException e) - { - // it's ok - node supposed to be locked - } - - /* addAspect test */ - try - { - fullNodeService.addAspect(noAspectNode, ContentModel.ASPECT_AUTHOR , null); - - fail("node should be locked"); - } - catch(NodeLockedException e) - { - // it's ok - node supposed to be locked - } - - /* removeAspect test */ - try - { - fullNodeService.removeAspect(noAspectNode, ContentModel.ASPECT_AUTHOR); - - fail("node should be locked"); - } - catch(NodeLockedException e) - { - // it's ok - node supposed to be locked - } - - /* setType test */ - try - { - fullNodeService.setType(noAspectNode, ContentModel.TYPE_CMOBJECT); - - fail("node should be locked"); - } - catch(NodeLockedException e) - { - // it's ok - node supposed to be locked - } - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - lockService.unlock(noAspectNode); - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); - } - - /** - * Test that covers MNT-17612 - having an expired ephemeral lock and a persistent one on the same node. - */ - @Test - public void testExpiredEphemeralLockAndPersistentLock() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check that the node is not currently locked - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, securedLockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", securedLockService.isLocked(noAspectNode)); - - // Check that there really is no lockable aspect - assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); - - // Lock the node - securedLockService.lock(noAspectNode, LockType.WRITE_LOCK, 1, Lifetime.EPHEMERAL); - - // Check that we can retrieve LockState - LockState lockState = securedLockService.getLockState(noAspectNode); - assertEquals("retrieved node should be the same", noAspectNode, lockState.getNodeRef()); - assertEquals("retrieved lock type should be write type", LockType.WRITE_LOCK, lockState.getLockType()); - assertEquals("retrieved lock owner should be the same", GOOD_USER_NAME, lockState.getOwner()); - assertEquals("retrieved lock lifetime should be ephemeral", Lifetime.EPHEMERAL, lockState.getLifetime()); - assertNotNull("retrieved expire date should not be null", lockState.getExpires()); - - // Wait for 2 seconds to give the ephemeral lock time to expire - try {Thread.sleep(2*1000);} catch (Exception exception){} - - assertFalse("noAspectNode should not be locked", securedLockService.isLocked(noAspectNode)); - - // Do a persistent lock with a different user (simulate an Edit Offline - MNT-17612) - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Lock the node - securedLockService.lock(noAspectNode, LockType.READ_ONLY_LOCK, 1000, Lifetime.PERSISTENT); - - assertTrue("noAspectNode should be locked", securedLockService.isLocked(noAspectNode)); - assertTrue("noAspectNode should be locked", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); - assertEquals("noAspectNode should have read only lock type", LockType.READ_ONLY_LOCK, securedLockService.getLockType(noAspectNode)); - - // Check that we can retrieve LockState - lockState = securedLockService.getLockState(noAspectNode); - assertEquals("retrieved node should be the same", noAspectNode, lockState.getNodeRef()); - assertEquals("retrieved lock type should be read only type", LockType.READ_ONLY_LOCK, lockState.getLockType()); - assertEquals("retrieved lock owner should be the same", BAD_USER_NAME, lockState.getOwner()); - assertEquals("retrieved lock lifetime should be persistent", Lifetime.PERSISTENT, lockState.getLifetime()); - - // Check unlock - securedLockService.unlock(noAspectNode); - - assertFalse("noAspectNode should not be locked", securedLockService.isLocked(noAspectNode)); - } - - @Test - public void testLockRevertedOnRollback() - { - // Preconditions of test - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); - assertEquals("rootNodeRef should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(rootNodeRef)); - assertFalse("rootNodeRef should not be locked", lockService.isLocked(rootNodeRef)); - - // Lock noAspectNode - lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); - - // Lock rootNodeRef - lockService.lock(rootNodeRef, LockType.NODE_LOCK, 0, Lifetime.EPHEMERAL); - - // Sometime later, a refresh occurs (so this should not be reverted to unlocked, but to this state) - lockService.lock(rootNodeRef, LockType.NODE_LOCK, 3600, Lifetime.EPHEMERAL); - - // Rollback - TestTransaction.end(); - - // This lock should not be present. - assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); - assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); - - // This lock should still be present. - assertEquals("rootNodeRef should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(rootNodeRef)); - assertTrue("rootNodeRef should be locked", lockService.isLocked(rootNodeRef)); - } - - /** - * Test unlock node - */ - @Test - public void testUnlock() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Lock the parent node - testLock(); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Try and unlock a locked node - try - { - this.lockService.unlock(this.parentNode); - // This will pass in the open workd - //fail("A user cannot unlock a node that is currently lock by another user."); - } - catch (UnableToReleaseLockException exception) - { - if (logger.isDebugEnabled()) - { - logger.debug(exception.getMessage()); - } - } - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Unlock the node - this.lockService.unlock(this.parentNode); - assertEquals( - "parentNode should not be locked", - LockStatus.NO_LOCK, - this.lockService.getLockStatus(this.parentNode)); - assertFalse("parentNode should not be locked", lockService.isLocked(parentNode)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - assertEquals( - "parentNode should not be locked", - LockStatus.NO_LOCK, - this.lockService.getLockStatus(this.parentNode)); - assertFalse("parentNode should not be locked", lockService.isLocked(parentNode)); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Try and unlock node with no lock - try - { - this.lockService.unlock(this.parentNode); - } - catch (Exception exception) - { - fail("Unlocking an unlocked node should not result in an exception being raised."); - } - - // Test with no aspect node - this.lockService.unlock(this.noAspectNode); - } - - /** - * Test getLockStatus - */ - @Test - public void testGetLockStatus() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check an unlocked node - LockStatus lockStatus1 = this.lockService.getLockStatus(this.parentNode); - assertEquals("parentNode should not be locked", LockStatus.NO_LOCK, lockStatus1); - - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check for locked status - LockStatus lockStatus2 = this.lockService.getLockStatus(this.parentNode); - assertEquals("parentNode should be locked", LockStatus.LOCKED, lockStatus2); - - // Check lockstore is not used for persistent locks - // Previously LockStore was doubling up as a cache - the change in requirements means a test - // is necessary to ensure the work has been implemented correctly (despite being an odd test) - LockStore lockStore = (LockStore) applicationContext.getBean("lockStore"); - lockStore.clear(); - LockState lockState = lockStore.get(parentNode); - // Nothing stored against node ref - assertNull("lock state should be null", lockState); - lockService.getLockStatus(parentNode); - // In-memory store still empty - only used for ephemeral locks - lockState = lockStore.get(parentNode); - assertNull("lock state should be null", lockState); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Check for lock owner status - LockStatus lockStatus3 = this.lockService.getLockStatus(this.parentNode); - assertEquals("lock status should be lock owner type", LockStatus.LOCK_OWNER, lockStatus3); - - // Test with no aspect node - this.lockService.getLockStatus(this.noAspectNode); - - // Test method overload - LockStatus lockStatus4 = this.lockService.getLockStatus(this.parentNode); - assertEquals("lock status should be lock owner type", LockStatus.LOCK_OWNER, lockStatus4); - } - - @Test - @Category(RedundantTests.class) - public void testGetLocks() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - LockServiceImpl lockService = (LockServiceImpl) this.lockService; - List locked1 = lockService.getLocks(this.storeRef); - assertNotNull("nodes list should not be null", locked1); - assertEquals("nodes list should be empty", 0, locked1.size()); - - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - - assertFalse("child node1 should not be locked", lockService.isLocked(childNode1)); - assertFalse("child node1 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode1)); - this.lockService.lock(this.childNode1, LockType.WRITE_LOCK); - assertTrue("child node1 should be locked", lockService.isLocked(childNode1)); - assertFalse("child node1 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode1)); - - assertFalse("child node2 should not be locked", lockService.isLocked(childNode2)); - assertFalse("child node2 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode2)); - this.lockService.lock(this.childNode2, LockType.READ_ONLY_LOCK); - assertTrue("child node2 should be locked", lockService.isLocked(childNode2)); - assertTrue("child node2 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode2)); - - List locked2 = lockService.getLocks(this.storeRef); - assertNotNull("nodes list should not be null", locked2); - assertEquals("nodes list should have size of three", 3, locked2.size()); - - List locked3 = lockService.getLocks(this.storeRef, LockType.WRITE_LOCK); - assertNotNull("nodes list should not be null", locked3); - assertEquals("nodes list should have size of two", 2, locked3.size()); - - List locked4 = lockService.getLocks(this.storeRef, LockType.READ_ONLY_LOCK); - assertNotNull("nodes list should not be null", locked4); - assertEquals("nodes list should have size of one", 1, locked4.size()); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - List locked5 = lockService.getLocks(this.storeRef); - assertNotNull("nodes list should not be null", locked5); - assertEquals("nodes list should be empty", 0, locked5.size()); - } - - /** - * Test getLockType (and isLocked/isLockedReadOnly) - */ - @Test - public void testGetLockType() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Get the lock type (should be null since the object is not locked) - LockType lockType1 = this.lockService.getLockType(this.parentNode); - assertNull("lock type should be null", lockType1); - - // Lock the object for writing - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - LockType lockType2 = this.lockService.getLockType(this.parentNode); - assertNotNull("lock type should not be null", lockType2); - assertEquals("lock should be write type", LockType.WRITE_LOCK, lockType2); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Unlock the node - this.lockService.unlock(this.parentNode); - LockType lockType3 = this.lockService.getLockType(this.parentNode); - assertNull("lock type should be null", lockType3); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Lock the object for read only - this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK); - LockType lockType4 = this.lockService.getLockType(this.parentNode); - assertNotNull("lock type should not be null", lockType4); - assertEquals("lock should be read only type", LockType.READ_ONLY_LOCK, lockType4); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Lock the object for node lock - this.lockService.lock(this.parentNode, LockType.NODE_LOCK); - LockType lockType5 = this.lockService.getLockType(this.parentNode); - assertNotNull("lock type should not be null", lockType5); - assertEquals("lock should be node type", LockType.NODE_LOCK, lockType5); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Unlock the node - this.lockService.unlock(this.parentNode); - LockType lockType6 = this.lockService.getLockType(this.parentNode); - assertNull("lock type should be null", lockType6); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Test with no aspect node - LockType lockType7 = this.lockService.getLockType(this.noAspectNode); - assertNull("lock type should be null", lockType7); - } - - @Test - public void testGetLockTypeEphemeral() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Get the lock type (should be null since the object is not locked) - LockType lockType1 = this.lockService.getLockType(this.parentNode); - assertNull("lock type should be null", lockType1); - - // Lock the object for writing - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); - LockType lockType2 = this.lockService.getLockType(this.parentNode); - assertNotNull("lock type should not be null", lockType2); - assertEquals("lock should be write type", LockType.WRITE_LOCK, lockType2); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Unlock the node - this.lockService.unlock(this.parentNode); - LockType lockType3 = this.lockService.getLockType(this.parentNode); - assertNull("lock type should be null", lockType3); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Lock the object for read only - this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK, 0, Lifetime.EPHEMERAL); - LockType lockType4 = this.lockService.getLockType(this.parentNode); - assertNotNull("lock type should not be null", lockType4); - assertEquals("lock should be read only type", LockType.READ_ONLY_LOCK, lockType4); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Lock the object for node lock - this.lockService.lock(this.parentNode, LockType.NODE_LOCK, 0, Lifetime.EPHEMERAL); - LockType lockType5 = this.lockService.getLockType(this.parentNode); - assertNotNull("lock type should not be null", lockType5); - assertEquals("lock should be node type", LockType.NODE_LOCK, lockType5); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Unlock the node - this.lockService.unlock(this.parentNode); - LockType lockType6 = this.lockService.getLockType(this.parentNode); - assertNull("lock type should be null", lockType6); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); - - // Test with no apect node - LockType lockType7 = this.lockService.getLockType(this.noAspectNode); - assertNull("lock type should be null", lockType7); - } - - @Test - public void testTimeToExpire() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1); - assertEquals("lock status should be owner", LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode)); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - assertEquals("lock status should be locked", LockStatus.LOCKED, this.lockService.getLockStatus(this.parentNode)); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - - // Wait for 2 second before re-testing the status - try {Thread.sleep(2*1000);} catch (Exception exception){} - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - - // Re-lock and then update the time to expire before lock expires - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 0); - try - { - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1); - fail("Can not update lock info if not lock owner"); - } - catch (UnableToAquireLockException exception) - { - // Expected - } - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1); - assertEquals("lock status should be owner", LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode)); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - assertEquals("lock status should be locked", LockStatus.LOCKED, this.lockService.getLockStatus(this.parentNode)); - assertTrue("parent node should be locked", lockService.isLocked(parentNode)); - - // Wait for 2 second before re-testing the status - try {Thread.sleep(2*1000);} catch (Exception exception){} - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - } - - @Test - public void testEphemeralExpiryThreshold() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - final int origThresh = ((LockServiceImpl)lockService).getEphemeralExpiryThreshold(); - // Check the default situation is that the threshold does not apply. - assertEquals("threshold should not apply", LockServiceImpl.MAX_EPHEMERAL_LOCK_SECONDS, origThresh); - try - { - // Set the ephemeral expiry threshold to a much smaller value than the default - // so that it takes effect. - lockService.setEphemeralExpiryThreshold(300); - - // Check for an expiry time that should be unaffected by the threshold. - checkLifetimeForExpiry(Lifetime.EPHEMERAL, 0, Lifetime.EPHEMERAL); - checkLifetimeForExpiry(Lifetime.EPHEMERAL, 150, Lifetime.EPHEMERAL); - - // Check the largest allowed ephemeral expiry time. - checkLifetimeForExpiry(Lifetime.EPHEMERAL, 300, Lifetime.EPHEMERAL); - - // When the expiry is greater than the threshold, then the lock should be - // applied as a persistent lock. - checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL); - - // Switch off ephemeral locks entirely - lockService.setEphemeralExpiryThreshold(-1); - // Always persistent... - checkLifetimeForExpiry(Lifetime.PERSISTENT, 0, Lifetime.EPHEMERAL); - checkLifetimeForExpiry(Lifetime.PERSISTENT, 150, Lifetime.EPHEMERAL); - checkLifetimeForExpiry(Lifetime.PERSISTENT, 300, Lifetime.EPHEMERAL); - checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL); - } - finally - { - lockService.setEphemeralExpiryThreshold(origThresh); - } - } - - private void checkLifetimeForExpiry(Lifetime expectedLifetime, int expirySecs, Lifetime requestedLifetime) - { - lockService.unlock(parentNode); - assertNotEquals("lock status should not be locked", LockStatus.LOCKED ,lockService.getLockStatus(parentNode)); - lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, requestedLifetime); - LockState lock = lockService.getLockState(parentNode); - assertEquals("lock lifetime should be the same", expectedLifetime, lock.getLifetime()); - - // Check that for any timeouts we test, a request for a persistent lock always yields a persistent lock. - lockService.unlock(parentNode); - assertNotEquals("lock status should not be locked", LockStatus.LOCKED ,lockService.getLockStatus(parentNode)); - lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, Lifetime.PERSISTENT); - lock = lockService.getLockState(parentNode); - assertEquals("lock lifetime should be persistent", Lifetime.PERSISTENT, lock.getLifetime()); - } - - /** - * Unit test to validate the behaviour of creating children of locked nodes. - * No lock - can create children - * READ_ONLY_LOCK - can't create children - * WRITE_LOCK - owner can create children - * non owner can't create children - * NODE_LOCK non owner can create children - * owner can create children - */ - @Test - public void testCreateChildrenOfLockedNodes() throws Exception - { - - /* - * Check we can create a child of an unlocked node. - */ - assertEquals( - "parent node should not be locked", - LockStatus.NO_LOCK, - this.lockService.getLockStatus(this.parentNode)); - assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); - - nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildA"), ContentModel.TYPE_FOLDER); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); - - // Owner can create children - nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildB"), ContentModel.TYPE_FOLDER); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - try - { - // Non owner can't create children with a write lock in place - nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildB"), ContentModel.TYPE_FOLDER); - fail("could create a child with a read only lock"); - } - catch (NodeLockedException e) - { - logger.debug("exception while trying to create a child of a read only lock", e); - } - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - this.lockService.lock(this.parentNode, LockType.NODE_LOCK); - - // owner can create children with a node lock - nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildD"), ContentModel.TYPE_FOLDER); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Non owner can create children with a node lock - nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildC"), ContentModel.TYPE_FOLDER); - - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK); - - // owner should not be able to create children with a READ_ONLY_LOCK - try - { - nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildD"), ContentModel.TYPE_FOLDER); - fail("could create a child with a read only lock"); - } - catch (NodeLockedException e) - { - logger.debug("exception while trying to create a child of a read only lock", e); - } - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // Non owner should not be able to create children with READ_ONLY_LOCK - try - { - nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildE"), ContentModel.TYPE_FOLDER); - fail("could create a child with a read only lock"); - } - catch (NodeLockedException e) - { - logger.debug("exception while trying to create a child of a read only lock", e); - } - } - - /** - * Test that it is impossible to unlock a checked out node - */ - @Test - public void testUnlockCheckedOut() - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - try - { - this.lockService.unlock(checkedOutNode); - fail("could unlock a checked out node"); - } - catch (UnableToReleaseLockException e) - { - logger.debug("exception while trying to unlock a checked out node", e); - } - - assertTrue("checkedOutNode should be locked", lockService.isLocked(checkedOutNode)); - assertTrue("checkedOutNode should be locked and not write lock type", lockService.isLockedAndReadOnly(checkedOutNode)); - } - - @SuppressWarnings("deprecation") - @Test - public void testUnlockNodeWithAdminUserAndAllPermissionsUser() - { - for (Lifetime lt : new Lifetime[]{Lifetime.EPHEMERAL, Lifetime.PERSISTENT}) - { - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - /* create node */ - final NodeRef testNode = - this.nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{}testNode"), ContentModel.TYPE_CONTAINER).getChildRef(); - - // lock it as GOOD user - this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null); - - // check lock state and status as GOOD user - assertNotNull("lock state should not be null", this.securedLockService.getLockState(testNode)); - assertNotNull("lock status should not be null", this.securedLockService.getLockStatus(testNode)); - assertTrue("test node should be locked", this.securedLockService.isLocked(testNode)); - assertFalse("test node should not be locked or write lock type", this.securedLockService.isLockedAndReadOnly(testNode)); - - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - - // check lock state and status as BAD user - assertNotNull("lock state should not be null", this.securedLockService.getLockState(testNode)); - assertNotNull("lock status should not be null", this.securedLockService.getLockStatus(testNode)); - assertTrue("test node should be locked", this.securedLockService.isLocked(testNode)); - assertTrue("test node should be locked and not write lock type", this.securedLockService.isLockedAndReadOnly(testNode)); - - try - { - // try to unlock as bad user - this.securedLockService.unlock(testNode); - fail("BAD user shouldn't be able to unlock " + lt + " lock"); - } - catch(AccessDeniedException e) - { - // expected exception - } - - TestWithUserUtils.authenticateUser(AuthenticationUtil.getAdminUserName(), "admin", rootNodeRef, this.authenticationService); - - // check lock state and status as ADMIN user - assertNotNull("lock state should not be null", this.securedLockService.getLockState(testNode)); - assertNotNull("lock status should not be null", this.securedLockService.getLockStatus(testNode)); - assertTrue("test node should not be locked", this.securedLockService.isLocked(testNode)); - assertTrue("checkedOutNode should be locked and not write lock type", this.securedLockService.isLockedAndReadOnly(testNode)); - - // try to unlock as ADMIN user - this.securedLockService.unlock(testNode); - - // test that bad use able to lock/unlock node - TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null); - this.securedLockService.unlock(testNode); - - this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null); - - // user who has ALL PERMISSIONS is able to unlock another's user lock - TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); - this.securedLockService.unlock(testNode); - - this.nodeService.deleteNode(testNode); - } - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.lock; + +import static org.apache.commons.lang3.RandomStringUtils.secure; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.Serializable; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.annotation.Transactional; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.lock.mem.Lifetime; +import org.alfresco.repo.lock.mem.LockState; +import org.alfresco.repo.lock.mem.LockStore; +import org.alfresco.repo.policy.BehaviourDefinition; +import org.alfresco.repo.policy.ClassBehaviourBinding; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.search.IndexerAndSearcher; +import org.alfresco.repo.search.SearcherComponent; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.lock.NodeLockedException; +import org.alfresco.service.cmr.lock.UnableToAquireLockException; +import org.alfresco.service.cmr.lock.UnableToReleaseLockException; +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.ResultSet; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.test_category.BaseSpringTestsCategory; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.TestWithUserUtils; +import org.alfresco.util.testing.category.RedundantTests; + +/** + * Simple lock service test + * + * @author Roy Wetherall + */ +@Category({BaseSpringTestsCategory.class}) +@Transactional +public class LockServiceImplTest extends BaseSpringTest +{ + /** + * Services used in tests + */ + private NodeService nodeService; + private LockService lockService; + private MutableAuthenticationService authenticationService; + + private LockService securedLockService; + /** + * Data used in tests + */ + private NodeRef parentNode; + private NodeRef childNode1; + private NodeRef childNode2; + private NodeRef noAspectNode; + private NodeRef checkedOutNode; + + private static final String GOOD_USER_NAME = "goodUser"; + private static final String BAD_USER_NAME = "badUser"; + private static final String PWD = secure().nextAlphabetic(10); + + NodeRef rootNodeRef; + private StoreRef storeRef; + + private PolicyComponent policyComponent; + + public class LockServicePoliciesImpl implements LockServicePolicies.BeforeLock, + LockServicePolicies.BeforeUnlock + { + @Override + public void beforeLock(NodeRef nodeRef, LockType lockType) + { + if (logger.isDebugEnabled()) + { + logger.debug("Invoked beforeLock() for nodeRef: " + nodeRef + + " and lockType: " + lockType); + } + } + + @Override + public void beforeUnlock(NodeRef nodeRef) + { + if (logger.isDebugEnabled()) + { + logger.debug("Invoked beforeUnlock() for nodeRef: " + nodeRef); + } + } + } + + @Before + public void before() throws Exception + { + this.nodeService = (NodeService) applicationContext.getBean("dbNodeService"); + this.lockService = (LockService) applicationContext.getBean("lockService"); + + this.securedLockService = (LockService) applicationContext.getBean("LockService"); + PermissionService permissionService = (PermissionService) applicationContext.getBean("PermissionService"); + + this.authenticationService = (MutableAuthenticationService) applicationContext.getBean("authenticationService"); + CheckOutCheckInService cociService = (CheckOutCheckInService) applicationContext.getBean( + "checkOutCheckInService"); + + this.policyComponent = (PolicyComponent) applicationContext.getBean("policyComponent"); + + // Set the authentication + AuthenticationComponent authComponent = (AuthenticationComponent) this.applicationContext.getBean("authenticationComponent"); + authComponent.setSystemUserAsCurrentUser(); + + // Create the node properties + HashMap nodeProperties = new HashMap<>(); + nodeProperties.put(QName.createQName("{test}property1"), "value1"); + + // Create a workspace that contains the 'live' nodes + storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + + // Get a reference to the root node + rootNodeRef = this.nodeService.getRootNode(storeRef); + + // Create node + this.parentNode = this.nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}ParentNode"), + ContentModel.TYPE_CONTAINER, + nodeProperties).getChildRef(); + this.nodeService.addAspect(this.parentNode, ContentModel.ASPECT_LOCKABLE, new HashMap<>()); + HashMap audProps = new HashMap<>(); + audProps.put(ContentModel.PROP_CREATOR, "Monkey"); + this.nodeService.addAspect(this.parentNode, ContentModel.ASPECT_AUDITABLE, audProps); + assertNotNull("parentNode should not be null", this.parentNode); + + // Add some children to the node + this.childNode1 = this.nodeService.createNode( + this.parentNode, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}ChildNode1"), + ContentModel.TYPE_CONTAINER, + nodeProperties).getChildRef(); + this.nodeService.addAspect(this.childNode1, ContentModel.ASPECT_LOCKABLE, new HashMap<>()); + assertNotNull("childNode1 should not be null", this.childNode1); + this.childNode2 = this.nodeService.createNode( + this.parentNode, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}ChildNode2"), + ContentModel.TYPE_CONTAINER, + nodeProperties).getChildRef(); + this.nodeService.addAspect(this.childNode2, ContentModel.ASPECT_LOCKABLE, new HashMap<>()); + assertNotNull("childNode2 should not be null", this.childNode2); + + // Create a node with no lockAspect + this.noAspectNode = this.nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}noAspectNode"), + ContentModel.TYPE_CONTAINER, + nodeProperties).getChildRef(); + assertNotNull("noAspectNode should not be null", this.noAspectNode); + + // Create node with checkedOut + this.checkedOutNode = this.nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{}checkedOutNode"), + ContentModel.TYPE_CONTAINER, + nodeProperties).getChildRef(); + assertNotNull("checkedOutNode node should not be null", this.checkedOutNode); + + // Check out test file + NodeRef fileWorkingCopyNodeRef = cociService.checkout(checkedOutNode); + assertNotNull("node file working copy should not be null", fileWorkingCopyNodeRef); + assertTrue("checkedOutNode should have checked out aspect", nodeService.hasAspect(checkedOutNode, ContentModel.ASPECT_CHECKED_OUT)); + assertTrue("checkedOutNode should have lockable aspect", nodeService.hasAspect(checkedOutNode, ContentModel.ASPECT_LOCKABLE)); + + // Create the users + TestWithUserUtils.createUser(GOOD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); + TestWithUserUtils.createUser(BAD_USER_NAME, PWD, rootNodeRef, this.nodeService, this.authenticationService); + + permissionService.setPermission(rootNodeRef, GOOD_USER_NAME, PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.CHECK_OUT, true); + permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.WRITE, true); + permissionService.setPermission(rootNodeRef, BAD_USER_NAME, PermissionService.READ, true); + + // Stash the user node ref's for later use + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + } + + @Test + public void testLockServicePolicies() + { + LockServicePoliciesImpl mockedLockServicePoliciesImpl = mock(LockServicePoliciesImpl.class); + + BehaviourDefinition lockDef = this.policyComponent.bindClassBehaviour(LockServicePolicies.BeforeLock.QNAME, ContentModel.TYPE_BASE, + new JavaBehaviour(mockedLockServicePoliciesImpl, "beforeLock")); + + BehaviourDefinition unlockDef = this.policyComponent.bindClassBehaviour(LockServicePolicies.BeforeUnlock.QNAME, ContentModel.TYPE_BASE, + new JavaBehaviour(mockedLockServicePoliciesImpl, "beforeUnlock")); + + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + + verify(mockedLockServicePoliciesImpl, times(1)).beforeLock(this.parentNode, LockType.WRITE_LOCK); + + this.lockService.unlock(this.parentNode); + + verify(mockedLockServicePoliciesImpl, times(1)).beforeUnlock(this.parentNode); + + // cleanup: + this.policyComponent.removeClassDefinition(lockDef); + this.policyComponent.removeClassDefinition(unlockDef); + } + + /** + * Test lock + */ + private void testLock() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check that the node is not currently locked + assertEquals("parent node should not be locked", LockStatus.NO_LOCK, this.lockService.getLockStatus(this.parentNode)); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + + // Test valid lock + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + assertEquals("parent node should be locked", LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode)); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + + // Check that we can retrieve LockState + LockState lockState = lockService.getLockState(parentNode); + assertEquals("retrieved node should be the same", parentNode, lockState.getNodeRef()); + assertEquals("retrieved lock type should be write type", LockType.WRITE_LOCK, lockState.getLockType()); + assertEquals("retrieved lock owner should be the same", GOOD_USER_NAME, lockState.getOwner()); + assertEquals("retrieved lock lifetime should be persistent", Lifetime.PERSISTENT, lockState.getLifetime()); + assertNull("retrieved expire date should be null", lockState.getExpires()); + assertNull("retrieved additional info should be null", lockState.getAdditionalInfo()); + + // Check the correct properties have been set + Map props = nodeService.getProperties(parentNode); + assertEquals("node owner should be the same", GOOD_USER_NAME, props.get(ContentModel.PROP_LOCK_OWNER)); + assertEquals("lock should be write type", LockType.WRITE_LOCK.toString(), props.get(ContentModel.PROP_LOCK_TYPE)); + assertEquals("lifetime should be persistent", Lifetime.PERSISTENT.toString(), props.get(ContentModel.PROP_LOCK_LIFETIME)); + assertNull("expire date should be null", props.get(ContentModel.PROP_EXPIRY_DATE)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + assertEquals( + "parent node should be locked", + LockStatus.LOCKED, + this.lockService.getLockStatus(this.parentNode)); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + + // Test lock when already locked + try + { + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + fail("The user should not be able to lock the node since it is already locked by another user."); + } + catch (UnableToAquireLockException exception) + { + if (logger.isDebugEnabled()) + { + logger.debug(exception.getMessage()); + } + } + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Test already locked by this user + try + { + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + } + catch (Exception exception) + { + fail("No error should be thrown when a node is re-locked by the current lock owner."); + } + + // Test with no aspect node + this.lockService.lock(this.noAspectNode, LockType.WRITE_LOCK); + assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); + } + + @Test + public void testPersistentLockMayStoreAdditionalInfo() + { + lockService.lock(noAspectNode, LockType.NODE_LOCK, 0, Lifetime.PERSISTENT, "additional info"); + + LockState lockState = lockService.getLockState(noAspectNode); + assertEquals("retrieved additional info should be the same", "additional info", lockState.getAdditionalInfo()); + } + + @Test + public void testEphemeralLock() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check that the node is not currently locked + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); + + // Check that there really is no lockable aspect + assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); + + // Lock the node + lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL, "some extra data"); + + // Check additionalInfo has been stored + assertEquals("retrieved additional info should be the same", "some extra data", lockService.getAdditionalInfo(noAspectNode)); + + // Check that we can retrieve LockState + LockState lockState = lockService.getLockState(noAspectNode); + assertEquals("retrieved node should be the same", noAspectNode, lockState.getNodeRef()); + assertEquals("retrieved lock type should be write type", LockType.WRITE_LOCK, lockState.getLockType()); + assertEquals("retrieved lock owner should be the same", GOOD_USER_NAME, lockState.getOwner()); + assertEquals("retrieved lock lifetime should be ephemeral", Lifetime.EPHEMERAL, lockState.getLifetime()); + assertNotNull("retrieved expire date should not be null", lockState.getExpires()); + assertEquals("retrieved additional info should be the same", "some extra data", lockState.getAdditionalInfo()); + // The node should be locked + assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); + assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); + + // The node must still not have the lockable aspect applied + assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); + // ...though the full node service should report that it is present + NodeService fullNodeService = (NodeService) applicationContext.getBean("nodeService"); + assertTrue("noAspectNode should have aspect", fullNodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + assertEquals("noAspectNode should be locked", LockStatus.LOCKED, lockService.getLockStatus(noAspectNode)); + assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); + + // Test lock when already locked + try + { + lockService.lock(noAspectNode, LockType.WRITE_LOCK); + fail("The user should not be able to lock the node since it is already locked by another user."); + } + catch (UnableToAquireLockException exception) + { + if (logger.isDebugEnabled()) + { + logger.debug(exception.getMessage()); + } + } + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); + assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); + + // Test already locked by this user - relock + try + { + lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); + } + catch (Exception exception) + { + fail("No error should be thrown when a node is re-locked by the current lock owner."); + } + + // The node should be locked + assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); + assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); + // If we remove the lock info directly from the memory store then the node should no longer + // be reported as locked (as it is an ephemeral lock) + LockStore lockStore = (LockStore) applicationContext.getBean("lockStore"); + lockStore.clear(); + // The node must no longer be reported as locked + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); + + // Lock again, ready to test unlocking an ephemeral lock. + try + { + lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); + } + catch (Exception exception) + { + fail("No error should be thrown when a node is re-locked by the current lock owner."); + } + + assertEquals("noAspectNode should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(noAspectNode)); + assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); + + lockService.unlock(noAspectNode); + + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); + } + + @Test + @Category(RedundantTests.class) + public void testEphemeralLockIndexing() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, authenticationService); + + IndexerAndSearcher indexerAndSearcher = (IndexerAndSearcher) applicationContext.getBean("indexerAndSearcherFactory"); + SearcherComponent searcher = new SearcherComponent(); + searcher.setIndexerAndSearcherFactory(indexerAndSearcher); + + // Create a lock (owned by the current user) + lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL); + + // Query for the user's locks + final String query = String.format("+@cm\\:lockOwner:\"%s\" +@cm\\:lockType:\"WRITE_LOCK\"", GOOD_USER_NAME); + ResultSet rs = searcher.query(storeRef, "lucene", query); + assertTrue("lock should be found", rs.getNodeRefs().contains(noAspectNode)); + + // Unlock the node + lockService.unlock(noAspectNode); + + // Perform a new search, the index should reflect that it is not locked. + rs = searcher.query(storeRef, "lucene", query); + assertFalse("lock should not be found", rs.getNodeRefs().contains(noAspectNode)); + } + + /* MNT-10477 related test */ + @Test + public void testEphemeralLockModifyNode() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check that the node is not currently locked + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); + + // Check that there really is no lockable aspect + assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); + + // Lock the node + lockService.lock(noAspectNode, LockType.WRITE_LOCK, 86400, Lifetime.EPHEMERAL, "some extra data"); + + // get bad user + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + assertEquals("noAspectNode should be locked", LockStatus.LOCKED, lockService.getLockStatus(noAspectNode)); + assertTrue("noAspectNode should be locked", lockService.isLocked(noAspectNode)); + + NodeService fullNodeService = (NodeService) applicationContext.getBean("nodeService"); + + /* addProperties test */ + try + { + Map props = new HashMap<>(); + props.put(ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis()); + props.put(ContentModel.PROP_TITLE, "title" + System.currentTimeMillis()); + fullNodeService.addProperties(noAspectNode, props); + + fail("node should be locked"); + } + catch (NodeLockedException e) + { + // it's ok - node supposed to be locked + } + + /* setProperty test */ + try + { + fullNodeService.setProperty(noAspectNode, ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis()); + + fail("node should be locked"); + } + catch (NodeLockedException e) + { + // it's ok - node supposed to be locked + } + + /* setProperties test */ + try + { + Map props = new HashMap<>(); + props.put(ContentModel.PROP_DESCRIPTION, "descr" + System.currentTimeMillis()); + props.put(ContentModel.PROP_TITLE, "title" + System.currentTimeMillis()); + fullNodeService.setProperties(noAspectNode, props); + + fail("node should be locked"); + } + catch (NodeLockedException e) + { + // it's ok - node supposed to be locked + } + + /* removeProperty test */ + try + { + fullNodeService.removeProperty(noAspectNode, ContentModel.PROP_DESCRIPTION); + + fail("node should be locked"); + } + catch (NodeLockedException e) + { + // it's ok - node supposed to be locked + } + + /* addAspect test */ + try + { + fullNodeService.addAspect(noAspectNode, ContentModel.ASPECT_AUTHOR, null); + + fail("node should be locked"); + } + catch (NodeLockedException e) + { + // it's ok - node supposed to be locked + } + + /* removeAspect test */ + try + { + fullNodeService.removeAspect(noAspectNode, ContentModel.ASPECT_AUTHOR); + + fail("node should be locked"); + } + catch (NodeLockedException e) + { + // it's ok - node supposed to be locked + } + + /* setType test */ + try + { + fullNodeService.setType(noAspectNode, ContentModel.TYPE_CMOBJECT); + + fail("node should be locked"); + } + catch (NodeLockedException e) + { + // it's ok - node supposed to be locked + } + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + lockService.unlock(noAspectNode); + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); + } + + /** + * Test that covers MNT-17612 - having an expired ephemeral lock and a persistent one on the same node. + */ + @Test + public void testExpiredEphemeralLockAndPersistentLock() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check that the node is not currently locked + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, securedLockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", securedLockService.isLocked(noAspectNode)); + + // Check that there really is no lockable aspect + assertFalse("noAspectNode should not have lockable aspect", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); + + // Lock the node + securedLockService.lock(noAspectNode, LockType.WRITE_LOCK, 1, Lifetime.EPHEMERAL); + + // Check that we can retrieve LockState + LockState lockState = securedLockService.getLockState(noAspectNode); + assertEquals("retrieved node should be the same", noAspectNode, lockState.getNodeRef()); + assertEquals("retrieved lock type should be write type", LockType.WRITE_LOCK, lockState.getLockType()); + assertEquals("retrieved lock owner should be the same", GOOD_USER_NAME, lockState.getOwner()); + assertEquals("retrieved lock lifetime should be ephemeral", Lifetime.EPHEMERAL, lockState.getLifetime()); + assertNotNull("retrieved expire date should not be null", lockState.getExpires()); + + // Wait to give the ephemeral lock time to expire + await().pollInSameThread() + .atMost(MAX_ASYNC_TIMEOUT) + .until(() -> !securedLockService.isLocked(noAspectNode)); + + assertFalse("noAspectNode should not be locked", securedLockService.isLocked(noAspectNode)); + + // Do a persistent lock with a different user (simulate an Edit Offline - MNT-17612) + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Lock the node + securedLockService.lock(noAspectNode, LockType.READ_ONLY_LOCK, 1000, Lifetime.PERSISTENT); + + assertTrue("noAspectNode should be locked", securedLockService.isLocked(noAspectNode)); + assertTrue("noAspectNode should be locked", nodeService.hasAspect(noAspectNode, ContentModel.ASPECT_LOCKABLE)); + assertEquals("noAspectNode should have read only lock type", LockType.READ_ONLY_LOCK, securedLockService.getLockType(noAspectNode)); + + // Check that we can retrieve LockState + lockState = securedLockService.getLockState(noAspectNode); + assertEquals("retrieved node should be the same", noAspectNode, lockState.getNodeRef()); + assertEquals("retrieved lock type should be read only type", LockType.READ_ONLY_LOCK, lockState.getLockType()); + assertEquals("retrieved lock owner should be the same", BAD_USER_NAME, lockState.getOwner()); + assertEquals("retrieved lock lifetime should be persistent", Lifetime.PERSISTENT, lockState.getLifetime()); + + // Check unlock + securedLockService.unlock(noAspectNode); + + assertFalse("noAspectNode should not be locked", securedLockService.isLocked(noAspectNode)); + } + + @Test + public void testLockRevertedOnRollback() + { + // Preconditions of test + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); + assertEquals("rootNodeRef should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(rootNodeRef)); + assertFalse("rootNodeRef should not be locked", lockService.isLocked(rootNodeRef)); + + // Lock noAspectNode + lockService.lock(noAspectNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); + + // Lock rootNodeRef + lockService.lock(rootNodeRef, LockType.NODE_LOCK, 0, Lifetime.EPHEMERAL); + + // Sometime later, a refresh occurs (so this should not be reverted to unlocked, but to this state) + lockService.lock(rootNodeRef, LockType.NODE_LOCK, 3600, Lifetime.EPHEMERAL); + + // Rollback + TestTransaction.end(); + + // This lock should not be present. + assertEquals("noAspectNode should not be locked", LockStatus.NO_LOCK, lockService.getLockStatus(noAspectNode)); + assertFalse("noAspectNode should not be locked", lockService.isLocked(noAspectNode)); + + // This lock should still be present. + assertEquals("rootNodeRef should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(rootNodeRef)); + assertTrue("rootNodeRef should be locked", lockService.isLocked(rootNodeRef)); + } + + /** + * Test unlock node + */ + @Test + public void testUnlock() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Lock the parent node + testLock(); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Try and unlock a locked node + try + { + this.lockService.unlock(this.parentNode); + // This will pass in the open workd + // fail("A user cannot unlock a node that is currently lock by another user."); + } + catch (UnableToReleaseLockException exception) + { + if (logger.isDebugEnabled()) + { + logger.debug(exception.getMessage()); + } + } + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Unlock the node + this.lockService.unlock(this.parentNode); + assertEquals( + "parentNode should not be locked", + LockStatus.NO_LOCK, + this.lockService.getLockStatus(this.parentNode)); + assertFalse("parentNode should not be locked", lockService.isLocked(parentNode)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + assertEquals( + "parentNode should not be locked", + LockStatus.NO_LOCK, + this.lockService.getLockStatus(this.parentNode)); + assertFalse("parentNode should not be locked", lockService.isLocked(parentNode)); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Try and unlock node with no lock + try + { + this.lockService.unlock(this.parentNode); + } + catch (Exception exception) + { + fail("Unlocking an unlocked node should not result in an exception being raised."); + } + + // Test with no aspect node + this.lockService.unlock(this.noAspectNode); + } + + /** + * Test getLockStatus + */ + @Test + public void testGetLockStatus() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check an unlocked node + LockStatus lockStatus1 = this.lockService.getLockStatus(this.parentNode); + assertEquals("parentNode should not be locked", LockStatus.NO_LOCK, lockStatus1); + + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check for locked status + LockStatus lockStatus2 = this.lockService.getLockStatus(this.parentNode); + assertEquals("parentNode should be locked", LockStatus.LOCKED, lockStatus2); + + // Check lockstore is not used for persistent locks + // Previously LockStore was doubling up as a cache - the change in requirements means a test + // is necessary to ensure the work has been implemented correctly (despite being an odd test) + LockStore lockStore = (LockStore) applicationContext.getBean("lockStore"); + lockStore.clear(); + LockState lockState = lockStore.get(parentNode); + // Nothing stored against node ref + assertNull("lock state should be null", lockState); + lockService.getLockStatus(parentNode); + // In-memory store still empty - only used for ephemeral locks + lockState = lockStore.get(parentNode); + assertNull("lock state should be null", lockState); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Check for lock owner status + LockStatus lockStatus3 = this.lockService.getLockStatus(this.parentNode); + assertEquals("lock status should be lock owner type", LockStatus.LOCK_OWNER, lockStatus3); + + // Test with no aspect node + this.lockService.getLockStatus(this.noAspectNode); + + // Test method overload + LockStatus lockStatus4 = this.lockService.getLockStatus(this.parentNode); + assertEquals("lock status should be lock owner type", LockStatus.LOCK_OWNER, lockStatus4); + } + + @Test + @Category(RedundantTests.class) + public void testGetLocks() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + LockServiceImpl lockService = (LockServiceImpl) this.lockService; + List locked1 = lockService.getLocks(this.storeRef); + assertNotNull("nodes list should not be null", locked1); + assertEquals("nodes list should be empty", 0, locked1.size()); + + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + + assertFalse("child node1 should not be locked", lockService.isLocked(childNode1)); + assertFalse("child node1 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode1)); + this.lockService.lock(this.childNode1, LockType.WRITE_LOCK); + assertTrue("child node1 should be locked", lockService.isLocked(childNode1)); + assertFalse("child node1 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode1)); + + assertFalse("child node2 should not be locked", lockService.isLocked(childNode2)); + assertFalse("child node2 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode2)); + this.lockService.lock(this.childNode2, LockType.READ_ONLY_LOCK); + assertTrue("child node2 should be locked", lockService.isLocked(childNode2)); + assertTrue("child node2 should not be locked or write lock type", lockService.isLockedAndReadOnly(childNode2)); + + List locked2 = lockService.getLocks(this.storeRef); + assertNotNull("nodes list should not be null", locked2); + assertEquals("nodes list should have size of three", 3, locked2.size()); + + List locked3 = lockService.getLocks(this.storeRef, LockType.WRITE_LOCK); + assertNotNull("nodes list should not be null", locked3); + assertEquals("nodes list should have size of two", 2, locked3.size()); + + List locked4 = lockService.getLocks(this.storeRef, LockType.READ_ONLY_LOCK); + assertNotNull("nodes list should not be null", locked4); + assertEquals("nodes list should have size of one", 1, locked4.size()); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + List locked5 = lockService.getLocks(this.storeRef); + assertNotNull("nodes list should not be null", locked5); + assertEquals("nodes list should be empty", 0, locked5.size()); + } + + /** + * Test getLockType (and isLocked/isLockedReadOnly) + */ + @Test + public void testGetLockType() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Get the lock type (should be null since the object is not locked) + LockType lockType1 = this.lockService.getLockType(this.parentNode); + assertNull("lock type should be null", lockType1); + + // Lock the object for writing + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + LockType lockType2 = this.lockService.getLockType(this.parentNode); + assertNotNull("lock type should not be null", lockType2); + assertEquals("lock should be write type", LockType.WRITE_LOCK, lockType2); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Unlock the node + this.lockService.unlock(this.parentNode); + LockType lockType3 = this.lockService.getLockType(this.parentNode); + assertNull("lock type should be null", lockType3); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Lock the object for read only + this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK); + LockType lockType4 = this.lockService.getLockType(this.parentNode); + assertNotNull("lock type should not be null", lockType4); + assertEquals("lock should be read only type", LockType.READ_ONLY_LOCK, lockType4); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Lock the object for node lock + this.lockService.lock(this.parentNode, LockType.NODE_LOCK); + LockType lockType5 = this.lockService.getLockType(this.parentNode); + assertNotNull("lock type should not be null", lockType5); + assertEquals("lock should be node type", LockType.NODE_LOCK, lockType5); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Unlock the node + this.lockService.unlock(this.parentNode); + LockType lockType6 = this.lockService.getLockType(this.parentNode); + assertNull("lock type should be null", lockType6); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Test with no aspect node + LockType lockType7 = this.lockService.getLockType(this.noAspectNode); + assertNull("lock type should be null", lockType7); + } + + @Test + public void testGetLockTypeEphemeral() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Get the lock type (should be null since the object is not locked) + LockType lockType1 = this.lockService.getLockType(this.parentNode); + assertNull("lock type should be null", lockType1); + + // Lock the object for writing + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 0, Lifetime.EPHEMERAL); + LockType lockType2 = this.lockService.getLockType(this.parentNode); + assertNotNull("lock type should not be null", lockType2); + assertEquals("lock should be write type", LockType.WRITE_LOCK, lockType2); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Unlock the node + this.lockService.unlock(this.parentNode); + LockType lockType3 = this.lockService.getLockType(this.parentNode); + assertNull("lock type should be null", lockType3); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Lock the object for read only + this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK, 0, Lifetime.EPHEMERAL); + LockType lockType4 = this.lockService.getLockType(this.parentNode); + assertNotNull("lock type should not be null", lockType4); + assertEquals("lock should be read only type", LockType.READ_ONLY_LOCK, lockType4); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Lock the object for node lock + this.lockService.lock(this.parentNode, LockType.NODE_LOCK, 0, Lifetime.EPHEMERAL); + LockType lockType5 = this.lockService.getLockType(this.parentNode); + assertNotNull("lock type should not be null", lockType5); + assertEquals("lock should be node type", LockType.NODE_LOCK, lockType5); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + assertTrue("parent node should be locked and not write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Unlock the node + this.lockService.unlock(this.parentNode); + LockType lockType6 = this.lockService.getLockType(this.parentNode); + assertNull("lock type should be null", lockType6); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + assertFalse("parent node should not be locked or write lock type", lockService.isLockedAndReadOnly(parentNode)); + + // Test with no apect node + LockType lockType7 = this.lockService.getLockType(this.noAspectNode); + assertNull("lock type should be null", lockType7); + } + + @Test + public void testTimeToExpire() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1); + assertEquals("lock status should be owner", LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode)); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + assertEquals("lock status should be locked", LockStatus.LOCKED, this.lockService.getLockStatus(this.parentNode)); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + + // Wait for lock to expire before re-testing the status + await().pollInSameThread() + .pollDelay(Duration.ofMillis(500)) + .atMost(MAX_ASYNC_TIMEOUT).until(() -> lockService.getLockStatus(parentNode), LockStatus.LOCK_EXPIRED::equals); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + + // Re-lock and then update the time to expire before lock expires + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 0); + try + { + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1); + fail("Can not update lock info if not lock owner"); + } + catch (UnableToAquireLockException exception) + { + // Expected + } + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK, 1); + assertEquals("lock status should be owner", LockStatus.LOCK_OWNER, this.lockService.getLockStatus(this.parentNode)); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + assertEquals("lock status should be locked", LockStatus.LOCKED, this.lockService.getLockStatus(this.parentNode)); + assertTrue("parent node should be locked", lockService.isLocked(parentNode)); + + // Wait for lock to expire before re-testing the status + await().pollInSameThread() + .pollDelay(Duration.ofMillis(500)) + .atMost(MAX_ASYNC_TIMEOUT) + .until(() -> lockService.getLockStatus(parentNode), LockStatus.LOCK_EXPIRED::equals); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + assertEquals("lock status should be expired", LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode)); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + } + + @Test + public void testEphemeralExpiryThreshold() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + final int origThresh = ((LockServiceImpl) lockService).getEphemeralExpiryThreshold(); + // Check the default situation is that the threshold does not apply. + assertEquals("threshold should not apply", LockServiceImpl.MAX_EPHEMERAL_LOCK_SECONDS, origThresh); + try + { + // Set the ephemeral expiry threshold to a much smaller value than the default + // so that it takes effect. + lockService.setEphemeralExpiryThreshold(300); + + // Check for an expiry time that should be unaffected by the threshold. + checkLifetimeForExpiry(Lifetime.EPHEMERAL, 0, Lifetime.EPHEMERAL); + checkLifetimeForExpiry(Lifetime.EPHEMERAL, 150, Lifetime.EPHEMERAL); + + // Check the largest allowed ephemeral expiry time. + checkLifetimeForExpiry(Lifetime.EPHEMERAL, 300, Lifetime.EPHEMERAL); + + // When the expiry is greater than the threshold, then the lock should be + // applied as a persistent lock. + checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL); + + // Switch off ephemeral locks entirely + lockService.setEphemeralExpiryThreshold(-1); + // Always persistent... + checkLifetimeForExpiry(Lifetime.PERSISTENT, 0, Lifetime.EPHEMERAL); + checkLifetimeForExpiry(Lifetime.PERSISTENT, 150, Lifetime.EPHEMERAL); + checkLifetimeForExpiry(Lifetime.PERSISTENT, 300, Lifetime.EPHEMERAL); + checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL); + } + finally + { + lockService.setEphemeralExpiryThreshold(origThresh); + } + } + + private void checkLifetimeForExpiry(Lifetime expectedLifetime, int expirySecs, Lifetime requestedLifetime) + { + lockService.unlock(parentNode); + assertNotEquals("lock status should not be locked", LockStatus.LOCKED, lockService.getLockStatus(parentNode)); + lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, requestedLifetime); + LockState lock = lockService.getLockState(parentNode); + assertEquals("lock lifetime should be the same", expectedLifetime, lock.getLifetime()); + + // Check that for any timeouts we test, a request for a persistent lock always yields a persistent lock. + lockService.unlock(parentNode); + assertNotEquals("lock status should not be locked", LockStatus.LOCKED, lockService.getLockStatus(parentNode)); + lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, Lifetime.PERSISTENT); + lock = lockService.getLockState(parentNode); + assertEquals("lock lifetime should be persistent", Lifetime.PERSISTENT, lock.getLifetime()); + } + + /** + * Unit test to validate the behaviour of creating children of locked nodes. No lock - can create children READ_ONLY_LOCK - can't create children WRITE_LOCK - owner can create children non owner can't create children NODE_LOCK non owner can create children owner can create children + */ + @Test + public void testCreateChildrenOfLockedNodes() throws Exception + { + + /* Check we can create a child of an unlocked node. */ + assertEquals( + "parent node should not be locked", + LockStatus.NO_LOCK, + this.lockService.getLockStatus(this.parentNode)); + assertFalse("parent node should not be locked", lockService.isLocked(parentNode)); + + nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildA"), ContentModel.TYPE_FOLDER); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + this.lockService.lock(this.parentNode, LockType.WRITE_LOCK); + + // Owner can create children + nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildB"), ContentModel.TYPE_FOLDER); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + try + { + // Non owner can't create children with a write lock in place + nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildB"), ContentModel.TYPE_FOLDER); + fail("could create a child with a read only lock"); + } + catch (NodeLockedException e) + { + logger.debug("exception while trying to create a child of a read only lock", e); + } + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + this.lockService.lock(this.parentNode, LockType.NODE_LOCK); + + // owner can create children with a node lock + nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildD"), ContentModel.TYPE_FOLDER); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Non owner can create children with a node lock + nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildC"), ContentModel.TYPE_FOLDER); + + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + this.lockService.lock(this.parentNode, LockType.READ_ONLY_LOCK); + + // owner should not be able to create children with a READ_ONLY_LOCK + try + { + nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildD"), ContentModel.TYPE_FOLDER); + fail("could create a child with a read only lock"); + } + catch (NodeLockedException e) + { + logger.debug("exception while trying to create a child of a read only lock", e); + } + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // Non owner should not be able to create children with READ_ONLY_LOCK + try + { + nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("ChildE"), ContentModel.TYPE_FOLDER); + fail("could create a child with a read only lock"); + } + catch (NodeLockedException e) + { + logger.debug("exception while trying to create a child of a read only lock", e); + } + } + + /** + * Test that it is impossible to unlock a checked out node + */ + @Test + public void testUnlockCheckedOut() + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + try + { + this.lockService.unlock(checkedOutNode); + fail("could unlock a checked out node"); + } + catch (UnableToReleaseLockException e) + { + logger.debug("exception while trying to unlock a checked out node", e); + } + + assertTrue("checkedOutNode should be locked", lockService.isLocked(checkedOutNode)); + assertTrue("checkedOutNode should be locked and not write lock type", lockService.isLockedAndReadOnly(checkedOutNode)); + } + + @SuppressWarnings("deprecation") + @Test + public void testUnlockNodeWithAdminUserAndAllPermissionsUser() + { + for (Lifetime lt : new Lifetime[]{Lifetime.EPHEMERAL, Lifetime.PERSISTENT}) + { + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + /* create node */ + final NodeRef testNode = this.nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{}testNode"), ContentModel.TYPE_CONTAINER).getChildRef(); + + // lock it as GOOD user + this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null); + + // check lock state and status as GOOD user + assertNotNull("lock state should not be null", this.securedLockService.getLockState(testNode)); + assertNotNull("lock status should not be null", this.securedLockService.getLockStatus(testNode)); + assertTrue("test node should be locked", this.securedLockService.isLocked(testNode)); + assertFalse("test node should not be locked or write lock type", this.securedLockService.isLockedAndReadOnly(testNode)); + + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + + // check lock state and status as BAD user + assertNotNull("lock state should not be null", this.securedLockService.getLockState(testNode)); + assertNotNull("lock status should not be null", this.securedLockService.getLockStatus(testNode)); + assertTrue("test node should be locked", this.securedLockService.isLocked(testNode)); + assertTrue("test node should be locked and not write lock type", this.securedLockService.isLockedAndReadOnly(testNode)); + + try + { + // try to unlock as bad user + this.securedLockService.unlock(testNode); + fail("BAD user shouldn't be able to unlock " + lt + " lock"); + } + catch (AccessDeniedException e) + { + // expected exception + } + + TestWithUserUtils.authenticateUser(AuthenticationUtil.getAdminUserName(), "admin", rootNodeRef, this.authenticationService); + + // check lock state and status as ADMIN user + assertNotNull("lock state should not be null", this.securedLockService.getLockState(testNode)); + assertNotNull("lock status should not be null", this.securedLockService.getLockStatus(testNode)); + assertTrue("test node should not be locked", this.securedLockService.isLocked(testNode)); + assertTrue("checkedOutNode should be locked and not write lock type", this.securedLockService.isLockedAndReadOnly(testNode)); + + // try to unlock as ADMIN user + this.securedLockService.unlock(testNode); + + // test that bad use able to lock/unlock node + TestWithUserUtils.authenticateUser(BAD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null); + this.securedLockService.unlock(testNode); + + this.securedLockService.lock(testNode, LockType.WRITE_LOCK, 2 * 86400, lt, null); + + // user who has ALL PERMISSIONS is able to unlock another's user lock + TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService); + this.securedLockService.unlock(testNode); + + this.nodeService.deleteNode(testNode); + } + } +} diff --git a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index 17890a0f60..e3d1acc652 100644 --- a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -1,2029 +1,1970 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2023 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.rule; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeMessage; -import jakarta.transaction.UserTransaction; - -import junit.framework.TestCase; - -import org.alfresco.model.ApplicationModel; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.ActionServiceImplTest; -import org.alfresco.repo.action.ActionServiceImplTest.AsyncTest; -import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; -import org.alfresco.repo.action.evaluator.InCategoryEvaluator; -import org.alfresco.repo.action.evaluator.NoConditionEvaluator; -import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; -import org.alfresco.repo.action.executer.CheckInActionExecuter; -import org.alfresco.repo.action.executer.CheckOutActionExecuter; -import org.alfresco.repo.action.executer.CopyActionExecuter; -import org.alfresco.repo.action.executer.ImageTransformActionExecuter; -import org.alfresco.repo.action.executer.LinkCategoryActionExecuter; -import org.alfresco.repo.action.executer.MailActionExecuter; -import org.alfresco.repo.action.executer.MoveActionExecuter; -import org.alfresco.repo.action.executer.SimpleWorkflowActionExecuter; -import org.alfresco.repo.action.executer.TransformActionExecuter; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.transform.AbstractContentTransformerTest; -import org.alfresco.repo.dictionary.DictionaryDAO; -import org.alfresco.repo.dictionary.IndexTokenisationMode; -import org.alfresco.repo.dictionary.M2Aspect; -import org.alfresco.repo.dictionary.M2Model; -import org.alfresco.repo.dictionary.M2Property; -import org.alfresco.repo.management.subsystems.ApplicationContextFactory; -import org.alfresco.repo.node.integrity.IntegrityException; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockStatus; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.CopyService; -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.rule.Rule; -import org.alfresco.service.cmr.rule.RuleService; -import org.alfresco.service.cmr.rule.RuleServiceException; -import org.alfresco.service.cmr.rule.RuleType; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.test_category.OwnJVMTestsCategory; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.GUID; -import org.junit.experimental.categories.Category; -import org.springframework.context.ApplicationContext; -import org.springframework.util.StopWatch; - -/** - * @author Roy Wetherall - */ -@Category(OwnJVMTestsCategory.class) -public class RuleServiceCoverageTest extends TestCase -{ - /** - * Application context used during the test - */ - static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); - - /** - * Services used during the tests - */ - private TransactionService transactionService; - private RuleService ruleService; - private NodeService nodeService; - private StoreRef testStoreRef; - private NodeRef rootNodeRef; - private NodeRef nodeRef; - private CheckOutCheckInService cociService; - private LockService lockService; - private ContentService contentService; - private ServiceRegistry serviceRegistry; - private DictionaryDAO dictionaryDAO; - private ActionService actionService; - private CopyService copyService; - private AuthenticationComponent authenticationComponent; - private FileFolderService fileFolderService; - - /** - * Category related values - */ - private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/rulesystemtest"; - private static final QName CAT_PROP_QNAME = QName.createQName(TEST_NAMESPACE, "region"); - private QName regionCategorisationQName; - private NodeRef catContainer; - private NodeRef catRoot; - private NodeRef catRBase; - private NodeRef catROne; - private NodeRef catRTwo; - @SuppressWarnings("unused") - private NodeRef catRThree; - - /** - * Standard content text - */ - private static final String STANDARD_TEXT_CONTENT = "standardTextContent"; - - /** - * Setup method - */ - @Override - protected void setUp() throws Exception - { - // Get the required services - this.serviceRegistry = (ServiceRegistry)applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); - this.nodeService = serviceRegistry.getNodeService(); - this.ruleService = serviceRegistry.getRuleService(); - this.cociService = serviceRegistry.getCheckOutCheckInService(); - this.lockService = serviceRegistry.getLockService(); - this.copyService = serviceRegistry.getCopyService(); - this.contentService = serviceRegistry.getContentService(); - this.dictionaryDAO = (DictionaryDAO)applicationContext.getBean("dictionaryDAO"); - this.actionService = serviceRegistry.getActionService(); - this.transactionService = serviceRegistry.getTransactionService(); - this.authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); - this.fileFolderService = serviceRegistry.getFileFolderService(); - - //authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); - //authenticationComponent.setSystemUserAsCurrentUser(); - RetryingTransactionCallback setUserCallback = new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); - return null; - } - }; - transactionService.getRetryingTransactionHelper().doInTransaction(setUserCallback); - - this.testStoreRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); - - // Create the node used for tests - this.nodeRef = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTAINER).getChildRef(); - } - - private Rule createRule( - String ruleTypeName, - String actionName, - Map actionParams, - String conditionName, - Map conditionParams) - { - Rule rule = new Rule(); - rule.setTitle(GUID.generate()); - rule.setRuleType(ruleTypeName); - - Action action = this.actionService.createAction(actionName, actionParams); - ActionCondition condition = this.actionService.createActionCondition(conditionName, conditionParams); - action.addActionCondition(condition); - rule.setAction(action); - - return rule; - } - - /** - * Create the categories used in the tests - */ - private void createTestCategories() - { - // Create the test model - M2Model model = M2Model.createModel("test:rulecategory"); - model.createNamespace(TEST_NAMESPACE, "test"); - model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, "d"); - model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX); - - // Create the region category - regionCategorisationQName = QName.createQName(TEST_NAMESPACE, "region"); - M2Aspect generalCategorisation = model.createAspect("test:" + regionCategorisationQName.getLocalName()); - generalCategorisation.setParentName("cm:" + ContentModel.ASPECT_CLASSIFIABLE.getLocalName()); - M2Property genCatProp = generalCategorisation.createProperty("test:region"); - genCatProp.setIndexed(true); - genCatProp.setIndexedAtomically(true); - genCatProp.setMandatory(true); - genCatProp.setMultiValued(true); - genCatProp.setStoredInIndex(true); - genCatProp.setIndexTokenisationMode(IndexTokenisationMode.TRUE); - genCatProp.setType("d:" + DataTypeDefinition.CATEGORY.getLocalName()); - - // Save the mode - dictionaryDAO.putModel(model); - - // Create the category value container and root - catContainer = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "categoryContainer"), ContentModel.TYPE_CONTAINER).getChildRef(); - catRoot = nodeService.createNode(catContainer, ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "categoryRoot"), ContentModel.TYPE_CATEGORYROOT).getChildRef(); - - // Create the category values - catRBase = nodeService.createNode(catRoot, ContentModel.ASSOC_CATEGORIES, QName.createQName(TEST_NAMESPACE, "region"), ContentModel.TYPE_CATEGORY).getChildRef(); - catROne = nodeService.createNode(catRBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "Europe"), ContentModel.TYPE_CATEGORY).getChildRef(); - catRTwo = nodeService.createNode(catRBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "RestOfWorld"), ContentModel.TYPE_CATEGORY).getChildRef(); - catRThree = nodeService.createNode(catRTwo, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "US"), ContentModel.TYPE_CATEGORY).getChildRef(); - } - - /** - * Asynchronous rule tests - */ - - /** - * Check async rule execution - */ - public void testAsyncRuleExecution() - { - final NodeRef newNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback() - { - public NodeRef execute() - { - RuleServiceCoverageTest.this.nodeService.addAspect( - RuleServiceCoverageTest.this.nodeRef, - ContentModel.ASPECT_LOCKABLE, - null); - - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - rule.setExecuteAsynchronously(true); - - RuleServiceCoverageTest.this.ruleService.saveRule(RuleServiceCoverageTest.this.nodeRef, rule); - - NodeRef newNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( - RuleServiceCoverageTest.this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - - return newNodeRef; - } - }); - - ActionServiceImplTest.postAsyncActionTest( - this.transactionService, - 5000, - 12, - new AsyncTest() - { - public String executeTest() - { - boolean result = RuleServiceCoverageTest.this.nodeService.hasAspect( - newNodeRef, - ContentModel.ASPECT_VERSIONABLE); - return result ? null : "Expected aspect Versionable"; - }; - }); - } - - // TODO check compensating action execution - - /** - * Standard rule coverage tests - */ - - /** - * Test: - * rule type: inbound - * condition: no-condition() - * action: add-features( - * aspect-name = versionable) - */ - public void testAddFeaturesAction() - { - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); - - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - Map params2 = new HashMap(2); - params2.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ApplicationModel.ASPECT_SIMPLE_WORKFLOW); - params2.put(ApplicationModel.PROP_APPROVE_STEP.toString(), "approveStep"); - params2.put(ApplicationModel.PROP_APPROVE_MOVE.toString(), false); - - // Test that rule can be updated and execute correctly - //rule.removeAllActions(); - Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME, params2); - rule.setAction(action2); - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef2); - assertTrue(this.nodeService.hasAspect(newNodeRef2, ApplicationModel.ASPECT_SIMPLE_WORKFLOW)); - assertEquals("approveStep", this.nodeService.getProperty(newNodeRef2, ApplicationModel.PROP_APPROVE_STEP)); - assertEquals(false, this.nodeService.getProperty(newNodeRef2, ApplicationModel.PROP_APPROVE_MOVE)); - - // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - - public void testCheckThatModifyNameDoesNotTriggerInboundRule() throws Exception - { - //this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); - Map folderProps = new HashMap(1); - folderProps.put(ContentModel.PROP_NAME, "myTestFolder"); - NodeRef folder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - folderProps).getChildRef(); - - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(folder, rule); - - Map contentProps = new HashMap(1); - contentProps.put(ContentModel.PROP_NAME, "myTestDocument.txt"); - NodeRef newNodeRef = this.nodeService.createNode( - folder, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myTestDocument.txt"), - ContentModel.TYPE_CONTENT, - contentProps).getChildRef(); - //addContentToNode(newNodeRef); - nodeService.removeAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Use the file folder to change the name of the node - this.fileFolderService.rename(newNodeRef, "myNewName.txt"); - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - } - - public void testCheckThatModifyNameDoesNotTriggerOutboundRule() throws Exception - { - Map folderProps = new HashMap(1); - folderProps.put(ContentModel.PROP_NAME, "myTestFolder"); - NodeRef folder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - folderProps).getChildRef(); - - Map params = new HashMap(1); - params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.OUTBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(folder, rule); - - Map contentProps = new HashMap(1); - contentProps.put(ContentModel.PROP_NAME, "myTestDocument.txt"); - NodeRef newNodeRef = fileFolderService.create(folder, "abc.txt", ContentModel.TYPE_CONTENT).getNodeRef(); - assertFalse("Should not be versionable", nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Use the file folder to change the name of the node - fileFolderService.rename(newNodeRef, "myNewName.txt"); - assertFalse("Should not be versionable", nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - } - - public void testCheckThatChildRuleFiresOnMove() throws Exception - { - //ALF-9415 test - Map folderProps = new HashMap(1); - folderProps.put(ContentModel.PROP_NAME, "myTestFolder"); - NodeRef folder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - folderProps).getChildRef(); - - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - rule.applyToChildren(true); - this.ruleService.saveRule(folder, rule); - - folderProps.put(ContentModel.PROP_NAME, "myMoveFolder"); - NodeRef folderForMove = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - folderProps).getChildRef(); - - NodeRef testFile = this.fileFolderService.create(folderForMove, "testFile.txt", ContentModel.TYPE_CONTENT).getNodeRef(); - - this.fileFolderService.move(folderForMove, folder, null); - assertTrue("Should be versionable", nodeService.hasAspect(testFile, ContentModel.ASPECT_VERSIONABLE)); - } - - /** - * ALF-4926: Incorrect behavior of update and move rule for the same folder - *

- * Two rules:

    - *
  • When items are deleted, copy to another folder.
  • - *
  • In addition, when items are updated, add an aspect (or any other rule).
- * Ensure that the first copy does not result in rules being fired on the target. - */ - public void testUpdateAndMoveRuleOnSameFolder() throws Exception - { - NodeRef sourceFolder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}sourceFolder"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef targetFolder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}targetFolder"), - ContentModel.TYPE_FOLDER).getChildRef(); - - // Create UPDATE rule to add lockable aspect - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_LOCKABLE); - Rule rule = createRule( - RuleType.UPDATE, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - this.ruleService.saveRule(sourceFolder, rule); - - // Check that the UPDATE rule works - NodeRef testNodeOneRef = fileFolderService.create(sourceFolder, "one.txt", ContentModel.TYPE_CONTENT).getNodeRef(); - assertFalse( - "Node should not have lockable aspect", - nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); - nodeService.setProperty(testNodeOneRef, ContentModel.PROP_LOCALE, Locale.CANADA); - assertTrue( - "Node should have lockable aspect", - nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); - fileFolderService.delete(testNodeOneRef); - - // Create OUTBOUND rule to copy node being deleted - params = new HashMap(1); - params.put(CopyActionExecuter.PARAM_DESTINATION_FOLDER, targetFolder); - Rule copyRule = createRule( - RuleType.OUTBOUND, - CopyActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - copyRule.applyToChildren(true); - this.ruleService.saveRule(sourceFolder, copyRule); - - // Check that this OUTBOUND rule works - NodeRef testNodeTwoRef = fileFolderService.create(sourceFolder, "two.txt", ContentModel.TYPE_CONTENT).getNodeRef(); - assertFalse( - "Node should not have lockable aspect", - nodeService.hasAspect(testNodeTwoRef, ContentModel.ASPECT_LOCKABLE)); - fileFolderService.delete(testNodeTwoRef); - assertFalse("Node was not deleted", fileFolderService.exists(testNodeTwoRef)); - assertEquals( - "There should not be any children in source folder", - 0, - fileFolderService.listFiles(sourceFolder).size()); - List targetFolderFileList = fileFolderService.listFiles(targetFolder); - assertEquals( - "Node should have been copied to target folder", - 1, - targetFolderFileList.size()); - assertFalse( - "The node copy should not be lockable", - nodeService.hasAspect(targetFolderFileList.get(0).getNodeRef(), ContentModel.ASPECT_LOCKABLE)); - } - - public void testDisableIndividualRules() - { - Map params = new HashMap(1); - params.put("aspect-name", ApplicationModel.ASPECT_CONFIGURABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - rule.setRuleDisabled(true); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - assertFalse(this.nodeService.hasAspect(newNodeRef, ApplicationModel.ASPECT_CONFIGURABLE)); - - Rule rule2 = this.ruleService.getRule(rule.getNodeRef()); - rule2.setRuleDisabled(false); - this.ruleService.saveRule(this.nodeRef, rule2); - - // Re-try the test now the rule has been re-enabled - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef2); - assertTrue(this.nodeService.hasAspect(newNodeRef2, ApplicationModel.ASPECT_CONFIGURABLE)); - - } - - public void testDisableRule() - { - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); - - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - this.ruleService.disableRule(rule); - - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - this.ruleService.enableRule(rule); - - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef2); - assertTrue(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); - - } - - public void testAddFeaturesToAFolder() - { - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_TEMPLATABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_FOLDER).getChildRef(); - - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_TEMPLATABLE)); - - // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - - public void testCopyFolderToTriggerRules() - { - // Create the folders and content - NodeRef copyToFolder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}copyToFolder"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef folderToCopy = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}folderToCopy"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef contentToCopy = this.nodeService.createNode( - folderToCopy, - ContentModel.ASSOC_CONTAINS, - QName.createQName("{test}contentToCopy"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(contentToCopy); - - Map params = new HashMap(1); - params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPLATABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - rule.applyToChildren(true); - this.ruleService.saveRule(copyToFolder, rule); - - // Copy the folder in order to try and trigger the rule - NodeRef copiedFolder = this.copyService.copy(folderToCopy, copyToFolder, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}coppiedFolder"), true); - assertNotNull(copiedFolder); - - // Check that the rule has been applied to the copied folder and content - assertTrue(this.nodeService.hasAspect(copiedFolder, ContentModel.ASPECT_TEMPLATABLE)); - for (ChildAssociationRef childAssoc : this.nodeService.getChildAssocs(copiedFolder)) - { - assertTrue(this.nodeService.hasAspect(childAssoc.getChildRef(), ContentModel.ASPECT_TEMPLATABLE)); - } - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - - private Map getContentProperties() - { - // Map properties = new HashMap(1); - // properties.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - return null; - } - - /** - * Test: - * rule type: inbound - * condition: no-condition - * action: simple-workflow - */ - public void testSimpleWorkflowAction() - { - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); - - Map params = new HashMap(1); - params.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_STEP, "approveStep"); - params.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_FOLDER, this.rootNodeRef); - params.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_MOVE, true); - params.put(SimpleWorkflowActionExecuter.PARAM_REJECT_STEP, "rejectStep"); - params.put(SimpleWorkflowActionExecuter.PARAM_REJECT_FOLDER, this.rootNodeRef); - params.put(SimpleWorkflowActionExecuter.PARAM_REJECT_MOVE, false); - - Rule rule = createRule( - RuleType.INBOUND, - SimpleWorkflowActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - - assertTrue(this.nodeService.hasAspect(newNodeRef, ApplicationModel.ASPECT_SIMPLE_WORKFLOW)); - assertEquals("approveStep", this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_APPROVE_STEP)); - assertEquals(this.rootNodeRef, this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_APPROVE_FOLDER)); - assertTrue(((Boolean)this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_APPROVE_MOVE)).booleanValue()); - assertTrue(this.nodeService.hasAspect(newNodeRef, ApplicationModel.ASPECT_SIMPLE_WORKFLOW)); - assertEquals("rejectStep", this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_REJECT_STEP)); - assertEquals(this.rootNodeRef, this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_REJECT_FOLDER)); - assertFalse(((Boolean)this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_REJECT_MOVE)).booleanValue()); - - // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - - /** - * Test: - * rule type: inbound - * condition: in-category - * action: add-feature - */ - public void testInCategoryCondition() - { - // Create categories used in tests - createTestCategories(); - - try - { - Map params = new HashMap(1); - params.put(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, this.regionCategorisationQName); - params.put(InCategoryEvaluator.PARAM_CATEGORY_VALUE, this.catROne); - - Map params2 = new HashMap(1); - params2.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params2, - InCategoryEvaluator.NAME, - params); - - this.ruleService.saveRule(this.nodeRef, rule); - - // Check rule does not get fired when a node without the aspect is added - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "noAspect"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef2); - assertFalse(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); - - // Check rule gets fired when node contains category value - RetryingTransactionCallback callback1 = new RetryingTransactionCallback() - { - public NodeRef execute() throws Throwable - { - NodeRef newNodeRef = nodeService.createNode( - nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "hasAspectAndValue"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - Map catProps = new HashMap(); - catProps.put(CAT_PROP_QNAME, catROne); - nodeService.addAspect(newNodeRef, regionCategorisationQName, catProps); - return newNodeRef; - } - }; - NodeRef newNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(callback1); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Check rule does not get fired when the node has the incorrect category value - RetryingTransactionCallback callback3 = new RetryingTransactionCallback() - { - public NodeRef execute() throws Throwable - { - NodeRef newNodeRef3 = nodeService.createNode( - nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "hasAspectAndValue"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef3); - Map catProps3 = new HashMap(); - catProps3.put(CAT_PROP_QNAME, catRTwo); - nodeService.addAspect(newNodeRef3, regionCategorisationQName, catProps3); - return newNodeRef3; - } - }; - NodeRef newNodeRef3 = transactionService.getRetryingTransactionHelper().doInTransaction(callback3); - assertFalse(this.nodeService.hasAspect(newNodeRef3, ContentModel.ASPECT_VERSIONABLE)); - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - catch (Exception exception) - { - throw new RuntimeException(exception); - } - } - - /** - * Test: - * rule type: inbound - * condition: no-condition - * action: link-category - */ - @SuppressWarnings("unchecked") - public void testLinkCategoryAction() - { - // Create categories used in tests - createTestCategories(); - - Map params = new HashMap(1); - params.put(LinkCategoryActionExecuter.PARAM_CATEGORY_ASPECT, this.regionCategorisationQName); - params.put(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE, this.catROne); - - Rule rule = createRule( - RuleType.INBOUND, - LinkCategoryActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "noAspect"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef2); - - PropertyDefinition catPropDef = this.dictionaryDAO.getProperty(CAT_PROP_QNAME); - if (catPropDef == null) - { - // Why is it undefined? - } - - // Check that the category value has been set - // It has been declared as a multi-value property, so we expect that here - Collection setValue = (Collection) this.nodeService.getProperty(newNodeRef2, CAT_PROP_QNAME); - assertNotNull(setValue); - assertEquals(1, setValue.size()); - assertEquals(this.catROne, setValue.toArray()[0]); -} - - - /** - * Test: - * rule type: inbound - * condition: no-condition - * action: mail - * @throws MessagingException - * @throws IOException - */ - public void testMailAction() throws MessagingException, IOException - { - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); - - Map params = new HashMap(1); - params.put(MailActionExecuter.PARAM_TO, "alfresco.test@gmail.com"); - params.put(MailActionExecuter.PARAM_SUBJECT, "Unit test"); - params.put(MailActionExecuter.PARAM_TEXT, "This is a test to check that the mail action is working."); - - Rule rule = createRule( - RuleType.INBOUND, - MailActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - MailActionExecuter mailService = (MailActionExecuter) ((ApplicationContextFactory) applicationContext - .getBean("OutboundSMTP")).getApplicationContext().getBean("mail"); - mailService.setTestMode(true); - mailService.clearLastTestMessage(); - - this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - - // An email should appear in the recipients email - // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - MimeMessage lastMessage = mailService.retrieveLastTestMessage(); - assertNotNull("Message should have been sent", lastMessage); - System.out.println("Sent email with subject: " + lastMessage.getSubject()); - System.out.println("Sent email with content: " + lastMessage.getContent()); - } - - public void testMailNotSentIfRollback() - { - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); - - Map params = new HashMap(1); - params.put(MailActionExecuter.PARAM_TO, "alfresco.test@gmail.com"); - params.put(MailActionExecuter.PARAM_SUBJECT, "testMailNotSentIfRollback()"); - params.put(MailActionExecuter.PARAM_TEXT, "This email should NOT have been sent."); - - Rule rule = createRule( - RuleType.INBOUND, - MailActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - String illegalName = "MyName.txt "; // space at end - - MailActionExecuter mailService = (MailActionExecuter) ((ApplicationContextFactory) applicationContext - .getBean("OutboundSMTP")).getApplicationContext().getBean("mail"); - mailService.setTestMode(true); - mailService.clearLastTestMessage(); - - try - { - this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - makeNameProperty(illegalName)).getChildRef(); - fail("createNode() should have failed."); - } - catch(IntegrityException e) - { - // Expected exception. - // An email should NOT appear in the recipients email - } - - MimeMessage lastMessage = mailService.retrieveLastTestMessage(); - assertNull("Message should NOT have been sent", lastMessage); - } - - private Map makeNameProperty(String name) - { - Map properties = new HashMap(1); - properties.put(ContentModel.PROP_NAME, name); - return properties; - } - - /** - * Test: - * rule type: inbound - * condition: no-condition() - * action: copy() - */ - public void testCopyAction() - { - String localName = getName() + System.currentTimeMillis(); - - Map params = new HashMap(1); - params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - - Rule rule = createRule( - RuleType.INBOUND, - CopyActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, localName), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - - // Check that the created node is still there - List origRefs = this.nodeService.getChildAssocs( - this.nodeRef, - RegexQNamePattern.MATCH_ALL, - QName.createQName(TEST_NAMESPACE, localName)); - assertNotNull(origRefs); - assertEquals(1, origRefs.size()); - NodeRef origNodeRef = origRefs.get(0).getChildRef(); - assertEquals(newNodeRef, origNodeRef); - - // Check that the created node has been copied - List copyChildAssocRefs = this.nodeService.getChildAssocs( - this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, localName)); - assertNotNull(copyChildAssocRefs); - - // ********************************** - // NOTE: Changed expected result to get build running - // ********************************** - assertEquals(1, copyChildAssocRefs.size()); - - NodeRef copyNodeRef = copyChildAssocRefs.get(0).getChildRef(); - assertTrue(this.nodeService.hasAspect(copyNodeRef, ContentModel.ASPECT_COPIEDFROM)); - NodeRef source = copyService.getOriginal(copyNodeRef); - assertEquals(newNodeRef, source); - - // TODO test deep copy !! - } - - /** - * Test: - * rule type: inbound - * condition: no-condition() - * action: transform() - */ - public void testTransformAction() throws Throwable - { - Map params = new HashMap(1); - params.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_TEXT_PLAIN); - params.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - params.put(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - params.put(TransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "transformed")); - - Rule rule = createRule( - RuleType.INBOUND, - TransformActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - UserTransaction tx = transactionService.getUserTransaction(); - tx.begin(); - - Map props =new HashMap(1); - props.put(ContentModel.PROP_NAME, "test.xls"); - - // Create the node at the root - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "origional"), - ContentModel.TYPE_CONTENT, - props).getChildRef(); - - // Set some content on the origional - ContentWriter contentWriter = this.contentService.getWriter(newNodeRef, ContentModel.PROP_CONTENT, true); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_EXCEL); - File testFile = AbstractContentTransformerTest.loadQuickTestFile("xls"); - contentWriter.putContent(testFile); - - tx.commit(); - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - - AuthenticationComponent authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); - authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); - - // Check that the created node is still there - List origRefs = this.nodeService.getChildAssocs( - this.nodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); - assertNotNull(origRefs); - assertEquals(1, origRefs.size()); - NodeRef origNodeRef = origRefs.get(0).getChildRef(); - assertEquals(newNodeRef, origNodeRef); - - // Check that the created node has been copied - List copyChildAssocRefs = this.nodeService.getChildAssocs( - this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "test.txt")); - assertNotNull(copyChildAssocRefs); - assertEquals(1, copyChildAssocRefs.size()); - NodeRef copyNodeRef = copyChildAssocRefs.get(0).getChildRef(); - assertTrue(this.nodeService.hasAspect(copyNodeRef, ContentModel.ASPECT_COPIEDFROM)); - NodeRef source = copyService.getOriginal(copyNodeRef); - assertEquals(newNodeRef, source); - - // Check the transformed content - ContentData contentData = (ContentData) nodeService.getProperty(copyNodeRef, ContentModel.PROP_CONTENT); - assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentData.getMimetype()); - } - - /** - * Test image transformation - * - */ - public void testImageTransformAction() throws Throwable - { - Map params = new HashMap(1); - params.put(ImageTransformActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - params.put(ImageTransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - params.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG); - params.put(ImageTransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "transformed")); - params.put(ImageTransformActionExecuter.PARAM_CONVERT_COMMAND, "-negate"); - - Rule rule = createRule( - RuleType.INBOUND, - ImageTransformActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - UserTransaction tx = transactionService.getUserTransaction(); - tx.begin(); - - Map props =new HashMap(1); - props.put(ContentModel.PROP_NAME, "test.gif"); - - // Create the node at the root - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "origional"), - ContentModel.TYPE_CONTENT, - props).getChildRef(); - - // Set some content on the origional - ContentWriter contentWriter = this.contentService.getWriter(newNodeRef, ContentModel.PROP_CONTENT, true); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_IMAGE_GIF); - File testFile = AbstractContentTransformerTest.loadQuickTestFile("gif"); - contentWriter.putContent(testFile); - - tx.commit(); - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - - // Check that the created node is still there - List origRefs = this.nodeService.getChildAssocs( - this.nodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); - assertNotNull(origRefs); - assertEquals(1, origRefs.size()); - NodeRef origNodeRef = origRefs.get(0).getChildRef(); - assertEquals(newNodeRef, origNodeRef); - - // Check that the created node has been copied - List copyChildAssocRefs = this.nodeService.getChildAssocs( - this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "test.jpg")); - assertNotNull(copyChildAssocRefs); - assertEquals(1, copyChildAssocRefs.size()); - NodeRef copyNodeRef = copyChildAssocRefs.get(0).getChildRef(); - assertTrue(this.nodeService.hasAspect(copyNodeRef, ContentModel.ASPECT_COPIEDFROM)); - NodeRef source = copyService.getOriginal(copyNodeRef); - assertEquals(newNodeRef, source); - } - - /** - * Test: - * rule type: inbound - * condition: no-condition() - * action: move() - */ - public void testMoveAction() - { - Map params = new HashMap(1); - params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - - Rule rule = createRule( - RuleType.INBOUND, - MoveActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "origional"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - - // Check that the created node has been moved - List origRefs = this.nodeService.getChildAssocs( - this.nodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); - assertNotNull(origRefs); - assertEquals(0, origRefs.size()); - - // Check that the created node is in the new location - List copyChildAssocRefs = this.nodeService.getChildAssocs( - this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); - assertNotNull(copyChildAssocRefs); - assertEquals(1, copyChildAssocRefs.size()); - NodeRef movedNodeRef = copyChildAssocRefs.get(0).getChildRef(); - assertEquals(newNodeRef, movedNodeRef); - } - - /** - * Test: - * rule type: inbound - * condition: no-condition() - * action: checkout() - */ - public void testCheckOutAction() - { - Rule rule = createRule( - RuleType.INBOUND, - CheckOutActionExecuter.NAME, - null, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - NodeRef newNodeRef = null; - UserTransaction tx = this.transactionService.getUserTransaction(); - try - { - tx.begin(); - - // Create a new node - newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "checkout"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - - tx.commit(); - } - catch (Exception exception) - { - throw new RuntimeException(exception); - } - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - - // Check that the new node has been checked out - List children = this.nodeService.getChildAssocs(this.nodeRef); - assertNotNull(children); - assertEquals(3, children.size()); // includes rule folder - for (ChildAssociationRef child : children) - { - NodeRef childNodeRef = child.getChildRef(); - if (childNodeRef.equals(newNodeRef) == true) - { - // check that the node has been locked - LockStatus lockStatus = this.lockService.getLockStatus(childNodeRef); - assertEquals(LockStatus.LOCK_OWNER, lockStatus); - } - else if (this.nodeService.hasAspect(childNodeRef, ContentModel.ASPECT_WORKING_COPY) == true) - { - // assert that it is the working copy that relates to the origional node - NodeRef copiedFromNodeRef = copyService.getOriginal(childNodeRef); - assertEquals(newNodeRef, copiedFromNodeRef); - } - } - } - - /** - * Test: - * rule type: inbound - * condition: no-condition() - * action: checkin() - */ - public void testCheckInAction() - { - Map params = new HashMap(1); - params.put(CheckInActionExecuter.PARAM_DESCRIPTION, "The version description."); - - Rule rule = createRule( - RuleType.INBOUND, - CheckInActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - List list = transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback>() - { - public List execute() - { - // Create a new node and check-it out - NodeRef newNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( - RuleServiceCoverageTest.this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "origional"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - NodeRef workingCopy = RuleServiceCoverageTest.this.cociService.checkout(newNodeRef); - - // Move the working copy into the actionable folder - RuleServiceCoverageTest.this.nodeService.moveNode( - workingCopy, - RuleServiceCoverageTest.this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "moved")); - - List result = new ArrayList(); - result.add(newNodeRef); - result.add(workingCopy); - return result; - } - - }); - - // Check that the working copy has been removed - assertFalse(this.nodeService.exists(list.get(1))); - - // Check that the origional is no longer locked - assertEquals(LockStatus.NO_LOCK, this.lockService.getLockStatus(list.get(0))); - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - } - - /** - * Check that the rules can be enabled and disabled - */ - public void testRulesDisabled() - { - Map actionParams = new HashMap(1); - actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - actionParams, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - RetryingTransactionCallback noRulesWork = new RetryingTransactionCallback() - { - @Override - public NodeRef execute() throws Throwable - { - ruleService.disableRules(nodeRef); - - NodeRef newNodeRef = nodeService.createNode( - nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef); - return newNodeRef; - } - }; - NodeRef newNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(noRulesWork); - assertFalse(nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - RetryingTransactionCallback withRulesWork = new RetryingTransactionCallback() - { - @Override - public NodeRef execute() throws Throwable - { - NodeRef newNodeRef2 = nodeService.createNode( - nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef2); - return newNodeRef2; - } - }; - NodeRef newNodeRef2 = transactionService.getRetryingTransactionHelper().doInTransaction(withRulesWork); - assertTrue(nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); - } - - /** - * Adds content to a given node. - *

- * Used to trigger rules of type of incomming. - * - * @param nodeRef the node reference - */ - private void addContentToNode(NodeRef nodeRef) - { - ContentWriter contentWriter = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - contentWriter.setEncoding("UTF-8"); - assertNotNull(contentWriter); - contentWriter.putContent(STANDARD_TEXT_CONTENT + System.currentTimeMillis()); - } - - /** - * Test checkMandatoryProperties method - */ - public void testCheckMandatoryProperties() - { - Map actionParams = new HashMap(1); - actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Map condParams = new HashMap(1); - // should be setting the condition parameter here - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - actionParams, - ComparePropertyValueEvaluator.NAME, - condParams); - - this.ruleService.saveRule(this.nodeRef, rule); - - try - { - // Try and create a node .. should fail since the rule is invalid - Map props2 = getContentProperties(); - props2.put(ContentModel.PROP_NAME, "bobbins.doc"); - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - props2).getChildRef(); - addContentToNode(newNodeRef2); - fail("An exception should have been thrown since a mandatory parameter was missing from the condition."); - } - catch (Throwable ruleServiceException) - { - // Success since we where expecting the exception - } - } - - /** - * Test: - * rule type: inbound - * condition: match-text( - * text = .doc, - * operation = CONTAINS) - * action: add-features( - * aspect-name = versionable) - */ - public void testContainsTextCondition() - { - Map actionParams = new HashMap(1); - actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - // ActionCondition parameter's - Map condParams = new HashMap(1); - condParams.put(ComparePropertyValueEvaluator.PARAM_VALUE, ".doc"); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - actionParams, - ComparePropertyValueEvaluator.NAME, - condParams); - - this.ruleService.saveRule(this.nodeRef, rule); - - // Test condition failure - Map props1 = new HashMap(); - props1.put(ContentModel.PROP_NAME, "bobbins.txt"); - // props1.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - props1).getChildRef(); - addContentToNode(newNodeRef); - - //Map map = this.nodeService.getProperties(newNodeRef); - //String value = (String)this.nodeService.getProperty(newNodeRef, ContentModel.PROP_NAME); - - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Test condition success - Map props2 = new HashMap(); - props2.put(ContentModel.PROP_NAME, "bobbins.doc"); - //props2.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - props2).getChildRef(); - addContentToNode(newNodeRef2); - assertTrue(this.nodeService.hasAspect( - newNodeRef2, - ContentModel.ASPECT_VERSIONABLE)); - - try - { - // Test name not set - NodeRef newNodeRef3 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(newNodeRef3); - } - catch (RuleServiceException exception) - { - // Correct since text-match is a mandatory property - } - - // Test begins with - Map condParamsBegins = new HashMap(1); - condParamsBegins.put(ComparePropertyValueEvaluator.PARAM_VALUE, "bob*"); - rule.getAction().removeAllActionConditions(); - ActionCondition condition1 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME, condParamsBegins); - rule.getAction().addActionCondition(condition1); - this.ruleService.saveRule(this.nodeRef, rule); - Map propsx = new HashMap(); - propsx.put(ContentModel.PROP_NAME, "mybobbins.doc"); - //propsx.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - NodeRef newNodeRefx = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - propsx).getChildRef(); - addContentToNode(newNodeRefx); - assertFalse(this.nodeService.hasAspect(newNodeRefx, ContentModel.ASPECT_VERSIONABLE)); - Map propsy = new HashMap(); - propsy.put(ContentModel.PROP_NAME, "bobbins.doc"); - //propsy.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - NodeRef newNodeRefy = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - propsy).getChildRef(); - addContentToNode(newNodeRefy); - assertTrue(this.nodeService.hasAspect( - newNodeRefy, - ContentModel.ASPECT_VERSIONABLE)); - - // Test ends with - Map condParamsEnds = new HashMap(1); - condParamsEnds.put(ComparePropertyValueEvaluator.PARAM_VALUE, "*s.doc"); - rule.getAction().removeAllActionConditions(); - ActionCondition condition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME, condParamsEnds); - rule.getAction().addActionCondition(condition2); - this.ruleService.saveRule(this.nodeRef, rule); - Map propsa = new HashMap(); - propsa.put(ContentModel.PROP_NAME, "bobbins.document"); - // propsa.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - NodeRef newNodeRefa = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - propsa).getChildRef(); - addContentToNode(newNodeRefa); - assertFalse(this.nodeService.hasAspect(newNodeRefa, ContentModel.ASPECT_VERSIONABLE)); - Map propsb = new HashMap(); - propsb.put(ContentModel.PROP_NAME, "bobbins.doc"); - //propsb.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - NodeRef newNodeRefb = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT, - propsb).getChildRef(); - addContentToNode(newNodeRefb); - assertTrue(this.nodeService.hasAspect( - newNodeRefb, - ContentModel.ASPECT_VERSIONABLE)); - } - - public void testInboundRuleType() - { - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - // Create a non-content node - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTAINER).getChildRef(); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Create a content node - NodeRef contentNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // ALF-14744 / MNT-187: Create a content node - this time with 'empty content' in the same transaction - contentNodeRef = this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - @Override - public NodeRef execute() throws Throwable - { - NodeRef contentNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( - RuleServiceCoverageTest.this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - ContentWriter contentWriter = RuleServiceCoverageTest.this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); - assertNotNull(contentWriter); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - contentWriter.setEncoding("UTF-8"); - contentWriter.putContent(""); - return contentNodeRef; - } - }); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // ALF-14744 / MNT-187: Create a content node - this time with the 'no content' aspect in the same transaction - contentNodeRef = this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - @Override - public NodeRef execute() throws Throwable - { - NodeRef contentNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( - RuleServiceCoverageTest.this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - RuleServiceCoverageTest.this.nodeService.addAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT, null); - return contentNodeRef; - } - }); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT); - assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Create a node to be moved - NodeRef moveNode = this.nodeService.createNode( - newNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - addContentToNode(moveNode); - assertFalse(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); - this.nodeService.moveNode( - moveNode, - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children")); - assertTrue(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); - - // Enusre the rule type does not get fired when the node is updated - this.nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - this.nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, "name.txt"); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - } - - public void testUpdateRuleType() - { - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - Rule rule = createRule( - RuleType.UPDATE, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - // Create a non-content node - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_FOLDER).getChildRef(); - this.nodeService.removeAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Update the non-content node - this.nodeService.setProperty(newNodeRef, ContentModel.PROP_NAME, "testName"); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Create a content node - NodeRef contentNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Create a non content node, setting a property at the same time - Map props = new HashMap(1); - props.put(ContentModel.PROP_NAME, "testName"); - NodeRef nodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_FOLDER, - props).getChildRef(); - nodeService.removeAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); - this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName"); - assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); - this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName2"); - assertTrue(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); - - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - Map props = new HashMap(1); - props.put(ContentModel.PROP_NAME, "testName"); - NodeRef nodeRef3 = RuleServiceCoverageTest.this.nodeService.createNode( - RuleServiceCoverageTest.this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_FOLDER, - props).getChildRef(); - assertFalse(RuleServiceCoverageTest.this.nodeService.hasAspect(nodeRef3, ContentModel.ASPECT_VERSIONABLE)); - RuleServiceCoverageTest.this.nodeService.setProperty(nodeRef3, ContentModel.PROP_NAME, "testName2"); - assertFalse(RuleServiceCoverageTest.this.nodeService.hasAspect(nodeRef3, ContentModel.ASPECT_VERSIONABLE)); - - return null; - } - }); - } - - public void testAssociationUpdateRule() - { - //ALF-9661 test - NodeRef sourceFolder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}sourceFolder"), - ContentModel.TYPE_FOLDER).getChildRef(); - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - //create a rule that adds an aspect after a property is updated - Rule rule = createRule( - RuleType.UPDATE, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(sourceFolder, rule); - //create folders - NodeRef testNodeOneRef = this.nodeService.createNode( - sourceFolder, - ContentModel.ASSOC_CONTAINS, - QName.createQName(TEST_NAMESPACE, "original1"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(testNodeOneRef); - - NodeRef testNodeTwoRef = this.nodeService.createNode( - sourceFolder, - ContentModel.ASSOC_CONTAINS, - QName.createQName(TEST_NAMESPACE, "original2"), - ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); - addContentToNode(testNodeTwoRef); - //there is no aspect - assertFalse(this.nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_VERSIONABLE)); - //create an association - this.nodeService.addAspect(testNodeOneRef, ContentModel.ASPECT_REFERENCING, null); - this.nodeService.createAssociation(testNodeOneRef, testNodeTwoRef, ContentModel.ASSOC_REFERENCES); - //there should be the versionable aspect added - assertTrue(this.nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_VERSIONABLE)); - } - - /** - * Test: - * rule type: outbound - * condition: no-condition() - * action: add-features( - * aspect-name = versionable) - */ - public void testOutboundRuleType() - { - this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); - - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - "outbound", - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - // Create a node - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTAINER).getChildRef(); - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Move the node out of the actionable folder - this.nodeService.moveNode( - newNodeRef, - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children")); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - // Check the deletion of a node - - //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); - NodeRef newNodeRef2 = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTAINER).getChildRef(); - this.nodeService.deleteNode(newNodeRef2); - } - - /** - * Performance guideline test - * - */ - public void xtestPerformanceOfRuleExecution() - { - try - { - StopWatch sw = new StopWatch(); - - // Create actionable nodes - sw.start("create nodes with no rule executed"); - UserTransaction userTransaction1 = this.transactionService.getUserTransaction(); - userTransaction1.begin(); - - for (int i = 0; i < 100; i++) - { - this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASSOC_CONTAINS, - ContentModel.TYPE_CONTAINER).getChildRef(); - assertFalse(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)); - } - - userTransaction1.commit(); - sw.stop(); - - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - - Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - sw.start("create nodes with one rule run (apply versionable aspect)"); - UserTransaction userTransaction2 = this.transactionService.getUserTransaction(); - userTransaction2.begin(); - - NodeRef[] nodeRefs = new NodeRef[100]; - for (int i = 0; i < 100; i++) - { - NodeRef nodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTAINER).getChildRef(); - addContentToNode(nodeRef); - nodeRefs[i] = nodeRef; - - // Check that the versionable aspect has not yet been applied - assertFalse(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)); - } - - userTransaction2.commit(); - sw.stop(); - - // Check that the versionable aspect has been applied to all the created nodes - for (NodeRef ref : nodeRefs) - { - assertTrue(this.nodeService.hasAspect(ref, ContentModel.ASPECT_VERSIONABLE)); - } - - System.out.println(sw.prettyPrint()); - } - catch (Exception exception) - { - throw new RuntimeException(exception); - } - } - - public void testAsyncExecutionWithPotentialLoop() - { - try - { - Map params = new HashMap(1); - params.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_TEXT_PLAIN); - params.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, this.nodeRef); - params.put(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS); - params.put(TransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "transformed")); - - Rule rule = createRule( - RuleType.INBOUND, - TransformActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - rule.setExecuteAsynchronously(true); - rule.setTitle("Transform document to text"); - - UserTransaction tx0 = transactionService.getUserTransaction(); - tx0.begin(); - this.ruleService.saveRule(this.nodeRef, rule); - tx0.commit(); - - UserTransaction tx = transactionService.getUserTransaction(); - tx.begin(); - - Map props =new HashMap(1); - props.put(ContentModel.PROP_NAME, "test.xls"); - - // Create the node at the root - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "origional"), - ContentModel.TYPE_CONTENT, - props).getChildRef(); - - // Set some content on the origional - ContentWriter contentWriter = this.contentService.getWriter(newNodeRef, ContentModel.PROP_CONTENT, true); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_EXCEL); - File testFile = AbstractContentTransformerTest.loadQuickTestFile("xls"); - contentWriter.putContent(testFile); - - tx.commit(); - - // Sleep to ensure work is done b4 execution is canceled - Thread.sleep(10000); - } - catch (Exception exception) - { - throw new RuntimeException(exception); - } - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.rule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import jakarta.transaction.UserTransaction; + +import junit.framework.TestCase; +import org.junit.experimental.categories.Category; +import org.springframework.context.ApplicationContext; +import org.springframework.util.StopWatch; + +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.evaluator.InCategoryEvaluator; +import org.alfresco.repo.action.evaluator.NoConditionEvaluator; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; +import org.alfresco.repo.action.executer.CheckInActionExecuter; +import org.alfresco.repo.action.executer.CheckOutActionExecuter; +import org.alfresco.repo.action.executer.CopyActionExecuter; +import org.alfresco.repo.action.executer.ImageTransformActionExecuter; +import org.alfresco.repo.action.executer.LinkCategoryActionExecuter; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.action.executer.MoveActionExecuter; +import org.alfresco.repo.action.executer.SimpleWorkflowActionExecuter; +import org.alfresco.repo.action.executer.TransformActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.AbstractContentTransformerTest; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.IndexTokenisationMode; +import org.alfresco.repo.dictionary.M2Aspect; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.dictionary.M2Property; +import org.alfresco.repo.management.subsystems.ApplicationContextFactory; +import org.alfresco.repo.node.integrity.IntegrityException; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.CopyService; +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.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleServiceException; +import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.test_category.OwnJVMTestsCategory; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; + +/** + * @author Roy Wetherall + */ +@Category(OwnJVMTestsCategory.class) +public class RuleServiceCoverageTest extends TestCase +{ + /** + * Application context used during the test + */ + static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); + + /** + * Services used during the tests + */ + private TransactionService transactionService; + private RuleService ruleService; + private NodeService nodeService; + private StoreRef testStoreRef; + private NodeRef rootNodeRef; + private NodeRef nodeRef; + private CheckOutCheckInService cociService; + private LockService lockService; + private ContentService contentService; + private ServiceRegistry serviceRegistry; + private DictionaryDAO dictionaryDAO; + private ActionService actionService; + private CopyService copyService; + private AuthenticationComponent authenticationComponent; + private FileFolderService fileFolderService; + + /** + * Category related values + */ + private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/rulesystemtest"; + private static final QName CAT_PROP_QNAME = QName.createQName(TEST_NAMESPACE, "region"); + private QName regionCategorisationQName; + private NodeRef catContainer; + private NodeRef catRoot; + private NodeRef catRBase; + private NodeRef catROne; + private NodeRef catRTwo; + @SuppressWarnings("unused") + private NodeRef catRThree; + + /** + * Standard content text + */ + private static final String STANDARD_TEXT_CONTENT = "standardTextContent"; + + /** + * Setup method + */ + @Override + protected void setUp() throws Exception + { + // Get the required services + this.serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + this.nodeService = serviceRegistry.getNodeService(); + this.ruleService = serviceRegistry.getRuleService(); + this.cociService = serviceRegistry.getCheckOutCheckInService(); + this.lockService = serviceRegistry.getLockService(); + this.copyService = serviceRegistry.getCopyService(); + this.contentService = serviceRegistry.getContentService(); + this.dictionaryDAO = (DictionaryDAO) applicationContext.getBean("dictionaryDAO"); + this.actionService = serviceRegistry.getActionService(); + this.transactionService = serviceRegistry.getTransactionService(); + this.authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); + this.fileFolderService = serviceRegistry.getFileFolderService(); + + // authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + // authenticationComponent.setSystemUserAsCurrentUser(); + RetryingTransactionCallback setUserCallback = new RetryingTransactionCallback() { + public Object execute() throws Exception + { + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(setUserCallback); + + this.testStoreRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); + + // Create the node used for tests + this.nodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTAINER).getChildRef(); + } + + private Rule createRule( + String ruleTypeName, + String actionName, + Map actionParams, + String conditionName, + Map conditionParams) + { + Rule rule = new Rule(); + rule.setTitle(GUID.generate()); + rule.setRuleType(ruleTypeName); + + Action action = this.actionService.createAction(actionName, actionParams); + ActionCondition condition = this.actionService.createActionCondition(conditionName, conditionParams); + action.addActionCondition(condition); + rule.setAction(action); + + return rule; + } + + /** + * Create the categories used in the tests + */ + private void createTestCategories() + { + // Create the test model + M2Model model = M2Model.createModel("test:rulecategory"); + model.createNamespace(TEST_NAMESPACE, "test"); + model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, "d"); + model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX); + + // Create the region category + regionCategorisationQName = QName.createQName(TEST_NAMESPACE, "region"); + M2Aspect generalCategorisation = model.createAspect("test:" + regionCategorisationQName.getLocalName()); + generalCategorisation.setParentName("cm:" + ContentModel.ASPECT_CLASSIFIABLE.getLocalName()); + M2Property genCatProp = generalCategorisation.createProperty("test:region"); + genCatProp.setIndexed(true); + genCatProp.setIndexedAtomically(true); + genCatProp.setMandatory(true); + genCatProp.setMultiValued(true); + genCatProp.setStoredInIndex(true); + genCatProp.setIndexTokenisationMode(IndexTokenisationMode.TRUE); + genCatProp.setType("d:" + DataTypeDefinition.CATEGORY.getLocalName()); + + // Save the mode + dictionaryDAO.putModel(model); + + // Create the category value container and root + catContainer = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "categoryContainer"), ContentModel.TYPE_CONTAINER).getChildRef(); + catRoot = nodeService.createNode(catContainer, ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "categoryRoot"), ContentModel.TYPE_CATEGORYROOT).getChildRef(); + + // Create the category values + catRBase = nodeService.createNode(catRoot, ContentModel.ASSOC_CATEGORIES, QName.createQName(TEST_NAMESPACE, "region"), ContentModel.TYPE_CATEGORY).getChildRef(); + catROne = nodeService.createNode(catRBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "Europe"), ContentModel.TYPE_CATEGORY).getChildRef(); + catRTwo = nodeService.createNode(catRBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "RestOfWorld"), ContentModel.TYPE_CATEGORY).getChildRef(); + catRThree = nodeService.createNode(catRTwo, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "US"), ContentModel.TYPE_CATEGORY).getChildRef(); + } + + /** + * Asynchronous rule tests + */ + + /** + * Check async rule execution + */ + public void testAsyncRuleExecution() throws InterruptedException + { + final NodeRef newNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public NodeRef execute() + { + RuleServiceCoverageTest.this.nodeService.addAspect( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASPECT_LOCKABLE, + null); + + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + rule.setExecuteAsynchronously(true); + + RuleServiceCoverageTest.this.ruleService.saveRule(RuleServiceCoverageTest.this.nodeRef, rule); + + NodeRef newNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + + return newNodeRef; + } + }); + + await().pollInSameThread() + .pollDelay(Duration.ofMillis(50)) + .until(() -> nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + assertThat(nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)) + .as("Expected aspect Versionable") + .isTrue(); + } + + /** + * Standard rule coverage tests + */ + + /** + * Test: rule type: inbound condition: no-condition() action: add-features( aspect-name = versionable) + */ + public void testAddFeaturesAction() + { + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); + + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + Map params2 = new HashMap(2); + params2.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ApplicationModel.ASPECT_SIMPLE_WORKFLOW); + params2.put(ApplicationModel.PROP_APPROVE_STEP.toString(), "approveStep"); + params2.put(ApplicationModel.PROP_APPROVE_MOVE.toString(), false); + + // Test that rule can be updated and execute correctly + // rule.removeAllActions(); + Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME, params2); + rule.setAction(action2); + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef2); + assertTrue(this.nodeService.hasAspect(newNodeRef2, ApplicationModel.ASPECT_SIMPLE_WORKFLOW)); + assertEquals("approveStep", this.nodeService.getProperty(newNodeRef2, ApplicationModel.PROP_APPROVE_STEP)); + assertEquals(false, this.nodeService.getProperty(newNodeRef2, ApplicationModel.PROP_APPROVE_MOVE)); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + + public void testCheckThatModifyNameDoesNotTriggerInboundRule() throws Exception + { + // this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "myTestFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(folder, rule); + + Map contentProps = new HashMap(1); + contentProps.put(ContentModel.PROP_NAME, "myTestDocument.txt"); + NodeRef newNodeRef = this.nodeService.createNode( + folder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myTestDocument.txt"), + ContentModel.TYPE_CONTENT, + contentProps).getChildRef(); + // addContentToNode(newNodeRef); + nodeService.removeAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Use the file folder to change the name of the node + this.fileFolderService.rename(newNodeRef, "myNewName.txt"); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + public void testCheckThatModifyNameDoesNotTriggerOutboundRule() throws Exception + { + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "myTestFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + + Map params = new HashMap(1); + params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.OUTBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(folder, rule); + + Map contentProps = new HashMap(1); + contentProps.put(ContentModel.PROP_NAME, "myTestDocument.txt"); + NodeRef newNodeRef = fileFolderService.create(folder, "abc.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse("Should not be versionable", nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Use the file folder to change the name of the node + fileFolderService.rename(newNodeRef, "myNewName.txt"); + assertFalse("Should not be versionable", nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + public void testCheckThatChildRuleFiresOnMove() throws Exception + { + // ALF-9415 test + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "myTestFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + rule.applyToChildren(true); + this.ruleService.saveRule(folder, rule); + + folderProps.put(ContentModel.PROP_NAME, "myMoveFolder"); + NodeRef folderForMove = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + + NodeRef testFile = this.fileFolderService.create(folderForMove, "testFile.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + + this.fileFolderService.move(folderForMove, folder, null); + assertTrue("Should be versionable", nodeService.hasAspect(testFile, ContentModel.ASPECT_VERSIONABLE)); + } + + /** + * ALF-4926: Incorrect behavior of update and move rule for the same folder + *

+ * Two rules:
+ *

    + *
  • When items are deleted, copy to another folder.
  • + *
  • In addition, when items are updated, add an aspect (or any other rule).
  • + *
+ * Ensure that the first copy does not result in rules being fired on the target. + */ + public void testUpdateAndMoveRuleOnSameFolder() throws Exception + { + NodeRef sourceFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}sourceFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef targetFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}targetFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // Create UPDATE rule to add lockable aspect + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_LOCKABLE); + Rule rule = createRule( + RuleType.UPDATE, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + this.ruleService.saveRule(sourceFolder, rule); + + // Check that the UPDATE rule works + NodeRef testNodeOneRef = fileFolderService.create(sourceFolder, "one.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse( + "Node should not have lockable aspect", + nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); + nodeService.setProperty(testNodeOneRef, ContentModel.PROP_LOCALE, Locale.CANADA); + assertTrue( + "Node should have lockable aspect", + nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); + fileFolderService.delete(testNodeOneRef); + + // Create OUTBOUND rule to copy node being deleted + params = new HashMap(1); + params.put(CopyActionExecuter.PARAM_DESTINATION_FOLDER, targetFolder); + Rule copyRule = createRule( + RuleType.OUTBOUND, + CopyActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + copyRule.applyToChildren(true); + this.ruleService.saveRule(sourceFolder, copyRule); + + // Check that this OUTBOUND rule works + NodeRef testNodeTwoRef = fileFolderService.create(sourceFolder, "two.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse( + "Node should not have lockable aspect", + nodeService.hasAspect(testNodeTwoRef, ContentModel.ASPECT_LOCKABLE)); + fileFolderService.delete(testNodeTwoRef); + assertFalse("Node was not deleted", fileFolderService.exists(testNodeTwoRef)); + assertEquals( + "There should not be any children in source folder", + 0, + fileFolderService.listFiles(sourceFolder).size()); + List targetFolderFileList = fileFolderService.listFiles(targetFolder); + assertEquals( + "Node should have been copied to target folder", + 1, + targetFolderFileList.size()); + assertFalse( + "The node copy should not be lockable", + nodeService.hasAspect(targetFolderFileList.get(0).getNodeRef(), ContentModel.ASPECT_LOCKABLE)); + } + + public void testDisableIndividualRules() + { + Map params = new HashMap(1); + params.put("aspect-name", ApplicationModel.ASPECT_CONFIGURABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + rule.setRuleDisabled(true); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + assertFalse(this.nodeService.hasAspect(newNodeRef, ApplicationModel.ASPECT_CONFIGURABLE)); + + Rule rule2 = this.ruleService.getRule(rule.getNodeRef()); + rule2.setRuleDisabled(false); + this.ruleService.saveRule(this.nodeRef, rule2); + + // Re-try the test now the rule has been re-enabled + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef2); + assertTrue(this.nodeService.hasAspect(newNodeRef2, ApplicationModel.ASPECT_CONFIGURABLE)); + + } + + public void testDisableRule() + { + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); + + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + this.ruleService.disableRule(rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + this.ruleService.enableRule(rule); + + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef2); + assertTrue(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); + + } + + public void testAddFeaturesToAFolder() + { + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_TEMPLATABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_FOLDER).getChildRef(); + + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_TEMPLATABLE)); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + + public void testCopyFolderToTriggerRules() + { + // Create the folders and content + NodeRef copyToFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}copyToFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef folderToCopy = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}folderToCopy"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef contentToCopy = this.nodeService.createNode( + folderToCopy, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}contentToCopy"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(contentToCopy); + + Map params = new HashMap(1); + params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPLATABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + rule.applyToChildren(true); + this.ruleService.saveRule(copyToFolder, rule); + + // Copy the folder in order to try and trigger the rule + NodeRef copiedFolder = this.copyService.copy(folderToCopy, copyToFolder, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}coppiedFolder"), true); + assertNotNull(copiedFolder); + + // Check that the rule has been applied to the copied folder and content + assertTrue(this.nodeService.hasAspect(copiedFolder, ContentModel.ASPECT_TEMPLATABLE)); + for (ChildAssociationRef childAssoc : this.nodeService.getChildAssocs(copiedFolder)) + { + assertTrue(this.nodeService.hasAspect(childAssoc.getChildRef(), ContentModel.ASPECT_TEMPLATABLE)); + } + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + + private Map getContentProperties() + { + // Map properties = new HashMap(1); + // properties.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); + return null; + } + + /** + * Test: rule type: inbound condition: no-condition action: simple-workflow + */ + public void testSimpleWorkflowAction() + { + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); + + Map params = new HashMap(1); + params.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_STEP, "approveStep"); + params.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_FOLDER, this.rootNodeRef); + params.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_MOVE, true); + params.put(SimpleWorkflowActionExecuter.PARAM_REJECT_STEP, "rejectStep"); + params.put(SimpleWorkflowActionExecuter.PARAM_REJECT_FOLDER, this.rootNodeRef); + params.put(SimpleWorkflowActionExecuter.PARAM_REJECT_MOVE, false); + + Rule rule = createRule( + RuleType.INBOUND, + SimpleWorkflowActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + + assertTrue(this.nodeService.hasAspect(newNodeRef, ApplicationModel.ASPECT_SIMPLE_WORKFLOW)); + assertEquals("approveStep", this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_APPROVE_STEP)); + assertEquals(this.rootNodeRef, this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_APPROVE_FOLDER)); + assertTrue(((Boolean) this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_APPROVE_MOVE)).booleanValue()); + assertTrue(this.nodeService.hasAspect(newNodeRef, ApplicationModel.ASPECT_SIMPLE_WORKFLOW)); + assertEquals("rejectStep", this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_REJECT_STEP)); + assertEquals(this.rootNodeRef, this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_REJECT_FOLDER)); + assertFalse(((Boolean) this.nodeService.getProperty(newNodeRef, ApplicationModel.PROP_REJECT_MOVE)).booleanValue()); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + + /** + * Test: rule type: inbound condition: in-category action: add-feature + */ + public void testInCategoryCondition() + { + // Create categories used in tests + createTestCategories(); + + try + { + Map params = new HashMap(1); + params.put(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, this.regionCategorisationQName); + params.put(InCategoryEvaluator.PARAM_CATEGORY_VALUE, this.catROne); + + Map params2 = new HashMap(1); + params2.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params2, + InCategoryEvaluator.NAME, + params); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Check rule does not get fired when a node without the aspect is added + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "noAspect"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef2); + assertFalse(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); + + // Check rule gets fired when node contains category value + RetryingTransactionCallback callback1 = new RetryingTransactionCallback() { + public NodeRef execute() throws Throwable + { + NodeRef newNodeRef = nodeService.createNode( + nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "hasAspectAndValue"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + Map catProps = new HashMap(); + catProps.put(CAT_PROP_QNAME, catROne); + nodeService.addAspect(newNodeRef, regionCategorisationQName, catProps); + return newNodeRef; + } + }; + NodeRef newNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(callback1); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Check rule does not get fired when the node has the incorrect category value + RetryingTransactionCallback callback3 = new RetryingTransactionCallback() { + public NodeRef execute() throws Throwable + { + NodeRef newNodeRef3 = nodeService.createNode( + nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "hasAspectAndValue"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef3); + Map catProps3 = new HashMap(); + catProps3.put(CAT_PROP_QNAME, catRTwo); + nodeService.addAspect(newNodeRef3, regionCategorisationQName, catProps3); + return newNodeRef3; + } + }; + NodeRef newNodeRef3 = transactionService.getRetryingTransactionHelper().doInTransaction(callback3); + assertFalse(this.nodeService.hasAspect(newNodeRef3, ContentModel.ASPECT_VERSIONABLE)); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + catch (Exception exception) + { + throw new RuntimeException(exception); + } + } + + /** + * Test: rule type: inbound condition: no-condition action: link-category + */ + @SuppressWarnings("unchecked") + public void testLinkCategoryAction() + { + // Create categories used in tests + createTestCategories(); + + Map params = new HashMap(1); + params.put(LinkCategoryActionExecuter.PARAM_CATEGORY_ASPECT, this.regionCategorisationQName); + params.put(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE, this.catROne); + + Rule rule = createRule( + RuleType.INBOUND, + LinkCategoryActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "noAspect"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef2); + + PropertyDefinition catPropDef = this.dictionaryDAO.getProperty(CAT_PROP_QNAME); + if (catPropDef == null) + { + // Why is it undefined? + } + + // Check that the category value has been set + // It has been declared as a multi-value property, so we expect that here + Collection setValue = (Collection) this.nodeService.getProperty(newNodeRef2, CAT_PROP_QNAME); + assertNotNull(setValue); + assertEquals(1, setValue.size()); + assertEquals(this.catROne, setValue.toArray()[0]); + } + + /** + * Test: rule type: inbound condition: no-condition action: mail + * + * @throws MessagingException + * @throws IOException + */ + public void testMailAction() throws MessagingException, IOException + { + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); + + Map params = new HashMap(1); + params.put(MailActionExecuter.PARAM_TO, "alfresco.test@gmail.com"); + params.put(MailActionExecuter.PARAM_SUBJECT, "Unit test"); + params.put(MailActionExecuter.PARAM_TEXT, "This is a test to check that the mail action is working."); + + Rule rule = createRule( + RuleType.INBOUND, + MailActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + MailActionExecuter mailService = (MailActionExecuter) ((ApplicationContextFactory) applicationContext + .getBean("OutboundSMTP")).getApplicationContext().getBean("mail"); + mailService.setTestMode(true); + mailService.clearLastTestMessage(); + + this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + + // An email should appear in the recipients email + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + MimeMessage lastMessage = mailService.retrieveLastTestMessage(); + assertNotNull("Message should have been sent", lastMessage); + System.out.println("Sent email with subject: " + lastMessage.getSubject()); + System.out.println("Sent email with content: " + lastMessage.getContent()); + } + + public void testMailNotSentIfRollback() + { + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); + + Map params = new HashMap(1); + params.put(MailActionExecuter.PARAM_TO, "alfresco.test@gmail.com"); + params.put(MailActionExecuter.PARAM_SUBJECT, "testMailNotSentIfRollback()"); + params.put(MailActionExecuter.PARAM_TEXT, "This email should NOT have been sent."); + + Rule rule = createRule( + RuleType.INBOUND, + MailActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + String illegalName = "MyName.txt "; // space at end + + MailActionExecuter mailService = (MailActionExecuter) ((ApplicationContextFactory) applicationContext + .getBean("OutboundSMTP")).getApplicationContext().getBean("mail"); + mailService.setTestMode(true); + mailService.clearLastTestMessage(); + + try + { + this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + makeNameProperty(illegalName)).getChildRef(); + fail("createNode() should have failed."); + } + catch (IntegrityException e) + { + // Expected exception. + // An email should NOT appear in the recipients email + } + + MimeMessage lastMessage = mailService.retrieveLastTestMessage(); + assertNull("Message should NOT have been sent", lastMessage); + } + + private Map makeNameProperty(String name) + { + Map properties = new HashMap(1); + properties.put(ContentModel.PROP_NAME, name); + return properties; + } + + /** + * Test: rule type: inbound condition: no-condition() action: copy() + */ + public void testCopyAction() + { + String localName = getName() + System.currentTimeMillis(); + + Map params = new HashMap(1); + params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); + + Rule rule = createRule( + RuleType.INBOUND, + CopyActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, localName), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + + // Check that the created node is still there + List origRefs = this.nodeService.getChildAssocs( + this.nodeRef, + RegexQNamePattern.MATCH_ALL, + QName.createQName(TEST_NAMESPACE, localName)); + assertNotNull(origRefs); + assertEquals(1, origRefs.size()); + NodeRef origNodeRef = origRefs.get(0).getChildRef(); + assertEquals(newNodeRef, origNodeRef); + + // Check that the created node has been copied + List copyChildAssocRefs = this.nodeService.getChildAssocs( + this.rootNodeRef, + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, localName)); + assertNotNull(copyChildAssocRefs); + + // ********************************** + // NOTE: Changed expected result to get build running + // ********************************** + assertEquals(1, copyChildAssocRefs.size()); + + NodeRef copyNodeRef = copyChildAssocRefs.get(0).getChildRef(); + assertTrue(this.nodeService.hasAspect(copyNodeRef, ContentModel.ASPECT_COPIEDFROM)); + NodeRef source = copyService.getOriginal(copyNodeRef); + assertEquals(newNodeRef, source); + + // TODO test deep copy !! + } + + /** + * Test: rule type: inbound condition: no-condition() action: transform() + */ + public void testTransformAction() throws Throwable + { + Map params = new HashMap(1); + params.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_TEXT_PLAIN); + params.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); + params.put(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); + params.put(TransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "transformed")); + + Rule rule = createRule( + RuleType.INBOUND, + TransformActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + UserTransaction tx = transactionService.getUserTransaction(); + tx.begin(); + + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "test.xls"); + + // Create the node at the root + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "origional"), + ContentModel.TYPE_CONTENT, + props).getChildRef(); + + // Set some content on the origional + ContentWriter contentWriter = this.contentService.getWriter(newNodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_EXCEL); + File testFile = AbstractContentTransformerTest.loadQuickTestFile("xls"); + contentWriter.putContent(testFile); + + tx.commit(); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + + AuthenticationComponent authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + + // Check that the created node is still there + List origRefs = this.nodeService.getChildAssocs( + this.nodeRef, + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); + assertNotNull(origRefs); + assertEquals(1, origRefs.size()); + NodeRef origNodeRef = origRefs.get(0).getChildRef(); + assertEquals(newNodeRef, origNodeRef); + + // Check that the created node has been copied + List copyChildAssocRefs = this.nodeService.getChildAssocs( + this.rootNodeRef, + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "test.txt")); + assertNotNull(copyChildAssocRefs); + assertEquals(1, copyChildAssocRefs.size()); + NodeRef copyNodeRef = copyChildAssocRefs.get(0).getChildRef(); + assertTrue(this.nodeService.hasAspect(copyNodeRef, ContentModel.ASPECT_COPIEDFROM)); + NodeRef source = copyService.getOriginal(copyNodeRef); + assertEquals(newNodeRef, source); + + // Check the transformed content + ContentData contentData = (ContentData) nodeService.getProperty(copyNodeRef, ContentModel.PROP_CONTENT); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentData.getMimetype()); + } + + /** + * Test image transformation + */ + public void testImageTransformAction() throws Throwable + { + Map params = new HashMap(1); + params.put(ImageTransformActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); + params.put(ImageTransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); + params.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG); + params.put(ImageTransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "transformed")); + params.put(ImageTransformActionExecuter.PARAM_CONVERT_COMMAND, "-negate"); + + Rule rule = createRule( + RuleType.INBOUND, + ImageTransformActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + UserTransaction tx = transactionService.getUserTransaction(); + tx.begin(); + + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "test.gif"); + + // Create the node at the root + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "origional"), + ContentModel.TYPE_CONTENT, + props).getChildRef(); + + // Set some content on the origional + ContentWriter contentWriter = this.contentService.getWriter(newNodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_IMAGE_GIF); + File testFile = AbstractContentTransformerTest.loadQuickTestFile("gif"); + contentWriter.putContent(testFile); + + tx.commit(); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + + // Check that the created node is still there + List origRefs = this.nodeService.getChildAssocs( + this.nodeRef, + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); + assertNotNull(origRefs); + assertEquals(1, origRefs.size()); + NodeRef origNodeRef = origRefs.get(0).getChildRef(); + assertEquals(newNodeRef, origNodeRef); + + // Check that the created node has been copied + List copyChildAssocRefs = this.nodeService.getChildAssocs( + this.rootNodeRef, + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "test.jpg")); + assertNotNull(copyChildAssocRefs); + assertEquals(1, copyChildAssocRefs.size()); + NodeRef copyNodeRef = copyChildAssocRefs.get(0).getChildRef(); + assertTrue(this.nodeService.hasAspect(copyNodeRef, ContentModel.ASPECT_COPIEDFROM)); + NodeRef source = copyService.getOriginal(copyNodeRef); + assertEquals(newNodeRef, source); + } + + /** + * Test: rule type: inbound condition: no-condition() action: move() + */ + public void testMoveAction() + { + Map params = new HashMap(1); + params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); + + Rule rule = createRule( + RuleType.INBOUND, + MoveActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "origional"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + + // Check that the created node has been moved + List origRefs = this.nodeService.getChildAssocs( + this.nodeRef, + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); + assertNotNull(origRefs); + assertEquals(0, origRefs.size()); + + // Check that the created node is in the new location + List copyChildAssocRefs = this.nodeService.getChildAssocs( + this.rootNodeRef, + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); + assertNotNull(copyChildAssocRefs); + assertEquals(1, copyChildAssocRefs.size()); + NodeRef movedNodeRef = copyChildAssocRefs.get(0).getChildRef(); + assertEquals(newNodeRef, movedNodeRef); + } + + /** + * Test: rule type: inbound condition: no-condition() action: checkout() + */ + public void testCheckOutAction() + { + Rule rule = createRule( + RuleType.INBOUND, + CheckOutActionExecuter.NAME, + null, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = null; + UserTransaction tx = this.transactionService.getUserTransaction(); + try + { + tx.begin(); + + // Create a new node + newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "checkout"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + + tx.commit(); + } + catch (Exception exception) + { + throw new RuntimeException(exception); + } + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + + // Check that the new node has been checked out + List children = this.nodeService.getChildAssocs(this.nodeRef); + assertNotNull(children); + assertEquals(3, children.size()); // includes rule folder + for (ChildAssociationRef child : children) + { + NodeRef childNodeRef = child.getChildRef(); + if (childNodeRef.equals(newNodeRef) == true) + { + // check that the node has been locked + LockStatus lockStatus = this.lockService.getLockStatus(childNodeRef); + assertEquals(LockStatus.LOCK_OWNER, lockStatus); + } + else if (this.nodeService.hasAspect(childNodeRef, ContentModel.ASPECT_WORKING_COPY) == true) + { + // assert that it is the working copy that relates to the origional node + NodeRef copiedFromNodeRef = copyService.getOriginal(childNodeRef); + assertEquals(newNodeRef, copiedFromNodeRef); + } + } + } + + /** + * Test: rule type: inbound condition: no-condition() action: checkin() + */ + public void testCheckInAction() + { + Map params = new HashMap(1); + params.put(CheckInActionExecuter.PARAM_DESCRIPTION, "The version description."); + + Rule rule = createRule( + RuleType.INBOUND, + CheckInActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + List list = transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback>() { + public List execute() + { + // Create a new node and check-it out + NodeRef newNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "origional"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + NodeRef workingCopy = RuleServiceCoverageTest.this.cociService.checkout(newNodeRef); + + // Move the working copy into the actionable folder + RuleServiceCoverageTest.this.nodeService.moveNode( + workingCopy, + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "moved")); + + List result = new ArrayList(); + result.add(newNodeRef); + result.add(workingCopy); + return result; + } + + }); + + // Check that the working copy has been removed + assertFalse(this.nodeService.exists(list.get(1))); + + // Check that the origional is no longer locked + assertEquals(LockStatus.NO_LOCK, this.lockService.getLockStatus(list.get(0))); + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + } + + /** + * Check that the rules can be enabled and disabled + */ + public void testRulesDisabled() + { + Map actionParams = new HashMap(1); + actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + actionParams, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + RetryingTransactionCallback noRulesWork = new RetryingTransactionCallback() { + @Override + public NodeRef execute() throws Throwable + { + ruleService.disableRules(nodeRef); + + NodeRef newNodeRef = nodeService.createNode( + nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + return newNodeRef; + } + }; + NodeRef newNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(noRulesWork); + assertFalse(nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + RetryingTransactionCallback withRulesWork = new RetryingTransactionCallback() { + @Override + public NodeRef execute() throws Throwable + { + NodeRef newNodeRef2 = nodeService.createNode( + nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef2); + return newNodeRef2; + } + }; + NodeRef newNodeRef2 = transactionService.getRetryingTransactionHelper().doInTransaction(withRulesWork); + assertTrue(nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); + } + + /** + * Adds content to a given node. + *

+ * Used to trigger rules of type of incomming. + * + * @param nodeRef + * the node reference + */ + private void addContentToNode(NodeRef nodeRef) + { + ContentWriter contentWriter = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter.setEncoding("UTF-8"); + assertNotNull(contentWriter); + contentWriter.putContent(STANDARD_TEXT_CONTENT + System.currentTimeMillis()); + } + + /** + * Test checkMandatoryProperties method + */ + public void testCheckMandatoryProperties() + { + Map actionParams = new HashMap(1); + actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Map condParams = new HashMap(1); + // should be setting the condition parameter here + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + actionParams, + ComparePropertyValueEvaluator.NAME, + condParams); + + this.ruleService.saveRule(this.nodeRef, rule); + + try + { + // Try and create a node .. should fail since the rule is invalid + Map props2 = getContentProperties(); + props2.put(ContentModel.PROP_NAME, "bobbins.doc"); + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + props2).getChildRef(); + addContentToNode(newNodeRef2); + fail("An exception should have been thrown since a mandatory parameter was missing from the condition."); + } + catch (Throwable ruleServiceException) + { + // Success since we where expecting the exception + } + } + + /** + * Test: rule type: inbound condition: match-text( text = .doc, operation = CONTAINS) action: add-features( aspect-name = versionable) + */ + public void testContainsTextCondition() + { + Map actionParams = new HashMap(1); + actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + // ActionCondition parameter's + Map condParams = new HashMap(1); + condParams.put(ComparePropertyValueEvaluator.PARAM_VALUE, ".doc"); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + actionParams, + ComparePropertyValueEvaluator.NAME, + condParams); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Test condition failure + Map props1 = new HashMap(); + props1.put(ContentModel.PROP_NAME, "bobbins.txt"); + // props1.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + props1).getChildRef(); + addContentToNode(newNodeRef); + + // Map map = this.nodeService.getProperties(newNodeRef); + // String value = (String)this.nodeService.getProperty(newNodeRef, ContentModel.PROP_NAME); + + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Test condition success + Map props2 = new HashMap(); + props2.put(ContentModel.PROP_NAME, "bobbins.doc"); + // props2.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + props2).getChildRef(); + addContentToNode(newNodeRef2); + assertTrue(this.nodeService.hasAspect( + newNodeRef2, + ContentModel.ASPECT_VERSIONABLE)); + + try + { + // Test name not set + NodeRef newNodeRef3 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef3); + } + catch (RuleServiceException exception) + { + // Correct since text-match is a mandatory property + } + + // Test begins with + Map condParamsBegins = new HashMap(1); + condParamsBegins.put(ComparePropertyValueEvaluator.PARAM_VALUE, "bob*"); + rule.getAction().removeAllActionConditions(); + ActionCondition condition1 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME, condParamsBegins); + rule.getAction().addActionCondition(condition1); + this.ruleService.saveRule(this.nodeRef, rule); + Map propsx = new HashMap(); + propsx.put(ContentModel.PROP_NAME, "mybobbins.doc"); + // propsx.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); + NodeRef newNodeRefx = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + propsx).getChildRef(); + addContentToNode(newNodeRefx); + assertFalse(this.nodeService.hasAspect(newNodeRefx, ContentModel.ASPECT_VERSIONABLE)); + Map propsy = new HashMap(); + propsy.put(ContentModel.PROP_NAME, "bobbins.doc"); + // propsy.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); + NodeRef newNodeRefy = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + propsy).getChildRef(); + addContentToNode(newNodeRefy); + assertTrue(this.nodeService.hasAspect( + newNodeRefy, + ContentModel.ASPECT_VERSIONABLE)); + + // Test ends with + Map condParamsEnds = new HashMap(1); + condParamsEnds.put(ComparePropertyValueEvaluator.PARAM_VALUE, "*s.doc"); + rule.getAction().removeAllActionConditions(); + ActionCondition condition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME, condParamsEnds); + rule.getAction().addActionCondition(condition2); + this.ruleService.saveRule(this.nodeRef, rule); + Map propsa = new HashMap(); + propsa.put(ContentModel.PROP_NAME, "bobbins.document"); + // propsa.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); + NodeRef newNodeRefa = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + propsa).getChildRef(); + addContentToNode(newNodeRefa); + assertFalse(this.nodeService.hasAspect(newNodeRefa, ContentModel.ASPECT_VERSIONABLE)); + Map propsb = new HashMap(); + propsb.put(ContentModel.PROP_NAME, "bobbins.doc"); + // propsb.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); + NodeRef newNodeRefb = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + propsb).getChildRef(); + addContentToNode(newNodeRefb); + assertTrue(this.nodeService.hasAspect( + newNodeRefb, + ContentModel.ASPECT_VERSIONABLE)); + } + + public void testInboundRuleType() + { + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Create a non-content node + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTAINER).getChildRef(); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a content node + NodeRef contentNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // ALF-14744 / MNT-187: Create a content node - this time with 'empty content' in the same transaction + contentNodeRef = this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { + @Override + public NodeRef execute() throws Throwable + { + NodeRef contentNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + ContentWriter contentWriter = RuleServiceCoverageTest.this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); + assertNotNull(contentWriter); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter.setEncoding("UTF-8"); + contentWriter.putContent(""); + return contentNodeRef; + } + }); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // ALF-14744 / MNT-187: Create a content node - this time with the 'no content' aspect in the same transaction + contentNodeRef = this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { + @Override + public NodeRef execute() throws Throwable + { + NodeRef contentNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + RuleServiceCoverageTest.this.nodeService.addAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT, null); + return contentNodeRef; + } + }); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a node to be moved + NodeRef moveNode = this.nodeService.createNode( + newNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + addContentToNode(moveNode); + assertFalse(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.moveNode( + moveNode, + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children")); + assertTrue(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); + + // Enusre the rule type does not get fired when the node is updated + this.nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, "name.txt"); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + public void testUpdateRuleType() + { + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + Rule rule = createRule( + RuleType.UPDATE, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Create a non-content node + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_FOLDER).getChildRef(); + this.nodeService.removeAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Update the non-content node + this.nodeService.setProperty(newNodeRef, ContentModel.PROP_NAME, "testName"); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a content node + NodeRef contentNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a non content node, setting a property at the same time + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "testName"); + NodeRef nodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_FOLDER, + props).getChildRef(); + nodeService.removeAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName"); + assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName2"); + assertTrue(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { + public Object execute() throws Exception + { + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "testName"); + NodeRef nodeRef3 = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_FOLDER, + props).getChildRef(); + assertFalse(RuleServiceCoverageTest.this.nodeService.hasAspect(nodeRef3, ContentModel.ASPECT_VERSIONABLE)); + RuleServiceCoverageTest.this.nodeService.setProperty(nodeRef3, ContentModel.PROP_NAME, "testName2"); + assertFalse(RuleServiceCoverageTest.this.nodeService.hasAspect(nodeRef3, ContentModel.ASPECT_VERSIONABLE)); + + return null; + } + }); + } + + public void testAssociationUpdateRule() + { + // ALF-9661 test + NodeRef sourceFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}sourceFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + // create a rule that adds an aspect after a property is updated + Rule rule = createRule( + RuleType.UPDATE, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(sourceFolder, rule); + // create folders + NodeRef testNodeOneRef = this.nodeService.createNode( + sourceFolder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, "original1"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(testNodeOneRef); + + NodeRef testNodeTwoRef = this.nodeService.createNode( + sourceFolder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, "original2"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(testNodeTwoRef); + // there is no aspect + assertFalse(this.nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_VERSIONABLE)); + // create an association + this.nodeService.addAspect(testNodeOneRef, ContentModel.ASPECT_REFERENCING, null); + this.nodeService.createAssociation(testNodeOneRef, testNodeTwoRef, ContentModel.ASSOC_REFERENCES); + // there should be the versionable aspect added + assertTrue(this.nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_VERSIONABLE)); + } + + /** + * Test: rule type: outbound condition: no-condition() action: add-features( aspect-name = versionable) + */ + public void testOutboundRuleType() + { + this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); + + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + "outbound", + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Create a node + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTAINER).getChildRef(); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Move the node out of the actionable folder + this.nodeService.moveNode( + newNodeRef, + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children")); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Check the deletion of a node + + // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTAINER).getChildRef(); + this.nodeService.deleteNode(newNodeRef2); + } + + /** + * Performance guideline test + */ + public void xtestPerformanceOfRuleExecution() + { + try + { + StopWatch sw = new StopWatch(); + + // Create actionable nodes + sw.start("create nodes with no rule executed"); + UserTransaction userTransaction1 = this.transactionService.getUserTransaction(); + userTransaction1.begin(); + + for (int i = 0; i < 100; i++) + { + this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CONTAINS, + ContentModel.ASSOC_CONTAINS, + ContentModel.TYPE_CONTAINER).getChildRef(); + assertFalse(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + userTransaction1.commit(); + sw.stop(); + + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + sw.start("create nodes with one rule run (apply versionable aspect)"); + UserTransaction userTransaction2 = this.transactionService.getUserTransaction(); + userTransaction2.begin(); + + NodeRef[] nodeRefs = new NodeRef[100]; + for (int i = 0; i < 100; i++) + { + NodeRef nodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTAINER).getChildRef(); + addContentToNode(nodeRef); + nodeRefs[i] = nodeRef; + + // Check that the versionable aspect has not yet been applied + assertFalse(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + userTransaction2.commit(); + sw.stop(); + + // Check that the versionable aspect has been applied to all the created nodes + for (NodeRef ref : nodeRefs) + { + assertTrue(this.nodeService.hasAspect(ref, ContentModel.ASPECT_VERSIONABLE)); + } + + System.out.println(sw.prettyPrint()); + } + catch (Exception exception) + { + throw new RuntimeException(exception); + } + } + + public void testAsyncExecutionWithPotentialLoop() + { + try + { + Map params = new HashMap(1); + params.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_TEXT_PLAIN); + params.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, this.nodeRef); + params.put(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS); + params.put(TransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "transformed")); + + Rule rule = createRule( + RuleType.INBOUND, + TransformActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + rule.setExecuteAsynchronously(true); + rule.setTitle("Transform document to text"); + + UserTransaction tx0 = transactionService.getUserTransaction(); + tx0.begin(); + this.ruleService.saveRule(this.nodeRef, rule); + tx0.commit(); + + UserTransaction tx = transactionService.getUserTransaction(); + tx.begin(); + + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "test.xls"); + + // Create the node at the root + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "origional"), + ContentModel.TYPE_CONTENT, + props).getChildRef(); + + // Set some content on the origional + ContentWriter contentWriter = this.contentService.getWriter(newNodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_EXCEL); + File testFile = AbstractContentTransformerTest.loadQuickTestFile("xls"); + contentWriter.putContent(testFile); + + tx.commit(); + + // Sleep to ensure work is done b4 execution is canceled + Thread.sleep(10000); + } + catch (Exception exception) + { + throw new RuntimeException(exception); + } + } +} diff --git a/repository/src/test/java/org/alfresco/util/BaseSpringTest.java b/repository/src/test/java/org/alfresco/util/BaseSpringTest.java index dcc0095675..ec2823fc07 100644 --- a/repository/src/test/java/org/alfresco/util/BaseSpringTest.java +++ b/repository/src/test/java/org/alfresco/util/BaseSpringTest.java @@ -1,57 +1,57 @@ -/* - * #%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.util; - -import junit.framework.TestCase; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextCustomizerFactories; -import org.springframework.test.context.junit4.SpringRunner; - -/** - * Base test class providing Hibernate sessions. - *

- * By default this is auto-wired by type. If a this is going to - * result in a conlict the use auto-wire by name. This can be done by - * setting populateProtectedVariables to true in the constructor and - * then adding protected members with the same name as the bean you require. - * - * @author Derek Hulley - */ -@RunWith(SpringRunner.class) -@ContextConfiguration({"classpath:alfresco/application-context.xml"}) -@ContextCustomizerFactories(factories = {}, mergeMode = ContextCustomizerFactories.MergeMode.REPLACE_DEFAULTS) -public abstract class BaseSpringTest extends TestCase -{ - public Log logger = LogFactory.getLog(getClass().getName()); - - @Autowired - protected ApplicationContext applicationContext; -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 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.util; + +import java.time.Duration; + +import junit.framework.TestCase; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextCustomizerFactories; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Base test class providing Hibernate sessions. + *

+ * By default this is auto-wired by type. If a this is going to result in a conlict the use auto-wire by name. This can be done by setting populateProtectedVariables to true in the constructor and then adding protected members with the same name as the bean you require. + * + * @author Derek Hulley + */ +@RunWith(SpringRunner.class) +@ContextConfiguration({"classpath:alfresco/application-context.xml"}) +@ContextCustomizerFactories(factories = {}, mergeMode = ContextCustomizerFactories.MergeMode.REPLACE_DEFAULTS) +public abstract class BaseSpringTest extends TestCase +{ + protected static final Duration MAX_ASYNC_TIMEOUT = Duration.ofSeconds(10); + public Log logger = LogFactory.getLog(getClass().getName()); + + @Autowired + protected ApplicationContext applicationContext; +}