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 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.
@@ -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/
@@ -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
@@ -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,3 @@
1
+ module Secondhand
2
+ VERSION = "0.1.0"
3
+ end
@@ -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
@@ -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
+