diff --git a/build-support/solutions/Spring.Net.Quartz.2005.sln b/build-support/solutions/Spring.Net.Quartz.2005.sln index 839078d3..2c00764c 100644 --- a/build-support/solutions/Spring.Net.Quartz.2005.sln +++ b/build-support/solutions/Spring.Net.Quartz.2005.sln @@ -57,6 +57,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5ECE7B2B Release.AspNetCompiler.Debug = "False" EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Core.Tests.2005", "..\..\test\Spring\Spring.Core.Tests\Spring.Core.Tests.2005.csproj", "{44B16BAA-6DF8-447C-9D7F-3AD3D854D904}" + ProjectSection(WebsiteProperties) = preProject + Debug.AspNetCompiler.Debug = "True" + Release.AspNetCompiler.Debug = "False" + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|.NET = Debug|.NET @@ -133,17 +139,28 @@ Global {E4328F68-C3FC-43E2-9D63-30CEC55215B0}.Release|Any CPU.Build.0 = Release|Any CPU {E4328F68-C3FC-43E2-9D63-30CEC55215B0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {E4328F68-C3FC-43E2-9D63-30CEC55215B0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|.NET.ActiveCfg = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|.NET.ActiveCfg = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Any CPU.Build.0 = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {3A3A4E65-45A6-4B20-B460-0BEDC302C02C} = {438FE108-6F55-4C8A-8BF4-C85D0906D134} - {710961A3-0DF4-49E4-A26E-F5B9C044AC84} = {438FE108-6F55-4C8A-8BF4-C85D0906D134} {AE00E5AB-C39A-436F-86D2-33BFE33E2E40} = {438FE108-6F55-4C8A-8BF4-C85D0906D134} {E823D54C-CE82-4868-929F-5F95A999F61E} = {438FE108-6F55-4C8A-8BF4-C85D0906D134} - {9FE720ED-2BD9-4FB9-89C8-FFFA4A491CB5} = {5ECE7B2B-6975-4800-982E-618794909499} + {710961A3-0DF4-49E4-A26E-F5B9C044AC84} = {438FE108-6F55-4C8A-8BF4-C85D0906D134} {E4328F68-C3FC-43E2-9D63-30CEC55215B0} = {5ECE7B2B-6975-4800-982E-618794909499} + {9FE720ED-2BD9-4FB9-89C8-FFFA4A491CB5} = {5ECE7B2B-6975-4800-982E-618794909499} + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904} = {5ECE7B2B-6975-4800-982E-618794909499} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution NAntAddinLastFileName = Spring.build diff --git a/build-support/solutions/Spring.Net.Quartz.2008.sln b/build-support/solutions/Spring.Net.Quartz.2008.sln index 088b9eeb..832a462d 100644 --- a/build-support/solutions/Spring.Net.Quartz.2008.sln +++ b/build-support/solutions/Spring.Net.Quartz.2008.sln @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Scheduling.Quartz.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Scheduling.Quartz.Integration.Tests.2008", "..\..\test\Spring\Spring.Scheduling.Quartz.Integration.Tests\Spring.Scheduling.Quartz.Integration.Tests.2008.csproj", "{E4328F68-C3FC-43E2-9D63-30CEC55215B0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Core.Tests.2008", "..\..\test\Spring\Spring.Core.Tests\Spring.Core.Tests.2008.csproj", "{44B16BAA-6DF8-447C-9D7F-3AD3D854D904}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|.NET = Debug|.NET @@ -97,6 +99,16 @@ Global {E4328F68-C3FC-43E2-9D63-30CEC55215B0}.Release|Any CPU.Build.0 = Release|Any CPU {E4328F68-C3FC-43E2-9D63-30CEC55215B0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {E4328F68-C3FC-43E2-9D63-30CEC55215B0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|.NET.ActiveCfg = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|.NET.ActiveCfg = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Any CPU.Build.0 = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -108,6 +120,7 @@ Global {E823D54C-CE82-4868-929F-5F95A999F61E} = {438FE108-6F55-4C8A-8BF4-C85D0906D134} {9FE720ED-2BD9-4FB9-89C8-FFFA4A491CB5} = {5ECE7B2B-6975-4800-982E-618794909499} {E4328F68-C3FC-43E2-9D63-30CEC55215B0} = {5ECE7B2B-6975-4800-982E-618794909499} + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904} = {5ECE7B2B-6975-4800-982E-618794909499} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution NAntAddinLastFileName = Spring.build diff --git a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/AdaptableJobFactory.cs b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/AdaptableJobFactory.cs index aea613ed..57377bc0 100644 --- a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/AdaptableJobFactory.cs +++ b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/AdaptableJobFactory.cs @@ -100,6 +100,10 @@ namespace Spring.Scheduling.Quartz { return new DelegatingJob((ThreadStart)jobObject); } + else if (jobObject is IThreadRunnable) + { + return new DelegatingJob(new ThreadStart(((IThreadRunnable) jobObject).Run)); + } else { throw new ArgumentException( diff --git a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/MethodInvokingJobDetailFactoryObject.cs b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/MethodInvokingJobDetailFactoryObject.cs index e66d74ff..40a87269 100644 --- a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/MethodInvokingJobDetailFactoryObject.cs +++ b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/MethodInvokingJobDetailFactoryObject.cs @@ -50,15 +50,20 @@ namespace Spring.Scheduling.Quartz /// Alef Arendsen /// /// - public class MethodInvokingJobDetailFactoryObject : ArgumentConvertingMethodInvoker, IFactoryObject, IObjectNameAware, - IInitializingObject + public class MethodInvokingJobDetailFactoryObject : ArgumentConvertingMethodInvoker, + IObjectFactoryAware, + IFactoryObject, + IObjectNameAware, + IInitializingObject { private string name; private string group; private bool concurrent = true; private string[] jobListenerNames; + private string targetObjectName; private string objectName; private JobDetail jobDetail; + private IObjectFactory objectFactory; /// /// Initializes a new instance of the class. @@ -146,6 +151,30 @@ namespace Spring.Scheduling.Quartz } /// + /// Set the name of the target object in the Spring object factory. + /// + /// + /// This is an alternative to specifying + /// allowing for non-singleton objects to be invoked. Note that specified + /// "TargetObject" and values will + /// override the corresponding effect of this "targetObjectName" setting + ///(i.e. statically pre-define the object type or even the target object). + /// + public string TargetObjectName + { + set { targetObjectName = value; } + } + + /// + /// Sets the object factory. + /// + /// The object factory. + public IObjectFactory ObjectFactory + { + set { objectFactory = value; } + } + + /// /// Return an instance (possibly shared or independent) of the object /// managed by this factory. /// @@ -174,7 +203,18 @@ namespace Spring.Scheduling.Quartz /// public virtual Type ObjectType { - get { return typeof (JobDetail); } + get + { + if (targetObjectName != null) + { + if (objectFactory == null) + { + throw new InvalidOperationException("ObjectFactory must be set when using 'TargetObjectName'"); + } + return objectFactory.GetType(targetObjectName); + } + return typeof (JobDetail); + } } /// diff --git a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerAccessor.cs b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerAccessor.cs new file mode 100644 index 00000000..384ee892 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerAccessor.cs @@ -0,0 +1,452 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +using System; +using System.Collections; + +using Common.Logging; + +using Quartz; +using Quartz.Xml; + +using Spring.Collections; +using Spring.Context; +using Spring.Core.IO; +using Spring.Transaction; +using Spring.Transaction.Support; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Common base class for accessing a Quartz Scheduler, i.e. for registering jobs, + /// triggers and listeners on a instance. + /// + /// + ///

For concrete usage, check out the and + /// classes. + /// + /// Juergen Hoeller + /// Marko Lahma (.NET) + public abstract class SchedulerAccessor : IResourceLoaderAware + { + protected readonly ILog logger; + private bool overwriteExistingJobs; + + private string[] jobSchedulingDataLocations; + + private IList jobDetails; + private IDictionary calendars; + private IList triggers; + + private ISchedulerListener[] schedulerListeners; + private IJobListener[] globalJobListeners; + private IJobListener[] jobListeners; + private ITriggerListener[] globalTriggerListeners; + private ITriggerListener[] triggerListeners; + + private IPlatformTransactionManager transactionManager; + protected IResourceLoader resourceLoader; + + protected SchedulerAccessor() + { + logger = LogManager.GetLogger(GetType()); + } + + ///

+ /// Set whether any jobs defined on this SchedulerFactoryObject should overwrite + /// existing job definitions. Default is "false", to not overwrite already + /// registered jobs that have been read in from a persistent job store. + /// + public virtual bool OverwriteExistingJobs + { + set { overwriteExistingJobs = value; } + } + + + /// + /// Set the locations of Quartz job definition XML files that follow the + /// "job_scheduling_data_1_5" XSD. Can be specified to automatically + /// register jobs that are defined in such files, possibly in addition + /// to jobs defined directly on this SchedulerFactoryObject. + /// + /// + public virtual string[] JobSchedulingDataLocations + { + set { jobSchedulingDataLocations = value; } + } + + /// + /// Set the location of a Quartz job definition XML file that follows the + /// "job_scheduling_data" XSD. Can be specified to automatically + /// register jobs that are defined in such a file, possibly in addition + /// to jobs defined directly on this SchedulerFactoryObject. + /// + /// + /// + public virtual string JobSchedulingDataLocation + { + set { jobSchedulingDataLocations = new string[] {value}; } + } + + /// + /// Register a list of JobDetail objects with the Scheduler that + /// this FactoryObject creates, to be referenced by Triggers. + ///

This is not necessary when a Trigger determines the JobDetail + /// itself: In this case, the JobDetail will be implicitly registered + /// in combination with the Trigger. + ///

