From 416d934714a01073c3e94fd450be7ef3a1b17ee1 Mon Sep 17 00:00:00 2001 From: Steve Bohlen Date: Fri, 11 May 2012 09:05:37 -0400 Subject: [PATCH] removing refs to linking of files from Quartz1x projects --- .../JobMethodInvocationFailedException.cs | 46 + .../Scheduling/Quartz/AdaptableJobFactory.cs | 144 ++ .../Scheduling/Quartz/DelegatingJob.cs | 77 + .../Quartz/IJobDetailAwareTrigger.cs | 52 + .../Quartz/ISchedulerContextAware.cs | 41 + .../Quartz/ISchedulingTaskExecutor.cs | 32 + .../Scheduling/Quartz/ITaskExecutor.cs | 31 + .../Quartz/LocalDataSourceJobStore.cs | 102 ++ .../Quartz/LocalTaskExecutorThreadPool.cs | 160 ++ .../Scheduling/Quartz/MethodInvokingJob.cs | 96 ++ .../Quartz/MethodInvokingRunnable.cs | 139 ++ .../Scheduling/Quartz/QuartzJobObject.cs | 90 ++ .../Quartz/SchedulerAccessorObject.cs | 154 ++ .../Quartz/SchedulerFactoryObject.cs | 871 +++++++++++ .../Scheduling/Quartz/SchedulingException.cs | 27 + .../Quartz/SimpleThreadPoolTaskExecutor.cs | 127 ++ .../Quartz/SpringDbProviderAdapter.cs | 291 ++++ .../Quartz/SpringObjectJobFactory.cs | 120 ++ .../Quartz/TaskRejectedException.cs | 11 + .../Spring.Scheduling.Quartz20.2010.csproj | 76 +- .../QuartzCompilerOptionsTests.cs | 43 + .../Quartz/AdaptableJobFactoryTest.cs | 106 ++ .../Quartz/CronTriggerObjectTest.cs | 118 ++ .../Scheduling/Quartz/JobDetailObjectTest.cs | 165 ++ ...ethodInvokingJobDetailFactoryObjectTest.cs | 95 ++ .../Quartz/MethodInvokingJobTest.cs | 213 +++ .../Scheduling/Quartz/QuartzSupportTests.cs | 1366 +++++++++++++++++ .../Scheduling/Quartz/QuartzTestObject.cs | 45 + .../Quartz/SchedulerFactoryObjectTest.cs | 386 +++++ .../Quartz/SimpleTriggerObjectTest.cs | 134 ++ .../Quartz/SpringObjectJobFactoryTest.cs | 147 ++ .../Quartz/TestMethodInvokingTask.cs | 74 + .../Scheduling/Quartz/TestUtil.cs | 60 + .../Scheduling/Quartz/TriggerObjectTest.cs | 159 ++ ...ring.Scheduling.Quartz20.Tests.2010.csproj | 61 +- 35 files changed, 5758 insertions(+), 101 deletions(-) create mode 100644 src/Spring/Spring.Scheduling.Quartz20/JobMethodInvocationFailedException.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/AdaptableJobFactory.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/DelegatingJob.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/IJobDetailAwareTrigger.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulerContextAware.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulingTaskExecutor.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ITaskExecutor.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalDataSourceJobStore.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalTaskExecutorThreadPool.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingJob.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingRunnable.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/QuartzJobObject.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulerAccessorObject.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulerFactoryObject.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulingException.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SimpleThreadPoolTaskExecutor.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringDbProviderAdapter.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringObjectJobFactory.cs create mode 100644 src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/TaskRejectedException.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/QuartzCompilerOptionsTests.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/AdaptableJobFactoryTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/CronTriggerObjectTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/JobDetailObjectTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobDetailFactoryObjectTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzSupportTests.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzTestObject.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SimpleTriggerObjectTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SpringObjectJobFactoryTest.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestMethodInvokingTask.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestUtil.cs create mode 100644 test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TriggerObjectTest.cs diff --git a/src/Spring/Spring.Scheduling.Quartz20/JobMethodInvocationFailedException.cs b/src/Spring/Spring.Scheduling.Quartz20/JobMethodInvocationFailedException.cs new file mode 100644 index 00000000..140da946 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/JobMethodInvocationFailedException.cs @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2010 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 Spring.Objects.Support; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Exception that wraps an exception thrown from a target method. + /// Propagated to the Quartz scheduler from a Job that reflectively invokes + /// an arbitrary target method. + /// + /// Juergen Hoeller + /// + public class JobMethodInvocationFailedException : Exception // TODO, in Java NestedRuntimeException + { + /// + /// Constructor for JobMethodInvocationFailedException. + /// + /// the MethodInvoker used for reflective invocation + /// the root cause (as thrown from the target method) + public JobMethodInvocationFailedException(MethodInvoker methodInvoker, Exception cause) : + base("Invocation of method '" + methodInvoker.TargetMethod + + "' on target class [" + methodInvoker.TargetType + "] failed", cause) + { + + } + + } +} + diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/AdaptableJobFactory.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/AdaptableJobFactory.cs new file mode 100644 index 00000000..983efa40 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/AdaptableJobFactory.cs @@ -0,0 +1,144 @@ +/* +* Copyright 2002-2010 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.Threading; + +using Quartz; +using Quartz.Spi; +using Quartz.Util; + +#if QUARTZ_2_0 +using JobDetail = Quartz.IJobDetail; +#else +using JobDetail = Quartz.JobDetail; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// JobFactory implementation that supports + /// objects as well as standard Quartz instances. + /// + /// Juergen Hoeller + /// Marko Lahma (.NET) + /// + /// + public class AdaptableJobFactory : IJobFactory + { +#if QUARTZ_2_0 + /// + /// Called by the scheduler at the time of the trigger firing, in order to + /// produce a instance on which to call Execute. + /// + /// + /// It should be extremely rare for this method to throw an exception - + /// basically only the the case where there is no way at all to instantiate + /// and prepare the Job for execution. When the exception is thrown, the + /// Scheduler will move all triggers associated with the Job into the + /// state, which will require human + /// intervention (e.g. an application restart after fixing whatever + /// configuration problem led to the issue wih instantiating the Job. + /// + /// The TriggerFiredBundle from which the + /// and other info relating to the trigger firing can be obtained. + /// The scheduler instance. + /// the newly instantiated Job + /// SchedulerException if there is a problem instantiating the Job. + public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) +#else + /// + /// Called by the scheduler at the time of the trigger firing, in order to + /// produce a instance on which to call Execute. + /// + /// + /// It should be extremely rare for this method to throw an exception - + /// basically only the the case where there is no way at all to instantiate + /// and prepare the Job for execution. When the exception is thrown, the + /// Scheduler will move all triggers associated with the Job into the + /// state, which will require human + /// intervention (e.g. an application restart after fixing whatever + /// configuration problem led to the issue wih instantiating the Job. + /// + /// The TriggerFiredBundle from which the + /// and other info relating to the trigger firing can be obtained. + /// the newly instantiated Job + /// SchedulerException if there is a problem instantiating the Job. + public virtual IJob NewJob(TriggerFiredBundle bundle) +#endif + { + try + { + object jobObject = CreateJobInstance(bundle); + return AdaptJob(jobObject); + } + catch (Exception ex) + { + throw new SchedulerException("Job instantiation failed", ex); + } + } + + /// + /// Create an instance of the specified job class. + ///

+ /// Can be overridden to post-process the job instance. + ///

+ ///
+ /// + /// The TriggerFiredBundle from which the JobDetail + /// and other info relating to the trigger firing can be obtained. + /// + /// The job instance. + protected virtual object CreateJobInstance(TriggerFiredBundle bundle) + { +#if QUARTZ_2_0 + return ObjectUtils.InstantiateType(bundle.JobDetail.JobType); +#else + return ObjectUtils.InstantiateType(bundle.JobDetail.JobType); +#endif + } + + /// + /// Adapt the given job object to the Quartz Job interface. + /// + /// + /// The default implementation supports straight Quartz Jobs + /// as well as Runnables, which get wrapped in a DelegatingJob. + /// + /// + /// The original instance of the specified job class. + /// + /// The adapted Quartz Job instance. + /// + protected virtual IJob AdaptJob(object jobObject) + { + if (jobObject is IJob) + { + return (IJob)jobObject; + } + if (jobObject is ThreadStart) + { + return new DelegatingJob((ThreadStart)jobObject); + } + if (jobObject is IThreadRunnable) + { + return new DelegatingJob(((IThreadRunnable)jobObject).Run); + } + + string message = string.Format("Unable to execute job class [{0}]: only [IJob] and [ThreadStart] supported.", jobObject.GetType().FullName); + throw new ArgumentException(message); + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/DelegatingJob.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/DelegatingJob.cs new file mode 100644 index 00000000..544abcc0 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/DelegatingJob.cs @@ -0,0 +1,77 @@ +/* +* Copyright 2002-2010 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; + +using Quartz; + +using Spring.Util; + +#if QUARTZ_2_0 +using JobExecutionContext = Quartz.IJobExecutionContext; +#else +using JobExecutionContext = Quartz.JobExecutionContext; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Simple Quartz IJob adapter that delegates to a + /// given instance. + /// + /// + /// Typically used in combination with property injection on the + /// Runnable instance, receiving parameters from the Quartz JobDataMap + /// that way instead of via the JobExecutionContext. + /// + /// Juergen Hoeller + /// Marko Lahma (.NET) + /// + /// + public class DelegatingJob : IJob + { + private readonly ThreadStart delegateInstance; + + /// + /// Return the wrapped Runnable implementation. + /// + /// The delegate. + public virtual ThreadStart Delegate + { + get { return delegateInstance; } + } + + /// + /// Create a new DelegatingJob. + /// + /// + /// The Runnable implementation to delegate to. + /// + public DelegatingJob(ThreadStart delegateInstance) + { + AssertUtils.ArgumentNotNull(delegateInstance, "delegateInstance", "Delegate must not be null"); + this.delegateInstance = delegateInstance; + } + + + /// + /// Delegates execution to the underlying ThreadStart. + /// + public virtual void Execute(JobExecutionContext context) + { + delegateInstance.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/IJobDetailAwareTrigger.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/IJobDetailAwareTrigger.cs new file mode 100644 index 00000000..8882120b --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/IJobDetailAwareTrigger.cs @@ -0,0 +1,52 @@ +/* +* Copyright 2002-2010 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. +*/ + +#if QUARTZ_2_0 +using JobDetail = Quartz.IJobDetail; +#else +using JobDetail = Quartz.JobDetail; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Interface to be implemented by Quartz Triggers that are aware + /// of the JobDetail object that they are associated with. + /// + /// + ///

+ /// SchedulerFactoryObject will auto-detect Triggers that implement this + /// interface and register them for the respective JobDetail accordingly. + ///

+ /// + ///

+ /// The alternative is to configure a Trigger for a Job name and group: + /// This involves the need to register the JobDetail object separately + /// with SchedulerFactoryObject. + ///

+ ///
+ /// Juergen Hoeller + /// + /// + public interface IJobDetailAwareTrigger + { + /// + /// Return the JobDetail that this Trigger is associated with. + /// + /// The associated JobDetail, or null if none + JobDetail JobDetail { get; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulerContextAware.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulerContextAware.cs new file mode 100644 index 00000000..35c6422c --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulerContextAware.cs @@ -0,0 +1,41 @@ +/* +* Copyright 2002-2010 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 Quartz; +using Quartz.Spi; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Callback interface to be implemented by Spring-managed + /// Quartz artifacts that need access to the SchedulerContext + /// (without having natural access to it). + /// + /// + /// Currently only supported for custom JobFactory implementations + /// that are passed in via Spring's SchedulerFactoryObject. + /// + /// Juergen Hoeller + /// + /// + public interface ISchedulerContextAware + { + /// + /// Set the SchedulerContext of the current Quartz Scheduler. + /// + /// + SchedulerContext SchedulerContext { set; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulingTaskExecutor.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulingTaskExecutor.cs new file mode 100644 index 00000000..4cbd1c1d --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ISchedulingTaskExecutor.cs @@ -0,0 +1,32 @@ +/* +* Copyright 2002-2010 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. +*/ + +namespace Spring.Scheduling +{ + /// + /// + /// + public interface ISchedulingTaskExecutor : ITaskExecutor + { + /// + /// Gets a value indicating whether´this instance prefers short lived tasks. + /// + /// + /// true if prefers short lived tasks; otherwise, false. + /// + bool PrefersShortLivedTasks { get; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ITaskExecutor.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ITaskExecutor.cs new file mode 100644 index 00000000..93952687 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/ITaskExecutor.cs @@ -0,0 +1,31 @@ +/* +* Copyright 2002-2010 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 +{ + /// + /// + /// + public interface ITaskExecutor + { + /// + /// Executes this instance. + /// + void Execute(ThreadStart runnable); + } +} diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalDataSourceJobStore.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalDataSourceJobStore.cs new file mode 100644 index 00000000..bec9799e --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalDataSourceJobStore.cs @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2010 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 Quartz; +using Quartz.Impl.AdoJobStore; +using Quartz.Util; + +using Spring.Data.Support; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Subclass of Quartz's JobStoreCMT class that delegates to a Spring-managed + /// DataSource instead of using a Quartz-managed connection pool. This JobStore + /// will be used if SchedulerFactoryObject's "dbProvider" property is set. + /// + /// + ///

Operations performed by this JobStore will properly participate in any + /// kind of Spring-managed transaction, as it uses Spring's DataSourceUtils + /// connection handling methods that are aware of a current transaction.

+ /// + ///

Note that all Quartz Scheduler operations that affect the persistent + /// job store should usually be performed within active transactions, + /// as they assume to get proper locks etc.

