patriot-workflow-scheduler 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +15 -0
  2. data/bin/patriot +8 -0
  3. data/bin/patriot-init +35 -0
  4. data/lib/patriot.rb +11 -0
  5. data/lib/patriot/command.rb +71 -0
  6. data/lib/patriot/command/base.rb +199 -0
  7. data/lib/patriot/command/command_group.rb +43 -0
  8. data/lib/patriot/command/command_macro.rb +141 -0
  9. data/lib/patriot/command/composite.rb +49 -0
  10. data/lib/patriot/command/parser.rb +78 -0
  11. data/lib/patriot/command/sh_command.rb +42 -0
  12. data/lib/patriot/controller.rb +2 -0
  13. data/lib/patriot/controller/package_controller.rb +81 -0
  14. data/lib/patriot/controller/worker_admin_controller.rb +159 -0
  15. data/lib/patriot/job_store.rb +66 -0
  16. data/lib/patriot/job_store/base.rb +159 -0
  17. data/lib/patriot/job_store/factory.rb +19 -0
  18. data/lib/patriot/job_store/in_memory_store.rb +252 -0
  19. data/lib/patriot/job_store/job.rb +118 -0
  20. data/lib/patriot/job_store/job_ticket.rb +30 -0
  21. data/lib/patriot/job_store/rdb_job_store.rb +353 -0
  22. data/lib/patriot/tool.rb +2 -0
  23. data/lib/patriot/tool/batch_parser.rb +102 -0
  24. data/lib/patriot/tool/patriot_command.rb +48 -0
  25. data/lib/patriot/tool/patriot_commands/execute.rb +92 -0
  26. data/lib/patriot/tool/patriot_commands/job.rb +62 -0
  27. data/lib/patriot/tool/patriot_commands/plugin.rb +41 -0
  28. data/lib/patriot/tool/patriot_commands/register.rb +77 -0
  29. data/lib/patriot/tool/patriot_commands/upgrade.rb +24 -0
  30. data/lib/patriot/tool/patriot_commands/validate.rb +84 -0
  31. data/lib/patriot/tool/patriot_commands/worker.rb +35 -0
  32. data/lib/patriot/tool/patriot_commands/worker_admin.rb +60 -0
  33. data/lib/patriot/util.rb +14 -0
  34. data/lib/patriot/util/config.rb +58 -0
  35. data/lib/patriot/util/config/base.rb +22 -0
  36. data/lib/patriot/util/config/inifile_config.rb +63 -0
  37. data/lib/patriot/util/cron_format_parser.rb +104 -0
  38. data/lib/patriot/util/date_util.rb +200 -0
  39. data/lib/patriot/util/db_client.rb +65 -0
  40. data/lib/patriot/util/db_client/base.rb +142 -0
  41. data/lib/patriot/util/db_client/hash_record.rb +53 -0
  42. data/lib/patriot/util/db_client/record.rb +25 -0
  43. data/lib/patriot/util/logger.rb +24 -0
  44. data/lib/patriot/util/logger/facade.rb +33 -0
  45. data/lib/patriot/util/logger/factory.rb +59 -0
  46. data/lib/patriot/util/logger/log4r_factory.rb +111 -0
  47. data/lib/patriot/util/logger/webrick_log_factory.rb +47 -0
  48. data/lib/patriot/util/param.rb +73 -0
  49. data/lib/patriot/util/retry.rb +30 -0
  50. data/lib/patriot/util/script.rb +52 -0
  51. data/lib/patriot/util/system.rb +120 -0
  52. data/lib/patriot/worker.rb +35 -0
  53. data/lib/patriot/worker/base.rb +153 -0
  54. data/lib/patriot/worker/info_server.rb +90 -0
  55. data/lib/patriot/worker/job_store_server.rb +32 -0
  56. data/lib/patriot/worker/multi_node_worker.rb +157 -0
  57. data/lib/patriot/worker/servlet.rb +23 -0
  58. data/lib/patriot/worker/servlet/job_servlet.rb +128 -0
  59. data/lib/patriot/worker/servlet/worker_status_servlet.rb +44 -0
  60. data/skel/batch/sample/daily/test.pbc +4 -0
  61. data/skel/config/patriot.ini +21 -0
  62. data/skel/public/css/bootstrap.css +2495 -0
  63. data/skel/public/css/original.css +54 -0
  64. data/skel/public/js/bootstrap-alerts.js +124 -0
  65. data/skel/public/js/bootstrap-buttons.js +64 -0
  66. data/skel/public/js/bootstrap-dropdown.js +55 -0
  67. data/skel/public/js/bootstrap-modal.js +260 -0
  68. data/skel/public/js/bootstrap-popover.js +90 -0
  69. data/skel/public/js/bootstrap-scrollspy.js +107 -0
  70. data/skel/public/js/bootstrap-tabs.js +80 -0
  71. data/skel/public/js/bootstrap-twipsy.js +321 -0
  72. data/skel/public/js/jquery-1.6.4.min.js +4 -0
  73. data/skel/public/templates/_jobs.erb +97 -0
  74. data/skel/public/templates/job.erb +119 -0
  75. data/skel/public/templates/jobs.erb +21 -0
  76. data/skel/public/templates/jobs_deleted.erb +6 -0
  77. data/skel/public/templates/layout.erb +103 -0
  78. data/skel/public/templates/state_updated.erb +6 -0
  79. metadata +235 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjRlNzEwZGJjMmY4MTRjMDU1NjBhYTg5OTRlNWYzYmUyZjQ5MWE3MQ==