+ /// + /// + /// + /// + /// + public virtual JobDetail[] JobDetails + { + set + { + // Use modifiable ArrayList here, to allow for further adding of + // JobDetail objects during autodetection of JobDetailAwareTriggers. + jobDetails = new ArrayList(value); + } + } + + /// + /// Register a list of Quartz ICalendar objects with the Scheduler + /// that this FactoryObject creates, to be referenced by Triggers. + /// + /// Map with calendar names as keys as Calendar objects as values + /// + /// + public virtual IDictionary Calendars + { + set { calendars = value; } + } + + /// + // Register a list of Trigger objects with the Scheduler that + /// this FactoryObject creates. + /// + /// + /// If the Trigger determines the corresponding JobDetail itself, + /// the job will be automatically registered with the Scheduler. + /// Else, the respective JobDetail needs to be registered via the + /// "jobDetails" property of this FactoryObject. + /// + /// + /// + /// + /// + /// + public virtual Trigger[] Triggers + { + set { triggers = new ArrayList(value); } + } + + /// + /// Specify Quartz SchedulerListeners to be registered with the Scheduler. + /// + public virtual ISchedulerListener[] SchedulerListeners + { + set { schedulerListeners = value; } + } + + /// + /// Specify global Quartz JobListeners to be registered with the Scheduler. + /// Such JobListeners will apply to all Jobs in the Scheduler. + /// + public virtual IJobListener[] GlobalJobListeners + { + set { globalJobListeners = value; } + } + + /// + /// Specify named Quartz JobListeners to be registered with the Scheduler. + /// Such JobListeners will only apply to Jobs that explicitly activate + /// them via their name. + /// + /// + /// + /// + public virtual IJobListener[] JobListeners + { + set { jobListeners = value; } + } + + + /// + /// Specify global Quartz TriggerListeners to be registered with the Scheduler. + /// Such TriggerListeners will apply to all Triggers in the Scheduler. + /// + public virtual ITriggerListener[] GlobalTriggerListeners + { + set { globalTriggerListeners = value; } + } + + + /// + /// Specify named Quartz TriggerListeners to be registered with the Scheduler. + /// Such TriggerListeners will only apply to Triggers that explicitly activate + /// them via their name. + /// + /// + /// + /// + /// + public virtual ITriggerListener[] TriggerListeners + { + set { triggerListeners = value; } + } + + + /// + /// Set the transaction manager to be used for registering jobs and triggers + /// that are defined by this SchedulerFactoryObject. Default is none; setting + /// this only makes sense when specifying a DataSource for the Scheduler. + /// + public virtual IPlatformTransactionManager TransactionManager + { + set { transactionManager = value; } + } + + /// + /// Sets the + /// that this object runs in. + /// + /// + /// + /// Invoked after population of normal objects properties but + /// before an init callback such as + /// 's + /// + /// or a custom init-method. Invoked before setting + /// 's + /// + /// property. + /// + public virtual IResourceLoader ResourceLoader + { + set { resourceLoader = value; } + } + + /// + /// Register jobs and triggers (within a transaction, if possible). + /// + protected virtual void RegisterJobsAndTriggers() + { + ITransactionStatus transactionStatus = null; + if (transactionManager != null) + { + transactionStatus = transactionManager.GetTransaction(new DefaultTransactionDefinition()); + } + try + { + if (jobSchedulingDataLocations != null) + { + JobSchedulingDataProcessor dataProcessor = new JobSchedulingDataProcessor(true, true); + for (int i = 0; i < this.jobSchedulingDataLocations.Length; i++) + { + dataProcessor.ProcessFileAndScheduleJobs( + jobSchedulingDataLocations[i], GetScheduler(), overwriteExistingJobs); + } + } + + // Register JobDetails. + if (jobDetails != null) + { + foreach (JobDetail jobDetail in jobDetails) + { + AddJobToScheduler(jobDetail); + } + } + else + { + // Create empty list for easier checks when registering triggers. + jobDetails = new LinkedList(); + } + + // Register Calendars. + if (calendars != null) + { + foreach (DictionaryEntry entry in calendars) + { + string calendarName = (string) entry.Key; + ICalendar calendar = (ICalendar) entry.Value; + GetScheduler().AddCalendar(calendarName, calendar, true, true); + } + } + + // Register Triggers. + if (triggers != null) + { + foreach (Trigger trigger in triggers) + { + AddTriggerToScheduler(trigger); + } + } + } + + catch (Exception ex) + { + if (transactionStatus != null) + { + try + { + transactionManager.Rollback(transactionStatus); + } + catch (TransactionException) + { + logger.Error("Job registration exception overridden by rollback exception", ex); + throw; + } + } + if (ex is SchedulerException) + { + throw; + } + throw new SchedulerException("Registration of jobs and triggers failed: " + ex.Message); + } + + if (transactionStatus != null) + { + transactionManager.Commit(transactionStatus); + } + } + + /// + /// Add the given job to the Scheduler, if it doesn't already exist. + /// Overwrites the job in any case if "overwriteExistingJobs" is set. + /// + /// the job to add + /// true if the job was actually added, false if it already existed before + private bool AddJobToScheduler(JobDetail jobDetail) + { + if (overwriteExistingJobs || + GetScheduler().GetJobDetail(jobDetail.Name, jobDetail.Group) == null) + { + GetScheduler().AddJob(jobDetail, true); + return true; + } + else + { + return false; + } + } + + /// + /// Add the given trigger to the Scheduler, if it doesn't already exist. + /// Overwrites the trigger in any case if "overwriteExistingJobs" is set. + /// + /// the trigger to add + /// true if the trigger was actually added, false if it already existed before + private bool AddTriggerToScheduler(Trigger trigger) + { + bool triggerExists = (GetScheduler().GetTrigger(trigger.Name, trigger.Group) != null); + if (!triggerExists || this.overwriteExistingJobs) + { + // Check if the Trigger is aware of an associated JobDetail. + if (trigger is IJobDetailAwareTrigger) + { + JobDetail jobDetail = ((IJobDetailAwareTrigger) trigger).JobDetail; + // Automatically register the JobDetail too. + if (!jobDetails.Contains(jobDetail) && AddJobToScheduler(jobDetail)) + { + jobDetails.Add(jobDetail); + } + } + if (!triggerExists) + { + try + { + GetScheduler().ScheduleJob(trigger); + } + catch (ObjectAlreadyExistsException ex) + { + if (logger.IsDebugEnabled) + { + logger.Debug( + "Unexpectedly found existing trigger, assumably due to cluster race condition: " + + ex.Message + " - can safely be ignored"); + } + if (overwriteExistingJobs) + { + GetScheduler().RescheduleJob(trigger.Name, trigger.Group, trigger); + } + } + } + else + { + GetScheduler().RescheduleJob(trigger.Name, trigger.Group, trigger); + } + return true; + } + else + { + return false; + } + } + + + /// + /// Register all specified listeners with the Scheduler. + /// + protected virtual void RegisterListeners() + { + if (schedulerListeners != null) + { + for (int i = 0; i < schedulerListeners.Length; i++) + { + GetScheduler().AddSchedulerListener(schedulerListeners[i]); + } + } + if (globalJobListeners != null) + { + for (int i = 0; i < globalJobListeners.Length; i++) + { + GetScheduler().AddGlobalJobListener(globalJobListeners[i]); + } + } + if (jobListeners != null) + { + for (int i = 0; i < jobListeners.Length; i++) + { + GetScheduler().AddJobListener(jobListeners[i]); + } + } + if (globalTriggerListeners != null) + { + for (int i = 0; i < globalTriggerListeners.Length; i++) + { + GetScheduler().AddGlobalTriggerListener(globalTriggerListeners[i]); + } + } + if (triggerListeners != null) + { + for (int i = 0; i < triggerListeners.Length; i++) + { + GetScheduler().AddTriggerListener(triggerListeners[i]); + } + } + } + + /// + /// Template method that determines the Scheduler to operate on. + /// To be implemented by subclasses. + /// + /// + protected abstract IScheduler GetScheduler(); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerAccessorObject.cs b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerAccessorObject.cs new file mode 100644 index 00000000..9197b2d6 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerAccessorObject.cs @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; + +using Quartz; +using Quartz.Impl; + +using Spring.Objects.Factory; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Spring class for accessing a Quartz Scheduler, i.e. for registering jobs, + /// triggers and listeners on a given instance. + /// + /// Juergen Hoeller + /// Marko Lahma (.NET) + /// + /// + public class SchedulerAccessorObject : SchedulerAccessor, IObjectFactoryAware, IInitializingObject + { + private string schedulerName; + private IScheduler scheduler; + private IObjectFactory objectFactory; + + /// + /// Specify the Quartz Scheduler to operate on via its scheduler name in the Spring + /// application context or also in the Quartz {@link org.quartz.impl.SchedulerRepository}. + /// + /// + /// Schedulers can be registered in the repository through custom bootstrapping, + /// e.g. via the or + /// factory classes. + /// However, in general, it's preferable to use Spring's + /// which includes the job/trigger/listener capabilities of this accessor as well. + /// + public string SchedulerName + { + set { schedulerName = value; } + } + + /// + /// Return the Quartz Scheduler instance that this accessor operates on. + /// + protected IScheduler Scheduler + { + set { scheduler = value; } + } + + protected override IScheduler GetScheduler() + { + return scheduler; + } + + /// + /// Return the Quartz Scheduler instance that this accessor operates on. + /// + public IObjectFactory ObjectFactory + { + set { objectFactory = value; } + } + + + public void AfterPropertiesSet() + { + if (scheduler == null) + { + if (schedulerName != null) + { + scheduler = FindScheduler(schedulerName); + } + else + { + throw new InvalidOperationException("No Scheduler specified"); + } + } + RegisterListeners(); + RegisterJobsAndTriggers(); + } + + protected virtual IScheduler FindScheduler(string schedulerName) + { + if (objectFactory is IListableObjectFactory) + { + IListableObjectFactory lbf = (IListableObjectFactory) objectFactory; + string[] objectNames = lbf.GetObjectNamesForType(typeof(IScheduler)); + for (int i = 0; i < objectNames.Length; i++) + { + IScheduler schedulerObject = (IScheduler)lbf.GetObject(objectNames[i]); + if (schedulerName.Equals(schedulerObject.SchedulerName)) + { + return schedulerObject; + } + } + } + IScheduler schedulerInRepo = SchedulerRepository.Instance.Lookup(schedulerName); + if (schedulerInRepo == null) + { + throw new InvalidOperationException("No Scheduler named '" + schedulerName + "' found"); + } + return schedulerInRepo; + } + + } +} diff --git a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerFactoryObject.cs b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerFactoryObject.cs index f4c828d0..8aaaf03f 100644 --- a/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerFactoryObject.cs +++ b/src/Spring/Spring.Scheduling.Quartz/Scheduling/Quartz/SchedulerFactoryObject.cs @@ -19,21 +19,16 @@ using System.Collections; using System.Collections.Specialized; using System.IO; -using Common.Logging; - using Quartz; using Quartz.Impl; using Quartz.Simpl; using Quartz.Spi; using Quartz.Util; -using Quartz.Xml; using Spring.Context; using Spring.Core.IO; using Spring.Data.Common; using Spring.Objects.Factory; -using Spring.Transaction; -using Spring.Transaction.Support; namespace Spring.Scheduling.Quartz { @@ -78,7 +73,7 @@ namespace Spring.Scheduling.Quartz /// /// /// - public class SchedulerFactoryObject : IFactoryObject, IApplicationContextAware, IInitializingObject, IDisposable + public class SchedulerFactoryObject : SchedulerAccessor, IFactoryObject, IObjectNameAware, IApplicationContextAware, IInitializingObject, IDisposable { /// /// Default thread count to be set to thread pool. @@ -126,45 +121,28 @@ namespace Spring.Scheduling.Quartz get { return configTimeTaskExecutor; } } - - /// - /// Logger for this instance and its sub-class instances. - /// - protected ILog logger; - private IApplicationContext applicationContext; private string applicationContextSchedulerContextKey; private bool autoStartup = true; - private IDictionary calendars; private IResource configLocation; - private IJobListener[] globalJobListeners; - private ITriggerListener[] globalTriggerListeners; - private ArrayList jobDetails; private IJobFactory jobFactory; private bool jobFactorySet; - private IJobListener[] jobListeners; - private string[] jobSchedulingDataLocations; - private bool overwriteExistingJobs; private IDictionary quartzProperties; private IScheduler scheduler; private IDictionary schedulerContextMap; private Type schedulerFactoryType; - private ISchedulerListener[] schedulerListeners; private string schedulerName; private TimeSpan startupDelay = TimeSpan.Zero; private ITaskExecutor taskExecutor; - private ITriggerListener[] triggerListeners; - private ArrayList triggers; + private bool exposeSchedulerInRepository; private bool waitForJobsToCompleteOnShutdown; private IDbProvider dbProvider; - private IPlatformTransactionManager transactionManager; /// /// Initializes a new instance of the class. /// public SchedulerFactoryObject() { - logger = LogManager.GetLogger(GetType()); schedulerFactoryType = typeof (StdSchedulerFactory); } @@ -325,149 +303,16 @@ namespace Spring.Scheduling.Quartz } } - /// - /// Set whether any jobs defined on this SchedulerFactoryObject should overwrite - /// existing job definitions. Default is "false", to not overwrite already - /// registered jobs that have been read in from a persistent job store. - /// - public virtual bool OverwriteExistingJobs + /// Set whether to expose the Spring-managed {@link Scheduler} instance in the + /// Quartz {@link SchedulerRepository}. Default is "false", since the Spring-managed + /// Scheduler is usually exclusively intended for access within the Spring context. + ///

