batch-kit 0.3

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +165 -0
  4. data/lib/batch-kit.rb +9 -0
  5. data/lib/batch-kit/arguments.rb +57 -0
  6. data/lib/batch-kit/config.rb +517 -0
  7. data/lib/batch-kit/configurable.rb +68 -0
  8. data/lib/batch-kit/core_ext/enumerable.rb +97 -0
  9. data/lib/batch-kit/core_ext/file.rb +69 -0
  10. data/lib/batch-kit/core_ext/file_utils.rb +103 -0
  11. data/lib/batch-kit/core_ext/hash.rb +17 -0
  12. data/lib/batch-kit/core_ext/numeric.rb +17 -0
  13. data/lib/batch-kit/core_ext/string.rb +88 -0
  14. data/lib/batch-kit/database.rb +133 -0
  15. data/lib/batch-kit/database/java_util_log_handler.rb +65 -0
  16. data/lib/batch-kit/database/log4r_outputter.rb +57 -0
  17. data/lib/batch-kit/database/models.rb +548 -0
  18. data/lib/batch-kit/database/schema.rb +229 -0
  19. data/lib/batch-kit/encryption.rb +7 -0
  20. data/lib/batch-kit/encryption/java_encryption.rb +178 -0
  21. data/lib/batch-kit/encryption/ruby_encryption.rb +175 -0
  22. data/lib/batch-kit/events.rb +157 -0
  23. data/lib/batch-kit/framework/acts_as_job.rb +197 -0
  24. data/lib/batch-kit/framework/acts_as_sequence.rb +123 -0
  25. data/lib/batch-kit/framework/definable.rb +169 -0
  26. data/lib/batch-kit/framework/job.rb +121 -0
  27. data/lib/batch-kit/framework/job_definition.rb +105 -0
  28. data/lib/batch-kit/framework/job_run.rb +145 -0
  29. data/lib/batch-kit/framework/runnable.rb +235 -0
  30. data/lib/batch-kit/framework/sequence.rb +87 -0
  31. data/lib/batch-kit/framework/sequence_definition.rb +38 -0
  32. data/lib/batch-kit/framework/sequence_run.rb +48 -0
  33. data/lib/batch-kit/framework/task_definition.rb +89 -0
  34. data/lib/batch-kit/framework/task_run.rb +53 -0
  35. data/lib/batch-kit/helpers/date_time.rb +54 -0
  36. data/lib/batch-kit/helpers/email.rb +198 -0
  37. data/lib/batch-kit/helpers/html.rb +175 -0
  38. data/lib/batch-kit/helpers/process.rb +101 -0
  39. data/lib/batch-kit/helpers/zip.rb +30 -0
  40. data/lib/batch-kit/job.rb +11 -0
  41. data/lib/batch-kit/lockable.rb +138 -0
  42. data/lib/batch-kit/loggable.rb +78 -0
  43. data/lib/batch-kit/logging.rb +169 -0
  44. data/lib/batch-kit/logging/java_util_logger.rb +87 -0
  45. data/lib/batch-kit/logging/log4r_logger.rb +71 -0
  46. data/lib/batch-kit/logging/null_logger.rb +35 -0
  47. data/lib/batch-kit/logging/stdout_logger.rb +96 -0
  48. data/lib/batch-kit/resources.rb +191 -0
  49. data/lib/batch-kit/sequence.rb +7 -0
  50. metadata +122 -0
