batch-kit 0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+