Switch this flag to "true" in order to expose the Scheduler globally. + /// This is not recommended unless you have an existing Spring application that + /// relies on this behavior. Note that such global exposure was the accidental + /// default in earlier Spring versions; this has been fixed as of Spring 2.5.6. + public virtual bool ExposeSchedulerInRepository { - set { overwriteExistingJobs = value; } - } - - ///

- /// Set the location of a Quartz job definition XML file that follows the - /// "job_scheduling_data" XSD. Can be specified to automatically - /// register jobs that are defined in such a file, possibly in addition - /// to jobs defined directly on this SchedulerFactoryObject. - /// - /// - /// - public virtual string JobSchedulingDataLocation - { - set { jobSchedulingDataLocations = new string[] {value}; } - } - - /// - /// Set the locations of Quartz job definition XML files that follow the - /// "job_scheduling_data" XSD. Can be specified to automatically - /// register jobs that are defined in such files, possibly in addition - /// to jobs defined directly on this SchedulerFactoryObject. - /// - /// The job scheduling data locations. - /// - /// - public virtual string[] JobSchedulingDataLocations - { - set { jobSchedulingDataLocations = value; } - } - - /// - /// Register a list of JobDetail objects with the Scheduler that - /// this FactoryObject creates, to be referenced by Triggers. - ///

This is not necessary when a Trigger determines the JobDetail - /// itself: In this case, the JobDetail will be implicitly registered - /// in combination with the Trigger.

- ///
- /// - /// - /// - /// - /// - public virtual JobDetail[] JobDetails - { - set - { - // Use modifiable ArrayList here, to allow for further adding of - // JobDetail objects during autodetection of JobDetailAwareTriggers. - jobDetails = new ArrayList(value); - } - } - - /// - /// Register a list of Quartz ICalendar objects with the Scheduler - /// that this FactoryObject creates, to be referenced by Triggers. - /// - /// The calendars. - /// - /// - public virtual IDictionary Calendars - { - set { calendars = value; } - } - - /// - /// Register a list of Trigger objects with the Scheduler that - /// this FactoryObject creates. - ///

- /// If the Trigger determines the corresponding JobDetail itself, - /// the job will be automatically registered with the Scheduler. - /// Else, the respective JobDetail needs to be registered via the - /// "jobDetails" property of this FactoryObject. - ///

- ///
- /// - /// - /// - /// - public virtual Trigger[] Triggers - { - set { triggers = new ArrayList(value); } - } - - /// - /// Specify Quartz SchedulerListeners to be registered with the Scheduler. - /// - public virtual ISchedulerListener[] SchedulerListeners - { - set { schedulerListeners = value; } - } - - /// - /// Specify global Quartz JobListeners to be registered with the Scheduler. - /// Such JobListeners will apply to all Jobs in the Scheduler. - /// - public virtual IJobListener[] GlobalJobListeners - { - set { globalJobListeners = value; } - } - - /// - /// Specify named Quartz JobListeners to be registered with the Scheduler. - /// Such JobListeners will only apply to Jobs that explicitly activate - /// them via their name. - /// - /// The job listeners. - /// - /// - /// - public virtual IJobListener[] JobListeners - { - set { jobListeners = value; } - } - - /// - /// Specify global Quartz TriggerListeners to be registered with the Scheduler. - /// Such TriggerListeners will apply to all Triggers in the Scheduler. - /// - /// The global trigger listeners. - public virtual ITriggerListener[] GlobalTriggerListeners - { - set { globalTriggerListeners = value; } - } - - /// - /// Specify named Quartz TriggerListeners to be registered with the Scheduler. - /// Such TriggerListeners will only apply to Triggers that explicitly activate - /// them via their name. - /// - /// - /// - /// - /// - public virtual ITriggerListener[] TriggerListeners - { - set { triggerListeners = value; } + set { exposeSchedulerInRepository = value; } } /// @@ -524,7 +369,6 @@ namespace Spring.Scheduling.Quartz ///

/// /// - /// /// public IDbProvider DbProvider { @@ -532,15 +376,27 @@ namespace Spring.Scheduling.Quartz } /// - /// Set the transaction manager to be used for registering jobs and triggers - /// that are defined by this SchedulerFactoryObject. Default is none; setting - /// this only makes sense when specifying a DataSource for the Scheduler. + /// Set the name of the object in the object factory that created this object. /// - /// - public IPlatformTransactionManager TransactionManager - { - set { transactionManager = value; } - } + /// The name of the object in the factory. + /// + ///

+ /// Invoked after population of normal object properties but before an init + /// callback like 's + /// + /// method or a custom init-method. + ///