@@ -0,0 +1,157 @@
1
+ class BatchKit
2
+
3
+ # Manages batch event notifications and subscriptions, which provide a
4
+ # useful means of decoupling different components of the batch library.
5
+ #
6
+ # The problem we are looking to solve here is that we want our batch jobs,
7
+ # tasks etc to be able to notify interested parties when something happens
8
+ # (e.g. a task starts, a job fails, etc) without these event sources needing
9
+ # to know all the interested parties to notify. We therefore introduce an
10
+ # intermediary, which is the Events system.
11
+ #
12
+ # Interested parties register their interest in specific events or event
13
+ # classes by subscribing to the events of interest. Framework classes then
14
+ # notify the Event system when an event occurs, and the event system routes
15
+ # these notifications on to all registered subscribers.
16
+ #
17
+ # One of the problems we need to solve for is how subscribers can define the
18
+ # scope of their interest. Is it all events of a particular type, regardless
19
+ # of source? Or are we only interested in events from a specific source (e.g
20
+ # job or task)?
21
+ class Events
22
+
23
+ # Records subscription details
24
+ class Subscription
25
+
26
+ attr_reader :source, :event, :callback, :raise_on_error
27
+
28
+
29
+ def initialize(source, event, options, callback)
30
+ @source = source
31
+ @event = event
32
+ @raise_on_error = options.fetch(:raise_on_error, true)
33
+ @callback = callback
34
+ end
35
+
36
+
37
+ def ===(obj)
38
+ @source.nil? || # Nil source means match any obj
39
+ (@source == obj) || # Source is obj
40
+ (@source === obj) || # obj is an instance of source class
41
+ (@source.instance_of?(Module) && obj.instance_of?(Class) &&
42
+ obj.include?(@source)) # Source is a module included by obj
43
+ end
44
+
45
+ end
46
+
47
+
48
+
49
+ class << self
50
+
51
+ # @param source [Object] The source of the event
52
+ # @param event [String] The name of the event
53
+ # @return [Boolean] whether there are any subscribers for the specified
54
+ # event.
55
+ def has_subscribers?(source, event)
56
+ subscribers.has_key?(event) && subscribers[event].size > 0 &&
57
+ subscribers[event].find{ |sub| sub === source }
58
+ end
59
+
60
+
61
+ # Setup a subscription for a particular event. When a matching event
62
+ # occurs, the supplied block will be called with the published
63
+ # arguments.
64
+ #
65
+ # @param source [Object] The type of source object from which to
66
+ # listen for events.
67
+ # @param event [String] The name of the event to subscribe to.
68
+ # @param options [Hash] An options hash defining optional settings
69
+ # for the subscription.
70
+ # @option options [Fixnum] :position The position within the list to
71
+ # insert the subscriber. Default is to add to the end of the list.
72
+ # @param callback [Proc] A block to be invoked when the event occurs.
73
+ def subscribe(source, event, options = {}, &callback)
74
+ @log.trace "Adding subscriber for #{source} event '#{event}'" if @log
75
+ position = options.fetch(:position, -1)
76
+ if event.is_a?(Array)
77
+ event.each{ |e| subscribers[e].insert(position, Subscription.new(source, e, options, callback)) }
78
+ else
79
+ subscribers[event].insert(position, Subscription.new(source, event, options, callback))
80
+ end
81
+ end
82
+
83
+
84
+ # Remove a subscriber
85
+ #
86
+ # @param source [Object] The object that is the source of the event
87
+ # from which to unsubscribe.
88
+ # @param event [String] The name of the event to unsubscribe from.
89
+ def unsubscribe(source, event)
90
+ @log.trace "Removing subscriber(s) for #{source} event '#{event}'" if @log
91
+ subscribers[event].delete_if{ |sub| sub === source }
92
+ end
93
+
94
+
95
+ # Publishes an event to all registered subscribers.
96
+ #
97
+ # @param source [Object] The source from which the event has been
98
+ # generated.
99
+ # @param event [String] The name of the event that has occurred.
100
+ # @param payload [*Object] Arguments passed with the event.
101
+ def publish(source, event, *payload)
102
+ @log.trace "Publishing event '#{event}' for #{source}" if @log
103
+ res = true
104
+ count = 0
105
+ if subscribers.has_key?(event)
106
+ subscribers[event].each do |sub|
107
+ if sub === source
108
+ begin
109
+ r = sub.callback.call(source, *payload)
110
+ count += 1
111
+ res &&= r
112
+ rescue StandardError => ex
113
+ if sub.raise_on_error
114
+ raise
115
+ else
116
+ STDERR.puts "Exception in '#{event}' event listener for #{source}: #{ex}\n" +
117
+ " at: #{ex.backtrace[0...10].join("\n")}"
118
+ end
119
+ end
120
+ end
121
+ end
122
+ @log.debug "Notified #{count} listeners of '#{event}'" if @log
123
+ end
124
+ res
125
+ end
126
+
127
+
128
+ # Enable/disable event debugging
129
+ def debug=(dbg)
130
+ @log = dbg ? LogManager.logger('batch.events') : nil
131
+ end
132
+
133
+
134
+ # Dumps a list of events and their subscribers to the logger
135
+ def dump_subscribers(show_event = nil, log = @log)
136
+ if log
137
+ subscribers.each do |event, subs|
138
+ if show_event.nil? || show_event == event
139
+ log.info "Subscribers for event '#{event}':"
140
+ subs.each{ |sub| log.detail sub.inspect }
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+
147
+ private
148
+
149
+ def subscribers
150
+ @subscribers ||= Hash.new{ |h, k| h[k] = [] }
151
+ end
152
+
153
+ end
154
+
155
+ end
156
+
157
+ end
@@ -0,0 +1,197 @@
1
+ class BatchKit
2
+
3
+ # When included into a class, marks the class as a BatchKit job.
4
+ # The including class has the following class methods added, which act as a
5
+ # DSL for specifying the job properties and behaviour:
6
+ # - {ClassMethods#desc desc} A method for setting a description for a
7
+ # subsequent job or task
8
+ # - {ClassMethods#job job} Defines a job entry method
9
+ # - {ClassMethods#task task} Defines a task method
10
+ # - {ClassMethods#job_definition job_definition} Returns the Job::Definition
11
+ # object for the including class
12
+ # - {ClassMethods#on_success on_success} defines a callback to be called if
13
+ # the job completes successfully.
14
+ # - {ClassMethods#on_failure on_failure} defines a callback to be called if
15
+ # the job encounters an unhandled exception.
16
+ # - {ClassMethods#on_completion} defines a callback to be called when the
17
+ # job completes.
18
+ #
19
+ # Instances of the including class also get the following instance methods:
20
+ # - {#job} Returns the Job::Definition for the class
21
+ # - {#job_run} Returns the Job::Run associated with this object instance.
22
+ module ActsAsJob
23
+
24
+ # Define methods to be added to the class that includes this module.
25
+ module ClassMethods
26
+
27
+ # @return The Job::Definition object used to hold attributes of this
28
+ # job.
29
+ def job_definition
30
+ @__job__
31
+ end
32
+ alias_method :definition, :job_definition
33
+
34
+
35
+ # Captures a description for the following task or job definition.
36
+ #
37
+ # @param desc [String] The description to associate with the next
38
+ # task or job that is defined.
39
+ def desc(desc)
40
+ @__desc__ = desc
41
+ end
42
+
43
+
44
+ # Defines the method that is used to run this job.
45
+ # This may be an existing method, in which case the name of the
46
+ # method must be passed as the first argument.
47
+ # Alternatively, a block may be supplied, which will be used to
48
+ # create the job method.
49
+ #
50
+ # @param job_method [Symbol] The name of an existing method that is
51
+ # to be the job entry point.
52
+ # @param job_opts [Hash] Options that affect the job definition.
53
+ # @option job_opts [Symbol] :method_name The name to be assigned to
54
+ # the job method created from the supplied block. Default is
55
+ # :execute.
56
+ # @option job_opts [String] :description A description for the job.
57
+ def job(job_method = nil, job_opts = @__desc__, &body)
58
+ # If called as an accessor, just return the @__job__
59
+ if job_method || job_opts || body
60
+ unless job_method.is_a?(Symbol)
61
+ job_opts = job_method
62
+ job_method = (job_opts && job_opts.is_a?(Hash) &&
63
+ job_opts[:method_name]) || :execute
64
+ end
65
+
66
+ job_desc = nil
67
+ if job_opts.is_a?(Hash)
68
+ job_desc = @__desc__
69
+ elsif job_opts.is_a?(String)
70
+ job_desc = job_opts
71
+ job_opts = {}
72
+ elsif job_opts.nil?
73
+ job_opts = {}
74
+ end
75
+ @__desc__ = nil
76
+
77
+ # Define job method if a body block was supplied
78
+ define_method(job_method, &body) if body
79
+
80
+ opts = job_opts.clone
81
+ opts[:description] = job_desc unless opts[:description]
82
+ opts[:method_name] = job_method
83
+ # The @__job__ instance variable is crated when this module is included
84
+ @__job__.set_from_options(opts)
85
+ end
86
+ @__job__
87
+ end
88
+
89
+
90
+ # Defines the method that is used to run a task.
91
+ # This may be an existing method, in which case the name of the
92
+ # method must be passed as the first argument.
93
+ # Alternatively, a block may be supplied, which will be used to
94
+ # create the task method.
95
+ #
96
+ # @param task_method [Symbol] The name for the method that is to be
97
+ # this task. May be the name of an existing method (in which case
98
+ # no block should be supplied), or the name to give to the method
99
+ # that will be created from the supplied block.
100
+ # @param task_opts [Hash] A hash containing options for the task
101
+ # being defined.
102
+ # @option task_opts [Symbol] :method_name The name for the method
103
+ # if no symbol was provided as the first argument.
104
+ # @option job_opts [String] :description A description for the task.
105
+ def task(task_method, task_opts = @__desc__, &body)
106
+ unless task_method.is_a?(Symbol)
107
+ task_opts = task_method
108
+ task_method = task_opts && task_opts[:method_name]
109
+ end
110
+ raise ArgumentError, "No method name specified for task" unless task_method
111
+
112
+ task_desc = nil
113
+ if task_opts.is_a?(Hash)
114
+ task_desc = @__desc__
115
+ elsif task_opts.is_a?(String)
116
+ task_desc = task_opts
117
+ task_opts = {}
118
+ elsif task_opts.nil?
119
+ task_opts = {}
120
+ end
121
+ @__desc__ = nil
122
+
123
+ # Define task method if a body block was supplied
124
+ define_method(task_method, &body) if body
125
+
126
+ opts = task_opts.clone
127
+ opts[:description] = task_desc unless opts[:description]
128
+
129
+ # Create a new TaskDefinition class for the task
130
+ task_defn = Task::Definition.new(self, task_method)
131
+ task_defn.set_from_options(opts)
132
+ task_defn
133
+ end
134
+
135
+
136
+ # Defines a handler to be invoked if the job encounters an unhandled
137
+ # exception.
138
+ def on_failure(mthd = nil, &blk)
139
+ Events.subscribe(self, 'job_run.failure'){ |jr, obj, ex| obj.send(mthd, ex) } if mthd
140
+ Events.subscribe(self, 'job_run.failure'){ |jr, obj, ex| obj.instance_exec(ex, &blk) } if blk
141
+ end
142
+
143
+
144
+ # Defines a handler to be invoked if the job ends successfully.
145
+ def on_success(mthd = nil, &blk)
146
+ Events.subscribe(self, 'job_run.success'){ |jr, obj| obj.send(mthd) } if mthd
147
+ Events.subscribe(self, 'job_run.success'){ |jr, obj| obj.instance_exec(&blk) } if blk
148
+ end
149
+
150
+
151
+ # Defines a handler to be invoked on completion of the job, whether
152
+ # the job completes successfully or fails. The handler may be specified
153
+ # as either a method name and/or via a block. Multiple calls to this
154
+ # method can be made to register multiple callbacks if desired.
155
+ #
156
+ # @param mthd [Symbol] The name of an existing method on the including
157
+ # class. This method will be called with the Job::Run object that
158
+ # represents the completing job run.
159
+ #
160
+ def on_completion(mthd = nil, &blk)
161
+ Events.subscribe(self, 'job_run.post-execute'){ |jr, obj, ok| obj.send(mthd, jr) } if mthd
162
+ Events.subscribe(self, 'job_run.post-execute'){ |jr, obj, ok| obj.instance_exec(jr, &blk) } if blk
163
+ end
164
+
165
+ end
166
+
167
+
168
+ # Hook used to extend the including class with class methods defined in
169
+ # the ActsAsJob::ClassMethods module.
170
+ #
171
+ # Creates a Job::Definition object to hold details of the job, and stores
172
+ # it away in a @__job__ class instance variable.
173
+ def self.included(base)
174
+ base.extend(ClassMethods)
175
+ caller.find{ |f| !(f =~ /batch-kit.framework/) } =~ /^((?:[a-zA-Z]:)?[^:]+)/
176
+ job_file = File.realpath($1)
177
+ job_defn = Job::Definition.new(base, job_file)
178
+ base.instance_variable_set :@__job__, job_defn
179
+ Events.publish(base, 'acts_as_job.included', job_defn)
180
+ end
181
+
182
+
183
+ # @return [Job::Definition] The JobDefinition for this job instance.
184
+ def job
185
+ self.class.job_definition
186
+ end
187
+
188
+
189
+ # @return [Job::Run] The JobRun for this job instance.
190
+ def job_run
191
+ @__job_run__
192
+ end
193
+
194
+ end
195
+
196
+ end
197
+
@@ -0,0 +1,123 @@
1
+ class BatchKit
2
+
3
+ # When included into a class, marks the class as a BatchKit sequence.
4
+ # The including class has the following class methods added, which act as a
5
+ # DSL for specifying the sequence properties and behaviour:
6
+ # - {ClassMethods#desc desc} A method for setting a description for a
7
+ # subsequent sequence
8
+ # - {ClassMethods#sequence sequence} Defines a sequence entry method
9
+ # - {ClassMethods#sequence_definition sequence_definition} Returns the
10
+ # Sequence::Definition object for the including class
11
+ # - {ClassMethods#on_success on_success} defines a callback to be called if
12
+ # the sequence completes successfully.
13
+ # - {ClassMethods#on_failure on_failure} defines a callback to be called if
14
+ # the sequence encounters an unhandled exception.
15
+ #
16
+ # Instances of the including class also get the following instance methods:
17
+ # - {#sequence} Returns the Sequence::Definition for the class
18
+ # - {#sequence_run} Returns the Sequence::Run associated with this object instance.
19
+ module ActsAsSequence
20
+
21
+ # Define methods to be added to the class that includes this module.
22
+ module ClassMethods
23
+
24
+ # @return The Sequence::Definition object used to hold attributes of this
25
+ # sequence.
26
+ def sequence_definition
27
+ @__sequence__
28
+ end
29
+ alias_method :definition, :sequence_definition
30
+
31
+
32
+ # Captures a description for the following sequence definition.
33
+ #
34
+ # @param desc [String] The description to associate with the next
35
+ # sequence, job, or task that is defined.
36
+ def desc(desc)
37
+ @__desc__ = desc
38
+ end
39
+
40
+
41
+ # Defines the method that is used to run this job.
42
+ # This may be an existing method, in which case the name of the
43
+ # method must be passed as the first argument.
44
+ # Alternatively, a block may be supplied, which will be used to
45
+ # create the job method.
46
+ #
47
+ # @param sequence_method [Symbol] The name of an existing method that is
48
+ # to be the sequence entry point.
49
+ # @param sequence_opts [Hash] Options that affect the sequence definition.
50
+ # @option sequence_opts [Symbol] :method_name The name to be assigned to
51
+ # the sequence method created from the supplied block. Default is
52
+ # :execute.
53
+ # @option sequence_opts [String] :description A description for the sequence.
54
+ def sequence(sequence_method = nil, sequence_opts = @__desc__, &body)
55
+ # If called as an accessor, just return the @__sequence__
56
+ if sequence_method || sequence_opts || body
57
+ unless sequence_method.is_a?(Symbol)
58
+ sequence_opts = sequence_method
59
+ sequence_method = (sequence_opts && sequence_opts.is_a?(Hash) &&
60
+ sequence_opts[:method_name]) || :execute
61
+ end
62
+
63
+ sequence_desc = nil
64
+ if sequence_opts.is_a?(Hash)
65
+ sequence_desc = @__desc__
66
+ elsif sequence_opts.is_a?(String)
67
+ sequence_desc = sequence_opts
68
+ sequence_opts = {}
69
+ elsif sequence_opts.nil?
70
+ sequence_opts = {}
71
+ end
72
+ @__desc__ = nil
73
+
74
+ # Define sequence method if a body block was supplied
75
+ define_method(sequence_method, &body) if body
76
+
77
+ opts = sequence_opts.clone
78
+ opts[:description] = sequence_desc unless opts[:description]
79
+ opts[:method_name] = sequence_method
80
+ # The @__sequence__ instance variable is crated when this module is included
81
+ @__sequence__.set_from_options(opts)
82
+ end
83
+ @__sequence__
84
+ end
85
+
86
+ end
87
+
88
+
89
+ # Hook used to extend the including class with class methods defined in
90
+ # the ActsAsSequence::ClassMethods module.
91
+ #
92
+ # Creates a Sequence::Definition object to hold details of the sequence,
93
+ # and stores it away in a @__sequence__ class instance variable.
94
+ def self.included(base)
95
+ base.extend(ClassMethods)
96
+ caller.find{ |f| !(f =~ /batch.framework/) } =~ /^((?:[a-zA-Z]:)?[^:]+)/
97
+ sequence_file = File.realpath($1)
98
+ sequence_defn = Sequence::Definition.new(base, sequence_file)
99
+ base.instance_variable_set :@__sequence__, sequence_defn
100
+ end
101
+
102
+
103
+ def parallel
104
+ # TODO: Implement running contents of block in parallel
105
+ yield
106
+ end
107
+
108
+
109
+ # @return [Sequence::Definition] The SequenceDefinition for this Sequence instance.
110
+ def sequence
111
+ self.class.sequence_definition
112
+ end
113
+
114
+
115
+ # @return [Sequence::Run] The SequenceRun for this Sequence instance.
116
+ def sequence_run
117
+ @__sequence_run__
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+