+ ///
+ /// Juergen Hoeller + /// Marko Lahma (.NET) + /// + /// + public class LocalDataSourceJobStore : JobStoreCMT + { + /// + /// Name used for the transactional ConnectionProvider for Quartz. + /// This provider will delegate to the local Spring-managed DataSource. + /// + /// + /// + public const string TX_DATA_SOURCE_PREFIX = "springTxDataSource."; + + private Data.Common.IDbProvider dbProvider; + + /// + /// Gets or sets the name of the instance. + /// + /// The name of the instance. + public override string InstanceName + { + get { return base.InstanceName; } + set + { + // use to catch property setting + base.InstanceName = value; + DataSource = TX_DATA_SOURCE_PREFIX + InstanceName; + // Register transactional ConnectionProvider for Quartz. + // Absolutely needs thread-bound DataSource to initialize. + dbProvider = SchedulerFactoryObject.ConfigTimeDbProvider; + if (dbProvider == null) + { + throw new SchedulerConfigException( + "No db provider found for configuration - " + + "'DbProvider' property must be set on SchedulerFactoryObject"); + } + DBConnectionManager.Instance.AddConnectionProvider( + TX_DATA_SOURCE_PREFIX + InstanceName, new SpringDbProviderAdapter(dbProvider)); + + } + } + + /// + /// Gets the non managed TX connection. + /// + /// + protected override ConnectionAndTransactionHolder GetNonManagedTXConnection() + { + ConnectionTxPair pair = ConnectionUtils.DoGetConnection(dbProvider); + return new ConnectionAndTransactionHolder(pair.Connection, pair.Transaction); + } + + /// + /// Closes the connection. + /// + /// The connection and transaction holder. + protected override void CloseConnection(ConnectionAndTransactionHolder connectionAndTransactionHolder) + { + // Will work for transactional and non-transactional connections. + ConnectionUtils.DisposeConnection(connectionAndTransactionHolder.Connection, dbProvider); + } + } +} diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalTaskExecutorThreadPool.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalTaskExecutorThreadPool.cs new file mode 100644 index 00000000..8b1062af --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/LocalTaskExecutorThreadPool.cs @@ -0,0 +1,160 @@ +/* +* Copyright 2002-2010 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 Common.Logging; +using Quartz; +using Quartz.Spi; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Quartz ThreadPool adapter that delegates to a Spring-managed + /// TaskExecutor instance, specified on SchedulerFactoryObject. + /// + /// Juergen Hoeller + /// + public class LocalTaskExecutorThreadPool : IThreadPool + { + /// + /// Logger available to subclasses. + /// + private readonly ILog logger; + + private ITaskExecutor taskExecutor; + + /// + /// Initializes a new instance of the class. + /// + public LocalTaskExecutorThreadPool() + { + logger = LogManager.GetLogger(GetType()); + } + + /// + /// Logger instance. + /// + protected ILog Logger + { + get { return logger; } + } + + /// + /// Gets the size of the pool. + /// + /// The size of the pool. + public virtual int PoolSize + { + get { return - 1; } + } + + /// + /// Inform the of the Scheduler instance's Id, + /// prior to initialize being invoked. + /// + public string InstanceId + { + set { } + } + + /// + /// Inform the of the Scheduler instance's name, + /// prior to initialize being invoked. + /// + public string InstanceName + { + set { } + } + + /// + /// Called by the QuartzScheduler before the is + /// used, in order to give the it a chance to Initialize. + /// + public virtual void Initialize() + { + // Absolutely needs thread-bound TaskExecutor to Initialize. + taskExecutor = SchedulerFactoryObject.ConfigTimeTaskExecutor; + if (taskExecutor == null) + { + throw new SchedulerConfigException("No local TaskExecutor found for configuration - " + + "'taskExecutor' property must be set on SchedulerFactoryObject"); + } + } + + /// + /// Called by the QuartzScheduler to inform the + /// that it should free up all of it's resources because the scheduler is + /// shutting down. + /// + /// + public virtual void Shutdown(bool waitForJobsToComplete) + { + } + + + /// + /// Execute the given in the next + /// available . + /// + /// + /// + /// + /// The implementation of this interface should not throw exceptions unless + /// there is a serious problem (i.e. a serious misconfiguration). If there + /// are no available threads, rather it should either queue the Runnable, or + /// block until a thread is available, depending on the desired strategy. + /// + public virtual bool RunInThread(IThreadRunnable runnable) + { + if (runnable == null) + { + return false; + } + try + { + taskExecutor.Execute(runnable.Run); + return true; + } + catch (TaskRejectedException ex) + { + logger.Error("Task has been rejected by TaskExecutor", ex); + return false; + } + } + + /// + /// Determines the number of threads that are currently available in in + /// the pool. Useful for determining the number of times + /// can be called before returning + /// false. + /// + /// + /// the number of currently available threads + /// + /// + /// The implementation of this method should block until there is at + /// least one available thread. + /// + public virtual int BlockForAvailableThreads() + { + // The present implementation always returns 1, making Quartz (1.6) + // always schedule any tasks that it feels like scheduling. + // This could be made smarter for specific TaskExecutors, + // for example calling getMaximumPoolSize() - getActiveCount() + // on a java.util.concurrent.ThreadPoolExecutor. + return 1; + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingJob.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingJob.cs new file mode 100644 index 00000000..a89343a7 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingJob.cs @@ -0,0 +1,96 @@ +/* +* Copyright 2002-2010 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.Reflection; + +using Common.Logging; + +using Quartz; + +using Spring.Objects.Support; + +#if QUARTZ_2_0 +using JobExecutionContext = Quartz.IJobExecutionContext; +#else +using JobExecutionContext = Quartz.JobExecutionContext; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Quartz Job implementation that invokes a specified method. + /// Automatically applied by MethodInvokingJobDetailFactoryObject. + /// + public class MethodInvokingJob : QuartzJobObject + { + private static readonly ILog logger = LogManager.GetLogger(typeof(MethodInvokingJob)); + private MethodInvoker methodInvoker; + private string errorMessage; + + /// + /// Set the MethodInvoker to use. + /// + public virtual MethodInvoker MethodInvoker + { + set + { + if (value == null) + { + throw new ArgumentException("Method invoker cannot be null", "value"); + } + methodInvoker = value; + errorMessage = + string.Format("Could not invoke method '{0}' on target object [{1}]", methodInvoker.TargetMethod, + methodInvoker.TargetObject); + } + } + + + /// + /// Invoke the method via the MethodInvoker. + /// + /// + protected override void ExecuteInternal(JobExecutionContext context) + { + if (methodInvoker == null) + { + throw new JobExecutionException("Could not execute job when method invoker is null"); + } + try + { + context.Result = methodInvoker.Invoke(); + } + catch (TargetInvocationException ex) + { + logger.Error(errorMessage, ex.GetBaseException()); + if (ex.GetBaseException() is JobExecutionException) + { + // -> JobExecutionException, to be logged at info level by Quartz + throw ex.GetBaseException(); + } + // -> "unhandled exception", to be logged at error level by Quartz + throw new JobMethodInvocationFailedException(methodInvoker, ex.GetBaseException()); + } + catch (Exception ex) + { + // -> "unhandled exception", to be logged at error level by Quartz + throw new JobMethodInvocationFailedException(methodInvoker, ex.GetBaseException()); + } + } + } + +} diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingRunnable.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingRunnable.cs new file mode 100644 index 00000000..e60635cb --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/MethodInvokingRunnable.cs @@ -0,0 +1,139 @@ +/* +* Copyright 2002-2010 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.Reflection; + +using Common.Logging; + +using Quartz; + +using Spring.Objects.Factory; +using Spring.Objects.Support; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Adapter that implements the Runnable interface as a configurable + /// method invocation based on Spring's MethodInvoker. + /// + /// + ///

+ /// Derives from ArgumentConvertingMethodInvoker, inheriting common + /// configuration properties from MethodInvoker. + ///

+ /// + ///

+ /// Useful to generically encapsulate a method invocation as timer task for + /// java.util.Timer, in combination with a DelegatingTimerTask adapter. + /// Can also be used with JDK 1.5's java.util.concurrent.Executor + /// abstraction, which works with plain Runnables. + ///

+ ///

+ /// Extended by Spring's MethodInvokingTimerTaskFactoryObject adapter + /// for TimerTask. Note that you can populate a + /// ScheduledTimerTask object with a plain MethodInvokingRunnable instance + /// as well, which will automatically get wrapped with a DelegatingTimerTask. + ///

+ ///
+ /// Juergen Hoeller + /// + /// + public class MethodInvokingRunnable : ArgumentConvertingMethodInvoker, IInitializingObject, IThreadRunnable + { + /// + /// Logger instance shared by this instance and its sub-class instances. + /// + private readonly ILog logger; + + /// + /// Initializes a new instance of the class. + /// + public MethodInvokingRunnable() + { + logger = LogManager.GetLogger(GetType()); + } + + /// + /// Logger instance. + /// + protected ILog Logger + { + get { return logger; } + } + + /// + /// Gets the invocation failure message. + /// + /// The invocation failure message. + protected virtual string InvocationFailureMessage + { + get { return string.Format("Invocation of method '{0}' on target object [{1}] failed", TargetMethod, TargetObject); } + } + + /// + /// Invoked by an + /// after it has injected all of an object's dependencies. + /// + /// + ///

+ /// This method allows the object instance to perform the kind of + /// initialization only possible when all of it's dependencies have + /// been injected (set), and to throw an appropriate exception in the + /// event of misconfiguration. + ///

+ ///

+ /// Please do consult the class level documentation for the + /// interface for a + /// description of exactly when this method is invoked. In + /// particular, it is worth noting that the + /// + /// and + /// callbacks will have been invoked prior to this method being + /// called. + ///

+ ///
+ /// + /// In the event of misconfiguration (such as the failure to set a + /// required property) or if initialization fails. + /// + public virtual void AfterPropertiesSet() + { + Prepare(); + } + + /// + /// This method has to be implemented in order that starting of the thread causes the object's + /// run method to be called in that separately executing thread. + /// + public virtual void Run() + { + try + { + Invoke(); + } + catch (TargetInvocationException ex) + { + logger.Error(InvocationFailureMessage, ex); + // Do not throw exception, else the main loop of the Timer will stop! + } + catch (Exception ex) + { + logger.Error(InvocationFailureMessage, ex); + // Do not throw exception, else the main loop of the Timer will stop! + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/QuartzJobObject.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/QuartzJobObject.cs new file mode 100644 index 00000000..98391757 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/QuartzJobObject.cs @@ -0,0 +1,90 @@ +/* +* Copyright 2002-2010 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 Quartz; + +using Spring.Objects; + +#if QUARTZ_2_0 +using JobExecutionContext = Quartz.IJobExecutionContext; +#else +using JobExecutionContext = Quartz.JobExecutionContext; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Simple implementation of the Quartz Job interface, applying the + /// passed-in JobDataMap and also the SchedulerContext as object property + /// values. This is appropriate because a new Job instance will be created + /// for each execution. JobDataMap entries will override SchedulerContext + /// entries with the same keys. + /// + /// + ///

+ /// For example, let's assume that the JobDataMap contains a key + /// "myParam" with value "5": The Job implementation can then expose + /// a object property "myParam" of type int to receive such a value, + /// i.e. a method "setMyParam(int)". This will also work for complex + /// types like business objects etc. + ///

+ /// + ///

+ /// Note: The QuartzJobObject class itself only implements the standard + /// Quartz IJob interface. Let your subclass explicitly implement the + /// Quartz IStatefulJob interface to mark your concrete job object as stateful. + ///

+ ///
+ /// Juergen Hoeller + /// + /// + /// + /// + /// + /// + /// + public abstract class QuartzJobObject : IJob + { + /// + /// This implementation applies the passed-in job data map as object property + /// values, and delegates to ExecuteInternal afterwards. + /// + /// + public void Execute(JobExecutionContext context) + { + try + { + ObjectWrapper bw = new ObjectWrapper(this); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.AddAll(context.Scheduler.Context); + pvs.AddAll(context.MergedJobDataMap); + bw.SetPropertyValues(pvs, true); + } + catch (SchedulerException ex) + { + throw new JobExecutionException(ex); + } + ExecuteInternal(context); + } + + /// + /// Execute the actual job. The job data map will already have been + /// applied as object property values by execute. The contract is + /// exactly the same as for the standard Quartz execute method. + /// + /// + protected abstract void ExecuteInternal(JobExecutionContext context); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulerAccessorObject.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulerAccessorObject.cs new file mode 100644 index 00000000..477e06c4 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulerAccessorObject.cs @@ -0,0 +1,154 @@ +/* + * Copyright 2002-2010 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; } + } + + /// + /// Template method that determines the Scheduler to operate on. + /// + /// + protected override IScheduler GetScheduler() + { + return scheduler; + } + + /// + /// Return the Quartz Scheduler instance that this accessor operates on. + /// + public IObjectFactory ObjectFactory + { + set { objectFactory = value; } + } + + + /// + /// Invoked by an + /// after it has injected all of an object's dependencies. + /// + /// + ///

+ /// This method allows the object instance to perform the kind of + /// initialization only possible when all of it's dependencies have + /// been injected (set), and to throw an appropriate exception in the + /// event of misconfiguration. + ///

+ ///

+ /// Please do consult the class level documentation for the + /// interface for a + /// description of exactly when this method is invoked. In + /// particular, it is worth noting that the + /// + /// and + /// callbacks will have been invoked prior to this method being + /// called. + ///

+ ///
+ /// + /// In the event of misconfiguration (such as the failure to set a + /// required property) or if initialization fails. + /// + public void AfterPropertiesSet() + { + if (scheduler == null) + { + if (schedulerName != null) + { + scheduler = FindScheduler(schedulerName); + } + else + { + throw new InvalidOperationException("No Scheduler specified"); + } + } + RegisterListeners(); + RegisterJobsAndTriggers(); + } + + /// + /// Finds the scheduler. + /// + /// Name of the scheduler. + /// + 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.Quartz20/Scheduling/Quartz/SchedulerFactoryObject.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulerFactoryObject.cs new file mode 100644 index 00000000..96742dbe --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulerFactoryObject.cs @@ -0,0 +1,871 @@ +/* +* Copyright 2002-2010 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.Collections.Specialized; +using System.IO; +#if QUARTZ_2_0 +using System.Linq; +#endif + +using Quartz; +using Quartz.Impl; +using Quartz.Simpl; +using Quartz.Spi; +using Quartz.Util; + +using Spring.Context; +using Spring.Context.Events; +using Spring.Core.IO; +using Spring.Data.Common; +using Spring.Objects.Factory; + +namespace Spring.Scheduling.Quartz +{ + /// + /// FactoryObject that sets up a Quartz Scheduler and exposes it for object references. + /// + /// + ///

+ /// Allows registration of JobDetails, Calendars and Triggers, automatically + /// starting the scheduler on initialization and shutting it down on destruction. + /// In scenarios that just require static registration of jobs at startup, there + /// is no need to access the Scheduler instance itself in application code. + ///

+ /// + ///

+ /// For dynamic registration of jobs at runtime, use a object reference to + /// this SchedulerFactoryObject to get direct access to the Quartz Scheduler + /// (). This allows you to create new jobs + /// and triggers, and also to control and monitor the entire Scheduler. + ///

+ /// + ///

+ /// Note that Quartz instantiates a new Job for each execution, in + /// contrast to Timer which uses a TimerTask instance that is shared + /// between repeated executions. Just JobDetail descriptors are shared. + ///

+ /// + ///

+ /// When using persistent jobs, it is strongly recommended to perform all + /// operations on the Scheduler within Spring-managed transactions. + /// Else, database locking will not properly work and might even break. + ///

+ ///

+ /// The preferred way to achieve transactional execution is to demarcate + /// declarative transactions at the business facade level, which will + /// automatically apply to Scheduler operations performed within those scopes. + /// Alternatively, define a TransactionProxyFactoryObject for the Scheduler itself. + ///

+ ///
+ /// Juergen Hoeller + /// Marko Lahma (.NET) + /// + /// + /// + public class SchedulerFactoryObject : SchedulerAccessor, IFactoryObject, IObjectNameAware, + IApplicationContextAware, IApplicationEventListener, IInitializingObject, IDisposable + { + /// + /// Default thread count to be set to thread pool. + /// + public const int DEFAULT_THREAD_COUNT = 10; + + /// + /// Property name for thread count in thread pool. + /// + public const string PROP_THREAD_COUNT = "quartz.threadPool.threadCount"; + + [ThreadStatic] + private static IDbProvider configTimeDbProvider; + + [ThreadStatic] + private static ITaskExecutor configTimeTaskExecutor; + + /// + /// Return the IDbProvider for the currently configured Quartz Scheduler, + /// to be used by LocalDataSourceJobStore. + /// + /// + /// This instance will be set before initialization of the corresponding + /// Scheduler, and reset immediately afterwards. It is thus only available + /// during configuration. + /// + /// + /// + public static IDbProvider ConfigTimeDbProvider + { + get { return configTimeDbProvider; } + } + + /// + /// Return the TaskExecutor for the currently configured Quartz Scheduler, + /// to be used by LocalTaskExecutorThreadPool. + /// + /// + /// This instance will be set before initialization of the corresponding + /// Scheduler, and reset immediately afterwards. It is thus only available + /// during configuration. + /// + public static ITaskExecutor ConfigTimeTaskExecutor + { + get { return configTimeTaskExecutor; } + } + + private IApplicationContext applicationContext; + private string applicationContextSchedulerContextKey; + private bool autoStartup = true; + private IResource configLocation; + private IJobFactory jobFactory; + private bool jobFactorySet; + private IDictionary quartzProperties; + private IScheduler scheduler; + private IDictionary schedulerContextMap; + private Type schedulerFactoryType; + private string schedulerName; + private TimeSpan startupDelay = TimeSpan.Zero; + private ITaskExecutor taskExecutor; + private bool exposeSchedulerInRepository; + private bool waitForJobsToCompleteOnShutdown; + private IDbProvider dbProvider; + + /// + /// Initializes a new instance of the class. + /// + public SchedulerFactoryObject() + { + schedulerFactoryType = typeof (StdSchedulerFactory); + } + + + /// + /// Set the Quartz SchedulerFactory implementation to use. + /// + /// + /// Default is StdSchedulerFactory, reading in the standard + /// quartz.properties from Quartz' dll. To use custom Quartz + /// properties, specify "configLocation" or "quartzProperties". + /// + /// The scheduler factory class. + /// + /// + /// + public virtual Type SchedulerFactoryType + { + set + { + if (value == null || !typeof (ISchedulerFactory).IsAssignableFrom(value)) + { + throw new ArgumentException("schedulerFactoryType must implement [Quartz.ISchedulerFactory]"); + } + schedulerFactoryType = value; + } + } + + /// + /// Set the name of the Scheduler to fetch from the SchedulerFactory. + /// If not specified, the default Scheduler will be used. + /// + /// The name of the scheduler. + /// + /// + public virtual string SchedulerName + { + set { schedulerName = value; } + } + + /// + /// Set the location of the Quartz properties config file, for example + /// as assembly resource "assembly:quartz.properties". + /// + /// + /// Note: Can be omitted when all necessary properties are specified + /// locally via this object, or when relying on Quartz' default configuration. + /// + /// + public virtual IResource ConfigLocation + { + set { configLocation = value; } + } + + /// + /// Set Quartz properties, like "quartz.threadPool.type". + /// + /// + /// Can be used to override values in a Quartz properties config file, + /// or to specify all necessary properties locally. + /// + /// + public virtual IDictionary QuartzProperties + { + set { quartzProperties = value; } + } + + /// + /// Set the Spring TaskExecutor to use as Quartz backend. + /// Exposed as thread pool through the Quartz SPI. + /// + /// + /// By default, a Quartz SimpleThreadPool will be used, configured through + /// the corresponding Quartz properties. + /// + /// The task executor. + /// + /// + public virtual ITaskExecutor TaskExecutor + { + set { taskExecutor = value; } + } + + /// + /// Register objects in the Scheduler context via a given Map. + /// These objects will be available to any Job that runs in this Scheduler. + /// + /// + /// Note: When using persistent Jobs whose JobDetail will be kept in the + /// database, do not put Spring-managed object or an ApplicationContext + /// reference into the JobDataMap but rather into the SchedulerContext. + /// + /// + /// Map with string keys and any objects as + /// values (for example Spring-managed objects) + /// + /// + public virtual IDictionary SchedulerContextAsMap + { + set { schedulerContextMap = value; } + } + + /// + /// Set the key of an IApplicationContext reference to expose in the + /// SchedulerContext, for example "applicationContext". Default is none. + /// Only applicable when running in a Spring ApplicationContext. + /// + /// + ///

+ /// Note: When using persistent Jobs whose JobDetail will be kept in the + /// database, do not put an IApplicationContext reference into the JobDataMap + /// but rather into the SchedulerContext. + ///

+ /// + ///

+ /// In case of a QuartzJobObject, the reference will be applied to the Job + /// instance as object property. An "applicationContext" attribute will + /// correspond to a "setApplicationContext" method in that scenario. + ///

+ /// + ///

+ /// Note that ObjectFactory callback interfaces like IApplicationContextAware + /// are not automatically applied to Quartz Job instances, because Quartz + /// itself is reponsible for the lifecycle of its Jobs. + ///

+ ///
+ /// The application context scheduler context key. + /// + public virtual string ApplicationContextSchedulerContextKey + { + set { applicationContextSchedulerContextKey = value; } + } + + /// + /// Set the Quartz JobFactory to use for this Scheduler. + /// + /// + ///

+ /// Default is Spring's , which supports + /// standard Quartz instances. Note that this default only applies + /// to a local Scheduler, not to a RemoteScheduler (where setting + /// a custom JobFactory is not supported by Quartz). + ///

+ ///

+ /// Specify an instance of Spring's here + /// (typically as an inner object definition) to automatically populate a job's + /// object properties from the specified job data map and scheduler context. + ///

+ ///
+ /// + /// + public virtual IJobFactory JobFactory + { + set + { + jobFactory = value; + jobFactorySet = true; + } + } + + /// + /// Set whether to expose the Spring-managed instance in the + /// Quartz . 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. + /// + public virtual bool ExposeSchedulerInRepository + { + set { exposeSchedulerInRepository = value; } + } + + /// + /// Set whether to automatically start the scheduler after initialization. + /// Default is "true"; set this to "false" to allow for manual startup. + /// + public virtual bool AutoStartup + { + set { autoStartup = value; } + } + + /// + /// Set the time span to wait after initialization before + /// starting the scheduler asynchronously. Default is 0, meaning + /// immediate synchronous startup on initialization of this object. + /// + /// + /// Setting this to 10 or 20 seconds makes sense if no jobs + /// should be run before the entire application has started up. + /// + public virtual TimeSpan StartupDelay + { + set { startupDelay = value; } + } + + /// + /// Set whether to wait for running jobs to complete on Shutdown. + /// Default is "false". + /// + /// + /// true if [wait for jobs to complete on Shutdown]; otherwise, false. + /// + /// + public virtual bool WaitForJobsToCompleteOnShutdown + { + set { waitForJobsToCompleteOnShutdown = value; } + } + + /// + /// Set the default DbProvider to be used by the Scheduler. If set, + /// this will override corresponding settings in Quartz properties. + /// + /// + ///

+ /// Note: If this is set, the Quartz settings should not define + /// a job store "dataSource" to avoid meaningless double configuration. + ///

+ ///

+ /// A Spring-specific subclass of Quartz' JobStoreSupport will be used. + /// It is therefore strongly recommended to perform all operations on + /// the Scheduler within Spring-managed transactions. + /// Else, database locking will not properly work and might even break + /// (e.g. if trying to obtain a lock on Oracle without a transaction). + ///

+ ///
+ /// + /// + public IDbProvider DbProvider + { + set { dbProvider = value; } + } + + /// + /// Set the name of the object in the object factory that created this object. + /// + /// 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. + /// + /// true if running; otherwise, false. + public virtual bool Running + { + get + { + if (scheduler != null) + { + try + { + return !scheduler.InStandbyMode; + } + catch (SchedulerException) + { + return false; + } + } + return false; + } + } + + #region IApplicationContextAware Members + + /// + /// Sets the that this + /// object runs in. + /// + /// + /// + ///

+ /// Normally this call will be used to initialize the object. + ///

+ ///

+ /// Invoked after population of normal object properties but before an + /// init callback such as + /// 's + /// + /// or a custom init-method. Invoked after the setting of any + /// 's + /// + /// property. + ///

+ ///
+ /// + /// In the case of application context initialization errors. + /// + /// + /// If thrown by any application context methods. + /// + /// + public virtual IApplicationContext ApplicationContext + { + set { applicationContext = value; } + } + + #endregion + + #region IDisposable Members + + /// + /// Shut down the Quartz scheduler on object factory Shutdown, + /// stopping all scheduled jobs. + /// + public virtual void Dispose() + { + logger.Info("Shutting down Quartz Scheduler"); + scheduler.Shutdown(waitForJobsToCompleteOnShutdown); + } + + #endregion + + /// + /// Template method that determines the Scheduler to operate on. + /// To be implemented by subclasses. + /// + /// + protected override IScheduler GetScheduler() + { + return scheduler; + } + + #region IFactoryObject Members + + /// + /// Return an instance (possibly shared or independent) of the object + /// managed by this factory. + /// + /// + /// An instance (possibly shared or independent) of the object managed by + /// this factory. + /// + /// + /// + /// If this method is being called in the context of an enclosing IoC container and + /// returns , the IoC container will consider this factory + /// object as not being fully initialized and throw a corresponding (and most + /// probably fatal) exception. + /// + /// + public virtual object GetObject() + { + return scheduler; + } + + /// + /// Return the of object that this + /// creates, or + /// if not known in advance. + /// + /// + public virtual Type ObjectType + { + get { return (scheduler != null) ? scheduler.GetType() : typeof (IScheduler); } + } + + /// + /// Is the object managed by this factory a singleton or a prototype? + /// + /// + public virtual bool IsSingleton + { + get { return true; } + } + + #endregion + + //--------------------------------------------------------------------- + // Implementation of IInitializingObject interface + //--------------------------------------------------------------------- + + #region IInitializingObject Members + + /// + /// Invoked by an + /// after it has injected all of an object's dependencies. + /// + /// + ///

+ /// This method allows the object instance to perform the kind of + /// initialization only possible when all of it's dependencies have + /// been injected (set), and to throw an appropriate exception in the + /// event of misconfiguration. + ///

+ ///

+ /// Please do consult the class level documentation for the + /// interface for a + /// description of exactly when this method is invoked. In + /// particular, it is worth noting that the + /// + /// and + /// callbacks will have been invoked prior to this method being + /// called. + ///

+ ///
+ /// + /// In the event of misconfiguration (such as the failure to set a + /// required property) or if initialization fails. + /// + public virtual void AfterPropertiesSet() + { + // Create SchedulerFactory instance. +#if QUARTZ_2_0 + ISchedulerFactory schedulerFactory = ObjectUtils.InstantiateType(schedulerFactoryType); +#else + ISchedulerFactory schedulerFactory = (ISchedulerFactory) ObjectUtils.InstantiateType(schedulerFactoryType); +#endif + + InitSchedulerFactory(schedulerFactory); + + if (taskExecutor != null) + { + // Make given TaskExecutor available for SchedulerFactory configuration. + configTimeTaskExecutor = taskExecutor; + } + if (dbProvider != null) + { + // Make given db provider available for SchedulerFactory configuration. + configTimeDbProvider = dbProvider; + } + + + try + { + // Get Scheduler instance from SchedulerFactory. + scheduler = CreateScheduler(schedulerFactory, schedulerName); + PopulateSchedulerContext(); + + if (!jobFactorySet && !(scheduler is RemoteScheduler)) + { + // Use AdaptableJobFactory as default for a local Scheduler, unless when + // explicitly given a null value through the "jobFactory" object property. + jobFactory = new AdaptableJobFactory(); + } + + if (jobFactory != null) + { + if (jobFactory is ISchedulerContextAware) + { + ((ISchedulerContextAware) jobFactory).SchedulerContext = scheduler.Context; + } + scheduler.JobFactory = jobFactory; + } + } + finally + { + if (taskExecutor != null) + { + configTimeTaskExecutor = null; + } + if (dbProvider != null) + { + configTimeDbProvider = null; + } + } + + RegisterListeners(); + RegisterJobsAndTriggers(); + } + + #endregion + + /// + /// Load and/or apply Quartz properties to the given SchedulerFactory. + /// + /// the SchedulerFactory to Initialize + private void InitSchedulerFactory(ISchedulerFactory schedulerFactory) + { + if (!(schedulerFactory is StdSchedulerFactory)) + { + if (configLocation != null || quartzProperties != null || schedulerName != null || + taskExecutor != null || dbProvider != null) + { + + throw new ArgumentException("StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory); + } + // 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); + } + + /// + /// Merges the properties into map. This effectively also + /// overwrites existing properties with same key in map. + /// + /// The properties to merge into given map. + /// The map to merge to. + protected virtual void MergePropertiesIntoMap(IDictionary properties, NameValueCollection map) + { + foreach (string key in properties.Keys) + { + map[key] = (string) properties[key]; + } + } + + + /// + /// Create the Scheduler instance for the given factory and scheduler name. + /// Called by afterPropertiesSet. + /// + /// + /// Default implementation invokes SchedulerFactory's GetScheduler + /// method. Can be overridden for custom Scheduler creation. + /// + /// the factory to create the Scheduler with + /// the name of the scheduler to create + /// the Scheduler instance + /// + /// + protected virtual IScheduler CreateScheduler(ISchedulerFactory schedulerFactory, string schedName) + { + 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; + } + } + + /// + /// Expose the specified context attributes and/or the current + /// IApplicationContext in the Quartz SchedulerContext. + /// + private void PopulateSchedulerContext() + { + // Put specified objects into Scheduler context. + if (schedulerContextMap != null) + { +#if QUARTZ_2_0 + var dictionary = schedulerContextMap.Cast().ToDictionary(entry => entry.Key.ToString(), entry => entry.Value); + scheduler.Context.PutAll(dictionary); +#else + scheduler.Context.PutAll(schedulerContextMap); +#endif + } + + // Register IApplicationContext in Scheduler context. + if (applicationContextSchedulerContextKey != null) + { + if (applicationContext == null) + { + throw new SystemException("SchedulerFactoryObject needs to be set up in an IApplicationContext " + + "to be able to handle an 'applicationContextSchedulerContextKey'"); + } + scheduler.Context.Put(applicationContextSchedulerContextKey, applicationContext); + } + } + + + /// + /// Start the Quartz Scheduler, respecting the "startDelay" setting. + /// + /// the Scheduler to start + /// the time span to wait before starting + /// the Scheduler asynchronously + protected virtual void StartScheduler(IScheduler sched, TimeSpan startDelay) + { + if (startDelay.TotalSeconds <= 0) + { + logger.Info("Starting Quartz Scheduler now"); + sched.Start(); + } + else + { + if (logger.IsInfoEnabled) + { + logger.Info( + string.Format("Will start Quartz Scheduler [{0}] in {1} seconds", sched.SchedulerName, + startDelay)); + } + sched.StartDelayed(startDelay); + } + } + + + //--------------------------------------------------------------------- + // Implementation of Lifecycle interface + //--------------------------------------------------------------------- + + /// + /// Starts this instance. + /// + public virtual void Start() + { + if (scheduler != null) + { + try + { + scheduler.Start(); + } + catch (SchedulerException ex) + { + throw new SchedulingException("Could not start Quartz Scheduler", ex); + } + } + } + + /// + /// Stops this instance. + /// + public virtual void Stop() + { + if (scheduler != null) + { + try + { + scheduler.Standby(); + } + catch (SchedulerException ex) + { + throw new SchedulingException("Could not stop Quartz Scheduler", ex); + } + } + } + + /// + /// Handles the application context's refresh event and starts the scheduler. + /// + public void HandleApplicationEvent(object sender, ApplicationEventArgs e) + { + // auto-start Scheduler if demanded + if (e is ContextRefreshedEventArgs && autoStartup) + { + try + { + StartScheduler(scheduler, startupDelay); + } + catch (SchedulerException ex) + { + throw new ObjectInitializationException("failed to auto-start scheduler", ex); + } + } + } + } +} diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulingException.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulingException.cs new file mode 100644 index 00000000..eb12e030 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SchedulingException.cs @@ -0,0 +1,27 @@ +using System; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Generic scheduling exception. + /// + public class SchedulingException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The message. + public SchedulingException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The original exception. + public SchedulingException(string message, Exception ex) : base(message, ex) + { + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SimpleThreadPoolTaskExecutor.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SimpleThreadPoolTaskExecutor.cs new file mode 100644 index 00000000..b1cd9f67 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SimpleThreadPoolTaskExecutor.cs @@ -0,0 +1,127 @@ +/* +* Copyright 2002-2010 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.Threading; + +using Quartz; +using Quartz.Simpl; +using Spring.Objects.Factory; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Subclass of Quartz's SimpleThreadPool that implements Spring's + /// TaskExecutor interface and listens to Spring lifecycle callbacks. + /// + /// Juergen Hoeller + /// + /// + /// + public class SimpleThreadPoolTaskExecutor : SimpleThreadPool, ISchedulingTaskExecutor, IInitializingObject, IDisposable + { + private bool waitForJobsToCompleteOnShutdown = false; + + /// + /// Set whether to wait for running jobs to complete on Shutdown. + /// Default is "false". + /// + /// + /// true if [wait for jobs to complete on shutdown]; otherwise, false. + /// + /// + public virtual bool WaitForJobsToCompleteOnShutdown + { + set { waitForJobsToCompleteOnShutdown = value; } + } + + /// + /// Invoked by an + /// after it has injected all of an object's dependencies. + /// + /// + ///

+ /// This method allows the object instance to perform the kind of + /// initialization only possible when all of it's dependencies have + /// been injected (set), and to throw an appropriate exception in the + /// event of misconfiguration. + ///

+ ///

+ /// Please do consult the class level documentation for the + /// interface for a + /// description of exactly when this method is invoked. In + /// particular, it is worth noting that the + /// + /// and + /// callbacks will have been invoked prior to this method being + /// called. + ///

+ ///
+ /// + /// In the event of misconfiguration (such as the failure to set a + /// required property) or if initialization fails. + /// + public virtual void AfterPropertiesSet() + { + Initialize(); + } + + /// + /// Executes the specified task. + /// + /// The task. + public virtual void Execute(ThreadStart task) + { + if (task == null) + { + throw new ArgumentException("Runnable must not be null", "task"); + } + if (!RunInThread(new ThreadRunnableDelegate(task))) + { + throw new SchedulingException("Quartz SimpleThreadPool already shut down"); + } + } + + /// This task executor prefers short-lived work units. + public virtual bool PrefersShortLivedTasks + { + get { return true; } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + Shutdown(waitForJobsToCompleteOnShutdown); + } + + internal class ThreadRunnableDelegate : IThreadRunnable + { + private ThreadStart ts; + + + public ThreadRunnableDelegate(ThreadStart ts) + { + this.ts = ts; + } + + public void Run() + { + ts.Invoke(); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringDbProviderAdapter.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringDbProviderAdapter.cs new file mode 100644 index 00000000..e83a3887 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringDbProviderAdapter.cs @@ -0,0 +1,291 @@ + /* + * Copyright 2002-2010 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.Data; +using System.Reflection; + +using Quartz.Impl.AdoJobStore; +using Quartz.Impl.AdoJobStore.Common; + +using IDbMetadata=Spring.Data.Common.IDbMetadata; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Adapts Spring's to Quartz's + /// . + /// + public class SpringDbProviderAdapter : IDbProvider + { + private readonly Data.Common.IDbProvider dbProvider; + private readonly DbMetadata metadata; + + /// + /// Initializes a new instance of the class. + /// + /// The Spring db provider. + public SpringDbProviderAdapter(Data.Common.IDbProvider dbProvider) + { + this.dbProvider = dbProvider; + metadata = new SpringMetadataAdapter(dbProvider.DbMetadata); + } + + + /// + /// Creates the command. + /// + /// + public IDbCommand CreateCommand() + { + return dbProvider.CreateCommand(); + } + + /// + /// Creates the command builder. + /// + /// + public object CreateCommandBuilder() + { + return dbProvider.CreateCommandBuilder(); + } + + /// + /// Creates the connection. + /// + /// + public IDbConnection CreateConnection() + { + return dbProvider.CreateConnection(); + } + + /// + /// Creates the parameter. + /// + /// + public IDbDataParameter CreateParameter() + { + return dbProvider.CreateParameter(); + } + + /// + /// Shutdowns this instance. + /// + public void Shutdown() + { + // no-op + } + + /// + /// Gets or sets the connection string. + /// + /// The connection string. + public string ConnectionString + { + get { return dbProvider.ConnectionString; } + set { dbProvider.ConnectionString = value; } + } + + /// + /// Gets the metadata. + /// + /// The metadata. + public DbMetadata Metadata + { + get { return metadata; } + } + } + + /// + /// Helper class to map between Quartz and Spring DB metadata. + /// + public class SpringMetadataAdapter : DbMetadata + { + private readonly IDbMetadata metadata; + private readonly Enum dbTypeBinary; + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to wrap and adapt. + public SpringMetadataAdapter(IDbMetadata metadata) + { + this.metadata = metadata; + // determine correct binary enum type + Type parameterType = metadata.ParameterDbType; + FieldInfo blobField = parameterType.GetField("Blob"); + FieldInfo imageField = parameterType.GetField("Image"); + if (blobField != null) + { + // uses Blob, for example Oracle + dbTypeBinary = (Enum) blobField.GetValue(Activator.CreateInstance(parameterType)); + } + else if (imageField != null) + { + // uses Image, SQL Server + dbTypeBinary = (Enum) imageField.GetValue(Activator.CreateInstance(parameterType)); + } + else + { + // use standard binary type + dbTypeBinary = DbType.Binary; + } + + } + + /// + /// Gets or sets the name of the product. + /// + /// The name of the product. + public override string ProductName + { + get { return metadata.ProductName; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the type of the connection. + /// + /// The type of the connection. + public override Type ConnectionType + { + get { return metadata.ConnectionType; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the type of the command. + /// + /// The type of the command. + public override Type CommandType + { + get { return metadata.CommandType; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the type of the parameter. + /// + /// The type of the parameter. + public override Type ParameterType + { + get { return metadata.ParameterType; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the type of the command builder. + /// + /// The type of the command builder. + public override Type CommandBuilderType + { + get { return metadata.CommandBuilderType; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the command builder derive parameters method. + /// + /// The command builder derive parameters method. + public override MethodInfo CommandBuilderDeriveParametersMethod + { + get { return metadata.CommandBuilderDeriveParametersMethod; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the parameter name prefix. + /// + /// The parameter name prefix. + public override string ParameterNamePrefix + { + get { return metadata.ParameterNamePrefix; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the type of the exception. + /// + /// The type of the exception. + public override Type ExceptionType + { + get { return metadata.ExceptionType; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets a value indicating whether [bind by name]. + /// + /// true if [bind by name]; otherwise, false. + public override bool BindByName + { + get { return metadata.BindByName; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the type of the parameter db. + /// + /// The type of the parameter db. + public override Type ParameterDbType + { + get { return metadata.ParameterDbType; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the parameter db type property. + /// + /// The parameter db type property. + public override PropertyInfo ParameterDbTypeProperty + { + get { return metadata.ParameterDbTypeProperty; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the parameter is nullable property. + /// + /// The parameter is nullable property. + public override PropertyInfo ParameterIsNullableProperty + { + get { return metadata.ParameterIsNullableProperty; } + set { throw new NotSupportedException(); } + } + + /// + /// Gets the type of the db binary. + /// + /// The type of the db binary. + public override Enum DbBinaryType + { + get { return dbTypeBinary; } + } + + /// + /// Gets or sets a value indicating whether [use parameter name prefix in parameter collection]. + /// + /// + /// true if [use parameter name prefix in parameter collection]; otherwise, false. + /// + public override bool UseParameterNamePrefixInParameterCollection + { + get { return metadata.UseParameterNamePrefixInParameterCollection; } + set { throw new NotSupportedException(); } + } + } + +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringObjectJobFactory.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringObjectJobFactory.cs new file mode 100644 index 00000000..87efe3e3 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/SpringObjectJobFactory.cs @@ -0,0 +1,120 @@ +/* +* Copyright 2002-2010 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 Quartz; +using Quartz.Spi; +using Spring.Objects; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Subclass of AdaptableJobFactory that also supports Spring-style + /// dependency injection on object properties. This is essentially the direct + /// equivalent of Spring's QuartzJobObject in the shape of a + /// Quartz JobFactory. + /// + /// + /// Applies scheduler context, job data map and trigger data map entries + /// as object property values. If no matching object property is found, the entry + /// is by default simply ignored. This is analogous to QuartzJobObject's behavior. + /// + /// Juergen Hoeller + /// + /// + public class SpringObjectJobFactory : AdaptableJobFactory, ISchedulerContextAware + { + private string[] ignoredUnknownProperties; + private SchedulerContext schedulerContext; + + /// + /// Specify the unknown properties (not found in the object) that should be ignored. + /// + /// + /// Default is null, indicating that all unknown properties + /// should be ignored. Specify an empty array to throw an exception in case + /// of any unknown properties, or a list of property names that should be + /// ignored if there is no corresponding property found on the particular + /// job class (all other unknown properties will still trigger an exception). + /// + public virtual string[] IgnoredUnknownProperties + { + set { ignoredUnknownProperties = value; } + } + + /// + /// Set the SchedulerContext of the current Quartz Scheduler. + /// + /// + /// + public virtual SchedulerContext SchedulerContext + { + set { schedulerContext = value; } + } + + + /// + /// Create the job instance, populating it with property values taken + /// from the scheduler context, job data map and trigger data map. + /// + protected override object CreateJobInstance(TriggerFiredBundle bundle) + { + ObjectWrapper ow = new ObjectWrapper(bundle.JobDetail.JobType); + if (IsEligibleForPropertyPopulation(ow.WrappedInstance)) + { + MutablePropertyValues pvs = new MutablePropertyValues(); + if (schedulerContext != null) + { + pvs.AddAll(schedulerContext); + } + pvs.AddAll(bundle.JobDetail.JobDataMap); + pvs.AddAll(bundle.Trigger.JobDataMap); + if (ignoredUnknownProperties != null) + { + for (int i = 0; i < ignoredUnknownProperties.Length; i++) + { + string propName = ignoredUnknownProperties[i]; + if (pvs.Contains(propName)) + { + pvs.Remove(propName); + } + } + ow.SetPropertyValues(pvs); + } + else + { + ow.SetPropertyValues(pvs, true); + } + } + return ow.WrappedInstance; + } + + /// + /// Return whether the given job object is eligible for having + /// its object properties populated. + ///

+ /// The default implementation ignores QuartzJobObject instances, + /// which will inject object properties themselves. + ///

+ ///
+ /// + /// The job object to introspect. + /// + /// + protected virtual bool IsEligibleForPropertyPopulation(object jobObject) + { + return (!(jobObject is QuartzJobObject)); + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/TaskRejectedException.cs b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/TaskRejectedException.cs new file mode 100644 index 00000000..4ac25434 --- /dev/null +++ b/src/Spring/Spring.Scheduling.Quartz20/Scheduling/Quartz/TaskRejectedException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Spring.Scheduling +{ + /// + /// Summary description for TaskRejectedException. + /// + public class TaskRejectedException : ApplicationException + { + } +} diff --git a/src/Spring/Spring.Scheduling.Quartz20/Spring.Scheduling.Quartz20.2010.csproj b/src/Spring/Spring.Scheduling.Quartz20/Spring.Scheduling.Quartz20.2010.csproj index e94b9e84..d0595f83 100644 --- a/src/Spring/Spring.Scheduling.Quartz20/Spring.Scheduling.Quartz20.2010.csproj +++ b/src/Spring/Spring.Scheduling.Quartz20/Spring.Scheduling.Quartz20.2010.csproj @@ -51,69 +51,31 @@ - - JobMethodInvocationFailedException.cs - - - Scheduling\Quartz\AdaptableJobFactory.cs - - - Scheduling\Quartz\DelegatingJob.cs - - - Scheduling\Quartz\IJobDetailAwareTrigger.cs - - - Scheduling\Quartz\ISchedulerContextAware.cs - - - Scheduling\Quartz\ISchedulingTaskExecutor.cs - - - Scheduling\Quartz\ITaskExecutor.cs - - - Scheduling\Quartz\LocalDataSourceJobStore.cs - - - Scheduling\Quartz\LocalTaskExecutorThreadPool.cs - - - Scheduling\Quartz\MethodInvokingJob.cs - - - Scheduling\Quartz\MethodInvokingRunnable.cs - - - Scheduling\Quartz\QuartzJobObject.cs - - - Scheduling\Quartz\SchedulerAccessorObject.cs - - - Scheduling\Quartz\SchedulerFactoryObject.cs - - - Scheduling\Quartz\SchedulingException.cs - - - Scheduling\Quartz\SimpleThreadPoolTaskExecutor.cs - - - Scheduling\Quartz\SpringDbProviderAdapter.cs - - - Scheduling\Quartz\SpringObjectJobFactory.cs - - - Scheduling\Quartz\TaskRejectedException.cs - + + + + + + + + + + + + + + + + + + + diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/QuartzCompilerOptionsTests.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/QuartzCompilerOptionsTests.cs new file mode 100644 index 00000000..8bc4feaf --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/QuartzCompilerOptionsTests.cs @@ -0,0 +1,43 @@ +#region License + +/* + * Copyright 2004-2009 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. + */ + +#endregion + +#region Imports + +using System.Reflection; +using NUnit.Framework; +using Spring.Scheduling.Quartz; + +#endregion + +namespace Spring +{ + /// Test that the assembly is built with the correct DebugAttributes in release and debug builds. + /// + /// Mark Pollack + [TestFixture] + public sealed class QuartzCompilerOptionTests : CompilerOptionsTests + { + [TestFixtureSetUp] + public void FixtureSetUp() + { + AssemblyToCheck = Assembly.GetAssembly(typeof (SchedulingException)); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/AdaptableJobFactoryTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/AdaptableJobFactoryTest.cs new file mode 100644 index 00000000..617bf92b --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/AdaptableJobFactoryTest.cs @@ -0,0 +1,106 @@ +/* +* Copyright 2002-2010 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 NUnit.Framework; +using Quartz; +using Quartz.Job; +using Quartz.Spi; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Tests for . + /// + /// Marko Lahma (.NET) + [TestFixture] + public class AdaptableJobFactoryTest + { + private AdaptableJobFactory jobFactory; + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + jobFactory = new AdaptableJobFactory(); + } + + /// + /// Tests job creation. + /// + [Test] + public void TestNewJob_IncompatibleJob() + { + try + { + // this actually fails already in Quartz level + TriggerFiredBundle bundle = TestUtil.CreateMinimalFiredBundleWithTypedJobDetail(typeof (object)); +#if QUARTZ_2_0 + jobFactory.NewJob(bundle, null); +#else + jobFactory.NewJob(bundle); +#endif + Assert.Fail("Created job which was not an IJob"); + } + catch (ArgumentException) + { + // ok + } + catch (SchedulerException) + { + // ok + } + catch (Exception) + { + Assert.Fail("Got exception that was not instance of SchedulerException or ArgumentException"); + } + } + + /// + /// Tests job creation. + /// + [Test] + public void TestNewJob_ThreadStartJob() + { + // TODO ThreadStart is not the way to go + } + + /// + /// Tests job creation. + /// + [Test] + public void TestNewJob_NormalIJob() + { + TriggerFiredBundle bundle = TestUtil.CreateMinimalFiredBundleWithTypedJobDetail(typeof (NoOpJob)); +#if QUARTZ_2_0 + IJob job = jobFactory.NewJob(bundle, null); +#else + IJob job = jobFactory.NewJob(bundle); +#endif + + Assert.IsNotNull(job, "Returned job was null"); + } + } + + internal class NoOpThreadStartJob : NoOpJob + { + public void Execute() + { + Execute(null); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/CronTriggerObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/CronTriggerObjectTest.cs new file mode 100644 index 00000000..4ceace1f --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/CronTriggerObjectTest.cs @@ -0,0 +1,118 @@ +/* +* Copyright 2002-2010 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.Collections.Generic; +using NUnit.Framework; + +using Quartz; +using Quartz.Job; + +#if QUARTZ_2_0 +using JobDetail = Quartz.Impl.JobDetailImpl; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Tests for . + /// + /// Marko Lahma (.NET) + [TestFixture] + public class CronTriggerObjectTest : TriggerObjectTest + { + private CronTriggerObject cronTrigger; + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + cronTrigger = new CronTriggerObject(); + cronTrigger.ObjectName = TRIGGER_NAME; + Trigger = cronTrigger; + } + + /// + /// Tests all possible misfire instructions for cron trigger + /// from strings to int. + /// + [Test] + public void TestMisfireInstructionNames() + { + string[] names = new string[] { "DoNothing", "FireOnceNow", "SmartPolicy" }; + foreach (string name in names) + { + cronTrigger.MisfireInstructionName = name; + } + } + + /// + /// Tests that JobDetail defaults values as expected in AfterPropertiesSet. + /// + [Test] + public override void TestAfterPropertiesSet_JobDetailGiven() + { + const string jobName = "jobName"; + const string jobGroup = "jobGroup"; + JobDetail jd = new JobDetail(jobName, jobGroup, typeof (NoOpJob)); + cronTrigger.JobDetail = jd; + cronTrigger.AfterPropertiesSet(); + base.TestAfterPropertiesSet_JobDetailGiven(); + Assert.AreSame(jd, cronTrigger.JobDetail, "job details weren't same"); + } + + /// + /// Tests that JobDetail maps job data map as expected. + /// + [Test] + public void TestJobDataAsMap() + { +#if QUARTZ_2_0 + IDictionary data = new Dictionary(); +#else + Hashtable data = new Hashtable(); +#endif + data["foo"] = "bar"; + data["number"] = 123; + cronTrigger.JobDataAsMap = data; + CollectionAssert.AreEquivalent(data, cronTrigger.JobDataMap, "Data differed"); + } + + /// + /// Tests that StartDelay is respected. + /// + [Test] + public void TestStartDelay() + { + TimeSpan expectedDelay = TimeSpan.FromMinutes(10); + cronTrigger.StartDelay = expectedDelay; + Assert.AreEqual(expectedDelay, cronTrigger.StartDelay); + + cronTrigger.AfterPropertiesSet(); + DateTime now = DateTime.UtcNow; + TimeSpan delay = cronTrigger.StartTimeUtc - now; + + // check roughly + Assert.IsTrue(delay > TimeSpan.FromMinutes(9).Add(TimeSpan.FromSeconds(55))); + Assert.IsTrue(delay < TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(5))); + } + + } + +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/JobDetailObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/JobDetailObjectTest.cs new file mode 100644 index 00000000..f83c6bfe --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/JobDetailObjectTest.cs @@ -0,0 +1,165 @@ +/* +* Copyright 2002-2010 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 NUnit.Framework; + +using Quartz; +using Quartz.Job; + +using Spring.Context.Support; + +namespace Spring.Scheduling.Quartz +{ + /// + /// Tests for . + /// + /// Marko Lahma (.NET) + [TestFixture] + public class JobDetailObjectTest + { + private JobDetailObject jobDetail; + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + jobDetail = new JobDetailObject(); + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestJobType_Null() + { + jobDetail.JobType = null; + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + public void TestJobType_NonIJob() + { + jobDetail.JobType = typeof(object); + Assert.AreEqual(typeof(object), jobDetail.JobType, "JobDetail did not create same type as expected"); + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + public void TestJobType_IJob() + { + Type CORRECT_IJOB = typeof (NoOpJob); + jobDetail.JobType = CORRECT_IJOB; + Assert.AreEqual(jobDetail.JobType, CORRECT_IJOB, "JobDetail did not register correct job type"); + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestJobDataAsMap_Null() + { + jobDetail.JobDataAsMap = null; + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + public void TestJobDataAsMap_ProperValues() + { + IDictionary values = new Hashtable(); + values["baz"] = "foo"; + values["foo"] = 123; + values["bar"] = null; + jobDetail.JobDataAsMap = values; + Assert.AreEqual(values.Count, jobDetail.JobDataMap.Count, "Data of inequal size"); + CollectionAssert.AreEquivalent(values.Keys, jobDetail.JobDataMap.Keys, "JobDataMap values not equal"); + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + public void TestAfterPropertiesSet_Defaults() + { + const string objectName = "springJobDetailObject"; + jobDetail.ObjectName = objectName; + jobDetail.Group = null; + jobDetail.AfterPropertiesSet(); + Assert.AreEqual(SchedulerConstants.DefaultGroup, jobDetail.Group, "Groups differ"); + Assert.AreEqual(objectName, jobDetail.Name, "Names differ"); + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + public void TestAfterPropertiesSet_CustomNameAndGroup() + { + const string objectName = "springJobDetailObject"; + const string jobDetailName = "jobDetailName"; + const string jobDetailGroup = "jobDetailGroup"; + jobDetail.ObjectName = objectName; + jobDetail.Name = jobDetailName; + jobDetail.Group = jobDetailGroup; + jobDetail.AfterPropertiesSet(); + Assert.AreEqual(jobDetailGroup, jobDetail.Group, "Groups differ"); + Assert.AreEqual(jobDetailName, jobDetail.Name, "Names differ"); + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + public void TestAfterPropertiesSet_ApplicationContextJobDataKeySetWithApplicationContext() + { + const string objectName = "springJobDetailObject"; + jobDetail.ObjectName = objectName; + StaticApplicationContext ctx = new StaticApplicationContext(); + jobDetail.ApplicationContext = ctx; + string key = "applicationContextJobDataKey"; + jobDetail.ApplicationContextJobDataKey = key; + jobDetail.AfterPropertiesSet(); + Assert.AreSame(ctx, jobDetail.ApplicationContext, "ApplicationContext was not set correctly"); + Assert.AreSame(ctx, jobDetail.JobDataMap[key], "ApplicationContext was not set to job data map"); + } + + /// + /// Tests job detail's property behavior. + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestAfterPropertiesSet_ApplicationContextJobDataKeySetWithoutApplicationContext() + { + const string objectName = "springJobDetailObject"; + jobDetail.ObjectName = objectName; + jobDetail.ApplicationContextJobDataKey = "applicationContextJobDataKey"; + jobDetail.AfterPropertiesSet(); + } + + } +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobDetailFactoryObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobDetailFactoryObjectTest.cs new file mode 100644 index 00000000..bc34b359 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobDetailFactoryObjectTest.cs @@ -0,0 +1,95 @@ +/* +* Copyright 2002-2010 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 NUnit.Framework; + +using Quartz; + +#if QUARTZ_2_0 +using JobDetail = Quartz.Impl.JobDetailImpl; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Unit tests for MethodInvokingJobDetailFactoryObject. + /// + /// Marko Lahma (.NET) + [TestFixture] + public class MethodInvokingJobDetailFactoryObjectTest + { + private const string FACTORY_NAME = "springObjectFactory"; + private MethodInvokingJobDetailFactoryObject factory; + + /// + /// Setup for the test. + /// + [SetUp] + public void SetUp() + { + factory = new MethodInvokingJobDetailFactoryObject(); + factory.ObjectName = FACTORY_NAME; + factory.TargetMethod = "Invoke"; + factory.TargetObject = new InvocationCountingJob(); + } + + /// + /// Tests JobDetail retrieval and it's set properties. + /// + [Test] + public void TestGetObject_MinimalDefaults() + { + factory.AfterPropertiesSet(); + JobDetail jd = (JobDetail) factory.GetObject(); + Assert.IsNotNull(jd, "job detail was null"); + Assert.AreEqual(FACTORY_NAME, jd.Name, "job name did not default to factory name"); + Assert.AreEqual(jd.JobType, typeof(MethodInvokingJob), "factory did not create method invoking job"); + Assert.IsTrue(jd.Durable, "job was not durable"); +#if !QUARTZ_2_0 + Assert.IsTrue(jd.Volatile, "job was not volatile"); +#endif + } + + /// + /// Tests JobDetail retrieval and it's set properties. + /// + [Test] + public void TestGetObject_ConcurrentJob() + { + factory.Concurrent = false; + factory.AfterPropertiesSet(); + JobDetail jd = (JobDetail)factory.GetObject(); + Assert.IsNotNull(jd, "job detail was null"); + Assert.AreEqual(jd.JobType, typeof(StatefulMethodInvokingJob), "factory did not create stateful method invoking job"); + } + +#if !QUARTZ_2_0 + /// + /// Tests JobDetail retrieval and it's set properties. + /// + [Test] + public void TestGetObject_TriggerListenersSet() + { + string[] LISTENER_NAMES = new string[] {"Foo", "Bar"}; + factory.JobListenerNames = LISTENER_NAMES; + factory.AfterPropertiesSet(); + JobDetail jd = (JobDetail)factory.GetObject(); + CollectionAssert.AreEquivalent(LISTENER_NAMES, jd.JobListenerNames); + } +#endif + + } +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobTest.cs new file mode 100644 index 00000000..6dfedcfd --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/MethodInvokingJobTest.cs @@ -0,0 +1,213 @@ +/* +* Copyright 2002-2010 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.Threading; + +using NUnit.Framework; + +using Quartz; +using Quartz.Job; +using Quartz.Spi; + +using Rhino.Mocks; + +using Spring.Objects.Support; + +#if QUARTZ_2_0 +using JobExecutionContext = Quartz.Impl.JobExecutionContextImpl; +using JobDetail = Quartz.Impl.JobDetailImpl; +using SimpleTrigger = Quartz.Impl.Triggers.SimpleTriggerImpl; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Tests for MethodInvokingJob. + /// + /// Marko Lahma (.NET) + [TestFixture] + public class MethodInvokingJobTest + { + private MethodInvokingJob methodInvokingJob; + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + methodInvokingJob = new MethodInvokingJob(); + } + + /// + /// Test method invoke via execute. + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestMethodInvoker_SetWithNull() + { + methodInvokingJob.MethodInvoker = null; + } + + /// + /// Test method invoke via execute. + /// + [Test] + [ExpectedException(typeof(JobExecutionException))] + public void TestMethodInvocation_NullMethodInvokder() + { + methodInvokingJob.Execute(CreateMinimalJobExecutionContext()); + } + + /// + /// Test method invoke via execute. + /// + [Test] + public void TestMethodInvoker_MethodSetCorrectly() + { + InvocationCountingJob job = new InvocationCountingJob(); + MethodInvoker mi = new MethodInvoker(); + mi.TargetObject = job; + mi.TargetMethod = "Invoke"; + mi.Prepare(); + methodInvokingJob.MethodInvoker = mi; + methodInvokingJob.Execute(CreateMinimalJobExecutionContext()); + Assert.AreEqual(1, job.CounterValue, "Job was not invoked once"); + } + + /// + /// Test that invocation result is set to execution context (SPRNET-1340). + /// + [Test] + public void TestMethodInvoker_ShouldSetResultToExecutionContext() + { + InvocationCountingJob job = new InvocationCountingJob(); + MethodInvoker mi = new MethodInvoker(); + mi.TargetObject = job; + mi.TargetMethod = "InvokeWithReturnValue"; + mi.Prepare(); + methodInvokingJob.MethodInvoker = mi; + JobExecutionContext context = CreateMinimalJobExecutionContext(); + methodInvokingJob.Execute(context); + + Assert.AreEqual(InvocationCountingJob.DefaultReturnValue, context.Result, "result value was not set to context"); + } + + /// + /// Test method invoke via execute. + /// + [Test] + public void TestMethodInvoker_MethodSetCorrectlyThrowsException() + { + InvocationCountingJob job = new InvocationCountingJob(); + MethodInvoker mi = new MethodInvoker(); + mi.TargetObject = job; + mi.TargetMethod = "InvokeAndThrowException"; + mi.Prepare(); + methodInvokingJob.MethodInvoker = mi; + try + { + methodInvokingJob.Execute(CreateMinimalJobExecutionContext()); + Assert.Fail("Successful invoke when method threw exception"); + } + catch (JobMethodInvocationFailedException) + { + // ok + } + Assert.AreEqual(1, job.CounterValue, "Job was not invoked once"); + } + + /// + /// Test method invoke via execute. + /// + [Test] + public void TestMethodInvoker_PrivateMethod() + { + InvocationCountingJob job = new InvocationCountingJob(); + MethodInvoker mi = new MethodInvoker(); + mi.TargetObject = job; + mi.TargetMethod = "PrivateMethod"; + mi.Prepare(); + methodInvokingJob.MethodInvoker = mi; + methodInvokingJob.Execute(CreateMinimalJobExecutionContext()); + } + + private static JobExecutionContext CreateMinimalJobExecutionContext() + { + MockRepository repo = new MockRepository(); + IScheduler sched = (IScheduler) repo.DynamicMock(typeof (IScheduler)); + JobExecutionContext ctx = new JobExecutionContext(sched, ConstructMinimalTriggerFiredBundle(), null); + return ctx; + } + + private static TriggerFiredBundle ConstructMinimalTriggerFiredBundle() + { + JobDetail jd = new JobDetail("jobName", "jobGroup", typeof(NoOpJob)); + SimpleTrigger trigger = new SimpleTrigger("triggerName", "triggerGroup"); + TriggerFiredBundle retValue = new TriggerFiredBundle(jd, trigger, null, false, null, null, null, null); + + return retValue; + } + + } + + /// + /// Test class for method invoker. + /// + public class InvocationCountingJob + { + private int counter; + internal const string DefaultReturnValue = "return value"; + + /// + /// Increments method invoke counter. + /// + public void Invoke() + { + Interlocked.Increment(ref counter); + } + + /// + /// Throws exception after incrementing counter. + /// + public void InvokeAndThrowException() + { + Interlocked.Increment(ref counter); + throw new Exception(); + } + + private void PrivateMethod() + { + } + + /// + /// Returns as return value. + /// + public string InvokeWithReturnValue() + { + return DefaultReturnValue; + } + + /// + /// Invocation count. + /// + public int CounterValue + { + get { return counter; } + } + } +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzSupportTests.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzSupportTests.cs new file mode 100644 index 00000000..882429c9 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzSupportTests.cs @@ -0,0 +1,1366 @@ +/* + * Copyright 2002-2010 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; + +#if QUARTZ_2_0 +using CronTrigger = Quartz.Impl.Triggers.CronTriggerImpl; +using JobExecutionContext = Quartz.IJobExecutionContext; +using JobDetail = Quartz.Impl.JobDetailImpl; +using SimpleTrigger = Quartz.Impl.Triggers.SimpleTriggerImpl; +using Trigger = Quartz.ITrigger; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// + /// Juergen Hoeller + /// Alef Arendsen + /// Rob Harrop + /// Marko Lahma (.NET) + [TestFixture] + public class QuartzSupportTests + { + /// + /// Executes parametrized test. + /// + [Test] + public void TestSchedulerFactoryObject() + { + DoTestSchedulerFactoryObject(false, false); + } + + /// + /// Executes parametrized test. + /// + [Test] + public void TestSchedulerFactoryObjectWithExplicitJobDetail() + { + DoTestSchedulerFactoryObject(true, false); + } + + /// + /// Executes parametrized test. + /// + [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 = MockRepository.GenerateMock(); + + scheduler.Stub(x => x.Context).Return(new SchedulerContext()); + + 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(); + schedulerFactoryObject.Start(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + scheduler.AssertWasCalled(x => x.ScheduleJob(trigger0)); + scheduler.AssertWasCalled(x => x.ScheduleJob(trigger1)); + + scheduler.AssertWasCalled(x => x.AddJob(jobDetail0, true)); + scheduler.AssertWasCalled(x => x.AddJob(jobDetail1, true)); + + scheduler.AssertWasCalled(x => x.Start()); + scheduler.AssertWasCalled(x => x.Shutdown(false)); + } + + /// + /// Executes parametrized test. + /// + [Test] + public void TestSchedulerFactoryObjectWithExistingJobs() + { + DoTestSchedulerFactoryObjectWithExistingJobs(false); + } + + /// + /// Executes parametrized test. + /// + [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 = MockRepository.GenerateMock(); + scheduler.Stub(x => x.Context).Return(new SchedulerContext()); +#if QUARTZ_2_0 + scheduler.Stub(x => x.GetTrigger(new TriggerKey("myTrigger1", SchedulerConstants.DefaultGroup))).Return(new SimpleTrigger()); +#else + scheduler.Stub(x => x.GetTrigger("myTrigger1", SchedulerConstants.DefaultGroup)).Return(new SimpleTrigger()); +#endif + if (overwrite) + { +#if QUARTZ_2_0 + scheduler.Stub(x => x.RescheduleJob(new TriggerKey("myTrigger1", SchedulerConstants.DefaultGroup), trigger1)).Return(DateTime.UtcNow); +#else + scheduler.Stub(x => x.RescheduleJob("myTrigger1", SchedulerConstants.DefaultGroup, trigger1)).Return(DateTime.UtcNow); +#endif + } + + 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(); + schedulerFactoryObject.Start(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + scheduler.AssertWasCalled(x => x.AddJob(jobDetail0, true)); + scheduler.AssertWasCalled(x => x.ScheduleJob(trigger0)); + scheduler.AssertWasCalled(x => x.Start()); + scheduler.AssertWasCalled(x => x.Shutdown(false)); + } + + /// + /// Executes parametrized test. + /// + [Test] + public void TestSchedulerFactoryObjectWithExistingJobsAndRaceCondition() + { + DoTestSchedulerFactoryObjectWithExistingJobsAndRaceCondition(false); + } + + /// + /// Executes parametrized test. + /// + [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 = MockRepository.GenerateMock(); + scheduler.Stub(x => x.Context).Return(new SchedulerContext()); +#if QUARTZ_2_0 + scheduler.Stub(x => x.GetTrigger(new TriggerKey("myTrigger1", SchedulerConstants.DefaultGroup))).Return(new SimpleTrigger()); +#else + scheduler.Stub(x => x.GetTrigger("myTrigger1", SchedulerConstants.DefaultGroup)).Return(new SimpleTrigger()); +#endif + if (overwrite) + { + scheduler.AddJob(jobDetail1, true); +#if QUARTZ_2_0 + scheduler.Stub(x => x.RescheduleJob(new TriggerKey("myTrigger1", SchedulerConstants.DefaultGroup), trigger1)).Return(DateTime.UtcNow); +#else + scheduler.Stub(x => x.RescheduleJob("myTrigger1", SchedulerConstants.DefaultGroup, trigger1)).Return(DateTime.UtcNow); +#endif + } + + scheduler.Stub(x => x.ScheduleJob(trigger0)).Throw(new ObjectAlreadyExistsException("")); + + if (overwrite) + { +#if QUARTZ_2_0 + scheduler.Stub(x => x.RescheduleJob(new TriggerKey("myTrigger0", SchedulerConstants.DefaultGroup), trigger0)).Return(DateTime.UtcNow); +#else + scheduler.Stub(x => x.RescheduleJob("myTrigger0", SchedulerConstants.DefaultGroup, trigger0)).Return(DateTime.UtcNow); +#endif + } + + scheduler.Start(); + scheduler.Shutdown(false); + + 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(); + schedulerFactoryObject.Start(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + scheduler.AssertWasCalled(x => x.AddJob(jobDetail0, true)); + + } + +#if !QUARTZ_2_0 + /// + /// + [Test] + public void TestSchedulerFactoryObjectWithListeners() + { + IJobFactory jobFactory = new AdaptableJobFactory(); + + IScheduler scheduler = MockRepository.GenerateMock(); + + 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)); + scheduler.AddSchedulerListener(schedulerListener); + scheduler.AddGlobalJobListener(globalJobListener); + scheduler.AddJobListener(jobListener); + scheduler.AddGlobalTriggerListener(globalTriggerListener); + scheduler.AddTriggerListener(triggerListener); + scheduler.Start(); + scheduler.Shutdown(false); + + 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(); + schedulerFactoryObject.Start(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + } + +#endif + /*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) + { + // 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) + { + // 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 = MockRepository.GenerateMock(); + + SchedulerFactoryObject schedulerFactoryObject = new TestSchedulerFactoryObject(scheduler); + + schedulerFactoryObject.JobFactory = (jobFactory); + schedulerFactoryObject.JobDetails = (new JobDetail[] {jobDetail0, jobDetail1}); + schedulerFactoryObject.Triggers = (new Trigger[] {trigger0, trigger1}); + try + { + schedulerFactoryObject.AfterPropertiesSet(); + schedulerFactoryObject.Start(); + } + finally + { + schedulerFactoryObject.Dispose(); + } + + scheduler.AssertWasCalled(x => x.JobFactory = jobFactory); + scheduler.AssertWasCalled(x => x.AddJob(jobDetail0, true)); + scheduler.AssertWasCalled(x => x.AddJob(jobDetail1, true)); +#if QUARTZ_2_0 + scheduler.AssertWasCalled(x => x.GetJobDetail(new JobKey("myJob0", SchedulerConstants.DefaultGroup))); + scheduler.AssertWasCalled(x => x.GetJobDetail(new JobKey("myJob1", SchedulerConstants.DefaultGroup))); + scheduler.AssertWasCalled(x => x.GetTrigger(new TriggerKey("myTrigger0", SchedulerConstants.DefaultGroup))); + scheduler.AssertWasCalled(x => x.GetTrigger(new TriggerKey("myTrigger1", SchedulerConstants.DefaultGroup))); +#else + scheduler.AssertWasCalled(x => x.GetJobDetail("myJob0", SchedulerConstants.DefaultGroup)); + scheduler.AssertWasCalled(x => x.GetJobDetail("myJob1", SchedulerConstants.DefaultGroup)); + scheduler.AssertWasCalled(x => x.GetTrigger("myTrigger0", SchedulerConstants.DefaultGroup)); + scheduler.AssertWasCalled(x => x.GetTrigger("myTrigger1", SchedulerConstants.DefaultGroup)); +#endif + } + + /// + /// + [Test] + public void TestSchedulerFactoryObjectWithApplicationContext() + { + TestObject tb = new TestObject("tb", 99); + StaticApplicationContext ac = new StaticApplicationContext(); + + IScheduler scheduler = MockRepository.GenerateMock(); + SchedulerContext schedulerContext = new SchedulerContext(); + scheduler.Stub(x => x.Context).Return(schedulerContext).Repeat.Times(4); + + 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(); + schedulerFactoryObject.Start(); + IScheduler returnedScheduler = (IScheduler) schedulerFactoryObject.GetObject(); + Assert.AreEqual(tb, returnedScheduler.Context["testObject"]); + Assert.AreEqual(ac, returnedScheduler.Context["appCtx"]); + } + finally + { + schedulerFactoryObject.Dispose(); + } + } + + /// + /// + [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")); + } + +#if !QUARTZ_2_0 + /// + /// + [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); + } +#endif + /// + /// + [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 factoryObject = new SchedulerFactoryObject(); + factoryObject.TaskExecutor = (taskExecutor); + factoryObject.Triggers = (new Trigger[] {trigger}); + factoryObject.JobDetails = (new JobDetail[] {jobDetail}); + factoryObject.AfterPropertiesSet(); + factoryObject.Start(); + + Thread.Sleep(500); + Assert.IsTrue(DummyJob.count > 0); + Assert.AreEqual(DummyJob.count, taskExecutor.count); + + factoryObject.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 factoryObject = new SchedulerFactoryObject(); + factoryObject.Triggers = new Trigger[] {trigger}; + factoryObject.JobDetails = new JobDetail[] {jobDetail}; + factoryObject.AfterPropertiesSet(); + factoryObject.Start(); + + DummyRunnable.runEvent.WaitOne(500); + Assert.IsTrue(DummyRunnable.count > 0); + + factoryObject.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 factoryObject = new SchedulerFactoryObject(); + factoryObject.Triggers = (new Trigger[] {trigger}); + factoryObject.JobDetails = (new JobDetail[] {jobDetail}); + factoryObject.AfterPropertiesSet(); + factoryObject.Start(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJobObject.param); + Assert.IsTrue(DummyJobObject.count > 0); + + factoryObject.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 factoryObject = new SchedulerFactoryObject(); + factoryObject.JobFactory = (new SpringObjectJobFactory()); + factoryObject.Triggers = (new Trigger[] {trigger}); + factoryObject.JobDetails = (new JobDetail[] {jobDetail}); + factoryObject.AfterPropertiesSet(); + factoryObject.Start(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJob.param); + Assert.IsTrue(DummyJob.count > 0); + + factoryObject.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 factoryObject = new SchedulerFactoryObject(); + factoryObject.JobFactory = (new SpringObjectJobFactory()); + factoryObject.Triggers = (new Trigger[] {trigger}); + factoryObject.JobDetails = (new JobDetail[] {jobDetail}); + factoryObject.AfterPropertiesSet(); + factoryObject.Start(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyRunnable.param); + Assert.IsTrue(DummyRunnable.count > 0); + + factoryObject.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 factoryObject = new SchedulerFactoryObject(); + factoryObject.JobFactory = (new SpringObjectJobFactory()); + factoryObject.Triggers = (new Trigger[] {trigger}); + factoryObject.JobDetails = (new JobDetail[] {jobDetail}); + factoryObject.AfterPropertiesSet(); + factoryObject.Start(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJobObject.param); + Assert.IsTrue(DummyJobObject.count > 0); + + factoryObject.Dispose(); + } + + /// + /// + /// + [Test] + public void TestSchedulerWithSpringObjectJobFactoryAndJobSchedulingData() + { + DummyJob.param = 0; + DummyJob.count = 0; + + SchedulerFactoryObject factoryObject = new SchedulerFactoryObject(); + factoryObject.JobFactory = new SpringObjectJobFactory(); + factoryObject.JobSchedulingDataLocation = "job-scheduling-data.xml"; + // TODO bean.ResourceLoader = (new FileSystemResourceLoader()); + factoryObject.AfterPropertiesSet(); + factoryObject.Start(); + + Thread.Sleep(500); + Assert.AreEqual(10, DummyJob.param); + Assert.IsTrue(DummyJob.count > 0); + + factoryObject.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(); + } + } + + /// + /// Tests calling of services with method invoke. + /// + [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(); + } + } + + /// + /// Tests how quartz triggers and services interact. + /// + [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 TestSchedulerAutoStartsOnContextRefreshedEventByDefault() + { + StaticApplicationContext context = new StaticApplicationContext(); + context.RegisterObjectDefinition("scheduler", new RootObjectDefinition(typeof (SchedulerFactoryObject))); + IScheduler scheduler = (IScheduler) context.GetObject("scheduler", typeof (IScheduler)); + Assert.IsFalse(scheduler.IsStarted); + context.Refresh(); + Assert.IsTrue(scheduler.IsStarted); + } + + [Test] + public void TestSchedulerAutoStartupFalse() + { + StaticApplicationContext context = new StaticApplicationContext(); + ObjectDefinitionBuilder beanDefinition = ObjectDefinitionBuilder + .GenericObjectDefinition(typeof(SchedulerFactoryObject)) + .AddPropertyValue("autoStartup", false); + + context.RegisterObjectDefinition("scheduler", beanDefinition.ObjectDefinition); + IScheduler scheduler = (IScheduler) context.GetObject("scheduler", typeof(IScheduler)); + + Assert.IsFalse(scheduler.IsStarted); + context.Refresh(); + Assert.IsFalse(scheduler.IsStarted); + } + + /// + /// Tests how scheduler is exposed to application context. + /// + [Test] + public void TestSchedulerRepositoryExposure() + { + XmlApplicationContext ctx = new XmlApplicationContext("schedulerRepositoryExposure.xml"); + Assert.AreSame(SchedulerRepository.Instance.Lookup("myScheduler"), ctx.GetObject("scheduler")); + ctx.Dispose(); + } + + +#if QUARTZ_2_0 + private class TestSchedulerListener : ISchedulerListener + { + public void JobScheduled(ITrigger trigger) + { + } + + public void JobUnscheduled(TriggerKey triggerKey) + { + } + + public void TriggerFinalized(ITrigger trigger) + { + } + + public void TriggerPaused(TriggerKey triggerKey) + { + } + + public void TriggersPaused(string triggerGroup) + { + } + + public void TriggerResumed(TriggerKey triggerKey) + { + } + + public void TriggersResumed(string triggerGroup) + { + } + + public void JobAdded(IJobDetail jobDetail) + { + } + + public void JobDeleted(JobKey jobKey) + { + } + + public void JobPaused(JobKey jobKey) + { + } + + public void JobsPaused(string jobGroup) + { + } + + public void JobResumed(JobKey jobKey) + { + } + + public void JobsResumed(string jobGroup) + { + } + + public void SchedulerError(string msg, SchedulerException cause) + { + } + + public void SchedulerInStandbyMode() + { + } + + public void SchedulerStarted() + { + } + + public void SchedulerShutdown() + { + } + + public void SchedulerShuttingdown() + { + } + + public void SchedulingDataCleared() + { + } + } + +#else + 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() + { + } + } +#endif + + 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) + { + } + } + + + /// + /// Simple task executor that tracks invocation count. + /// + public class CountingTaskExecutor : ITaskExecutor + { + internal int count; + + /// + /// Executes task instance. + /// + /// + public void Execute(ThreadStart task) + { + count++; + task.Invoke(); + } + } + + /// + /// Simple test job object. + /// + public class DummyJobObject : QuartzJobObject + { + internal static int param; + internal static int count; + + /// + /// Sets parameter value. + /// + /// + public void SetParam(int value) + { + if (param > 0) + { + throw new NotSupportedException("Param already set"); + } + param = value; + } + + /// + /// Execute the actual job. The job data map will already have been + /// applied as object property values by execute. The contract is + /// exactly the same as for the standard Quartz execute method. + /// + protected override void ExecuteInternal(JobExecutionContext jobExecutionContext) + { + count++; + } + } + + /// + /// Simple thread runnable. + /// + public class DummyRunnable : IThreadRunnable + { + internal static int param; + internal static int count; + internal static readonly ManualResetEvent runEvent = new ManualResetEvent(false); + + /// + /// Runs thread runnable. + /// + public void Run() + { + count++; + runEvent.Set(); + } + } + } + + /// + /// A simple job that tracks invocation count and allows setting of a simple parameter. + /// + public class DummyJob : IJob + { + internal static int param; + internal static int count; + + /// + /// Sets param value. + /// + /// + public void SetParam(int value) + { + if (param > 0) + { + throw new NotSupportedException("Param already set"); + } + param = value; + } + + /// + /// Executes this job instance. + /// + /// + public void Execute(JobExecutionContext jobExecutionContext) + { + count++; + } + } + + /// + /// Subclass of SchedulerFactoryObject for testing purposes. + /// + public class TestSchedulerFactoryObject : SchedulerFactoryObject + { + private readonly IScheduler sched; + + /// + /// Creates new instance of this class. + /// + /// + public TestSchedulerFactoryObject(IScheduler sched) + { + this.sched = sched; + } + + /// + /// Creates a scheduler actually returning the scheduler this intance + /// was instantiated with. + /// + /// + /// + /// + protected override IScheduler CreateScheduler(ISchedulerFactory schedulerFactory, String schedulerName) + { + return sched; + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzTestObject.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzTestObject.cs new file mode 100644 index 00000000..2d1e1346 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/QuartzTestObject.cs @@ -0,0 +1,45 @@ +namespace Spring.Scheduling.Quartz +{ + /// + /// A simple test object for Quartz.NET to run + /// that simulates imports and exports. + /// + /// Rob Harrop + public class QuartzTestObject + { + private int exportCount; + private int importCount; + + /// + /// Executes a fake import and increments counter. + /// + public void DoImport() + { + ++importCount; + } + + /// + /// Executes a fake export and increments counter. + /// + public void DoExport() + { + ++exportCount; + } + + /// + /// Tells how many times import has been done. + /// + public int ImportCount + { + get { return importCount; } + } + + /// + /// Tells how many times export has been done. + /// + public int ExportCount + { + get { return exportCount; } + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs new file mode 100644 index 00000000..7f3bc716 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SchedulerFactoryObjectTest.cs @@ -0,0 +1,386 @@ +/* +* Copyright 2002-2010 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.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Reflection; +using System.Text; +using NUnit.Framework; + +using Quartz; +using Quartz.Impl; +using Quartz.Spi; +using Rhino.Mocks; + +using Spring.Core.IO; + +#if QUARTZ_2_0 +using Trigger = Quartz.ITrigger; +using JobExecutionContext = Quartz.IJobExecutionContext; +using JobDetail = Quartz.IJobDetail; +using SimpleTrigger = Quartz.Impl.Triggers.SimpleTriggerImpl; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Tests for SchedulerFactoryObject. + /// + /// Marko Lahma (.NET) + [TestFixture] + public class SchedulerFactoryObjectTest + { + private static readonly MethodInfo m_InitSchedulerFactory = typeof(SchedulerFactoryObject).GetMethod("InitSchedulerFactory", + BindingFlags.Instance | BindingFlags.NonPublic); + private SchedulerFactoryObject factory; + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + factory = new SchedulerFactoryObject(); + + TestSchedulerFactory.Initialize(); + TestSchedulerFactory.MockScheduler.Stub(x => x.SchedulerName).Return("scheduler").Repeat.Any(); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_Defaults() + { + factory.AfterPropertiesSet(); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_NullJobFactory() + { + factory.JobFactory = null; + factory.AfterPropertiesSet(); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_NoAutoStartup() + { + // set expectations + TestSchedulerFactory.MockScheduler.JobFactory = null; + + factory.SchedulerFactoryType = typeof(TestSchedulerFactory); + factory.AutoStartup = false; + factory.AfterPropertiesSet(); + + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.JobFactory = null); + } + +#if !QUARTZ_2_0 + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_AddListeners() + { + InitForAfterPropertiesSetTest(); + + factory.SchedulerListeners = new ISchedulerListener[] { MockRepository.GenerateMock() }; + + factory.GlobalJobListeners = new IJobListener[] { MockRepository.GenerateMock() }; + + factory.JobListeners = new IJobListener[] { MockRepository.GenerateMock() }; + + factory.GlobalTriggerListeners = new ITriggerListener[] { MockRepository.GenerateMock() }; + + factory.TriggerListeners = new ITriggerListener[] { MockRepository.GenerateMock() }; + + factory.AfterPropertiesSet(); + + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.AddSchedulerListener(Arg.Is.NotNull)); + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.AddGlobalJobListener(Arg.Is.NotNull)); + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.AddJobListener(Arg.Is.NotNull)); + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.AddGlobalTriggerListener(Arg.Is.NotNull)); + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.AddTriggerListener(Arg.Is.NotNull)); + } +#endif + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_Calendars() + { + InitForAfterPropertiesSetTest(); + + const string calendarName = "calendar"; + ICalendar cal = MockRepository.GenerateMock(); + Hashtable calTable = new Hashtable(); + calTable[calendarName] = cal; + factory.Calendars = calTable; + + factory.AfterPropertiesSet(); + + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.AddCalendar(calendarName, cal, true, true)); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_Trigger_TriggerExists() + { + InitForAfterPropertiesSetTest(); + + const string TRIGGER_NAME = "trigName"; + const string TRIGGER_GROUP = "trigGroup"; + SimpleTrigger trigger = new SimpleTrigger(TRIGGER_NAME, TRIGGER_GROUP); + factory.Triggers = new Trigger[] { trigger }; + +#if QUARTZ_2_0 + TestSchedulerFactory.MockScheduler.Stub(x => x.GetTrigger(new TriggerKey(TRIGGER_NAME, TRIGGER_GROUP))).Return(trigger); +#else + TestSchedulerFactory.MockScheduler.Stub(x => x.GetTrigger(TRIGGER_NAME, TRIGGER_GROUP)).Return(trigger); +#endif + + factory.AfterPropertiesSet(); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_Trigger_TriggerDoesntExist() + { + InitForAfterPropertiesSetTest(); + + const string TRIGGER_NAME = "trigName"; + const string TRIGGER_GROUP = "trigGroup"; + SimpleTrigger trigger = new SimpleTrigger(TRIGGER_NAME, TRIGGER_GROUP); + factory.Triggers = new Trigger[] { trigger }; + + factory.AfterPropertiesSet(); + + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.ScheduleJob(trigger)); + } + + + private void InitForAfterPropertiesSetTest() + { + factory.AutoStartup = false; + // set expectations + factory.SchedulerFactoryType = typeof(TestSchedulerFactory); + TestSchedulerFactory.MockScheduler.JobFactory = null; + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestStart() + { + factory.SchedulerFactoryType = typeof(TestSchedulerFactory); + factory.AutoStartup = false; + factory.AfterPropertiesSet(); + factory.Start(); + + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.JobFactory = Arg.Is.NotNull); + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.Start()); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestStop() + { + factory.SchedulerFactoryType = typeof(TestSchedulerFactory); + factory.AutoStartup = false; + factory.AfterPropertiesSet(); + factory.Stop(); + + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.JobFactory = Arg.Is.NotNull); + TestSchedulerFactory.MockScheduler.AssertWasCalled(x => x.Standby()); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestGetObject() + { + factory.AfterPropertiesSet(); + IScheduler sched = (IScheduler)factory.GetObject(); + Assert.IsNotNull(sched, "scheduler was null"); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestSchedulerFactoryType_InvalidType() + { + factory.SchedulerFactoryType = typeof(SchedulerFactoryObjectTest); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestSchedulerFactoryType_ValidType() + { + factory.SchedulerFactoryType = typeof(StdSchedulerFactory); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestInitSchedulerFactory_MinimalDefaults() + { + factory.SchedulerName = "testFactoryObject"; + StdSchedulerFactory factoryToPass = new StdSchedulerFactory(); + m_InitSchedulerFactory.Invoke(factory, new object[] { factoryToPass }); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestInitSchedulerFactory_ConfigLocationReadingShouldPreserverExtraEqualsMarksAndTrimKeysAndValues() + { + const string ConnectionStringValue = "Server=(local);Database=quartz;Trusted_Connection=True;"; + const string ConnectionStringKey = "quartz.dataSource.default.connectionString"; + string configuration = + @"quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz +quartz.jobStore.useProperties = false +quartz.jobStore.dataSource = default" + Environment.NewLine + +ConnectionStringKey+ " = " + ConnectionStringValue + Environment.NewLine + +"quartz.dataSource.default.provider = SqlServer-20"; + + // initialize data + MemoryStream ms = new MemoryStream(); + byte[] data = Encoding.UTF8.GetBytes(configuration); + ms.Write(data, 0, data.Length); + ms.Seek(0, SeekOrigin.Begin); + ms.Position = 0; + + // intercept call + InterceptingStdSChedulerFactory factoryToPass = new InterceptingStdSChedulerFactory(); + + factory.ConfigLocation = new TestConfigLocation(ms, "description"); + + m_InitSchedulerFactory.Invoke(factory, new object[] { factoryToPass }); + + Assert.AreEqual(ConnectionStringValue, factoryToPass.Properties[ConnectionStringKey]); + + } + } + + internal class TestConfigLocation : InputStreamResource + { + public TestConfigLocation(Stream inputStream, string description) : base(inputStream, description) + { + } + } + + /// + /// ISchedulerFactory implementation for testing purposes. + /// + public class TestSchedulerFactory : ISchedulerFactory + { + private static IScheduler mockScheduler; + + /// + /// The mocked scheduler. + /// + public static IScheduler MockScheduler + { + get { return mockScheduler; } + } + + /// + /// + /// + public IScheduler GetScheduler() + { + return mockScheduler; + } + + /// + /// + /// + /// + public IScheduler GetScheduler(string schedName) + { + return mockScheduler; + } + +#if QUARTZ_2_0 + /// + /// + public ICollection AllSchedulers + { + get { return new List(); } + } +#else + /// + /// + public ICollection AllSchedulers + { + get { return new ArrayList(); } + } +#endif + + public static void Initialize() + { + mockScheduler = MockRepository.GenerateMock(); + } + } + + /// + /// Scheduler factory that supports property interception. + /// + public class InterceptingStdSChedulerFactory : StdSchedulerFactory + { + private NameValueCollection properties; + + /// + /// Initializes the factory. + /// + /// + public override void Initialize(NameValueCollection props) + { + this.properties = props; + } + + /// + /// Return propeties given to this factory at initialization time. + /// + public NameValueCollection Properties + { + get { return properties; } + } + } +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SimpleTriggerObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SimpleTriggerObjectTest.cs new file mode 100644 index 00000000..3486f2ad --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SimpleTriggerObjectTest.cs @@ -0,0 +1,134 @@ +/* +* Copyright 2002-2010 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.Collections.Generic; +using NUnit.Framework; + +using Quartz; +using Quartz.Job; + +#if QUARTZ_2_0 +using JobDetail = Quartz.Impl.JobDetailImpl; +#endif + + +namespace Spring.Scheduling.Quartz +{ + /// + /// Tests for . + /// + /// Marko Lahma (.NET) + [TestFixture] + public class SimpleTriggerObjectTest : TriggerObjectTest + { + private SimpleTriggerObject simpleTrigger; + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + simpleTrigger = new SimpleTriggerObject(); + simpleTrigger.ObjectName = TRIGGER_NAME; + Trigger = simpleTrigger; + } + + /// + /// Tests all possible misfire instructions for cron trigger + /// from strings to int. + /// + [Test] + public void TestMisfireInstructionNames() + { + string[] names = new string[] { "FireNow", "RescheduleNextWithExistingCount", "RescheduleNextWithRemainingCount", "RescheduleNowWithExistingRepeatCount", "RescheduleNowWithRemainingRepeatCount", "SmartPolicy" }; + foreach (string name in names) + { + simpleTrigger.MisfireInstructionName = name; + } + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public override void TestAfterPropertiesSet_Defaults() + { + simpleTrigger.AfterPropertiesSet(); + base.TestAfterPropertiesSet_Defaults(); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public override void TestAfterPropertiesSet_ValuesGiven() + { + simpleTrigger.StartDelay = TimeSpan.FromMilliseconds(100); + simpleTrigger.AfterPropertiesSet(); + base.TestAfterPropertiesSet_ValuesGiven(); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestAfterPropertiesSet_StartDelayGiven() + { + const int START_DELAY = 100000; + simpleTrigger.StartDelay = TimeSpan.FromMilliseconds(START_DELAY); + DateTime startTime = DateTime.UtcNow; + simpleTrigger.AfterPropertiesSet(); + AssertDateTimesEqualityWithAllowedDelta(startTime.AddMilliseconds(START_DELAY), simpleTrigger.StartTimeUtc, 1000); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public override void TestAfterPropertiesSet_JobDetailGiven() + { + const string jobName = "jobName"; + const string jobGroup = "jobGroup"; + JobDetail jd = new JobDetail(jobName, jobGroup, typeof(NoOpJob)); + simpleTrigger.JobDetail = jd; + simpleTrigger.AfterPropertiesSet(); + base.TestAfterPropertiesSet_JobDetailGiven(); + Assert.AreSame(jd, simpleTrigger.JobDetail, "job details weren't same"); + } + + /// + /// Tests AfterPropertiesSet behavior. + /// + [Test] + public void TestJobDataAsMap() + { +#if QUARTZ_2_0 + IDictionary data = new Dictionary(); +#else + Hashtable data = new Hashtable(); +#endif + data["foo"] = "bar"; + data["number"] = 123; + simpleTrigger.JobDataAsMap = data; + CollectionAssert.AreEquivalent(data, simpleTrigger.JobDataMap, "Data differed"); + } + + } + +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SpringObjectJobFactoryTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SpringObjectJobFactoryTest.cs new file mode 100644 index 00000000..97fd1587 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/SpringObjectJobFactoryTest.cs @@ -0,0 +1,147 @@ +/* +* Copyright 2002-2010 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.Collections; +using System.Collections.Generic; +using NUnit.Framework; + +using Quartz; +using Quartz.Job; +using Quartz.Spi; + +#if QUARTZ_2_0 +using Trigger = Quartz.Spi.IOperableTrigger; +using JobExecutionContext = Quartz.IJobExecutionContext; +using JobDetail = Quartz.IJobDetail; +using SimpleTrigger = Quartz.Impl.Triggers.SimpleTriggerImpl; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Unit tests for SpringObjectJobFactory. + /// + /// Marko Lahma (.NET) + [TestFixture] + public class SpringObjectJobFactoryTest + { + private SpringObjectJobFactory factory; + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + factory = new SpringObjectJobFactory(); + } + + /// + /// Tests job instane creation. + /// + [Test] + public void TestCreateJobInstance_SimpleDefaults() + { + Trigger trigger = new SimpleTrigger(); + TriggerFiredBundle bundle = TestUtil.CreateMinimalFiredBundleWithTypedJobDetail(typeof (NoOpJob), trigger); + +#if QUARTZ_2_0 + IJob job = factory.NewJob(bundle, null); +#else + IJob job = factory.NewJob(bundle); +#endif + Assert.IsNotNull(job, "Created job was null"); + } + + /// + /// Tests job instane creation. + /// + [Test] + public void TestCreateJobInstance_SchedulerContextGiven() + { + Trigger trigger = new SimpleTrigger(); + TriggerFiredBundle bundle = TestUtil.CreateMinimalFiredBundleWithTypedJobDetail(typeof(InjectableJob), trigger); + +#if QUARTZ_2_0 + IDictionary items = new Dictionary(); + items["foo"] = "bar"; + items["number"] = 123; + factory.SchedulerContext = new SchedulerContext(items); + InjectableJob job = (InjectableJob)factory.NewJob(bundle, null); +#else + IDictionary items = new Hashtable(); + items["foo"] = "bar"; + items["number"] = 123; + factory.SchedulerContext = new SchedulerContext(items); + InjectableJob job = (InjectableJob) factory.NewJob(bundle); +#endif + + Assert.IsNotNull(job, "Created job was null"); + Assert.AreEqual("bar", job.Foo, "string injection failed"); + Assert.AreEqual(123, job.Number, "integer injection failed"); + } + + /// + /// Tests job instane creation. + /// + [Test] + public void TestCreateJobInstance_IgnoredProperties() + { + factory.IgnoredUnknownProperties = new string[] {"foo", "baz"}; + Trigger trigger = new SimpleTrigger(); + trigger.JobDataMap["foo"] = "should not be injected"; + trigger.JobDataMap["number"] = 123; + TriggerFiredBundle bundle = TestUtil.CreateMinimalFiredBundleWithTypedJobDetail(typeof(InjectableJob), trigger); + +#if QUARTZ_2_0 + InjectableJob job = (InjectableJob)factory.NewJob(bundle, null); +#else + InjectableJob job = (InjectableJob)factory.NewJob(bundle); +#endif + Assert.IsNotNull(job, "Created job was null"); + Assert.AreEqual(123, job.Number, "integer injection failed"); + Assert.IsNull(job.Foo, "foo was injected when it was not supposed to "); + } + + } + + /// + /// Test job object that has injectable properties + /// + public class InjectableJob : NoOpJob + { + private int number; + private string foo; + + /// + /// Simple int property. + /// + public int Number + { + get { return number; } + set { number = value; } + } + + /// + /// Simple string property. + /// + public string Foo + { + get { return foo; } + set { foo = value; } + } + } +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestMethodInvokingTask.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestMethodInvokingTask.cs new file mode 100644 index 00000000..49ff0c8a --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestMethodInvokingTask.cs @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2010 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 +{ + + /// + /// Simple test task. + /// + /// Juergen Hoeller + public class TestMethodInvokingTask + { + /// + /// Counter for DoSomething and DoWait calls. + /// + public int counter; + private readonly object lockObject = new object(); + + /// + /// Simple test method. + /// + public void DoSomething() + { + counter++; + } + + /// + /// Waits until stop is called. + /// + public void DoWait() + { + counter++; + // wait until stop is called + lock (lockObject) + { + try + { + Monitor.Wait(lockObject); + } + catch (ThreadInterruptedException) + { + // fall through + } + } + } + + /// + /// Informs test object that stop should be called. + /// + public void Stop() + { + lock (lockObject) + { + Monitor.Pulse(lockObject); + } + } + + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestUtil.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestUtil.cs new file mode 100644 index 00000000..e4a13981 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TestUtil.cs @@ -0,0 +1,60 @@ +/* +* Copyright 2002-2010 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.Spi; + +#if QUARTZ_2_0 +using JobDetail = Quartz.Impl.JobDetailImpl; +using Trigger = Quartz.Spi.IOperableTrigger; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Quartz.NET integration testing helpers. + /// + /// Marko Lahma (.NET) + public class TestUtil + { + /// + /// Creates the minimal fired bundle with job detail that has + /// given job type. + /// + /// Type of the job. + /// Minimal TriggerFiredBundle + public static TriggerFiredBundle CreateMinimalFiredBundleWithTypedJobDetail(Type jobType) + { + return CreateMinimalFiredBundleWithTypedJobDetail(jobType, null); + } + + /// + /// Creates the minimal fired bundle with job detail that has + /// given job type. + /// + /// Type of the job. + /// The trigger. + /// Minimal TriggerFiredBundle + public static TriggerFiredBundle CreateMinimalFiredBundleWithTypedJobDetail(Type jobType, Trigger trigger) + { + JobDetail jd = new JobDetail("jobName", "jobGroup", jobType); + TriggerFiredBundle bundle = new TriggerFiredBundle(jd, trigger, null, false, null, null, null, null); + return bundle; + } + } +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TriggerObjectTest.cs b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TriggerObjectTest.cs new file mode 100644 index 00000000..f3bd2af9 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Scheduling/Quartz/TriggerObjectTest.cs @@ -0,0 +1,159 @@ +/* +* Copyright 2002-2010 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 NUnit.Framework; + +using Quartz; + +using Spring.Objects.Factory; + +#if QUARTZ_2_0 +using Trigger = Quartz.Spi.IOperableTrigger; +#endif + +namespace Spring.Scheduling.Quartz +{ + /// + /// Base class for testing triggers. Contains common functionality. + /// + [TestFixture] + public abstract class TriggerObjectTest + { + private Trigger trigger; + + /// + /// Constant name for tested triggers. + /// + protected const string TRIGGER_NAME = "trigger"; + + /// + /// TriggerObject under test. + /// + protected Trigger Trigger + { + set { trigger = value; } + } + + /// + /// Tests that TriggerObject defaults values as expected in AfterPropertiesSet. + /// + [Test] + public virtual void TestAfterPropertiesSet_Defaults() + { + ((IInitializingObject) trigger).AfterPropertiesSet(); + +#if QUARTZ_2_0 + Assert.AreEqual(TRIGGER_NAME, trigger.Key.Name, "trigger name mismatch"); + Assert.AreEqual(SchedulerConstants.DefaultGroup, trigger.Key.Group, "trigger group name mismatch"); + Assert.IsNull(trigger.JobKey, "trigger job name not null"); +#else + Assert.AreEqual(TRIGGER_NAME, trigger.Name, "trigger name mismatch"); + Assert.AreEqual(SchedulerConstants.DefaultGroup, trigger.Group, "trigger group name mismatch"); + Assert.IsNull(trigger.JobName, "trigger job name not null"); + Assert.AreEqual(SchedulerConstants.DefaultGroup, trigger.JobGroup, "trigger job group was not default"); +#endif + AssertDateTimesEqualityWithAllowedDelta(DateTime.UtcNow, trigger.StartTimeUtc, 1000); + } + + /// + /// Tests that TriggerObject defaults values as expected in AfterPropertiesSet. + /// + [Test] + public virtual void TestAfterPropertiesSet_ValuesGiven() + { + ((IInitializingObject)trigger).AfterPropertiesSet(); + + const string NAME = "newName"; + const string GROUP = "newGroup"; + DateTime START_TIME = new DateTime(1982, 6, 28, 13, 10, 0); + trigger.StartTimeUtc = START_TIME; +#if QUARTZ_2_0 + trigger.Key = new TriggerKey(NAME, GROUP); + Assert.AreEqual(NAME, trigger.Key.Name, "trigger name mismatch"); + Assert.AreEqual(GROUP, trigger.Key.Group, "trigger group name mismatch"); +#else + trigger.Name = NAME; + trigger.Group = GROUP; + Assert.AreEqual(NAME, trigger.Name, "trigger name mismatch"); + Assert.AreEqual(GROUP, trigger.Group, "trigger group name mismatch"); +#endif + AssertDateTimesEqualityWithAllowedDelta(START_TIME, trigger.StartTimeUtc, 1000); + } + + /// + /// Tests that TriggerObject defaults values as expected in AfterPropertiesSet. + /// + [Test] + public virtual void TestAfterPropertiesSet_JobDetailGiven() + { + ((IInitializingObject)trigger).AfterPropertiesSet(); + + const string jobName = "jobName"; + const string jobGroup = "jobGroup"; +#if QUARTZ_2_0 + Assert.AreEqual(jobName, trigger.JobKey.Name, "trigger job name was not from job detail"); + Assert.AreEqual(jobGroup, trigger.JobKey.Group, "trigger job group was not from job detail"); +#else + Assert.AreEqual(jobName, trigger.JobName, "trigger job name was not from job detail"); + Assert.AreEqual(jobGroup, trigger.JobGroup, "trigger job group was not from job detail"); +#endif + } + +#if QUARTZ_2_0 + /// + /// Tests whether two datetimes are close enough. + /// + /// + /// + /// + protected static void AssertDateTimesEqualityWithAllowedDelta(DateTimeOffset d1, DateTimeOffset d2, int allowedDeltaInMilliseconds) + { + int diffInMillis = (int) Math.Abs((d1 - d2).TotalMilliseconds); + Assert.LessOrEqual(diffInMillis, allowedDeltaInMilliseconds, "too much difference in times"); + } +#else + + /// + /// Tests whether two datetimes are close enough. + /// + /// + /// + /// + protected static void AssertDateTimesEqualityWithAllowedDelta(DateTime d1, DateTime d2, int allowedDeltaInMilliseconds) + { + int diffInMillis = (int)Math.Abs((d1 - d2).TotalMilliseconds); + Assert.LessOrEqual(diffInMillis, allowedDeltaInMilliseconds, "too much difference in times"); + } + + + /// + /// Tests that TriggerObject defaults values as expected in AfterPropertiesSet. + /// + [Test] + public virtual void TestTriggerListenerNames_Valid() + { + ((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"); + } +#endif + } + +} diff --git a/test/Spring/Spring.Scheduling.Quartz20.Tests/Spring.Scheduling.Quartz20.Tests.2010.csproj b/test/Spring/Spring.Scheduling.Quartz20.Tests/Spring.Scheduling.Quartz20.Tests.2010.csproj index ec550741..0a94ad29 100644 --- a/test/Spring/Spring.Scheduling.Quartz20.Tests/Spring.Scheduling.Quartz20.Tests.2010.csproj +++ b/test/Spring/Spring.Scheduling.Quartz20.Tests/Spring.Scheduling.Quartz20.Tests.2010.csproj @@ -71,50 +71,6 @@ Spring.Core.Tests.2010 - - - QuartzCompilerOptionsTests.cs - - - Scheduling\Quartz\AdaptableJobFactoryTest.cs - - - Scheduling\Quartz\CronTriggerObjectTest.cs - - - Scheduling\Quartz\JobDetailObjectTest.cs - - - Scheduling\Quartz\MethodInvokingJobDetailFactoryObjectTest.cs - - - Scheduling\Quartz\MethodInvokingJobTest.cs - - - Scheduling\Quartz\QuartzSupportTests.cs - - - Scheduling\Quartz\QuartzTestObject.cs - - - Scheduling\Quartz\SchedulerFactoryObjectTest.cs - - - Scheduling\Quartz\SimpleTriggerObjectTest.cs - - - Scheduling\Quartz\SpringObjectJobFactoryTest.cs - - - Scheduling\Quartz\TestMethodInvokingTask.cs - - - Scheduling\Quartz\TestUtil.cs - - - Scheduling\Quartz\TriggerObjectTest.cs - - PreserveNewest @@ -137,6 +93,23 @@ PreserveNewest + + + + + + + + + + + + + + + + +