+ ///
+ public string ObjectName + { + set + { + if (schedulerName == null) + { + schedulerName = value; + } + } + } /// /// Gets a value indicating whether this is running. @@ -615,6 +471,16 @@ namespace Spring.Scheduling.Quartz #endregion + /// + /// Template method that determines the Scheduler to operate on. + /// To be implemented by subclasses. + /// + /// + protected override IScheduler GetScheduler() + { + return scheduler; + } + #region IFactoryObject Members /// @@ -633,7 +499,7 @@ namespace Spring.Scheduling.Quartz /// probably fatal) exception. /// /// - public object GetObject() + public virtual object GetObject() { return scheduler; } @@ -720,7 +586,7 @@ namespace Spring.Scheduling.Quartz if (!jobFactorySet && !(scheduler is RemoteScheduler)) { // Use AdaptableJobFactory as default for a local Scheduler, unless when - // explicitly given a null value through the "jobFactory" bean property. + // explicitly given a null value through the "jobFactory" object property. jobFactory = new AdaptableJobFactory(); } @@ -763,70 +629,72 @@ namespace Spring.Scheduling.Quartz /// the SchedulerFactory to Initialize private void InitSchedulerFactory(ISchedulerFactory schedulerFactory) { - if (configLocation != null || quartzProperties != null || schedulerName != null || - taskExecutor != null || dbProvider != null) + if (!(schedulerFactory is StdSchedulerFactory)) { - if (!(schedulerFactory is StdSchedulerFactory)) + if (configLocation != null || quartzProperties != null || schedulerName != null || + taskExecutor != null || dbProvider != null) { - throw new ArgumentException("StdSchedulerFactory required for applying Quartz properties"); + + throw new ArgumentException("StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory); } - - NameValueCollection mergedProps = new NameValueCollection(); - - // Set necessary default properties here, as Quartz will not apply - // its default configuration when explicitly given properties. - if (taskExecutor != null) - { - mergedProps[StdSchedulerFactory.PropertyThreadPoolType] = - typeof (LocalTaskExecutorThreadPool).AssemblyQualifiedName; - } - else - { - mergedProps.Set(StdSchedulerFactory.PropertyThreadPoolType, typeof(SimpleThreadPool).Name); - mergedProps[PROP_THREAD_COUNT] = Convert.ToString(DEFAULT_THREAD_COUNT); - } - - if (configLocation != null) - { - if (logger.IsInfoEnabled) - { - logger.Info("Loading Quartz config from [" + configLocation + "]"); - } - using (StreamReader sr = new StreamReader(configLocation.InputStream)) - { - string line; - while ((line = sr.ReadLine()) != null) - { - string[] lineItems = line.Split(new char[] { '=' }, 2); - if (lineItems.Length == 2) - { - mergedProps[lineItems[0].Trim()] = lineItems[1].Trim(); - } - } - } - - } - - if (quartzProperties != null) - { - // if given quartz properties, merge to them to configuration - MergePropertiesIntoMap(quartzProperties, mergedProps); - } - - if (dbProvider != null) - { - mergedProps.Add(StdSchedulerFactory.PropertyJobStoreType, typeof(LocalDataSourceJobStore).AssemblyQualifiedName); - } - - - // Make sure to set the scheduler name as configured in the Spring configuration. - if (schedulerName != null) - { - mergedProps.Add(StdSchedulerFactory.PropertySchedulerInstanceName, schedulerName); - } - - ((StdSchedulerFactory) schedulerFactory).Initialize(mergedProps); + // Otherwise assume that no initialization is necessary... + return; } + NameValueCollection mergedProps = new NameValueCollection(); + + // Set necessary default properties here, as Quartz will not apply + // its default configuration when explicitly given properties. + if (taskExecutor != null) + { + mergedProps[StdSchedulerFactory.PropertyThreadPoolType] = + typeof (LocalTaskExecutorThreadPool).AssemblyQualifiedName; + } + else + { + mergedProps.Set(StdSchedulerFactory.PropertyThreadPoolType, typeof(SimpleThreadPool).AssemblyQualifiedName); + mergedProps[PROP_THREAD_COUNT] = Convert.ToString(DEFAULT_THREAD_COUNT); + } + + if (configLocation != null) + { + if (logger.IsInfoEnabled) + { + logger.Info("Loading Quartz config from [" + configLocation + "]"); + } + using (StreamReader sr = new StreamReader(configLocation.InputStream)) + { + string line; + while ((line = sr.ReadLine()) != null) + { + string[] lineItems = line.Split(new char[] { '=' }, 2); + if (lineItems.Length == 2) + { + mergedProps[lineItems[0].Trim()] = lineItems[1].Trim(); + } + } + } + + } + + if (quartzProperties != null) + { + // if given quartz properties, merge to them to configuration + MergePropertiesIntoMap(quartzProperties, mergedProps); + } + + if (dbProvider != null) + { + mergedProps.Add(StdSchedulerFactory.PropertyJobStoreType, typeof(LocalDataSourceJobStore).AssemblyQualifiedName); + } + + + // Make sure to set the scheduler name as configured in the Spring configuration. + if (schedulerName != null) + { + mergedProps.Add(StdSchedulerFactory.PropertySchedulerInstanceName, schedulerName); + } + + ((StdSchedulerFactory) schedulerFactory).Initialize(mergedProps); } /// @@ -859,10 +727,24 @@ namespace Spring.Scheduling.Quartz /// protected virtual IScheduler CreateScheduler(ISchedulerFactory schedulerFactory, string schedName) { - // StdSchedulerFactory's default "getScheduler" implementation - // uses the scheduler name specified in the Quartz properties, - // which we have set before (in "InitSchedulerFactory"). - return schedulerFactory.GetScheduler(); + SchedulerRepository repository = SchedulerRepository.Instance; + lock (repository) + { + IScheduler existingScheduler = (schedulerName != null ? repository.Lookup(schedulerName) : null); + IScheduler newScheduler = schedulerFactory.GetScheduler(); + if (newScheduler == existingScheduler) { + throw new InvalidOperationException( + string.Format( + "Active Scheduler of name '{0}' already registered in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!", + schedulerName)); + } + if (!exposeSchedulerInRepository) { + // Need to explicitly remove it if not intended for exposure, + // since Quartz shares the Scheduler instance by default! + SchedulerRepository.Instance.Remove(newScheduler.SchedulerName); + } + return newScheduler; + } } /// @@ -890,212 +772,6 @@ namespace Spring.Scheduling.Quartz } - /// - /// Register all specified listeners with the Scheduler. - /// - private void RegisterListeners() - { - if (schedulerListeners != null) - { - for (int i = 0; i < schedulerListeners.Length; i++) - { - scheduler.AddSchedulerListener(schedulerListeners[i]); - } - } - if (globalJobListeners != null) - { - for (int i = 0; i < globalJobListeners.Length; i++) - { - scheduler.AddGlobalJobListener(globalJobListeners[i]); - } - } - if (jobListeners != null) - { - for (int i = 0; i < jobListeners.Length; i++) - { - scheduler.AddJobListener(jobListeners[i]); - } - } - if (globalTriggerListeners != null) - { - for (int i = 0; i < globalTriggerListeners.Length; i++) - { - scheduler.AddGlobalTriggerListener(globalTriggerListeners[i]); - } - } - if (triggerListeners != null) - { - for (int i = 0; i < triggerListeners.Length; i++) - { - scheduler.AddTriggerListener(triggerListeners[i]); - } - } - } - - /// - /// Register jobs and triggers (within a transaction, if possible). - /// - private void RegisterJobsAndTriggers() - { - ITransactionStatus transactionStatus = null; - if (transactionManager != null) { - transactionStatus = transactionManager.GetTransaction(new DefaultTransactionDefinition()); - } - - try - { - if (jobSchedulingDataLocations != null) - { - ResourceJobSchedulingDataProcessor dataProcessor = new ResourceJobSchedulingDataProcessor(); - if (applicationContext != null) - { - dataProcessor.ResourceLoader = applicationContext; - } - for (int i = 0; i < jobSchedulingDataLocations.Length; i++) - { - dataProcessor.ProcessFileAndScheduleJobs(jobSchedulingDataLocations[i], scheduler, - overwriteExistingJobs); - } - } - - // Register JobDetails. - if (jobDetails != null) - { - foreach (JobDetail jobDetail in jobDetails) - { - AddJobToScheduler(jobDetail); - } - } - else - { - // Create empty list for easier checks when registering triggers. - jobDetails = new ArrayList(); - } - - // Register Calendars. - if (calendars != null) - { - foreach (DictionaryEntry entry in calendars) - { - string calendarName = (string) entry.Key; - ICalendar calendar = (ICalendar) entry.Value; - scheduler.AddCalendar(calendarName, calendar, true, true); - } - } - - // Register Triggers. - if (triggers != null) - { - foreach (Trigger trigger in triggers) - { - AddTriggerToScheduler(trigger); - } - } - } - catch (Exception ex) - { - if (transactionStatus != null) - { - try - { - transactionManager.Rollback(transactionStatus); - } - catch (TransactionException) - { - logger.Error("Job registration exception overridden by rollback exception", ex); - throw; - } - } - - if (ex is SchedulerException) - { - throw; - } - throw new SchedulerException("Registration of jobs and triggers failed: " + ex.Message, ex); - } - - if (transactionStatus != null) - { - transactionManager.Commit(transactionStatus); - } - } - - /// - /// Add the given job to the Scheduler, if it doesn't already exist. - /// Overwrites the job in any case if "overwriteExistingJobs" is set. - /// - /// the job to add - /// - /// true if the job was actually added, - /// false if it already existed before - /// - /// - private bool AddJobToScheduler(JobDetail jobDetail) - { - if (overwriteExistingJobs || scheduler.GetJobDetail(jobDetail.Name, jobDetail.Group) == null) - { - scheduler.AddJob(jobDetail, true); - return true; - } - return false; - } - - /// - /// Add the given trigger to the Scheduler, if it doesn't already exist. - /// Overwrites the trigger in any case if "overwriteExistingJobs" is set. - /// - /// the trigger to add - /// - /// true if the trigger was actually added, - /// false if it already existed before - /// - /// - private bool AddTriggerToScheduler(Trigger trigger) - { - bool triggerExists = (scheduler.GetTrigger(trigger.Name, trigger.Group) != null); - if (!triggerExists || overwriteExistingJobs) - { - // Check if the Trigger is aware of an associated JobDetail. - if (trigger is IJobDetailAwareTrigger) - { - JobDetail jobDetail = ((IJobDetailAwareTrigger) trigger).JobDetail; - // Automatically register the JobDetail too. - if (!jobDetails.Contains(jobDetail) && AddJobToScheduler(jobDetail)) - { - jobDetails.Add(jobDetail); - } - } - if (!triggerExists) - { - try - { - scheduler.ScheduleJob(trigger); - } - catch (ObjectAlreadyExistsException ex) - { - if (logger.IsDebugEnabled) - { - logger.Debug( - string.Format( - "Unexpectedly found existing trigger, assumably due to cluster race condition: {0} - can safely be ignored", - ex.Message)); - } - if (overwriteExistingJobs) - { - scheduler.RescheduleJob(trigger.Name, trigger.Group, trigger); - } - } - } - else - { - scheduler.RescheduleJob(trigger.Name, trigger.Group, trigger); - } - return true; - } - return false; - } - - /// /// Start the Quartz Scheduler, respecting the "startDelay" setting. /// diff --git a/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2005.csproj b/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2005.csproj index 94219426..876d3780 100644 --- a/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2005.csproj +++ b/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2005.csproj @@ -62,6 +62,8 @@ + + diff --git a/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2008.csproj b/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2008.csproj index f8e70671..37e7f168 100644 --- a/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2008.csproj +++ b/src/Spring/Spring.Scheduling.Quartz/Spring.Scheduling.Quartz.2008.csproj @@ -2,7 +2,7 @@ Debug AnyCPU - 9.0.21022 + 9.0.30729 2.0 {E823D54C-CE82-4868-929F-5F95A999F61E} Library @@ -58,6 +58,8 @@ + + diff --git a/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/QuartzSupportTests.cs b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/QuartzSupportTests.cs new file mode 100644 index 00000000..fb39a729 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/QuartzSupportTests.cs @@ -0,0 +1,1153 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections; +using System.Threading; + +using NUnit.Framework; + +using Quartz; +using Quartz.Impl; +using Quartz.Spi; + +using Rhino.Mocks; + +using Spring.Context.Support; +using Spring.Objects; +using Spring.Objects.Factory.Support; + +namespace Spring.Scheduling.Quartz +{ + /// + /// + /// Juergen Hoeller + /// Alef Arendsen + /// Rob Harrop + /// Marko Lahma (.NET) + [TestFixture] + public class QuartzSupportTests + { + private MockRepository mockery; + + [SetUp] + public void SetUp() + { + mockery = new MockRepository(); + } + + [Test] + public void TestSchedulerFactoryObject() + { + DoTestSchedulerFactoryObject(false, false); + } + + [Test] + public void TestSchedulerFactoryObjectWithExplicitJobDetail() + { + DoTestSchedulerFactoryObject(true, false); + } + + [Test] + [Ignore("Requires change to MethodInvoker for overriding target object and type")] + public void TestSchedulerFactoryObjectWithPrototypeJob() + { + DoTestSchedulerFactoryObject(false, true); + } + + private void DoTestSchedulerFactoryObject(bool explicitJobDetail, bool prototypeJob) + { + TestObject tb = new TestObject("tb", 99); + JobDetailObject jobDetail0 = new JobDetailObject(); + jobDetail0.JobType = typeof (IJob); + jobDetail0.ObjectName = ("myJob0"); + IDictionary jobData = new Hashtable(); + jobData.Add("testObject", tb); + jobDetail0.JobDataAsMap = (jobData); + jobDetail0.AfterPropertiesSet(); + Assert.AreEqual(tb, jobDetail0.JobDataMap.Get("testObject")); + + CronTriggerObject trigger0 = new CronTriggerObject(); + trigger0.ObjectName = ("myTrigger0"); + trigger0.JobDetail = (jobDetail0); + trigger0.CronExpressionString = ("0/1 * * * * ?"); + trigger0.AfterPropertiesSet(); + + TestMethodInvokingTask task1 = new TestMethodInvokingTask(); + MethodInvokingJobDetailFactoryObject mijdfb = new MethodInvokingJobDetailFactoryObject(); + mijdfb.ObjectName = ("myJob1"); + if (prototypeJob) + { + StaticListableObjectFactory objectFactory = new StaticListableObjectFactory(); + objectFactory.AddObject("task", task1); + mijdfb.TargetObjectName = ("task"); + mijdfb.ObjectFactory = objectFactory; + } + else + { + mijdfb.TargetObject = (task1); + } + mijdfb.TargetMethod = ("doSomething"); + mijdfb.AfterPropertiesSet(); + JobDetail jobDetail1 = (JobDetail) mijdfb.GetObject(); + + SimpleTriggerObject trigger1 = new SimpleTriggerObject(); + trigger1.ObjectName = ("myTrigger1"); + trigger1.JobDetail = (jobDetail1); + trigger1.StartDelay = TimeSpan.FromMilliseconds(0); + trigger1.RepeatInterval = TimeSpan.FromMilliseconds(20); + trigger1.AfterPropertiesSet(); + + IScheduler scheduler = (IScheduler) mockery.CreateMock(typeof (IScheduler)); + + Expect.Call(scheduler.Context).Return(new SchedulerContext()); + Expect.Call(scheduler.GetJobDetail("myJob0", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetJobDetail("myJob1", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetTrigger("myTrigger0", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetTrigger("myTrigger1", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(delegate { scheduler.AddJob(jobDetail0, true); }).IgnoreArguments(); + Expect.Call(scheduler.ScheduleJob(trigger0)).Return(DateTime.UtcNow); + Expect.Call(delegate { scheduler.AddJob(jobDetail1, true); }).IgnoreArguments(); + Expect.Call(scheduler.ScheduleJob(trigger1)).Return(DateTime.UtcNow); + Expect.Call(delegate { scheduler.Start(); }); + Expect.Call(delegate { scheduler.Shutdown(false); }); + + mockery.ReplayAll(); + + SchedulerFactoryObject schedulerFactoryObject = new TestSchedulerFactoryObject(scheduler); + schedulerFactoryObject.JobFactory = (null); + IDictionary schedulerContext = new Hashtable(); + schedulerContext.Add("otherTestObject", tb); + schedulerFactoryObject.SchedulerContextAsMap = (schedulerContext); + if (explicitJobDetail) + { + schedulerFactoryObject.JobDetails = (new JobDetail[] {jobDetail0}); + } + schedulerFactoryObject.Triggers = (new Trigger[] {trigger0, trigger1}); + try + { + schedulerFactoryObject.AfterPropertiesSet(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + mockery.VerifyAll(); + } + + [Test] + public void TestSchedulerFactoryObjectWithExistingJobs() + { + DoTestSchedulerFactoryObjectWithExistingJobs(false); + } + + [Test] + public void TestSchedulerFactoryObjectWithOverwriteExistingJobs() + { + DoTestSchedulerFactoryObjectWithExistingJobs(true); + } + + private void DoTestSchedulerFactoryObjectWithExistingJobs(bool overwrite) + { + TestObject tb = new TestObject("tb", 99); + JobDetailObject jobDetail0 = new JobDetailObject(); + jobDetail0.JobType = typeof (IJob); + jobDetail0.ObjectName = ("myJob0"); + IDictionary jobData = new Hashtable(); + jobData.Add("testObject", tb); + jobDetail0.JobDataAsMap = (jobData); + jobDetail0.AfterPropertiesSet(); + Assert.AreEqual(tb, jobDetail0.JobDataMap.Get("testObject")); + + CronTriggerObject trigger0 = new CronTriggerObject(); + trigger0.ObjectName = ("myTrigger0"); + trigger0.JobDetail = (jobDetail0); + trigger0.CronExpressionString = ("0/1 * * * * ?"); + trigger0.AfterPropertiesSet(); + + TestMethodInvokingTask task1 = new TestMethodInvokingTask(); + MethodInvokingJobDetailFactoryObject mijdfb = new MethodInvokingJobDetailFactoryObject(); + mijdfb.ObjectName = ("myJob1"); + mijdfb.TargetObject = (task1); + mijdfb.TargetMethod = ("doSomething"); + mijdfb.AfterPropertiesSet(); + JobDetail jobDetail1 = (JobDetail) mijdfb.GetObject(); + + SimpleTriggerObject trigger1 = new SimpleTriggerObject(); + trigger1.ObjectName = ("myTrigger1"); + trigger1.JobDetail = (jobDetail1); + trigger1.StartDelay = TimeSpan.FromMilliseconds(0); + trigger1.RepeatInterval = TimeSpan.FromMilliseconds(20); + trigger1.AfterPropertiesSet(); + + IScheduler scheduler = (IScheduler) mockery.CreateMock(typeof (IScheduler)); + Expect.Call(scheduler.Context).Return(new SchedulerContext()); + Expect.Call(scheduler.GetTrigger("myTrigger0", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetTrigger("myTrigger1", SchedulerConstants.DefaultGroup)).Return(new SimpleTrigger()); + if (overwrite) + { + Expect.Call(delegate { scheduler.AddJob(jobDetail1, true); }).IgnoreArguments(); + Expect.Call(scheduler.RescheduleJob("myTrigger1", SchedulerConstants.DefaultGroup, trigger1)).Return( + DateTime.UtcNow); + } + else + { + Expect.Call(scheduler.GetJobDetail("myJob0", SchedulerConstants.DefaultGroup)).Return(null); + } + Expect.Call(delegate { scheduler.AddJob(jobDetail0, true); }).IgnoreArguments(); + Expect.Call(scheduler.ScheduleJob(trigger0)).Return(DateTime.UtcNow); + ; + Expect.Call(delegate { scheduler.Start(); }); + Expect.Call(delegate { scheduler.Shutdown(false); }); + + mockery.ReplayAll(); + + SchedulerFactoryObject schedulerFactoryObject = new TestSchedulerFactoryObject(scheduler); + schedulerFactoryObject.JobFactory = (null); + IDictionary schedulerContext = new Hashtable(); + schedulerContext.Add("otherTestObject", tb); + schedulerFactoryObject.SchedulerContextAsMap = (schedulerContext); + schedulerFactoryObject.Triggers = (new Trigger[] {trigger0, trigger1}); + if (overwrite) + { + schedulerFactoryObject.OverwriteExistingJobs = (true); + } + try + { + schedulerFactoryObject.AfterPropertiesSet(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + mockery.VerifyAll(); + } + + [Test] + public void TestSchedulerFactoryObjectWithExistingJobsAndRaceCondition() + { + DoTestSchedulerFactoryObjectWithExistingJobsAndRaceCondition(false); + } + + [Test] + public void TestSchedulerFactoryObjectWithOverwriteExistingJobsAndRaceCondition() + { + DoTestSchedulerFactoryObjectWithExistingJobsAndRaceCondition(true); + } + + private void DoTestSchedulerFactoryObjectWithExistingJobsAndRaceCondition(bool overwrite) + { + TestObject tb = new TestObject("tb", 99); + JobDetailObject jobDetail0 = new JobDetailObject(); + jobDetail0.JobType = typeof (IJob); + jobDetail0.ObjectName = ("myJob0"); + IDictionary jobData = new Hashtable(); + jobData.Add("testObject", tb); + jobDetail0.JobDataAsMap = (jobData); + jobDetail0.AfterPropertiesSet(); + Assert.AreEqual(tb, jobDetail0.JobDataMap.Get("testObject")); + + CronTriggerObject trigger0 = new CronTriggerObject(); + trigger0.ObjectName = ("myTrigger0"); + trigger0.JobDetail = (jobDetail0); + trigger0.CronExpressionString = ("0/1 * * * * ?"); + trigger0.AfterPropertiesSet(); + + TestMethodInvokingTask task1 = new TestMethodInvokingTask(); + MethodInvokingJobDetailFactoryObject mijdfb = new MethodInvokingJobDetailFactoryObject(); + mijdfb.ObjectName = ("myJob1"); + mijdfb.TargetObject = (task1); + mijdfb.TargetMethod = ("doSomething"); + mijdfb.AfterPropertiesSet(); + JobDetail jobDetail1 = (JobDetail) mijdfb.GetObject(); + + SimpleTriggerObject trigger1 = new SimpleTriggerObject(); + trigger1.ObjectName = ("myTrigger1"); + trigger1.JobDetail = (jobDetail1); + trigger1.StartDelay = TimeSpan.FromMilliseconds(0); + trigger1.RepeatInterval = TimeSpan.FromMilliseconds(20); + trigger1.AfterPropertiesSet(); + + IScheduler scheduler = (IScheduler) mockery.CreateMock(typeof (IScheduler)); + Expect.Call(scheduler.Context).Return(new SchedulerContext()); + Expect.Call(scheduler.GetTrigger("myTrigger0", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetTrigger("myTrigger1", SchedulerConstants.DefaultGroup)).Return(new SimpleTrigger()); + if (overwrite) + { + Expect.Call(delegate { scheduler.AddJob(jobDetail1, true); }).IgnoreArguments(); + Expect.Call(scheduler.RescheduleJob("myTrigger1", SchedulerConstants.DefaultGroup, trigger1)).Return( + DateTime.UtcNow); + } + else + { + Expect.Call(scheduler.GetJobDetail("myJob0", SchedulerConstants.DefaultGroup)).Return(null); + } + Expect.Call(delegate { scheduler.AddJob(jobDetail0, true); }).IgnoreArguments(); + Expect.Call(scheduler.ScheduleJob(trigger0)).Throw(new ObjectAlreadyExistsException("")); + if (overwrite) + { + Expect.Call(scheduler.RescheduleJob("myTrigger0", SchedulerConstants.DefaultGroup, trigger0)).Return( + DateTime.UtcNow); + } + + Expect.Call(delegate { scheduler.Start(); }); + Expect.Call(delegate { scheduler.Shutdown(false); }); + + mockery.ReplayAll(); + + SchedulerFactoryObject schedulerFactoryObject = new TestSchedulerFactoryObject(scheduler); + + schedulerFactoryObject.JobFactory = (null); + IDictionary schedulerContext = new Hashtable(); + schedulerContext.Add("otherTestObject", tb); + schedulerFactoryObject.SchedulerContextAsMap = (schedulerContext); + schedulerFactoryObject.Triggers = (new Trigger[] {trigger0, trigger1}); + if (overwrite) + { + schedulerFactoryObject.OverwriteExistingJobs = (true); + } + try + { + schedulerFactoryObject.AfterPropertiesSet(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + mockery.VerifyAll(); + } + + [Test] + public void TestSchedulerFactoryObjectWithListeners() + { + IJobFactory jobFactory = new AdaptableJobFactory(); + + IScheduler scheduler = (IScheduler) mockery.CreateMock(typeof (IScheduler)); + + ISchedulerListener schedulerListener = new TestSchedulerListener(); + IJobListener globalJobListener = new TestJobListener(); + IJobListener jobListener = new TestJobListener(); + ITriggerListener globalTriggerListener = new TestTriggerListener(); + ITriggerListener triggerListener = new TestTriggerListener(); + + Expect.Call(scheduler.JobFactory = (jobFactory)); + Expect.Call(delegate { scheduler.AddSchedulerListener(schedulerListener); }); + Expect.Call(delegate { scheduler.AddGlobalJobListener(globalJobListener); }); + Expect.Call(delegate { scheduler.AddJobListener(jobListener); }); + Expect.Call(delegate { scheduler.AddGlobalTriggerListener(globalTriggerListener); }); + Expect.Call(delegate { scheduler.AddTriggerListener(triggerListener); }); + Expect.Call(delegate { scheduler.Start(); }); + Expect.Call(delegate { scheduler.Shutdown(false); }); + + mockery.ReplayAll(); + + SchedulerFactoryObject schedulerFactoryObject = new TestSchedulerFactoryObject(scheduler); + + schedulerFactoryObject.JobFactory = (jobFactory); + schedulerFactoryObject.SchedulerListeners = (new ISchedulerListener[] {schedulerListener}); + schedulerFactoryObject.GlobalJobListeners = (new IJobListener[] {globalJobListener}); + schedulerFactoryObject.JobListeners = (new IJobListener[] {jobListener}); + schedulerFactoryObject.GlobalTriggerListeners = (new ITriggerListener[] {globalTriggerListener}); + schedulerFactoryObject.TriggerListeners = (new ITriggerListener[] {triggerListener}); + try + { + schedulerFactoryObject.AfterPropertiesSet(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + mockery.VerifyAll(); + } + + /*public void TestMethodInvocationWithConcurrency() { + methodInvokingConcurrency(true); + }*/ + + // We can't test both since Quartz somehow seems to keep things in memory + // enable both and one of them will fail (order doesn't matter). + /*public void TestMethodInvocationWithoutConcurrency() { + methodInvokingConcurrency(false); + }*/ + + private void methodInvokingConcurrency(bool concurrent) + { + // Test the concurrency flag. + // Method invoking job with two triggers. + // If the concurrent flag is false, the triggers are NOT allowed + // to interfere with each other. + + TestMethodInvokingTask task1 = new TestMethodInvokingTask(); + MethodInvokingJobDetailFactoryObject mijdfb = new MethodInvokingJobDetailFactoryObject(); + // set the concurrency flag! + mijdfb.Concurrent = (concurrent); + mijdfb.ObjectName = ("myJob1"); + mijdfb.TargetObject = (task1); + mijdfb.TargetMethod = ("doWait"); + mijdfb.AfterPropertiesSet(); + JobDetail jobDetail1 = (JobDetail) mijdfb.GetObject(); + + SimpleTriggerObject trigger0 = new SimpleTriggerObject(); + trigger0.ObjectName = ("myTrigger1"); + trigger0.JobDetail = (jobDetail1); + trigger0.StartDelay = TimeSpan.FromMilliseconds(0); + trigger0.RepeatInterval = TimeSpan.FromMilliseconds(1); + trigger0.RepeatCount = (1); + trigger0.AfterPropertiesSet(); + + SimpleTriggerObject trigger1 = new SimpleTriggerObject(); + trigger1.ObjectName = ("myTrigger1"); + trigger1.JobDetail = (jobDetail1); + trigger1.StartDelay = TimeSpan.FromMilliseconds(1000L); + trigger1.RepeatInterval = TimeSpan.FromMilliseconds(1); + trigger1.RepeatCount = (1); + trigger1.AfterPropertiesSet(); + + SchedulerFactoryObject schedulerFactoryObject = new SchedulerFactoryObject(); + schedulerFactoryObject.JobDetails = (new JobDetail[] {jobDetail1}); + schedulerFactoryObject.Triggers = (new Trigger[] {trigger1, trigger0}); + schedulerFactoryObject.AfterPropertiesSet(); + + // ok scheduler is set up... let's wait for like 4 seconds + try + { + Thread.Sleep(4000); + } + catch (ThreadInterruptedException ex) + { + // fall through + } + + if (concurrent) + { + Assert.AreEqual(2, task1.counter); + task1.Stop(); + // we're done, both jobs have ran, let's call it a day + return; + } + else + { + Assert.AreEqual(1, task1.counter); + task1.Stop(); + // we need to check whether or not the test succeed with non-concurrent jobs + } + + try + { + Thread.Sleep(4000); + } + catch (ThreadInterruptedException ex) + { + // fall through + } + + task1.Stop(); + Assert.AreEqual(2, task1.counter); + + // Although we're destroying the scheduler, it does seem to keep things in memory: + // When executing both tests (concurrent and non-concurrent), the second test always + // fails. + schedulerFactoryObject.Dispose(); + } + + [Test] + public void TestSchedulerFactoryObjectWithPlainQuartzObjects() + { + IJobFactory jobFactory = new AdaptableJobFactory(); + + TestObject tb = new TestObject("tb", 99); + JobDetail jobDetail0 = new JobDetail(); + jobDetail0.JobType = typeof (IJob); + jobDetail0.Name = ("myJob0"); + jobDetail0.Group = (SchedulerConstants.DefaultGroup); + jobDetail0.JobDataMap.Add("testObject", tb); + Assert.AreEqual(tb, jobDetail0.JobDataMap.Get("testObject")); + + CronTrigger trigger0 = new CronTrigger(); + trigger0.Name = ("myTrigger0"); + trigger0.Group = SchedulerConstants.DefaultGroup; + trigger0.JobName = "myJob0"; + trigger0.JobGroup = SchedulerConstants.DefaultGroup; + trigger0.StartTimeUtc = (DateTime.UtcNow); + trigger0.CronExpressionString = ("0/1 * * * * ?"); + + TestMethodInvokingTask task1 = new TestMethodInvokingTask(); + MethodInvokingJobDetailFactoryObject mijdfb = new MethodInvokingJobDetailFactoryObject(); + mijdfb.Name = ("myJob1"); + mijdfb.Group = (SchedulerConstants.DefaultGroup); + mijdfb.TargetObject = (task1); + mijdfb.TargetMethod = ("doSomething"); + mijdfb.AfterPropertiesSet(); + JobDetail jobDetail1 = (JobDetail) mijdfb.GetObject(); + + SimpleTrigger trigger1 = new SimpleTrigger(); + trigger1.Name = "myTrigger1"; + trigger1.Group = SchedulerConstants.DefaultGroup; + trigger1.JobName = "myJob1"; + trigger1.JobGroup = SchedulerConstants.DefaultGroup; + trigger1.StartTimeUtc = (DateTime.UtcNow); + trigger1.RepeatCount = (SimpleTrigger.RepeatIndefinitely); + trigger1.RepeatInterval = TimeSpan.FromMilliseconds(20); + + IScheduler scheduler = (IScheduler) mockery.CreateMock(typeof (IScheduler)); + Expect.Call(scheduler.JobFactory = (jobFactory)); + Expect.Call(scheduler.GetJobDetail("myJob0", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetJobDetail("myJob1", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetTrigger("myTrigger0", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(scheduler.GetTrigger("myTrigger1", SchedulerConstants.DefaultGroup)).Return(null); + Expect.Call(delegate { scheduler.AddJob(jobDetail0, true); }).IgnoreArguments(); + Expect.Call(delegate { scheduler.AddJob(jobDetail1, true); }).IgnoreArguments(); + Expect.Call(scheduler.ScheduleJob(trigger0)).Return(DateTime.UtcNow); + Expect.Call(scheduler.ScheduleJob(trigger1)).Return(DateTime.UtcNow); + Expect.Call(delegate { scheduler.Start(); }); + Expect.Call(delegate { scheduler.Shutdown(false); }); + + mockery.ReplayAll(); + + SchedulerFactoryObject schedulerFactoryObject = new TestSchedulerFactoryObject(scheduler); + + schedulerFactoryObject.JobFactory = (jobFactory); + schedulerFactoryObject.JobDetails = (new JobDetail[] {jobDetail0, jobDetail1}); + schedulerFactoryObject.Triggers = (new Trigger[] {trigger0, trigger1}); + try + { + schedulerFactoryObject.AfterPropertiesSet(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + mockery.VerifyAll(); + } + + [Test] + public void TestSchedulerFactoryObjectWithApplicationContext() + { + TestObject tb = new TestObject("tb", 99); + StaticApplicationContext ac = new StaticApplicationContext(); + + IScheduler scheduler = (IScheduler) mockery.CreateMock(typeof (IScheduler)); + SchedulerContext schedulerContext = new SchedulerContext(); + Expect.Call(scheduler.Context).Return(schedulerContext).Repeat.Times(4); + Expect.Call(delegate { scheduler.Start(); }); + Expect.Call(delegate { scheduler.Shutdown(false); }); + + mockery.ReplayAll(); + + SchedulerFactoryObject schedulerFactoryObject = new TestSchedulerFactoryObject(scheduler); + schedulerFactoryObject.JobFactory = (null); + IDictionary schedulerContextMap = new Hashtable(); + schedulerContextMap.Add("testObject", tb); + schedulerFactoryObject.SchedulerContextAsMap = (schedulerContextMap); + schedulerFactoryObject.ApplicationContext = (ac); + schedulerFactoryObject.ApplicationContextSchedulerContextKey = ("appCtx"); + try + { + schedulerFactoryObject.AfterPropertiesSet(); + IScheduler returnedScheduler = (IScheduler) schedulerFactoryObject.GetObject(); + Assert.AreEqual(tb, returnedScheduler.Context["testObject"]); + Assert.AreEqual(ac, returnedScheduler.Context["appCtx"]); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + mockery.VerifyAll(); + } + + [Test] + public void TestJobDetailObjectWithApplicationContext() + { + TestObject tb = new TestObject("tb", 99); + StaticApplicationContext ac = new StaticApplicationContext(); + + JobDetailObject jobDetail = new JobDetailObject(); + jobDetail.JobType = typeof (IJob); + jobDetail.ObjectName = ("myJob0"); + IDictionary jobData = new Hashtable(); + jobData.Add("testObject", tb); + jobDetail.JobDataAsMap = (jobData); + jobDetail.ApplicationContext = (ac); + jobDetail.ApplicationContextJobDataKey = ("appCtx"); + jobDetail.AfterPropertiesSet(); + + Assert.AreEqual(tb, jobDetail.JobDataMap.Get("testObject")); + Assert.AreEqual(ac, jobDetail.JobDataMap.Get("appCtx")); + } + + [Test] + public void TestMethodInvokingJobDetailFactoryObjectWithListenerNames() + { + TestMethodInvokingTask task = new TestMethodInvokingTask(); + MethodInvokingJobDetailFactoryObject mijdfb = new MethodInvokingJobDetailFactoryObject(); + String[] names = new String[] {"test1", "test2"}; + mijdfb.Name = ("myJob1"); + mijdfb.Group = (SchedulerConstants.DefaultGroup); + mijdfb.TargetObject = (task); + mijdfb.TargetMethod = ("doSomething"); + mijdfb.JobListenerNames = (names); + mijdfb.AfterPropertiesSet(); + JobDetail jobDetail = (JobDetail) mijdfb.GetObject(); + ArrayList result = new ArrayList(jobDetail.JobListenerNames); + Assert.AreEqual(names, result); + } + + [Test] + public void TestJobDetailObjectWithListenerNames() + { + JobDetailObject jobDetail = new JobDetailObject(); + String[] names = new String[] {"test1", "test2"}; + jobDetail.JobListenerNames = (names); + ArrayList result = new ArrayList(jobDetail.JobListenerNames); + Assert.AreEqual(names, result); + } + + [Test] + public void TestCronTriggerObjectWithListenerNames() + { + CronTriggerObject trigger = new CronTriggerObject(); + String[] names = new String[] {"test1", "test2"}; + trigger.TriggerListenerNames = (names); + ArrayList result = new ArrayList(trigger.TriggerListenerNames); + Assert.AreEqual(names, result); + } + + [Test] + public void TestSimpleTriggerObjectWithListenerNames() + { + SimpleTriggerObject trigger = new SimpleTriggerObject(); + String[] names = new String[] {"test1", "test2"}; + trigger.TriggerListenerNames = (names); + ArrayList result = new ArrayList(trigger.TriggerListenerNames); + Assert.AreEqual(names, result); + } + + [Test] + public void TestSchedulerWithTaskExecutor() + { + CountingTaskExecutor taskExecutor = new CountingTaskExecutor(); + DummyJob.count = 0; + + JobDetail jobDetail = new JobDetail(); + jobDetail.JobType = typeof (DummyJob); + ; + jobDetail.Name = ("myJob"); + + SimpleTriggerObject trigger = new SimpleTriggerObject(); + trigger.Name = ("myTrigger"); + trigger.JobDetail = (jobDetail); + trigger.StartDelay = TimeSpan.FromMilliseconds(1); + trigger.RepeatInterval = TimeSpan.FromMilliseconds(500); + trigger.RepeatCount = (1); + trigger.AfterPropertiesSet(); + + SchedulerFactoryObject bean = new SchedulerFactoryObject(); + bean.TaskExecutor = (taskExecutor); + bean.Triggers = (new Trigger[] {trigger}); + bean.JobDetails = (new JobDetail[] {jobDetail}); + bean.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.IsTrue(DummyJob.count > 0); + Assert.AreEqual(DummyJob.count, taskExecutor.count); + + bean.Dispose(); + } + + [Test] + public void TestSchedulerWithRunnable() + { + DummyRunnable.count = 0; + + JobDetail jobDetail = new JobDetailObject(); + jobDetail.JobType = typeof (DummyRunnable); + jobDetail.Name = ("myJob"); + + SimpleTriggerObject trigger = new SimpleTriggerObject(); + trigger.Name = ("myTrigger"); + trigger.JobDetail = (jobDetail); + trigger.StartDelay = TimeSpan.FromMilliseconds(1); + trigger.RepeatInterval = TimeSpan.FromMilliseconds(500); + trigger.RepeatCount = (1); + trigger.AfterPropertiesSet(); + + SchedulerFactoryObject bean = new SchedulerFactoryObject(); + bean.Triggers = (new Trigger[] {trigger}); + bean.JobDetails = (new JobDetail[] {jobDetail}); + bean.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.IsTrue(DummyRunnable.count > 0); + + bean.Dispose(); + } + + [Test] + public void TestSchedulerWithQuartzJobObject() + { + DummyJob.param = 0; + DummyJob.count = 0; + + JobDetail jobDetail = new JobDetail(); + jobDetail.JobType = (typeof (DummyJobObject)); + jobDetail.Name = ("myJob"); + jobDetail.JobDataMap.Put("param", "10"); + + SimpleTriggerObject trigger = new SimpleTriggerObject(); + trigger.Name = ("myTrigger"); + trigger.JobDetail = (jobDetail); + trigger.StartDelay = TimeSpan.FromMilliseconds(1); + trigger.RepeatInterval = TimeSpan.FromMilliseconds(500); + trigger.RepeatCount = (1); + trigger.AfterPropertiesSet(); + + SchedulerFactoryObject sfo = new SchedulerFactoryObject(); + sfo.Triggers = (new Trigger[] {trigger}); + sfo.JobDetails = (new JobDetail[] {jobDetail}); + sfo.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJobObject.param); + Assert.IsTrue(DummyJobObject.count > 0); + + sfo.Dispose(); + } + + [Test] + public void TestSchedulerWithSpringObjectJobFactory() + { + DummyJob.param = 0; + DummyJob.count = 0; + + JobDetail jobDetail = new JobDetail(); + jobDetail.JobType = typeof(DummyJob); + jobDetail.Name = ("myJob"); + jobDetail.JobDataMap.Add("param", "10"); + jobDetail.JobDataMap.Add("ignoredParam", "10"); + + SimpleTriggerObject trigger = new SimpleTriggerObject(); + trigger.Name = ("myTrigger"); + trigger.JobDetail = (jobDetail); + trigger.StartDelay = TimeSpan.FromMilliseconds(1); + trigger.RepeatInterval = TimeSpan.FromMilliseconds(500); + trigger.RepeatCount = (1); + trigger.AfterPropertiesSet(); + + SchedulerFactoryObject bean = new SchedulerFactoryObject(); + bean.JobFactory = (new SpringObjectJobFactory()); + bean.Triggers = (new Trigger[] {trigger}); + bean.JobDetails = (new JobDetail[] {jobDetail}); + bean.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJob.param); + Assert.IsTrue(DummyJob.count > 0); + + bean.Dispose(); + } + + [Test] + public void TestSchedulerWithSpringObjectJobFactoryAndParamMismatchNotIgnored() + { + DummyJob.param = 0; + DummyJob.count = 0; + + JobDetail jobDetail = new JobDetail(); + jobDetail.JobType = typeof(DummyJob); + jobDetail.Name = ("myJob"); + jobDetail.JobDataMap.Add("para", "10"); + jobDetail.JobDataMap.Add("ignoredParam", "10"); + + SimpleTriggerObject trigger = new SimpleTriggerObject(); + trigger.Name = ("myTrigger"); + trigger.JobDetail = (jobDetail); + trigger.StartDelay = TimeSpan.FromMilliseconds(1); + trigger.RepeatInterval = TimeSpan.FromMilliseconds(500); + trigger.RepeatCount = (1); + trigger.AfterPropertiesSet(); + + SchedulerFactoryObject bean = new SchedulerFactoryObject(); + SpringObjectJobFactory jobFactory = new SpringObjectJobFactory(); + jobFactory.IgnoredUnknownProperties = (new String[] {"ignoredParam"}); + bean.JobFactory = (jobFactory); + bean.Triggers = (new Trigger[] {trigger}); + bean.JobDetails = (new JobDetail[] {jobDetail}); + bean.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.AreEqual(0, DummyJob.param); + Assert.IsTrue(DummyJob.count == 0); + + bean.Dispose(); + } + + [Test] + public void TestSchedulerWithSpringObjectJobFactoryAndRunnable() + { + DummyRunnable.param = 0; + DummyRunnable.count = 0; + + JobDetail jobDetail = new JobDetailObject(); + jobDetail.JobType = typeof (DummyRunnable); + jobDetail.Name = ("myJob"); + jobDetail.JobDataMap.Add("param", "10"); + + SimpleTriggerObject trigger = new SimpleTriggerObject(); + trigger.Name = ("myTrigger"); + trigger.JobDetail = (jobDetail); + trigger.StartDelay = TimeSpan.FromMilliseconds(1); + trigger.RepeatInterval = TimeSpan.FromMilliseconds(500); + trigger.RepeatCount = (1); + trigger.AfterPropertiesSet(); + + SchedulerFactoryObject bean = new SchedulerFactoryObject(); + bean.JobFactory = (new SpringObjectJobFactory()); + bean.Triggers = (new Trigger[] {trigger}); + bean.JobDetails = (new JobDetail[] {jobDetail}); + bean.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyRunnable.param); + Assert.IsTrue(DummyRunnable.count > 0); + + bean.Dispose(); + } + + [Test] + public void TestSchedulerWithSpringObjectJobFactoryAndQuartzJobObject() + { + DummyJobObject.param = 0; + DummyJobObject.count = 0; + + JobDetail jobDetail = new JobDetail(); + jobDetail.JobType = typeof (DummyJobObject); + jobDetail.Name = ("myJob"); + jobDetail.JobDataMap.Add("param", "10"); + + SimpleTriggerObject trigger = new SimpleTriggerObject(); + trigger.Name = ("myTrigger"); + trigger.JobDetail = (jobDetail); + trigger.StartDelay = TimeSpan.FromMilliseconds(1); + trigger.RepeatInterval = TimeSpan.FromMilliseconds(500); + trigger.RepeatCount = (1); + trigger.AfterPropertiesSet(); + + SchedulerFactoryObject bean = new SchedulerFactoryObject(); + bean.JobFactory = (new SpringObjectJobFactory()); + bean.Triggers = (new Trigger[] {trigger}); + bean.JobDetails = (new JobDetail[] {jobDetail}); + bean.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJobObject.param); + Assert.IsTrue(DummyJobObject.count > 0); + + bean.Dispose(); + } + + [Test] + public void TestSchedulerWithSpringObjectJobFactoryAndJobSchedulingData() + { + DummyJob.param = 0; + DummyJob.count = 0; + + SchedulerFactoryObject bean = new SchedulerFactoryObject(); + bean.JobFactory = (new SpringObjectJobFactory()); + bean.JobSchedulingDataLocation = ("job-scheduling-data.xml"); + // TODO bean.ResourceLoader = (new FileSystemResourceLoader()); + bean.AfterPropertiesSet(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJob.param); + Assert.IsTrue(DummyJob.count > 0); + + bean.Dispose(); + } + + + /// + /// Tests the creation of multiple schedulers (SPR-772) + /// + [Test] + public void TestMultipleSchedulers() + { + XmlApplicationContext ctx = new XmlApplicationContext("multipleSchedulers.xml"); + try + { + IScheduler scheduler1 = (IScheduler) ctx.GetObject("scheduler1"); + IScheduler scheduler2 = (IScheduler) ctx.GetObject("scheduler2"); + Assert.AreNotSame(scheduler1, scheduler2); + Assert.AreEqual("quartz1", scheduler1.SchedulerName); + Assert.AreEqual("quartz2", scheduler2.SchedulerName); + + XmlApplicationContext ctx2 = new XmlApplicationContext("multipleSchedulers.xml"); + try + { + IScheduler scheduler1a = (IScheduler) ctx2.GetObject("scheduler1"); + IScheduler scheduler2a = (IScheduler) ctx2.GetObject("scheduler2"); + Assert.AreNotSame(scheduler1a, scheduler2a); + Assert.AreNotSame(scheduler1a, scheduler1); + Assert.AreNotSame(scheduler2a, scheduler2); + Assert.AreEqual("quartz1", scheduler1a.SchedulerName); + Assert.AreEqual("quartz2", scheduler2a.SchedulerName); + } + finally + { + ctx2.Dispose(); + } + } + finally + { + ctx.Dispose(); + } + } + + [Test] + public void TestWithTwoAnonymousMethodInvokingJobDetailFactoryObjects() + { + XmlApplicationContext ctx = new XmlApplicationContext("multipleAnonymousMethodInvokingJobDetailFB.xml"); + Thread.Sleep(3000); + try + { + QuartzTestObject exportService = (QuartzTestObject) ctx.GetObject("exportService"); + QuartzTestObject importService = (QuartzTestObject) ctx.GetObject("importService"); + + Assert.AreEqual(0, exportService.ImportCount, "doImport called exportService"); + Assert.AreEqual(2, exportService.ExportCount, "doExport not called on exportService"); + Assert.AreEqual(2, importService.ImportCount, "doImport not called on importService"); + Assert.AreEqual(0, importService.ExportCount, "doExport called on importService"); + } + finally + { + ctx.Dispose(); + } + } + + [Test] + public void TestSchedulerAccessorObject() + { + XmlApplicationContext ctx = new XmlApplicationContext("schedulerAccessorObject.xml"); + Thread.Sleep(3000); + try + { + QuartzTestObject exportService = (QuartzTestObject) ctx.GetObject("exportService"); + QuartzTestObject importService = (QuartzTestObject) ctx.GetObject("importService"); + + Assert.AreEqual(0, exportService.ImportCount, "doImport called exportService"); + Assert.AreEqual(2, exportService.ExportCount, "doExport not called on exportService"); + Assert.AreEqual(2, importService.ImportCount, "doImport not called on importService"); + Assert.AreEqual(0, importService.ExportCount, "doExport called on importService"); + } + finally + { + ctx.Dispose(); + } + } + + [Test] + public void TestSchedulerRepositoryExposure() + { + XmlApplicationContext ctx = new XmlApplicationContext("schedulerRepositoryExposure.xml"); + Assert.AreSame(SchedulerRepository.Instance.Lookup("myScheduler"), ctx.GetObject("scheduler")); + ctx.Dispose(); + } + + + private class TestSchedulerListener : ISchedulerListener + { + public void JobScheduled(Trigger trigger) + { + } + + public void JobUnscheduled(String triggerName, String triggerGroup) + { + } + + public void TriggerFinalized(Trigger trigger) + { + } + + public void TriggersPaused(String triggerName, String triggerGroup) + { + } + + public void TriggersResumed(String triggerName, String triggerGroup) + { + } + + public void JobsPaused(String jobName, String jobGroup) + { + } + + public void JobsResumed(String jobName, String jobGroup) + { + } + + public void SchedulerError(String msg, SchedulerException cause) + { + } + + public void SchedulerShutdown() + { + } + } + + + private class TestJobListener : IJobListener + { + public string Name + { + get { return null; } + } + + public void JobToBeExecuted(JobExecutionContext context) + { + } + + public void JobExecutionVetoed(JobExecutionContext context) + { + } + + public void JobWasExecuted(JobExecutionContext context, JobExecutionException jobException) + { + } + } + + + private class TestTriggerListener : ITriggerListener + { + public string Name + { + get { return null; } + } + + public void TriggerFired(Trigger trigger, JobExecutionContext context) + { + } + + public bool VetoJobExecution(Trigger trigger, JobExecutionContext context) + { + return false; + } + + public void TriggerMisfired(Trigger trigger) + { + } + + public void TriggerComplete(Trigger trigger, JobExecutionContext context, + SchedulerInstruction triggerInstructionCode) + { + } + } + + + public class CountingTaskExecutor : ITaskExecutor + { + internal int count; + + public void Execute(ThreadStart task) + { + count++; + task.Invoke(); + } + } + + + public class DummyJobObject : QuartzJobObject + { + internal static int param; + internal static int count; + + public void SetParam(int value) + { + if (param > 0) + { + throw new NotSupportedException("Param already set"); + } + param = value; + } + + protected override void ExecuteInternal(JobExecutionContext jobExecutionContext) + { + count++; + } + } + + + public class DummyRunnable : IThreadRunnable + { + internal static int param; + + internal static int count; + + public void SetParam(int value) + { + if (param > 0) + { + throw new NotSupportedException("Param already set"); + } + param = value; + } + + public void Run() + { + count++; + } + } + } + + public class DummyJob : IJob + { + internal static int param; + + internal static int count; + + public void SetParam(int value) + { + if (param > 0) + { + throw new NotSupportedException("Param already set"); + } + param = value; + } + + public void Execute(JobExecutionContext jobExecutionContext) + { + count++; + } + } + + public class TestSchedulerFactoryObject : SchedulerFactoryObject + { + private readonly IScheduler sched; + + public TestSchedulerFactoryObject(IScheduler sched) + { + this.sched = sched; + } + + protected override IScheduler CreateScheduler(ISchedulerFactory schedulerFactory, String schedulerName) + { + return sched; + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/QuartzTestObject.cs b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/QuartzTestObject.cs new file mode 100644 index 00000000..5c6161ad --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/QuartzTestObject.cs @@ -0,0 +1,31 @@ +namespace Spring.Scheduling.Quartz +{ + /// + /// + /// Rob Harrop + public class QuartzTestObject + { + private int exportCount; + private int importCount; + + public void DoImport() + { + ++importCount; + } + + public void DoExport() + { + ++exportCount; + } + + public int ImportCount + { + get { return importCount; } + } + + public int ExportCount + { + get { return exportCount; } + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs index 8775c8ee..327b3e97 100644 --- a/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs +++ b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs @@ -50,6 +50,11 @@ namespace Spring.Scheduling.Quartz { factory = new SchedulerFactoryObject(); TestSchedulerFactory.Mockery.BackToRecordAll(); + + Expect + .Call(TestSchedulerFactory.MockScheduler.SchedulerName) + .Return("scheduler") + .Repeat.Any(); } [Test] diff --git a/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/TestMethodInvokingTask.cs b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/TestMethodInvokingTask.cs new file mode 100644 index 00000000..c41551c0 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/TestMethodInvokingTask.cs @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Threading; + +namespace Spring.Scheduling.Quartz +{ + + /// + /// + /// Juergen Hoeller + public class TestMethodInvokingTask + { + public int counter; + private readonly object lockObject = new object(); + + public void DoSomething() + { + counter++; + } + + public void DoWait() + { + counter++; + // wait until stop is called + lock (lockObject) + { + try + { + Monitor.Wait(lockObject); + } + catch (ThreadInterruptedException) + { + // fall through + } + } + } + + public void Stop() + { + lock (lockObject) + { + Monitor.Pulse(lockObject); + } + } + + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/TriggerObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/TriggerObjectTest.cs index 1c47ae28..873e9e2d 100644 --- a/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/TriggerObjectTest.cs +++ b/test/Spring/Spring.Scheduling.Quartz.Tests/Scheduling/Quartz/TriggerObjectTest.cs @@ -20,6 +20,8 @@ using NUnit.Framework; using Quartz; +using Spring.Objects.Factory; + namespace Spring.Scheduling.Quartz { /// @@ -40,6 +42,8 @@ namespace Spring.Scheduling.Quartz [Test] public virtual void TestAfterPropertiesSet_Defaults() { + ((IInitializingObject) trigger).AfterPropertiesSet(); + Assert.AreEqual(TRIGGER_NAME, trigger.Name, "trigger name mismatch"); Assert.AreEqual(SchedulerConstants.DefaultGroup, trigger.Group, "trigger group name mismatch"); AssertDateTimesEqualityWithAllowedDelta(DateTime.UtcNow, trigger.StartTimeUtc, 1000); @@ -50,6 +54,8 @@ namespace Spring.Scheduling.Quartz [Test] public virtual void TestAfterPropertiesSet_ValuesGiven() { + ((IInitializingObject)trigger).AfterPropertiesSet(); + const string NAME = "newName"; const string GROUP = "newGroup"; DateTime START_TIME = new DateTime(10000000); @@ -65,6 +71,8 @@ namespace Spring.Scheduling.Quartz [Test] public virtual void TestAfterPropertiesSet_JobDetailGiven() { + ((IInitializingObject)trigger).AfterPropertiesSet(); + const string jobName = "jobName"; const string jobGroup = "jobGroup"; Assert.AreEqual(jobName, trigger.JobName, "trigger job name was not from job detail"); @@ -74,7 +82,9 @@ namespace Spring.Scheduling.Quartz [Test] public virtual void TestTriggerListenerNames_Valis() { - string[] LISTENER_NAMES = new string[] {"Foo", "Bar", "Baz"}; + ((IInitializingObject)trigger).AfterPropertiesSet(); + + string[] LISTENER_NAMES = new string[] { "Foo", "Bar", "Baz" }; trigger.TriggerListenerNames = LISTENER_NAMES; CollectionAssert.AreEqual(LISTENER_NAMES, trigger.TriggerListenerNames, "Trigger listeners were not equal"); } diff --git a/test/Spring/Spring.Scheduling.Quartz.Tests/Spring.Scheduling.Quartz.Tests.2005.csproj b/test/Spring/Spring.Scheduling.Quartz.Tests/Spring.Scheduling.Quartz.Tests.2005.csproj index c20f1273..34a3e208 100644 --- a/test/Spring/Spring.Scheduling.Quartz.Tests/Spring.Scheduling.Quartz.Tests.2005.csproj +++ b/test/Spring/Spring.Scheduling.Quartz.Tests/Spring.Scheduling.Quartz.Tests.2005.csproj @@ -58,11 +58,18 @@ {E823D54C-CE82-4868-929F-5F95A999F61E} Spring.Scheduling.Quartz.2005 + + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904} + Spring.Core.Tests.2005 + + + + @@ -76,6 +83,23 @@ PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + +