diff --git a/source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java b/source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java
index dbff8d6c05..03a9dc10f2 100644
--- a/source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java
+++ b/source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -220,6 +220,19 @@ public class CronScheduledQueryBasedTemplateActionDefinition extends AbstractSch
// Build the actual query string
String queryTemplate = getQueryTemplate();
+
+ // MNT-11598 workaround: de-escape \$\{foo\} or \#\{foo\}
+ if (queryTemplate.contains("\\$\\{") || queryTemplate.contains("\\#\\{"))
+ {
+ queryTemplate = queryTemplate.replace("\\$\\{", "${");
+ queryTemplate = queryTemplate.replace("\\#\\{", "#{");
+
+ if (queryTemplate.contains("\\}"))
+ {
+ queryTemplate = queryTemplate.replace("\\}", "}");
+ }
+ }
+
String query = templateService.processTemplateString(getTemplateActionModelFactory().getTemplateEngine(),
queryTemplate, getTemplateActionModelFactory().getModel());
diff --git a/source/test-java/org/alfresco/Repository01TestSuite.java b/source/test-java/org/alfresco/Repository01TestSuite.java
index 9a1c576b93..96a0955156 100644
--- a/source/test-java/org/alfresco/Repository01TestSuite.java
+++ b/source/test-java/org/alfresco/Repository01TestSuite.java
@@ -368,6 +368,7 @@ public class Repository01TestSuite extends TestSuite
suite.addTestSuite(org.alfresco.repo.transfer.TransferVersionCheckerImplTest.class);
suite.addTestSuite(org.alfresco.repo.transfer.manifest.ManifestIntegrationTest.class);
suite.addTestSuite(org.alfresco.repo.transfer.script.ScriptTransferServiceTest.class);
+ suite.addTestSuite(org.alfresco.repo.action.scheduled.CronScheduledQueryBasedTemplateActionDefinitionTest.class);
}
static void tests58(TestSuite suite)
diff --git a/source/test-java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinitionTest.java b/source/test-java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinitionTest.java
new file mode 100644
index 0000000000..04b6d1db15
--- /dev/null
+++ b/source/test-java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinitionTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.action.scheduled;
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.transaction.Status;
+import javax.transaction.UserTransaction;
+
+import junit.framework.TestCase;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator;
+import org.alfresco.repo.policy.BehaviourFilter;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.util.ApplicationContextHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.quartz.Scheduler;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Test for {@link CronScheduledQueryBasedTemplateActionDefinition} class. The test assumes that not all test-cases require scheduling. Hence,
+ * {@link CronScheduledQueryBasedTemplateActionDefinition} instance is not registered as scheduled job. But in the same time this instance is unregistered as scheduled job after
+ * every test-case execution. Also default query template is not set for {@link CronScheduledQueryBasedTemplateActionDefinition} instance
+ *
+ * @author Dmitry Velichkevich
+ * @see CronScheduledQueryBasedTemplateActionDefinitionTest#initializeScheduler(ServiceRegistry)
+ */
+public class CronScheduledQueryBasedTemplateActionDefinitionTest extends TestCase
+{
+ private static final int AMOUNT_OF_DAYS_BEFORE = -1;
+
+ private static final int TEST_DOCUMENTS_AMOUNT = 5;
+
+
+ /**
+ * {@link SimpleTemplateActionDefinition#setActionName(String)}
+ */
+ private static final String SCRIPT_TEST_ACTION_NAME = "scriptTestActionName-" + System.currentTimeMillis();
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setQueryLanguage(String)}
+ */
+ private static final String SCHEDULER_QUERY_LANGUAGE = "lucene";
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setCronExpression(String)}
+ */
+ private static final String SCHEDULER_CRON_EXPRESSION = "0 50 * * * ?";
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setCompensatingActionMode(String)}
+ */
+ private static final String SCHEDULER_COMPENSATING_ACTION_MODE = "IGNORE";
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setTransactionMode(String)}
+ */
+ private static final String SCHEDULER_TRANSACTION_MODE = "UNTIL_FIRST_FAILURE";
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setJobName(String)}
+ */
+ private static final String SCHEDULER_JOB_NAME = "jobTestName-" + System.currentTimeMillis();
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setJobGroup(String)}
+ */
+ private static final String SCHEDULER_JOB_GROUP = "jobTestGroup-" + System.currentTimeMillis();
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setTriggerName(String)}
+ */
+ private static final String SCHEDULER_TRIGGER_NAME = "triggerTestName-" + System.currentTimeMillis();
+
+ /**
+ * {@link CronScheduledQueryBasedTemplateActionDefinition#setTriggerGroup(String)}
+ */
+ private static final String SCHEDULER_TRIGGER_GROUP = "triggerTestGroup-" + System.currentTimeMillis();
+
+
+ private static final String SCHEDULER_FACTORY_BEAN_NAME = "schedulerFactory";
+
+ private static final String POLICY_BEHAVIOUR_FILTER_BEAN_NAME = "policyBehaviourFilter";
+
+ private static final String ROOT_TEST_FOLDER_NAME_TEMPLATE = "RootTestFolder-%d";
+
+ private static final String TEST_DOCUMENT_NAME_TEMPLATE = "TestDocument-%d-%d.txt";
+
+ private static final String FRESH_TEST_DOCUMENT_NAME_TEMPLATE = "Fresh" + TEST_DOCUMENT_NAME_TEMPLATE;
+
+ private static final String YESTERDAY_TEST_DOCUMENT_NAME_TEMPLATE = "Yesterday" + TEST_DOCUMENT_NAME_TEMPLATE;
+
+
+ private static final String MNT_11598_QUERY_TEMPLATE = "@cm\\:created:\\$\\{luceneDateRange(yesterday, \"-P10Y\")\\}";
+
+
+ private ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext();
+
+
+ private UserTransaction transaction;
+
+
+ private NodeRef rootTestFolder;
+
+ private List freshNodes = new LinkedList();
+
+ private List yesterdayNodes = new LinkedList();
+
+
+ private CronScheduledQueryBasedTemplateActionDefinition scheduler;
+
+
+ @Before
+ @Override
+ public void setUp() throws Exception
+ {
+ ServiceRegistry registry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY);
+
+ initializeScheduler(registry);
+
+ AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
+
+ transaction = registry.getTransactionService().getUserTransaction(false);
+ transaction.begin();
+
+ createTestContent(registry);
+ }
+
+ /**
+ * Initializes target {@link CronScheduledQueryBasedTemplateActionDefinition} instance for testing. Default query template is not set! It must be set in test method. No
+ * need to register for every test for scheduling. Hence, {@link CronScheduledQueryBasedTemplateActionDefinition#afterPropertiesSet()} is omitted here and must be invoked for
+ * test which requires scheduling
+ *
+ * @param registry - {@link ServiceRegistry} instance
+ */
+ private void initializeScheduler(ServiceRegistry registry)
+ {
+ Scheduler factory = (Scheduler) applicationContext.getBean(SCHEDULER_FACTORY_BEAN_NAME);
+
+ FreeMarkerWithLuceneExtensionsModelFactory templateActionModelFactory = new FreeMarkerWithLuceneExtensionsModelFactory();
+ templateActionModelFactory.setServiceRegistry(registry);
+
+ SimpleTemplateActionDefinition templateActionDefinition = new SimpleTemplateActionDefinition();
+ templateActionDefinition.setApplicationContext(applicationContext);
+ templateActionDefinition.setActionService(registry.getActionService());
+ templateActionDefinition.setTemplateService(registry.getTemplateService());
+ templateActionDefinition.setDictionaryService(registry.getDictionaryService());
+ templateActionDefinition.setTemplateActionModelFactory(templateActionModelFactory);
+
+ templateActionDefinition.setActionName(SCRIPT_TEST_ACTION_NAME);
+
+ scheduler = new CronScheduledQueryBasedTemplateActionDefinition();
+ scheduler.setScheduler(factory);
+ scheduler.setTransactionService(registry.getTransactionService());
+ scheduler.setActionService(registry.getActionService());
+ scheduler.setSearchService(registry.getSearchService());
+ scheduler.setTemplateService(registry.getTemplateService());
+ scheduler.setRunAsUser(AuthenticationUtil.getSystemUserName());
+ scheduler.setTemplateActionDefinition(templateActionDefinition);
+ scheduler.setTemplateActionModelFactory(templateActionModelFactory);
+ scheduler.setStores(Collections.singletonList(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.toString()));
+
+ scheduler.setQueryLanguage(SCHEDULER_QUERY_LANGUAGE);
+ scheduler.setCronExpression(SCHEDULER_CRON_EXPRESSION);
+ scheduler.setCompensatingActionMode(SCHEDULER_COMPENSATING_ACTION_MODE);
+ scheduler.setTransactionMode(SCHEDULER_TRANSACTION_MODE);
+ scheduler.setJobName(SCHEDULER_JOB_NAME);
+ scheduler.setJobGroup(SCHEDULER_JOB_GROUP);
+ scheduler.setTriggerName(SCHEDULER_TRIGGER_NAME);
+ scheduler.setTriggerGroup(SCHEDULER_TRIGGER_GROUP);
+ }
+
+ /**
+ * Creates rootTestFolder
test folder as start of test content hierarchy. Then it creates
+ * {@link CronScheduledQueryBasedTemplateActionDefinitionTest#TEST_DOCUMENTS_AMOUNT} documents in the root folder with "today" creation date and the same amount of documents
+ * with "yesterday" creation date
+ *
+ * @param registry - {@link ServiceRegistry} instance
+ */
+ private void createTestContent(ServiceRegistry registry)
+ {
+ FileFolderService fileFolderService = registry.getFileFolderService();
+
+ NodeRef companyHomeNodeRef = registry.getNodeLocatorService().getNode(CompanyHomeNodeLocator.NAME, null, null);
+ String objectName = String.format(ROOT_TEST_FOLDER_NAME_TEMPLATE, System.currentTimeMillis());
+ rootTestFolder = fileFolderService.create(companyHomeNodeRef, objectName, ContentModel.TYPE_FOLDER).getNodeRef();
+
+ for (int i = 0; i < TEST_DOCUMENTS_AMOUNT; i++)
+ {
+ objectName = String.format(FRESH_TEST_DOCUMENT_NAME_TEMPLATE, i, System.currentTimeMillis());
+ FileInfo fileInfo = fileFolderService.create(rootTestFolder, objectName, ContentModel.TYPE_CONTENT);
+ freshNodes.add(fileInfo.getNodeRef());
+ }
+
+ Calendar calendar = GregorianCalendar.getInstance();
+ calendar.setTimeInMillis(System.currentTimeMillis());
+ calendar.add(Calendar.DAY_OF_YEAR, AMOUNT_OF_DAYS_BEFORE);
+ Date yesterdayTime = calendar.getTime();
+
+ NodeService nodeService = registry.getNodeService();
+ BehaviourFilter policyBehaviourFilter = (BehaviourFilter) applicationContext.getBean(POLICY_BEHAVIOUR_FILTER_BEAN_NAME);
+ policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
+ try
+ {
+ for (int i = 0; i < TEST_DOCUMENTS_AMOUNT; i++)
+ {
+ objectName = String.format(YESTERDAY_TEST_DOCUMENT_NAME_TEMPLATE, i, System.currentTimeMillis());
+ FileInfo fileInfo = fileFolderService.create(rootTestFolder, objectName, ContentModel.TYPE_CONTENT);
+ nodeService.setProperty(fileInfo.getNodeRef(), ContentModel.PROP_CREATED, yesterdayTime);
+ yesterdayNodes.add(fileInfo.getNodeRef());
+ }
+ }
+ finally
+ {
+ policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_ANULLABLE);
+ }
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception
+ {
+ scheduler.getScheduler().unscheduleJob(scheduler.getTriggerName(), scheduler.getJobGroup());
+
+ if (Status.STATUS_ROLLEDBACK != transaction.getStatus())
+ {
+ transaction.rollback();
+ }
+
+ freshNodes.clear();
+ yesterdayNodes.clear();
+
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+
+ @Test
+ public void testQueryTemplateFunctionsUnescapingMnt11598() throws Exception
+ {
+ scheduler.setQueryTemplate(MNT_11598_QUERY_TEMPLATE);
+
+ Set actualNodes = new HashSet(scheduler.getNodes());
+ assertNotNull("Result set must not be null!", actualNodes);
+ assertFalse("Result set must not be empty!", actualNodes.isEmpty());
+ assertTrue(("Result set must contain at least " + TEST_DOCUMENTS_AMOUNT + " nodes!"), actualNodes.size() >= yesterdayNodes.size());
+
+ for (NodeRef nodeRef : freshNodes)
+ {
+ assertFalse("No one of the nodes created \"today\" is expected in result set!", actualNodes.contains(nodeRef));
+ }
+
+ for (NodeRef nodeRef : yesterdayNodes)
+ {
+ assertTrue("One of the nodes created \"yesteday\" is missing in result set!", actualNodes.contains(nodeRef));
+ }
+ }
+}