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)); + } + } +}