From 177d2f46da9c0bb9e0a2aea9bac3f56a101d7bac Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 17 Aug 2010 15:13:56 +0000 Subject: [PATCH] Allow Context Aware quartz jobs, and start to implement scheduled persisted actions using this (ALF-4346) Also update ScheduledPersistedActionService to fetch the persisted folder in a cleaner way, and start on tests git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21844 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/scheduled-jobs-context.xml | 4 +- config/alfresco/scheduler-core-context.xml | 3 + .../ScheduledPersistedActionImpl.java | 6 + .../ScheduledPersistedActionServiceImpl.java | 114 ++++++++++++------ .../ScheduledPersistedActionServiceTest.java | 76 ++++++++++++ .../repo/scheduler/AlfrescoJobFactory.java | 53 ++++++++ .../scheduled/ScheduledPersistedAction.java | 4 + 7 files changed, 217 insertions(+), 43 deletions(-) create mode 100644 source/java/org/alfresco/repo/scheduler/AlfrescoJobFactory.java diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index 224145bc39..8fa4faef8d 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -487,10 +487,8 @@ - - - + diff --git a/config/alfresco/scheduler-core-context.xml b/config/alfresco/scheduler-core-context.xml index e361b6fdf8..ab0f205805 100644 --- a/config/alfresco/scheduler-core-context.xml +++ b/config/alfresco/scheduler-core-context.xml @@ -30,6 +30,9 @@ org.alfresco.repo.scheduler.AlfrescoSchedulerFactory + + + diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java index d75a048751..4c0e37e0f4 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java @@ -64,6 +64,12 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction return action; } + /** Get where the action lives */ + public NodeRef getActionNodeRef() + { + return action.getNodeRef(); + } + /** * Get the first date that the action should be run * on or after, or null if it should start shortly diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java index 7ae2ae8208..8e61d9f185 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java @@ -24,8 +24,10 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ActionModel; import org.alfresco.repo.action.RuntimeActionService; +import org.alfresco.repo.model.Repository; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction; @@ -33,16 +35,17 @@ import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService import org.alfresco.service.cmr.repository.ChildAssociationRef; 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.SearchService; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.quartz.Job; import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.extensions.surf.util.AbstractLifecycleBean; @@ -56,8 +59,9 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; */ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedActionService { - protected static final String SCHEDULED_ACTION_ROOT_PATH = "/app:company_home/app:dictionary/cm:Scheduled_x0020_Actions"; - + protected static final String JOB_SCHEDULE_NODEREF = "ScheduleNodeRef"; + protected static final String JOB_ACTION_NODEREF = "ActionNodeRef"; + protected static NodeRef SCHEDULED_ACTION_ROOT_NODE_REF; protected static final Set ACTION_TYPES = new HashSet(Arrays @@ -68,18 +72,10 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc private static final Log log = LogFactory.getLog(ScheduledPersistedActionServiceImpl.class); private Scheduler scheduler; - private NodeService nodeService; - private NodeService startupNodeService; - - private ActionService actionService; - - private SearchService startupSearchService; - - private NamespaceService namespaceService; - private RuntimeActionService runtimeActionService; + private Repository repositoryHelper; public void setScheduler(Scheduler scheduler) { @@ -100,26 +96,30 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc this.startupNodeService = startupNodeService; } - public void setStartupSearchService(SearchService startupSearchService) + public void setRepositoryHelper(Repository repositoryHelper) { - this.startupSearchService = startupSearchService; + this.repositoryHelper = repositoryHelper; } - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - - public void setRuntimeActionService(RuntimeActionService runtimeActionService) + public void setRuntimeActionService(RuntimeActionService runtimeActionService) { this.runtimeActionService = runtimeActionService; } + - public void setNamespaceService(NamespaceService namespaceService) + protected void locatePersistanceFolder() { - this.namespaceService = namespaceService; + NodeRef dataDictionary = startupNodeService.getChildByName( + repositoryHelper.getCompanyHome(), + ContentModel.ASSOC_CONTAINS, + "data dictionary" + ); + SCHEDULED_ACTION_ROOT_NODE_REF = startupNodeService.getChildByName( + dataDictionary, + ContentModel.ASSOC_CONTAINS, + "Scheduled Actions" + ); } - /** * Find all our previously persisted scheduled actions, and tell the @@ -128,18 +128,7 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc */ public void schedulePreviouslyPersisted() { - // Grab the path of our bit of the data dictionary - StoreRef spacesStore = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); - List nodes = startupSearchService.selectNodes(startupNodeService - .getRootNode(spacesStore), SCHEDULED_ACTION_ROOT_PATH, null, namespaceService, - false); - if (nodes.size() != 1) { throw new IllegalStateException( - "Tries to find the Scheduled Actions Data Dictionary" + " folder at " - + SCHEDULED_ACTION_ROOT_PATH + " but got " + nodes.size() - + "results"); } - SCHEDULED_ACTION_ROOT_NODE_REF = nodes.get(0); - - // Now, look up our persisted actions and schedule + // Look up our persisted actions and schedule List actions = listSchedules(startupNodeService); for (ScheduledPersistedAction action : actions) { @@ -254,12 +243,23 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc protected JobDetail buildJobDetail(ScheduledPersistedActionImpl schedule) { + // Build the details JobDetail detail = new JobDetail(schedule.getPersistedAtNodeRef().toString(), - SCHEDULER_GROUP, null // TODO + SCHEDULER_GROUP, + ScheduledJobWrapper.class ); - // TODO - + // Record the action that is to be executed + detail.getJobDataMap().put( + JOB_ACTION_NODEREF, + schedule.getActionNodeRef().toString() + ); + detail.getJobDataMap().put( + JOB_SCHEDULE_NODEREF, + schedule.getPersistedAtNodeRef().toString() + ); + + // All done return detail; } @@ -279,6 +279,7 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc public void onBootstrap(ApplicationEvent event) { + service.locatePersistanceFolder(); service.schedulePreviouslyPersisted(); } @@ -288,4 +289,37 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc // will stop running our jobs for us } } + + /** + * The thing that Quartz runs when the schedule fires. + * Handles fetching the action, and having it run asynchronously + */ + public static class ScheduledJobWrapper implements Job, ApplicationContextAware + { + private ActionService actionService; + private RuntimeActionService runtimeActionService; + + public void setApplicationContext(ApplicationContext applicationContext) + { + actionService = (ActionService)applicationContext.getBean("ActionService"); + runtimeActionService = (RuntimeActionService)applicationContext.getBean("actionService"); + } + + public void execute(JobExecutionContext jobContext) + { + // Create the action object + NodeRef actionNodeRef = new NodeRef( + (String)jobContext.get(JOB_ACTION_NODEREF) + ); + Action action = runtimeActionService.createAction( + actionNodeRef + ); + + // Have it executed asynchronously + actionService.executeAction( + action, (NodeRef)null, + false, true + ); + } + } } diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java index 622d0cdfba..458d639dfd 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java @@ -18,6 +18,8 @@ */ package org.alfresco.repo.action.scheduled; +import java.util.Date; + import javax.transaction.UserTransaction; import junit.framework.TestCase; @@ -33,6 +35,17 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; +import org.quartz.Job; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.Scheduler; +import org.quartz.SimpleTrigger; +import org.quartz.Trigger; +import org.quartz.core.jmx.JobDetailSupport; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; /** @@ -113,6 +126,36 @@ public class ScheduledPersistedActionServiceTest extends TestCase * and from an action */ + /** + * Tests that things get properly injected onto the job bean + */ + public void testJobBeanInjection() throws Exception + { + // The job should run almost immediately + Job job = new TestJob(); + JobDetail details = new JobDetail( + "ThisIsATest", null, job.getClass() + ); + Trigger now = new SimpleTrigger( + "TestTrigger", new Date(1) + ); + now.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); + + Scheduler scheduler = (Scheduler)ctx.getBean("schedulerFactory"); + scheduler.scheduleJob(details, now); + + // Allow it to run + for(int i=0; i<20; i++) + { + if(! TestJob.ran) + Thread.sleep(50); + } + + // Ensure it ran, and it got a copy of the context + assertEquals(true, TestJob.ran); + assertEquals(true, TestJob.gotContext); + } + /** * Tests that things actually get run correctly */ @@ -128,4 +171,37 @@ public class ScheduledPersistedActionServiceTest extends TestCase // every second // TODO } + + // ============================================================================ + + /** + * An action that updates a static count, so we + * can tell how often it is run. + * We have one of these persisted in the repository during + * the tests + * TODO + */ + + /** + * For unit testing only - not thread safe! + */ + public static class TestJob implements Job, ApplicationContextAware + { + private static boolean gotContext = false; + private static boolean ran = false; + + public TestJob() { + gotContext = false; + ran = false; + } + + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + gotContext = true; + } + public void execute(JobExecutionContext paramJobExecutionContext) + throws JobExecutionException { + ran = true; + } + } } diff --git a/source/java/org/alfresco/repo/scheduler/AlfrescoJobFactory.java b/source/java/org/alfresco/repo/scheduler/AlfrescoJobFactory.java new file mode 100644 index 0000000000..1e38b33c66 --- /dev/null +++ b/source/java/org/alfresco/repo/scheduler/AlfrescoJobFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2010 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.scheduler; + +import org.quartz.spi.TriggerFiredBundle; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.scheduling.quartz.SpringBeanJobFactory; + +/** + * A special Job Factory that is based on the usual {@link SpringBeanJobFactory}, + * but also handles {@link ApplicationContextAware} job beans. + * + * @author Nick Burch + */ +public class AlfrescoJobFactory extends SpringBeanJobFactory implements ApplicationContextAware +{ + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + { + this.context = applicationContext; + } + + @Override + protected Object createJobInstance(TriggerFiredBundle bundle) + throws Exception { + Object job = super.createJobInstance(bundle); + if(job instanceof ApplicationContextAware) + { + ((ApplicationContextAware)job).setApplicationContext(context); + } + return job; + } + +} diff --git a/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java b/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java index b1b7949aad..852cea6a18 100644 --- a/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java +++ b/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java @@ -21,6 +21,7 @@ package org.alfresco.service.cmr.action.scheduled; import java.util.Date; import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; /** * The scheduling wrapper around a persisted @@ -35,6 +36,9 @@ public interface ScheduledPersistedAction /** Get the action which the schedule applies to */ public Action getAction(); + /** Get the persisted {@link NodeRef} of the action this applies to */ + public NodeRef getActionNodeRef(); + /** * Get the first date that the action should be run * on or after, or null if it should start shortly