5
+ data.tar.gz: !binary |-
6
+ ZTA3YjNiN2JjMDczMjZkYWEzNTc3NWJhOWZjYmNkOTdhZTU1MzM5ZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ODY5NzRjNWZjYmU2MzU4N2E4M2U5YThlODRkN2I2NThlZjI3ZTUxYzcwZTg0
10
+ MDJmOGZhZTE5ZTE4ZWYzNWIwYzkyZmRhYTkwOTBmZWYxYWE0Mjg3YmQ1MWZl
11
+ ODgyMTljODVjNGU0ZmE5OTJlZGM5ODkxMzQ4MzIxY2I4ZmZjMGY=
12
+ data.tar.gz: !binary |-
13
+ YzMzYmU3YzU3ZGQ5YWUxNGRmNTYyNmM0NTMwMzI4OWVhNDgyODFiNDZkZTI5
14
+ NmExNTNjOGI0NmY3YTNiYzE1MTk4N2NhYTYxNGExNDQyNWM5NzhmNDA3NmU1
15
+ ZTM1NzIwZmNhMDZlNGIwZmE5NjdkNzczMmM3MzI4Njg4NDcyMDY=
data/bin/patriot ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ Encoding.default_external = 'utf-8'
3
+ $home = File.expand_path(File.dirname(File.expand_path(__FILE__)) + "/..")
4
+ $: << File.join($home,"lib")
5
+ require 'rubygems'
6
+ require 'patriot'
7
+
8
+ Patriot::Tool::PatriotCommand.start(ARGV)
data/bin/patriot-init ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+
4
+ target=ARGV[0]
5
+ unless File.exist?(target)
6
+ puts "directory #{target} does not exist"
7
+ Dir.mkdir(target)
8
+ end
9
+ base_dir = File.join(File.dirname(File.expand_path(__FILE__)), "..")
10
+ skel_dir = File.join(base_dir, "skel")
11
+ Dir.glob(File.join(skel_dir,"*")).each do |d|
12
+ if File.exist?(File.join(target, File.basename(d)))
13
+ puts "#{d} already exists"
14
+ else
15
+ puts "copy #{d} to #{target}"
16
+ FileUtils.cp_r(d, target)
17
+ end
18
+ end
19
+
20
+
21
+ def mkdir_ifnot_exist(dir)
22
+ if File.exist?(dir)
23
+ puts "#{dir} already exist"
24
+ else
25
+ puts "mkdir #{dir}"
26
+ FileUtils.mkdir_p(dir)
27
+ end
28
+ end
29
+
30
+ mkdir_ifnot_exist(File.join(target, "bin"))
31
+ FileUtils.cp(File.join(base_dir,"bin","patriot"),File.join(target,"bin","patriot"))
32
+ mkdir_ifnot_exist(File.join(target, "plugins"))
33
+ mkdir_ifnot_exist(File.join(target, "run"))
34
+
35
+
data/lib/patriot.rb ADDED
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'active_support'
4
+
5
+ require 'patriot/util'
6
+ require 'patriot/command'
7
+ require 'patriot/job_store'
8
+ require 'patriot/worker'
9
+ require 'patriot/controller'
10
+ require 'patriot/tool'
11
+
@@ -0,0 +1,71 @@
1
+ # the root name space for this scheduler
2
+ module Patriot
3
+ # a name space for commands
4
+ module Command
5
+ # a parameter key for command class name (used in jobs)
6
+ COMMAND_CLASS_KEY = "COMMAND_CLASS"
7
+
8
+ # attribute name for required products
9
+ REQUISITES_ATTR = "requisites"
10
+ # attribute name for produced products
11
+ PRODUCTS_ATTR = "products"
12
+ # attribute name for job state
13
+ STATE_ATTR = "state"
14
+ # attribute name for job priority
15
+ PRIORITY_ATTR = "priority"
16
+ # attribute name for job execution node constraint
17
+ EXEC_NODE_ATTR = "exec_node"
18
+ # attribute name for job execution host constraint
19
+ EXEC_HOST_ATTR = "exec_host"
20
+ # attribute name for start time constraint
21
+ START_DATETIME_ATTR = "start_datetime"
22
+ # attribute name for the skip on fail marker
23
+ SKIP_ON_FAIL_ATTR = "skip_on_fail"
24
+
25
+ # a list of comman attributes
26
+ COMMON_ATTRIBUTES = [
27
+ REQUISITES_ATTR,
28
+ PRODUCTS_ATTR,
29
+ STATE_ATTR,
30
+ PRIORITY_ATTR,
31
+ EXEC_NODE_ATTR,
32
+ EXEC_HOST_ATTR,
33
+ START_DATETIME_ATTR,
34
+ SKIP_ON_FAIL_ATTR
35
+ ]
36
+
37
+ # exit code of a command
38
+ module ExitCode
39
+ # successfully finished
40
+ SUCCEEDED = 0
41
+ # failed
42
+ FAILED = 1
43
+ # failed but skipped (marked skip_on_fail)
44
+ FAILURE_SKIPPED = 2
45
+ # skip (e.g., updated elsewhere)
46
+ SKIPPED = -1
47
+
48
+ # @param exit_code [Patriot::Command::ExitCode]
49
+ # @return [String] string expression of the exit code
50
+ def name_of(exit_code)
51
+ exit_code = exit_code.to_i
52
+ return case exit_code
53
+ when 0 then "SUCCEEDED"
54
+ when 1 then "FAILED"
55
+ when 2 then "FAILURE_SKIPPED"
56
+ when 4 then "FAILED" # for backward compatibility
57
+ else raise "unknown exit_code #{exit_code}"
58
+ end
59
+ end
60
+ module_function :name_of
61
+ end
62
+
63
+ require 'patriot/command/parser'
64
+ require 'patriot/command/command_macro'
65
+ require 'patriot/command/base'
66
+ require 'patriot/command/command_group'
67
+ require 'patriot/command/composite'
68
+ require 'patriot/command/sh_command'
69
+ end
70
+ end
71
+
@@ -0,0 +1,199 @@
1
+ require 'active_support/core_ext/hash/deep_merge'
2
+ module Patriot
3
+ module Command
4
+ # The base class of every command.
5
+ # The command is an executable form of the job.
6
+ class Base
7
+ include Patriot::Command::Parser
8
+ include Patriot::Util::Param
9
+ include Patriot::Util::DateUtil
10
+ include Patriot::Util::Logger
11
+
12
+ class << self
13
+ include Patriot::Command::CommandMacro
14
+ end
15
+
16
+ attr_accessor :parser, :test_mode, :target_datetime
17
+
18
+ # comman attributes handled distinctively (only effective in top level commands)
19
+ volatile_attr :requisites, :products, :priority, :start_after, :exec_date, :exec_node, :exec_host, :skip_on_fail
20
+
21
+ # @param config [Patriot::Util::Config::Base] configuration for this command
22
+ def initialize(config)
23
+ @config = config
24
+ @logger = create_logger(config)
25
+ @param = {}
26
+ @requisites = []
27
+ @products = []
28
+ @macros = {}
29
+ @test_mode = false
30
+ end
31
+
32
+ # convert this to a job so that it can be stored to JobStore
33
+ # @return [Patriot::JobStore::Job] a job for this command.
34
+ def to_job
35
+ job = Patriot::JobStore::Job.new(self.job_id)
36
+ job.read_command(self)
37
+ return job
38
+ end
39
+
40
+ # get the value of an attribute
41
+ # @param attr_name [String] attribute name
42
+ # @return [Object] the value of the attribute specified with argument
43
+ def [](attr_name)
44
+ return instance_variable_get("@#{attr_name}".to_sym)
45
+ end
46
+
47
+ # build the identifier of the job for this command.
48
+ # This method should be overriden in sub-classes
49
+ # @return [String] the identifier of the job.
50
+ def job_id
51
+ raise NotImplementedError
52
+ end
53
+
54
+ # set default command name
55
+ # replace :: to handle in JSON format
56
+ # @return [String] a simplified command namd
57
+ def command_name
58
+ return self.class.to_s.split("::").last.downcase.gsub(/command/,"")
59
+ end
60
+
61
+ # add products required by this job.
62
+ # @param requisites [Array<String>] a list of products required by this job.
63
+ def require(requisites)
64
+ return if requisites.nil?
65
+ @requisites |= requisites.flatten
66
+ end
67
+
68
+ # add products produced by this job.
69
+ # @param products [Array<String>] a list of products produced by this job
70
+ def produce(products)
71
+ return if products.nil?
72
+ @products |= products.flatten
73
+ end
74
+
75
+ # mark this job to skip execution
76
+ def skip
77
+ param 'state' => Patriot::JobStore::JobState::SUCCEEDED
78
+ end
79
+
80
+ # mark this job to suspend execution
81
+ def suspend
82
+ param 'state' => Patriot::JobStore::JobState::SUSPEND
83
+ end
84
+
85
+ # mark this job to skip in case of failures
86
+ def skip_on_fail?
87
+ return @skip_on_fail == 'true' || @skip_on_fail == true
88
+ end
89
+
90
+ # @return [String] the target month in '%Y-%m'
91
+ def _month_
92
+ return @target_datetime.strftime("%Y-%m")
93
+ end
94
+
95
+ # @return [String] the target date in '%Y-%m-%d'
96
+ def _date_
97
+ return @target_datetime.strftime("%Y-%m-%d")
98
+ end
99
+
100
+ # @return [Integer] the tergat hour
101
+ def _hour_
102
+ return @target_datetime.hour
103
+ end
104
+
105
+ # start datetime of this command.
106
+ # This command should be executed after the return value of this method
107
+ # @return [DateTime]
108
+ def start_date_time
109
+ return nil if @exec_date.nil? && @start_after.nil?
110
+ # set tomorrow as default
111
+ date = (@exec_date || date_add(_date_, 1)).split("-").map(&:to_i)
112
+ # set midnight as default
113
+ time = (@start_after || "00:00:00").split(":").map(&:to_i)
114
+ return DateTime.new(date[0], date[1], date[2], time[0], time[1], time[2])
115
+ end
116
+
117
+ # update parameters with a given hash.
118
+ # If the hash includes keys which values have already been defined,
119
+ # the value for the key is replaced with the new value.
120
+ # @param _param [Hash] a hash from attribute name to its value
121
+ def param(_param)
122
+ @param = @param.deep_merge(_param)
123
+ end
124
+
125
+ # build this command as executables
126
+ # @param _param [Hash] default parameter
127
+ def build(_param={})
128
+ @param = _param.deep_merge(@param)
129
+ init_param
130
+ @start_datetime = start_date_time
131
+ cmds = configure()
132
+ cmds = [cmds] unless cmds.is_a?(Array)
133
+ cmds.each(&:validate_command_attrs)
134
+ return cmds.flatten
135
+ end
136
+
137
+ # initialize command attributes
138
+ def init_param
139
+ # set parameter value to instance variable
140
+ @param.each do |k,v|
141
+ raise "a reserved word #{k} is used as parameter name" if Patriot::Command::COMMAND_CLASS_KEY == k
142
+ raise "#{k} is already used in #{self.job_id}" unless instance_variable_get("@#{k}".to_sym).nil?
143
+ # don't evaluate here since all parameters are not set to instance variables
144
+ instance_variable_set("@#{k}".to_sym,v)
145
+ end
146
+
147
+ # evaluate command attributes using parameters (instance variables)
148
+ self.class.command_attrs.each { |a, d| configure_attr(a, d) }
149
+ end
150
+ protected :init_param
151
+
152
+ # configure a command attribute and set as an instance variable
153
+ # @param attr [String] an attribute name to be configured
154
+ # @param default_value default value of the attribute
155
+ def configure_attr(attr, default_value = nil)
156
+ v = instance_variable_get("@#{attr}".to_sym)
157
+ v = default_value if v.nil?
158
+ instance_variable_set("@#{attr}".to_sym, eval_attr(v))
159
+ end
160
+
161
+ # a hook method to implement comand-specific configuration
162
+ def configure
163
+ return self
164
+ end
165
+
166
+ # validate values of command attributes
167
+ # @see Patriot::Command::CommandMacro#validate_attr
168
+ def validate_command_attrs
169
+ self.class.validation_logics.each do |attr, logics|
170
+ val = self.instance_variable_get("@#{attr}".to_sym)
171
+ logics.each do |l|
172
+ unless l.call(self, attr, val)
173
+ raise "validation error : #{attr}=#{val} (#{self.class})"
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ # add a sub command
180
+ # @raise if sub command is not supported
181
+ def add_subcommand
182
+ raise "sub command is not supported"
183
+ end
184
+
185
+ # @return description of this command
186
+ def description
187
+ self.job_id
188
+ end
189
+
190
+ # execute this command
191
+ def execute()
192
+ raise NotImplementedError
193
+ end
194
+
195
+ end
196
+ end
197
+ end
198
+
199
+
@@ -0,0 +1,43 @@
1
+ module Patriot
2
+ module Command
3
+ # define a group of jobs
4
+ class CommandGroup < Base
5
+ declare_command_name :command_group
6
+ declare_command_name :job_group
7
+ attr_accessor :subcommands
8
+
9
+ # @see Patriot::Command::Base#initialize
10
+ def initialize(config)
11
+ super
12
+ @subcommands = []
13
+ end
14
+
15
+ # add a command to this group
16
+ # @param cmd [Patriot::Command::Base] a command to be added to this group
17
+ def add_subcommand(cmd)
18
+ @subcommands << cmd
19
+ end
20
+
21
+ # configure thie group.
22
+ # pass the required/produced products and parameters to the commands in this group
23
+ # @see Patriot::Command::Base#configure
24
+ # @return [Array<Patriot::Command::Base>] a list of commands in this group
25
+ def configure
26
+ return @subcommands.map{|cmd|
27
+ cmd.require @requisites
28
+ cmd.produce @products
29
+ cmd.build(@param)
30
+ }.flatten
31
+ end
32
+
33
+ # execute each command in this group
34
+ # @see Patriot::Command::Base#execute
35
+ def execute
36
+ @subcommands.each do |k,v|
37
+ v.execute
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,141 @@
1
+ module Patriot
2
+ module Command
3
+ # macros to be used for implmentation of command classes
4
+ module CommandMacro
5
+
6
+ # add module methods to DSL methods
7
+ # @param command_module [Module] module defines methods for the use in DSL
8
+ def add_dsl_function(command_module)
9
+ class_eval{ include command_module }
10
+ end
11
+
12
+ # declare DSL method name for defining the command
13
+ # @param mth_name [String] the DSL method name
14
+ # @param parent_cls [Class] parent command to which the new command is added
15
+ # @param cmd_cls [Class]
16
+ def declare_command_name(mth_name, parent_cls=Patriot::Command::CommandGroup, cmd_cls=self)
17
+ parent_cls.class_eval do
18
+ define_method(mth_name) do |&cmd_def|
19
+ cmd = new_command(cmd_cls, &cmd_def)
20
+ add_subcommand(cmd)
21
+ end
22
+ end
23
+ end
24
+
25
+ # declare command attributes to be able to use in DSL
26
+ # @param attrs [String|Hash] attribute names or a Hash from attribute name to its default value
27
+ # @yield block to preprocess attribute values
28
+ # @yieldparam [Patriot::Command::Base] command instance
29
+ # @yieldparam [String] attribute name
30
+ # @yieldparam [String] passed value
31
+ def command_attr(*attrs, &blk)
32
+ @command_attrs = {} if @command_attrs.nil?
33
+ default_values = {}
34
+ if attrs.size == 1 && attrs[0].is_a?(Hash)
35
+ default_values = attrs[0]
36
+ attrs = default_values.keys
37
+ end
38
+ attrs.each do |a|
39
+ raise "a reserved word #{a} is used as parameter name" if Patriot::Command::COMMAND_CLASS_KEY == a
40
+ raise "#{a} is already defined" if self.instance_methods.include?(a)
41
+ @command_attrs[a] = default_values[a]
42
+ define_method(a) do |*args|
43
+ raise "illegal size of arguments (#{a} with #{args.inspect})" unless args.size == 1
44
+ val = args[0]
45
+ if block_given?
46
+ yield(self, a, val)
47
+ else
48
+ self.param a.to_s => val
49
+ end
50
+ end
51
+ attr_writer a
52
+ end
53
+ return attrs
54
+ end
55
+
56
+ # declare command attributes for only internal use
57
+ # @param attrs [String] attribute name
58
+ def private_command_attr(*attrs)
59
+ command_attr(*attrs) do |cmd, attr_name, attr_val|
60
+ raise "only internal call is expected for #{attr_name}"
61
+ end
62
+ end
63
+
64
+ # declare command attributes to be able to use in DSL
65
+ # values of thes attributes are supposed to be used in {Patriot::Command::Base#do_configure}
66
+ # and would not be serialized
67
+ # @param attrs [String|Hash] attribute names or a Hash from attribute name to its default value
68
+ # @yield block to preprocess attribute values
69
+ # @yieldparam [Patriot::Command::Base] command instance
70
+ # @yieldparam [String] attribute name
71
+ # @yieldparam [String] passed value
72
+ def volatile_attr(*attrs, &blk)
73
+ @volatile_attrs = [] if @volatile_attrs.nil?
74
+ attrs = command_attr(*attrs, &blk)
75
+ attrs.each do |a|
76
+ @volatile_attrs << a
77
+ end
78
+ end
79
+
80
+ # @return [Hash] a Hash from attribute name to its value
81
+ def command_attrs
82
+ super_attrs = {}
83
+ if self.superclass.respond_to?(:command_attrs)
84
+ super_attrs = self.superclass.command_attrs
85
+ end
86
+ return super_attrs if @command_attrs.nil?
87
+ return super_attrs.merge(@command_attrs)
88
+ end
89
+
90
+ # @return [Array] a list of volatile attribute names
91
+ def volatile_attrs
92
+ super_attrs = []
93
+ if self.superclass.respond_to?(:volatile_attrs)
94
+ super_attrs = self.superclass.volatile_attrs
95
+ end
96
+ return super_attrs if @volatile_attrs.nil?
97
+ return @volatile_attrs | super_attrs
98
+ end
99
+
100
+ # @return [Array] a array of attribute names which should be inclued in Job
101
+ def serde_attrs
102
+ return command_attrs.keys - volatile_attrs
103
+ end
104
+
105
+ # validate attriubte value
106
+ # @param attrs attribute names to be validated
107
+ # @yield logic which validates the value
108
+ # @yieldreturn [Boolean] true if the value is valid, otherwise false
109
+ def validate_attr(*attrs, &blk)
110
+ raise "validation logic not given" unless block_given?
111
+ unless blk.arity == 3
112
+ raise "validation logic arguments should be (cmd, attr_name, attr_val)"
113
+ end
114
+ @validation_logics = {} if @validation_logics.nil?
115
+ attrs.each do |a|
116
+ raise "#{a} is not command attr" unless command_attrs.include?(a)
117
+ @validation_logics[a] = [] unless @validation_logics.has_key?(a)
118
+ @validation_logics[a] << blk
119
+ end
120
+ end
121
+
122
+ # check whether the attribute value is not nil
123
+ # @see {validate_attribute_value}
124
+ def validate_existence(*attrs)
125
+ validate_attr(*attrs) do |cmd, a, v|
126
+ !v.nil?
127
+ end
128
+ end
129
+
130
+ # @return [Hash] a hash from attribute name to validation logic (Process)
131
+ def validation_logics
132
+ super_logics = {}
133
+ if self.superclass.respond_to?(:volatile_attrs)
134
+ super_logics = self.superclass.validation_logics
135
+ end
136
+ return super_logics if @validation_logics.nil?
137
+ return super_logics.merge(@validation_logics)
138
+ end
139
+ end
140
+ end
141
+ end