secondhand 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +19 -0
- data/README.md +286 -0
- data/Rakefile +82 -0
- data/lib/quartz/log4j-1.2.14.jar +0 -0
- data/lib/quartz/quartz-1.8.4.jar +0 -0
- data/lib/quartz/slf4j-api-1.6.0.jar +0 -0
- data/lib/quartz/slf4j-log4j12-1.6.0.jar +0 -0
- data/lib/secondhand.rb +89 -0
- data/lib/secondhand/job.rb +58 -0
- data/lib/secondhand/jobs.rb +25 -0
- data/lib/secondhand/logging.rb +30 -0
- data/lib/secondhand/scheduler.rb +51 -0
- data/lib/secondhand/trigger.rb +24 -0
- data/lib/secondhand/version.rb +3 -0
- data/test/integration/test_secondhand_run.rb +48 -0
- data/test/test_helper.rb +32 -0
- data/test/unit/test_job.rb +45 -0
- data/test/unit/test_jobs.rb +20 -0
- data/test/unit/test_logger.rb +30 -0
- data/test/unit/test_scheduler.rb +33 -0
- data/test/unit/test_secondhand.rb +29 -0
- data/test/unit/test_trigger.rb +22 -0
- metadata +84 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Amphora Reseach Systems Ltd.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
Secondhand
|
2
|
+
==========
|
3
|
+
|
4
|
+
Secondhand is a ruby-friendly wrapper around the [Quartz Job Scheduler][0].
|
5
|
+
Create jobs, tell Secondhand when they should run, then sit back and enjoy.
|
6
|
+
|
7
|
+
_Secondhand is JRuby only what with Quartz being Java and all._
|
8
|
+
|
9
|
+
|
10
|
+
Overview
|
11
|
+
--------
|
12
|
+
|
13
|
+
Secondhand was created to:
|
14
|
+
|
15
|
+
* provide a clean API to job scheduling (with few, if any, dependencies)
|
16
|
+
* give you more control over job resource utilization/allocation
|
17
|
+
* take advantage of the Quartz service
|
18
|
+
|
19
|
+
You can schedule jobs that will run right away, on a specific date, or use
|
20
|
+
a repeating schedule with a familiar cron syntax. Secondhand allows you to
|
21
|
+
adjust the number of Quartz worker threads, and create jobs in a way that makes
|
22
|
+
sense in your environment.
|
23
|
+
|
24
|
+
Secondhand does not try to be a full interface to all the power and
|
25
|
+
capabilities of Quartz. Think of it more like a nice subset that allows you
|
26
|
+
to have robust job scheduling without learning all about Quartz. (You can always
|
27
|
+
get to the underlying objects and tinker if you need to.)
|
28
|
+
|
29
|
+
Like Resque, Jobs in Secondhand can be any class or module that responds to
|
30
|
+
`perform` and, like some of the other Quartz+Ruby libraries out there, a job
|
31
|
+
can be a block/proc. (Secondhand jobs don't take parameters [yet].)
|
32
|
+
|
33
|
+
|
34
|
+
Workers
|
35
|
+
-------
|
36
|
+
|
37
|
+
Quartz spawns worker threads to execute jobs when triggers are fired. Secondhand
|
38
|
+
defaults to 5 workers as recommended in the Quartz documentation - while Quartz
|
39
|
+
defaults to 10. Note: Quartz will block itself if all worker threads are busy
|
40
|
+
when a trigger fires.
|
41
|
+
|
42
|
+
|
43
|
+
Jobs
|
44
|
+
----
|
45
|
+
|
46
|
+
A job in Secondhand might look like this:
|
47
|
+
|
48
|
+
class Scan
|
49
|
+
def self.perform # class method
|
50
|
+
do_some_work
|
51
|
+
log_it
|
52
|
+
do_some_more_work
|
53
|
+
close_connections
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# To run this job immediately you would use:
|
58
|
+
|
59
|
+
Secondhand.schedule Scan
|
60
|
+
|
61
|
+
|
62
|
+
### OR
|
63
|
+
|
64
|
+
You can go with the block syntax for an immediate job:
|
65
|
+
|
66
|
+
Secondhand.schedule :the_scan do
|
67
|
+
do_some_work
|
68
|
+
log_it
|
69
|
+
do_some_more_work
|
70
|
+
close_connections
|
71
|
+
end
|
72
|
+
|
73
|
+
This scheduled job is added to the Quartz scheduler and associated with a
|
74
|
+
trigger. In the case of the jobs above, the trigger is a `SimpleTrigger` that
|
75
|
+
executes immediately.
|
76
|
+
|
77
|
+
*Important*
|
78
|
+
Each job must have a unique name. Secondhand calls #to_s on the first
|
79
|
+
parameter and uses that for the job name. This means that you can't schedule the
|
80
|
+
same class twice [yet].
|
81
|
+
|
82
|
+
While the block syntax is nice to look at it does have some drawbacks. Object
|
83
|
+
allocation happens when the block is created and references to these objects
|
84
|
+
are held for the lifetime of the block. In our case, this lifetime is while
|
85
|
+
the Quartz service is running since the job is scheduled, stored, and called by
|
86
|
+
a trigger.
|
87
|
+
|
88
|
+
Having the ability to just schedule a class/module that responds to `perform` as
|
89
|
+
a job means that only a reference to that constant is held by the Quartz service
|
90
|
+
and object allocation happens when `perform` is executed.
|
91
|
+
|
92
|
+
Thinking of these jobs as atomic units of work may help when deciding how they
|
93
|
+
should be scheduled in your application.
|
94
|
+
|
95
|
+
## Persisted Jobs
|
96
|
+
|
97
|
+
Secondhand just uses the default Quartz RAM job scheduler. This provides no
|
98
|
+
durability for jobs to live after your application exists. It is up to you to
|
99
|
+
make sure they get rescheduled.
|
100
|
+
|
101
|
+
|
102
|
+
Usage
|
103
|
+
-----
|
104
|
+
|
105
|
+
The scheduler must be started before you schedule jobs.
|
106
|
+
|
107
|
+
Secondhand.start(NUM_THREADS)
|
108
|
+
|
109
|
+
Then you get to scheduling ...
|
110
|
+
|
111
|
+
... job that runs immediately after being scheduled:
|
112
|
+
|
113
|
+
Secondhand.schedule JobClass
|
114
|
+
|
115
|
+
... block that runs immediately:
|
116
|
+
|
117
|
+
Secondhand.schedule :some_job do
|
118
|
+
# some job stuff
|
119
|
+
end
|
120
|
+
|
121
|
+
... job that runs on Friday, Dec 10 at 11:53:35 PST:
|
122
|
+
|
123
|
+
Secondhand.schedule JobClass, :at => "Fri Dec 10 22:53:35 -0800 2010"
|
124
|
+
|
125
|
+
... job that runs two minutes from now:
|
126
|
+
|
127
|
+
Secondhand.schedule JobClass, :at => Time.now + 120
|
128
|
+
|
129
|
+
... job that runs every 15 seconds every day:
|
130
|
+
|
131
|
+
Secondhand.schedule JobClass, :with_cron => "0/15 * * * * ? *"
|
132
|
+
|
133
|
+
## :at
|
134
|
+
|
135
|
+
Pass in the :at options to provide a specific moment in time for a job to be
|
136
|
+
triggered. This is useful when you want to run a job once - perhaps in response
|
137
|
+
to some user input.
|
138
|
+
|
139
|
+
## :with_cron
|
140
|
+
|
141
|
+
Use :with_cron to provide Secondhand with a cron expression that indicates when
|
142
|
+
a job should be triggered. This expression is passed straight to Quartz so all
|
143
|
+
options are supported. Just send in a string with the following format:
|
144
|
+
|
145
|
+
_From the Quartz CronExpression documentation:_
|
146
|
+
|
147
|
+
> Cron expressions are comprised of 6 required fields and one optional field
|
148
|
+
> separated by white space. The fields respectively are described as follows:
|
149
|
+
|
150
|
+
Field Name Allowed Values Allowed Special Characters
|
151
|
+
|
152
|
+
Seconds 0-59 , - * /
|
153
|
+
Minutes 0-59 , - * /
|
154
|
+
Hours 0-23 , - * /
|
155
|
+
Day-of-month 1-31 , - * ? / L W
|
156
|
+
Month 1-12 or JAN-DEC , - * /
|
157
|
+
Day-of-Week 1-7 or SUN-SAT , - * ? / L #
|
158
|
+
Year (Optional) empty, 1970-2199 , - * /
|
159
|
+
|
160
|
+
> The `*` character is used to specify all values. For example, "*" in the
|
161
|
+
> minute field means "every minute".
|
162
|
+
>
|
163
|
+
> The `?` character is allowed for the day-of-month and day-of-week fields. It
|
164
|
+
> is used to specify 'no specific value'. This is useful when you need to
|
165
|
+
> specify something in one of the two fields, but not the other.
|
166
|
+
>
|
167
|
+
> The `-` character is used to specify ranges For example "10-12" in the hour
|
168
|
+
> field means "the hours 10, 11 and 12".
|
169
|
+
>
|
170
|
+
> The `,` character is used to specify additional values. For example
|
171
|
+
> "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday,
|
172
|
+
> and Friday".
|
173
|
+
>
|
174
|
+
> The `/` character is used to specify increments. For example "0/15" in the
|
175
|
+
> seconds field means "the seconds 0, 15, 30, and 45". And "5/15" in the seconds
|
176
|
+
> field means "the seconds 5, 20, 35, and 50". Specifying '*' before the '/' is
|
177
|
+
> equivalent to specifying 0 is the value to start with. Essentially, for each
|
178
|
+
> field in the expression, there is a set of numbers that can be turned on or
|
179
|
+
> off. For seconds and minutes, the numbers range from 0 to 59. For hours 0 to
|
180
|
+
> 23, for days of the month 0 to 31, and for months 1 to 12. The "/" character
|
181
|
+
> simply helps you turn on every "nth" value in the given set. Thus "7/6" in the
|
182
|
+
> month field only turns on month "7", it does NOT mean every 6th month, please
|
183
|
+
> note that subtlety.
|
184
|
+
>
|
185
|
+
> The `L` character is allowed for the day-of-month and day-of-week fields. This
|
186
|
+
> character is short-hand for "last", but it has different meaning in each of
|
187
|
+
> the two fields. For example, the value "L" in the day-of-month field means
|
188
|
+
> "the last day of the month" - day 31 for January, day 28 for February on
|
189
|
+
> non-leap years.
|
190
|
+
> If used in the day-of-week field by itself, it simply means "7" or "SAT". But
|
191
|
+
> if used in the day-of-week field after another value, it means "the last xxx
|
192
|
+
> day of the month" - for example "6L" means "the last friday of the month".
|
193
|
+
> When using the 'L' option, it is important not to specify lists, or ranges of
|
194
|
+
> values, as you'll get confusing results.
|
195
|
+
>
|
196
|
+
> The `W` character is allowed for the day-of-month field. This character is
|
197
|
+
> used to specify the weekday (Monday-Friday) nearest the given day. As an
|
198
|
+
> example, if you were to specify "15W" as the value for the day-of-month field,
|
199
|
+
> the meaning is: "the nearest weekday to the 15th of the month". So if the 15th
|
200
|
+
> is a Saturday, the trigger will fire on Friday the 14th. If the 15th is a
|
201
|
+
> Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday,
|
202
|
+
> then it will fire on Tuesday the 15th. However if you specify "1W" as the
|
203
|
+
> value for day-of-month, and the 1st is a Saturday, the trigger will fire on
|
204
|
+
> Monday the 3rd, as it will not 'jump' over the boundary of a month's days.
|
205
|
+
> The 'W' character can only be specified when the day-of-month is a single day,
|
206
|
+
> not a range or list of days.
|
207
|
+
>
|
208
|
+
> The `L` and `W` characters can also be combined for the day-of-month
|
209
|
+
> expression to yield `LW`, which translates to "last weekday of the month".
|
210
|
+
>
|
211
|
+
> The `#` character is allowed for the day-of-week field. This character is used
|
212
|
+
> to specify "the nth" XXX day of the month. For example, the value of "6#3" in
|
213
|
+
> the day-of-week field means the third Friday of the month (day 6 = Friday and
|
214
|
+
> "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of
|
215
|
+
> the month and "4#5" = the fifth Wednesday of the month. Note that if you
|
216
|
+
> specify "#5" and there is not 5 of the given day-of-week in the month, then
|
217
|
+
> no firing will occur that month. If the '#' character is used, there can only
|
218
|
+
> be one expression in the day-of-week field ("3#1,6#3" is not valid, since
|
219
|
+
> there are two expressions).
|
220
|
+
>
|
221
|
+
> The legal characters and the names of months and days of the week are not case
|
222
|
+
> sensitive.
|
223
|
+
>
|
224
|
+
> NOTES:
|
225
|
+
>
|
226
|
+
> Support for specifying both a day-of-week and a day-of-month value is not
|
227
|
+
> complete (you'll need to use the '?' character in one of these fields).
|
228
|
+
> Overflowing ranges is supported - that is, having a larger number on the left
|
229
|
+
> hand side than the right. You might do 22-2 to catch 10 o'clock at night until
|
230
|
+
> 2 o'clock in the morning, or you might have NOV-FEB. It is very important to
|
231
|
+
> note that overuse of overflowing ranges creates ranges that don't make sense
|
232
|
+
> and no effort has been made to determine which interpretation CronExpression
|
233
|
+
> chooses. An example would be "0 0 14-6 ? * FRI-MON".
|
234
|
+
|
235
|
+
|
236
|
+
Installation
|
237
|
+
------------
|
238
|
+
|
239
|
+
Coming soon to a command line near you.
|
240
|
+
|
241
|
+
|
242
|
+
## Quartz
|
243
|
+
|
244
|
+
Version supported in this release: 1.8.4
|
245
|
+
|
246
|
+
_Other version may work, but the tests and jars included are for 1.8.4._
|
247
|
+
|
248
|
+
All the jars needed for Quartz are in `lib\quartz`. They are used by Secondhand
|
249
|
+
unless JRuby::Rack is detected. If JRuby::Rack is present, it is assumed that
|
250
|
+
you will have the jars under WEB-INF or get them on the classpath however you
|
251
|
+
choose to.
|
252
|
+
|
253
|
+
|
254
|
+
What's in a name?
|
255
|
+
----------------
|
256
|
+
Secondhand was inspired from several other Quartz+Ruby implementations:
|
257
|
+
|
258
|
+
* quartz-jruby: <https://github.com/artha42/quartz-jruby>
|
259
|
+
* jruby-quartz: <https://github.com/techwhizbang/jruby-quartz>
|
260
|
+
|
261
|
+
Chiefly, quartz-jruby demonstrated what was needed to get the Quartz JobFactory
|
262
|
+
replaced with a Ruby class and playing nicely with the other components. Thanks
|
263
|
+
vagmi!
|
264
|
+
|
265
|
+
The jobs were taken straight from Resque <https://github.com/defunkt/resque> as
|
266
|
+
well as some other flourishes here and there.
|
267
|
+
|
268
|
+
The project started as a fork of quartz-jruby so I figured it was a second-hand
|
269
|
+
project and the clock reference wasn't bad either.
|
270
|
+
|
271
|
+
|
272
|
+
Contributors
|
273
|
+
------------
|
274
|
+
|
275
|
+
Don Morrison (@elskwid)
|
276
|
+
|
277
|
+
|
278
|
+
License
|
279
|
+
-------
|
280
|
+
|
281
|
+
Copyright (c) 2010 Amphora Research Systems Ltd.
|
282
|
+
|
283
|
+
Secondhand is licensed under the MIT license. See LICENSE for details.
|
284
|
+
|
285
|
+
|
286
|
+
[0]: http://www.quartz-scheduler.org/
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "rake/gempackagetask"
|
5
|
+
require "rake/rdoctask"
|
6
|
+
|
7
|
+
require "rake/testtask"
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
task :default => ["test"]
|
16
|
+
|
17
|
+
# This builds the actual gem. For details of what all these options
|
18
|
+
# mean, and other ones you can add, check the documentation here:
|
19
|
+
#
|
20
|
+
# http://rubygems.org/read/chapter/20
|
21
|
+
#
|
22
|
+
spec = Gem::Specification.new do |s|
|
23
|
+
|
24
|
+
# Change these as appropriate
|
25
|
+
s.name = "secondhand"
|
26
|
+
s.version = "0.1.0"
|
27
|
+
s.summary = "Secondhand is a ruby-friendly wrapper around the Quartz Job Scheduler. Create jobs, tell Secondhand when they should run, then sit back and enjoy."
|
28
|
+
s.author = "Don Morrison"
|
29
|
+
s.email = "elskwid@gmail.com"
|
30
|
+
s.homepage = "https://github.com/amphora/secondhand"
|
31
|
+
|
32
|
+
s.has_rdoc = true
|
33
|
+
s.extra_rdoc_files = %w(README.md)
|
34
|
+
s.rdoc_options = %w(--main README.md)
|
35
|
+
|
36
|
+
# Add any extra files to include in the gem
|
37
|
+
s.files = %w(LICENSE Rakefile README.md) + Dir.glob("{test,lib}/**/*")
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
|
40
|
+
# If you want to depend on other gems, add them here, along with any
|
41
|
+
# relevant versions
|
42
|
+
# s.add_dependency("some_other_gem", "~> 0.1.0")
|
43
|
+
|
44
|
+
# If your tests use any gems, include them here
|
45
|
+
# s.add_development_dependency("mocha") # for example
|
46
|
+
end
|
47
|
+
|
48
|
+
# This task actually builds the gem. We also regenerate a static
|
49
|
+
# .gemspec file, which is useful if something (i.e. GitHub) will
|
50
|
+
# be automatically building a gem for this project. If you're not
|
51
|
+
# using GitHub, edit as appropriate.
|
52
|
+
#
|
53
|
+
# To publish your gem online, install the 'gemcutter' gem; Read more
|
54
|
+
# about that here: http://gemcutter.org/pages/gem_docs
|
55
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
56
|
+
pkg.gem_spec = spec
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Build the gemspec file #{spec.name}.gemspec"
|
60
|
+
task :gemspec do
|
61
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
62
|
+
File.open(file, "w") {|f| f << spec.to_ruby }
|
63
|
+
end
|
64
|
+
|
65
|
+
# If you don't want to generate the .gemspec file, just remove this line. Reasons
|
66
|
+
# why you might want to generate a gemspec:
|
67
|
+
# - using bundler with a git source
|
68
|
+
# - building the gem without rake (i.e. gem build blah.gemspec)
|
69
|
+
# - maybe others?
|
70
|
+
task :package => :gemspec
|
71
|
+
|
72
|
+
# Generate documentation
|
73
|
+
Rake::RDocTask.new do |rd|
|
74
|
+
rd.main = "README.md"
|
75
|
+
rd.rdoc_files.include("README.md", "lib/**/*.rb")
|
76
|
+
rd.rdoc_dir = "rdoc"
|
77
|
+
end
|
78
|
+
|
79
|
+
desc 'Clear out RDoc and generated packages'
|
80
|
+
task :clean => [:clobber_rdoc, :clobber_package] do
|
81
|
+
rm "#{spec.name}.gemspec"
|
82
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/secondhand.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
# If JRuby::Rack::VERSION is defined then Secondhand is in a servlet.
|
4
|
+
# Secondhand does not need to require jar archives in this case
|
5
|
+
# as those should be in WEB-INF/lib and already in the classpath
|
6
|
+
unless defined?(JRuby::Rack::VERSION)
|
7
|
+
# jars
|
8
|
+
require 'quartz/log4j-1.2.14.jar'
|
9
|
+
require 'quartz/quartz-1.8.4.jar'
|
10
|
+
require 'quartz/slf4j-api-1.6.0.jar'
|
11
|
+
require 'quartz/slf4j-log4j12-1.6.0.jar'
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'secondhand/logging'
|
15
|
+
require 'secondhand/job'
|
16
|
+
require 'secondhand/jobs'
|
17
|
+
require 'secondhand/scheduler'
|
18
|
+
require 'secondhand/trigger'
|
19
|
+
require 'secondhand/version'
|
20
|
+
|
21
|
+
module Secondhand
|
22
|
+
import java.lang.System
|
23
|
+
import org.quartz.impl.StdScheduler
|
24
|
+
import org.quartz.impl.StdSchedulerFactory
|
25
|
+
|
26
|
+
extend Scheduler
|
27
|
+
extend self
|
28
|
+
|
29
|
+
# Raised when the scheduler is used before being initialized
|
30
|
+
class NoSchedulerError < RuntimeError; end
|
31
|
+
|
32
|
+
DEFAULT_GROUP = "SECONDHAND"
|
33
|
+
DEFAULT_THREAD_COUNT = 5
|
34
|
+
@scheduler = nil
|
35
|
+
|
36
|
+
def reset_thread_count
|
37
|
+
@thread_count = DEFAULT_THREAD_COUNT
|
38
|
+
end
|
39
|
+
|
40
|
+
def thread_count=(threads)
|
41
|
+
@thread_count = threads
|
42
|
+
end
|
43
|
+
|
44
|
+
def thread_count
|
45
|
+
reset_thread_count unless @thread_count
|
46
|
+
@thread_count
|
47
|
+
end
|
48
|
+
|
49
|
+
def start(threads = nil)
|
50
|
+
initialize_scheduler(threads)
|
51
|
+
scheduler.start
|
52
|
+
end
|
53
|
+
alias_method :run, :start
|
54
|
+
|
55
|
+
def initialize_scheduler(threads = nil)
|
56
|
+
@thread_count = threads if threads
|
57
|
+
# Quartz uses this property to set the number of worker threads
|
58
|
+
System.set_property("org.quartz.threadPool.threadCount", @thread_count.to_s)
|
59
|
+
# Stop Quartz from looking for updates
|
60
|
+
System.set_property("org.quartz.scheduler.skipUpdateCheck", "true")
|
61
|
+
# Initialize the default scheduler
|
62
|
+
@scheduler = StdSchedulerFactory.default_scheduler
|
63
|
+
# Set up our custom job factory
|
64
|
+
@scheduler.job_factory = Job::Factory.instance
|
65
|
+
end
|
66
|
+
|
67
|
+
def schedule(job, opts={}, &block)
|
68
|
+
name = job.to_s
|
69
|
+
scheduler.schedule_job(Job.create(name, block ? block : job), Trigger.create(name, opts))
|
70
|
+
end
|
71
|
+
|
72
|
+
# Shortcuts
|
73
|
+
|
74
|
+
# Raise an error if the scheduler isn't ready - with Quartz we have an order
|
75
|
+
# of operations to follow. This also lets us pass the threads in with .start
|
76
|
+
def scheduler
|
77
|
+
raise NoSchedulerError, "Scheduler isn't initialized. Call Secondhand.start before scheduling jobs." unless @scheduler
|
78
|
+
@scheduler
|
79
|
+
end
|
80
|
+
|
81
|
+
def jobs
|
82
|
+
Jobs
|
83
|
+
end
|
84
|
+
|
85
|
+
def job_names
|
86
|
+
Jobs.names
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'singleton' # for factory
|
2
|
+
|
3
|
+
module Secondhand
|
4
|
+
import org.quartz.JobDetail
|
5
|
+
import org.quartz.spi.JobFactory
|
6
|
+
|
7
|
+
module Job
|
8
|
+
|
9
|
+
def self.create(name, work)
|
10
|
+
# Store the job and work for our job factory impl
|
11
|
+
Details.new(name, Secondhand::DEFAULT_GROUP, work)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Quartz croaks with a Ruby created JobClass so, hijack the job here
|
15
|
+
# and bend it to our will
|
16
|
+
class Details < Java::OrgQuartz::JobDetail
|
17
|
+
attr_accessor :job
|
18
|
+
|
19
|
+
def initialize(name, group, work)
|
20
|
+
super()
|
21
|
+
# name & group are required by Quartz
|
22
|
+
set_name name.to_s
|
23
|
+
set_group group.to_s
|
24
|
+
# add the execute method if needed
|
25
|
+
work.extend Job::Runner unless work.respond_to?(:execute)
|
26
|
+
@job = work
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Override validate so Quartz doesn't throw an error about the JobClass
|
31
|
+
def validate; end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
# Thin wrapper around a job to provide the expected Quartz interface
|
36
|
+
module Runner
|
37
|
+
def execute(context = nil)
|
38
|
+
self.respond_to?(:call) ? self.call : self.perform
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create custom JobFactory to hand out jobs from a Ruby collection
|
43
|
+
# and not from the Quartz job list. Avoids problems with JRuby and
|
44
|
+
# interfaces to and from Ruby.
|
45
|
+
class Factory
|
46
|
+
include Singleton
|
47
|
+
include JobFactory
|
48
|
+
|
49
|
+
# Quartz calls new_job on the factory to get the job details
|
50
|
+
# * event = execution-time data about the job (from Quartz)
|
51
|
+
def new_job(event)
|
52
|
+
# Get a job from the factory - Quartz calls #execute(context) on this
|
53
|
+
event.get_job_detail.job
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Secondhand
|
2
|
+
module Jobs
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Provide an array-like method to get to the job details
|
6
|
+
def [](name)
|
7
|
+
details = scheduler.get_job_detail(name, group)
|
8
|
+
details.job if details
|
9
|
+
end
|
10
|
+
|
11
|
+
def names
|
12
|
+
Array(scheduler.get_job_names(DEFAULT_GROUP))
|
13
|
+
end
|
14
|
+
|
15
|
+
# Shortcuts
|
16
|
+
|
17
|
+
def scheduler
|
18
|
+
Secondhand.scheduler
|
19
|
+
end
|
20
|
+
|
21
|
+
def group
|
22
|
+
Secondhand::DEFAULT_GROUP
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Secondhand
|
4
|
+
module Logger
|
5
|
+
import org.apache.log4j
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def logger
|
9
|
+
Java::OrgApache::log4j::Logger.get_logger("Secondhand")
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(msg)
|
13
|
+
logger.log(Level::DEBUG, msg)
|
14
|
+
end
|
15
|
+
|
16
|
+
def info(msg)
|
17
|
+
logger.log(Level::INFO, msg)
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(msg)
|
21
|
+
logger.log(Level::ERROR, msg)
|
22
|
+
end
|
23
|
+
|
24
|
+
def warn(msg)
|
25
|
+
logger.log(Level::WARN, msg)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Secondhand
|
4
|
+
module Scheduler
|
5
|
+
extend Forwardable
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Add some friendly faces
|
9
|
+
def_delegators :scheduler, :shutdown,
|
10
|
+
:stop,
|
11
|
+
:stop_now,
|
12
|
+
:started?,
|
13
|
+
:running?,
|
14
|
+
:paused?,
|
15
|
+
:standing_by?,
|
16
|
+
:shutdown?,
|
17
|
+
:stopped?
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
# Add some sugar on StdScheduler
|
22
|
+
class Java::OrgQuartzImpl::StdScheduler
|
23
|
+
def run
|
24
|
+
start
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop_now
|
28
|
+
shutdown
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
shutdown(true) # waits for all jobs to finish
|
33
|
+
end
|
34
|
+
|
35
|
+
def running?
|
36
|
+
is_started
|
37
|
+
end
|
38
|
+
|
39
|
+
def paused?
|
40
|
+
is_in_standby_mode
|
41
|
+
end
|
42
|
+
alias_method :standing_by?, :paused?
|
43
|
+
|
44
|
+
def stopped?
|
45
|
+
is_shutdown
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Secondhand
|
2
|
+
import org.quartz.CronTrigger
|
3
|
+
import org.quartz.SimpleTrigger
|
4
|
+
|
5
|
+
class Trigger
|
6
|
+
|
7
|
+
# Return correct Quartz Trigger
|
8
|
+
def self.create(name, opts)
|
9
|
+
args = ["#{name}_trigger", Secondhand::DEFAULT_GROUP]
|
10
|
+
|
11
|
+
# with_cron trumps other options
|
12
|
+
if opts[:with_cron]
|
13
|
+
args << opts[:with_cron]
|
14
|
+
CronTrigger.new(*(args).compact)
|
15
|
+
else
|
16
|
+
args << opts[:at] || opts[:on]
|
17
|
+
SimpleTrigger.new(*(args).compact)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class TestSecondhandRun < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
Secondhand.reset_thread_count
|
7
|
+
org.apache.log4j.BasicConfigurator.configure
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_five_second_cron_job
|
11
|
+
Secondhand.start(1)
|
12
|
+
assert Secondhand.started?
|
13
|
+
|
14
|
+
# This job will say 'something' every 5 seconds
|
15
|
+
Secondhand.schedule SaySomething, :with_cron => "0/5 * * * * ?"
|
16
|
+
assert_equal SaySomething, Secondhand.jobs["SaySomething"]
|
17
|
+
|
18
|
+
# This job will say 'block something' every 10 seconds
|
19
|
+
Secondhand.schedule "my_block", :with_cron => "0/10 * * * * ?" do
|
20
|
+
puts "block something [10 sec]"
|
21
|
+
end
|
22
|
+
|
23
|
+
# This job will say 'One shot job!' as soon as it is scheduled
|
24
|
+
Secondhand.schedule "one_shot" do
|
25
|
+
puts "One shot job!"
|
26
|
+
end
|
27
|
+
|
28
|
+
puts "sleeping while the scheduler runs ..."
|
29
|
+
sleep 20
|
30
|
+
|
31
|
+
Secondhand.stop
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class SaySomething
|
37
|
+
# You could add the logging methods to your class
|
38
|
+
# extend Secondhand::Logger
|
39
|
+
|
40
|
+
def self.perform
|
41
|
+
puts "something [5sec]"
|
42
|
+
# Or you can just call the logger
|
43
|
+
# Secondhand::Logger.info "something [5sec] --> through Log4J"
|
44
|
+
|
45
|
+
# With extend Logging from above
|
46
|
+
# self.info "another thing ==> through Log4j"
|
47
|
+
end
|
48
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "/../lib"))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
# project
|
7
|
+
require 'secondhand'
|
8
|
+
|
9
|
+
# BasicConfigurator.configure uses a ConsoleAppender by default
|
10
|
+
# uncomment to see all the Quartz logging magic!
|
11
|
+
# org.apache.log4j.BasicConfigurator.configure
|
12
|
+
|
13
|
+
## NullAppender silences log4j messages
|
14
|
+
import org.apache.log4j.varia.NullAppender
|
15
|
+
org.apache.log4j.BasicConfigurator.configure(NullAppender.new)
|
16
|
+
|
17
|
+
module TestHelper
|
18
|
+
#
|
19
|
+
end
|
20
|
+
|
21
|
+
class Test::Unit::TestCase
|
22
|
+
include TestHelper
|
23
|
+
end
|
24
|
+
|
25
|
+
## utility classes for tests
|
26
|
+
class TestSimpleJob
|
27
|
+
def self.perform
|
28
|
+
"ALLOK"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class TestSimpleJob2 < TestSimpleJob; end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class TestJob < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
Secondhand.start(1)
|
7
|
+
@factory = Secondhand::Job::Factory.instance
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_simple_job_details
|
11
|
+
details = Secondhand::Job.create "TestSimpleJob2", TestSimpleJob2
|
12
|
+
assert_kind_of Java::OrgQuartz::JobDetail, details
|
13
|
+
assert_equal TestSimpleJob2, details.job
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_simple_class_job
|
17
|
+
details = Secondhand::Job.create "TestSimpleJob", TestSimpleJob
|
18
|
+
assert_equal TestSimpleJob, details.job
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_simple_named_job_block
|
22
|
+
details = Secondhand::Job.create "some_job", Proc.new{"Some job text"}
|
23
|
+
assert_equal "Some job text", details.job.call
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_runner_module_on_class
|
27
|
+
details = Secondhand::Job.create "TestSimpleJob2", TestSimpleJob2
|
28
|
+
assert details.job.respond_to?(:execute)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_runner_module_on_block
|
32
|
+
details = Secondhand::Job.create "some_other_job", Proc.new{"Some other job text"}
|
33
|
+
assert details.job.respond_to?(:execute)
|
34
|
+
end
|
35
|
+
|
36
|
+
# job factory tests
|
37
|
+
def test_kind_of
|
38
|
+
assert_kind_of Java::OrgQuartz::spi::JobFactory, @factory
|
39
|
+
assert_kind_of Secondhand::Job::Factory, @factory
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_respond_to_new_job
|
43
|
+
assert @factory.respond_to?(:new_job)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class TestJobs < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
Secondhand.start(1)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_bracket_method
|
10
|
+
# jobs aren't in this list until they are scheduled - quartz limitation
|
11
|
+
Secondhand.schedule TestSimpleJob
|
12
|
+
assert_equal TestSimpleJob, Secondhand::Jobs["TestSimpleJob"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_names
|
16
|
+
Secondhand.schedule TestSimpleJob2
|
17
|
+
assert Secondhand::Jobs.names.include?("TestSimpleJob2")
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class TestLogger < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
end
|
7
|
+
|
8
|
+
class IncludeLogger
|
9
|
+
include Secondhand::Logger
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def test_included_logger
|
14
|
+
inst = IncludeLogger.new
|
15
|
+
assert inst.respond_to?(:logger)
|
16
|
+
assert inst.respond_to?(:warn)
|
17
|
+
end
|
18
|
+
|
19
|
+
class ExtendLogger
|
20
|
+
extend Secondhand::Logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_extended_logger
|
24
|
+
klass = ExtendLogger
|
25
|
+
assert klass.respond_to?(:logger)
|
26
|
+
assert klass.respond_to?(:error)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class TestScheduler < Test::Unit::TestCase
|
4
|
+
import org.quartz.impl.StdScheduler
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Secondhand.reset_thread_count
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_default_thread_count
|
11
|
+
assert_equal 5, Secondhand.thread_count
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_thread_count_setter
|
15
|
+
Secondhand.thread_count = 2
|
16
|
+
assert_equal 2, Secondhand.thread_count
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_scheduler_instance
|
20
|
+
Secondhand.initialize_scheduler
|
21
|
+
assert_instance_of StdScheduler, Secondhand.scheduler
|
22
|
+
assert_equal Secondhand.scheduler, Secondhand.scheduler
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_state_and_argument
|
26
|
+
sh = Secondhand
|
27
|
+
sh.start(1)
|
28
|
+
assert sh.running?
|
29
|
+
assert sh.started?
|
30
|
+
assert_equal 1, sh.thread_count
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class TestSecondhand < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
# Secondhand.jobs.clear
|
7
|
+
Secondhand.start(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_simple_class_job
|
11
|
+
Secondhand.schedule TestSimpleJob
|
12
|
+
assert_equal TestSimpleJob, Secondhand.jobs["TestSimpleJob"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_simple_named_job_block
|
16
|
+
Secondhand.schedule "some_job" do
|
17
|
+
"Some job text"
|
18
|
+
end
|
19
|
+
assert Secondhand.jobs["some_job"].is_a? Proc
|
20
|
+
assert_equal "Some job text", Secondhand.jobs["some_job"].call
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_schedule_simple_job
|
24
|
+
date = Time.now + 120 # 120 seconds from now
|
25
|
+
Secondhand.schedule TestSimpleJob2, :at => date
|
26
|
+
assert_equal TestSimpleJob2, Secondhand.jobs["TestSimpleJob2"]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class TestTrigger < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_at
|
9
|
+
date = "Dec 10 22:53:35 -0800 2011"
|
10
|
+
sched = Secondhand::Trigger.create("some_job", :at => Time.parse(date))
|
11
|
+
assert_kind_of Java::OrgQuartz::SimpleTrigger, sched
|
12
|
+
assert_equal Time.parse(date), Time.parse(sched.get_start_time.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_with_cron
|
16
|
+
exp = "0 0 12 * * ? *"
|
17
|
+
sched = Secondhand::Trigger.create("some_cronjob", :with_cron => exp)
|
18
|
+
assert_kind_of Java::OrgQuartz::CronTrigger, sched
|
19
|
+
assert_equal exp, sched.get_cron_expression
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: secondhand
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Don Morrison
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-13 00:00:00 -08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: elskwid@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.md
|
29
|
+
files:
|
30
|
+
- LICENSE
|
31
|
+
- Rakefile
|
32
|
+
- README.md
|
33
|
+
- test/test_helper.rb
|
34
|
+
- test/integration/test_secondhand_run.rb
|
35
|
+
- test/unit/test_job.rb
|
36
|
+
- test/unit/test_jobs.rb
|
37
|
+
- test/unit/test_logger.rb
|
38
|
+
- test/unit/test_scheduler.rb
|
39
|
+
- test/unit/test_secondhand.rb
|
40
|
+
- test/unit/test_trigger.rb
|
41
|
+
- lib/secondhand.rb
|
42
|
+
- lib/quartz/log4j-1.2.14.jar
|
43
|
+
- lib/quartz/quartz-1.8.4.jar
|
44
|
+
- lib/quartz/slf4j-api-1.6.0.jar
|
45
|
+
- lib/quartz/slf4j-log4j12-1.6.0.jar
|
46
|
+
- lib/secondhand/job.rb
|
47
|
+
- lib/secondhand/jobs.rb
|
48
|
+
- lib/secondhand/logging.rb
|
49
|
+
- lib/secondhand/scheduler.rb
|
50
|
+
- lib/secondhand/trigger.rb
|
51
|
+
- lib/secondhand/version.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: https://github.com/amphora/secondhand
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --main
|
59
|
+
- README.md
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.6
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Secondhand is a ruby-friendly wrapper around the Quartz Job Scheduler. Create jobs, tell Secondhand when they should run, then sit back and enjoy.
|
83
|
+
test_files: []
|
84
|
+
|