PageRenderTime 39ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/ruby-spqr-0.3.5/lib/spqr/manageable.rb

#
Ruby | 363 lines | 251 code | 68 blank | 44 comment | 19 complexity | c49b3dbd895ff8ce13a5e15b17e240c8 MD5 | raw file
Possible License(s): Apache-2.0
  1. # SPQR: Schema Processor for QMF/Ruby agents
  2. #
  3. # Manageable object mixin and support classes.
  4. #
  5. # Copyright (c) 2009--2010 Red Hat, Inc.
  6. #
  7. # Author: William Benton (willb@redhat.com)
  8. #
  9. # Licensed under the Apache License, Version 2.0 (the "License");
  10. # you may not use this file except in compliance with the License.
  11. # You may obtain a copy of the License at
  12. #
  13. # http://www.apache.org/licenses/LICENSE-2.0
  14. module SPQR
  15. class ManageableObjectError < RuntimeError
  16. attr_accessor :status, :result
  17. def initialize(status, message=nil, rval=nil)
  18. super(message)
  19. @status = status
  20. @result = rval
  21. end
  22. end
  23. class ManageableMeta < Struct.new(:classname, :package, :description, :mmethods, :options, :statistics, :properties)
  24. def initialize(*a)
  25. super *a
  26. self.options = (({} unless self.options) or self.options.dup)
  27. self.statistics = [] unless self.statistics
  28. self.properties = [] unless self.properties
  29. self.mmethods ||= {}
  30. end
  31. def declare_method(name, desc, options, blk=nil)
  32. result = MethodMeta.new name, desc, options
  33. blk.call(result.args) if blk
  34. self.mmethods[name] = result
  35. end
  36. def manageable_methods
  37. self.mmethods.values
  38. end
  39. def declare_statistic(name, kind, options)
  40. declare_basic(:statistic, name, kind, options)
  41. end
  42. def declare_property(name, kind, options)
  43. declare_basic(:property, name, kind, options)
  44. end
  45. private
  46. def declare_basic(what, name, kind, options)
  47. what_plural = "#{what.to_s.gsub(/y$/, 'ie')}s"
  48. w_get = what_plural.to_sym
  49. w_set = "#{what_plural}=".to_sym
  50. self.send(w_set, (self.send(w_get) or []))
  51. w_class = "#{what.to_s.capitalize}Meta"
  52. self.send(w_get) << SPQR.const_get(w_class).new(name, kind, options)
  53. end
  54. end
  55. class MethodMeta < Struct.new(:name, :description, :args, :options)
  56. def initialize(*a)
  57. super *a
  58. self.options = (({} unless self.options) or self.options.dup)
  59. self.args = gen_args
  60. end
  61. def formals_in
  62. self.args.select {|arg| arg.direction == :in or arg.direction == :inout}.collect{|arg| arg.name.to_s}
  63. end
  64. def formals_out
  65. self.args.select {|arg| arg.direction == :inout or arg.direction == :out}.collect{|arg| arg.name.to_s}
  66. end
  67. def types_in
  68. self.args.select {|arg| arg.direction == :in or arg.direction == :inout}.collect{|arg| arg.kind.to_s}
  69. end
  70. def types_out
  71. self.args.select {|arg| arg.direction == :inout or arg.direction == :out}.collect{|arg| arg.kind.to_s}
  72. end
  73. def type_of(param)
  74. @types_for ||= self.args.inject({}) do |acc,arg|
  75. k = arg.name
  76. v = arg.kind.to_s
  77. acc[k] = v
  78. acc[k.to_s] = v
  79. acc
  80. end
  81. @types_for[param]
  82. end
  83. private
  84. def gen_args
  85. result = []
  86. def result.declare(name, kind, direction, description=nil, options=nil)
  87. options ||= {}
  88. arg = ::SPQR::ArgMeta.new name, kind, direction, description, options.dup
  89. self << arg
  90. end
  91. result
  92. end
  93. end
  94. class ArgMeta < Struct.new(:name, :kind, :direction, :description, :options)
  95. def initialize(*a)
  96. super *a
  97. self.options = (({} unless self.options) or self.options.dup)
  98. end
  99. end
  100. class PropertyMeta < Struct.new(:name, :kind, :options)
  101. def initialize(*a)
  102. super *a
  103. self.options = (({} unless self.options) or self.options.dup)
  104. end
  105. end
  106. class StatisticMeta < Struct.new(:name, :kind, :options)
  107. def initialize(*a)
  108. super *a
  109. self.options = (({} unless self.options) or self.options.dup)
  110. end
  111. end
  112. module ManageableClassMixins
  113. def spqr_meta
  114. @spqr_meta ||= ::SPQR::ManageableMeta.new
  115. end
  116. def log=(logger)
  117. @spqr_log = logger
  118. end
  119. def log
  120. @spqr_log || ::SPQR::Sink.new
  121. end
  122. def app=(app)
  123. @spqr_app = app
  124. end
  125. def app
  126. @spqr_app
  127. end
  128. # Exposes a method to QMF
  129. def expose(name, description=nil, options=nil, &blk)
  130. spqr_meta.declare_method(name, description, options, blk)
  131. end
  132. # Declares that this class is a singleton class; that is, only one
  133. # instance will be published over QMF
  134. def is_singleton
  135. def self.instances
  136. @instances ||= [self.new]
  137. end
  138. def self.find_all
  139. instances
  140. end
  141. def self.find_by_id(id)
  142. instances[0]
  143. end
  144. end
  145. # Declares that instances of this class will automatically be
  146. # tracked (and find_all and find_by_id methods generated).
  147. # Instances of automatically-tracked classes must be explicitly
  148. # deleted (with the delete class method). Do not use automatic
  149. # tracking with Rhubarb, which automatically tracks instances for
  150. # you.
  151. def is_tracked
  152. # no pun intended, I promise
  153. alias_method :old_new, :new
  154. def self.instances
  155. @instances ||= {}
  156. end
  157. def self.find_all
  158. instances.values
  159. end
  160. def self.find_by_id(id)
  161. instances[id]
  162. end
  163. # XXX: would it make more sense to call allocate and initialize explicitly?
  164. def self.new(*args)
  165. result = old_new(*args)
  166. instances[result.qmf_oid] = result
  167. result
  168. end
  169. def self.delete(instance)
  170. instances.delete(instance.qmf_oid)
  171. end
  172. end
  173. def qmf_package_name(nm)
  174. spqr_meta.package = nm
  175. end
  176. def qmf_class_name(nm)
  177. spqr_meta.classname = nm
  178. end
  179. def qmf_description(d)
  180. spqr_meta.description = d
  181. end
  182. def qmf_options(opts)
  183. spqr_meta.options = opts.dup
  184. end
  185. def qmf_statistic(name, kind, options=nil)
  186. spqr_meta.declare_statistic(name, kind, options)
  187. self.class_eval do
  188. # XXX: are we only interested in declaring a reader for
  189. # statistics? Doesn't it really makes more sense for the managed
  190. # class to declare a method with the same name as the
  191. # statistic so we aren't declaring anything at all here?
  192. # XXX: should cons up a "safe_attr_reader" method that works
  193. # like this:
  194. attr_reader name.to_sym unless instance_methods.include? "#{name}"
  195. attr_writer name.to_sym unless instance_methods.include? "#{name}="
  196. end
  197. end
  198. def qmf_property(name, kind, options=nil)
  199. spqr_meta.declare_property(name, kind, options)
  200. # add a property accessor to instances of other
  201. self.class_eval do
  202. # XXX: should cons up a "safe_attr_accessor" method that works like this:
  203. attr_reader name.to_sym unless instance_methods.include? "#{name}"
  204. attr_writer name.to_sym unless instance_methods.include? "#{name}="
  205. end
  206. if options and options[:index]
  207. # if this is an index property, add a find-by method if one
  208. # does not already exist
  209. spqr_define_index_find(name)
  210. end
  211. end
  212. private
  213. def spqr_define_index_find(name)
  214. find_by_prop = "find_by_#{name}".to_sym
  215. return if self.respond_to? find_by_prop
  216. define_method find_by_prop do |arg|
  217. raise "#{self} must define find_by_#{name}(arg)"
  218. end
  219. end
  220. end
  221. module Manageable
  222. # fail takes either (up to) three arguments or a hash
  223. # the three arguments are:
  224. # * =status= (an integer failure code)
  225. # * =message= (a descriptive failure message, defaults to nil)
  226. # * =result= (a value to return, defaults to nil; currently ignored by QMF)
  227. # the hash simply maps from keys =:status=, =:message=, and =:result= to their respective values. Only =:status= is required.
  228. def fail(*args)
  229. unless args.size <= 3 && args.size >= 1
  230. raise RuntimeError.new("SPQR::Manageable#fail takes at least one parameter but not more than three; received #{args.inspect}")
  231. end
  232. if args.size == 1 and args[0].class = Hash
  233. failhash = args[0]
  234. unless failhash[:status] && failhash[:status].is_a?(Fixnum)
  235. raise RuntimeError.new("SPQR::Manageable#fail requires a Fixnum-valued :status parameter when called with keyword arguments; received #{failhash[:status].inspect}")
  236. end
  237. raise ManageableObjectError.new(failhash[:status], failhash[:message], failhash[:result])
  238. end
  239. raise ManageableObjectError.new(*args)
  240. end
  241. # Returns the user ID of the QMF user invoking this method
  242. def qmf_user_id
  243. Thread.current[:qmf_user_id]
  244. end
  245. # Returns QMF context of the current method invocation
  246. def qmf_context
  247. Thread.current[:qmf_context]
  248. end
  249. def qmf_oid
  250. result = 0
  251. if self.respond_to? :spqr_object_id
  252. result = spqr_object_id
  253. elsif self.respond_to? :row_id
  254. result = row_id
  255. else
  256. result = object_id
  257. end
  258. # XXX: this foolishly assumes that we are running on a 64-bit machine or a 32-bit machine
  259. result & (0.size == 8 ? 0x7fffffff : 0x3fffffff)
  260. end
  261. def qmf_id
  262. [qmf_oid, self.class.class_id]
  263. end
  264. def log
  265. self.class.log
  266. end
  267. def self.included(other)
  268. class << other
  269. include ManageableClassMixins
  270. alias_method :qmf_singleton, :is_singleton
  271. end
  272. unless other.respond_to? :find_by_id
  273. def other.find_by_id(id)
  274. raise "#{self} must define find_by_id(id)"
  275. end
  276. end
  277. unless other.respond_to? :find_all
  278. def other.find_all
  279. raise "#{self} must define find_all"
  280. end
  281. end
  282. unless other.respond_to? :class_id
  283. def other.class_id
  284. package_list = spqr_meta.package.to_s.split(".")
  285. cls = spqr_meta.classname.to_s or self.name.to_s
  286. # XXX: this foolishly assumes that we are running on a 64-bit machine or a 32-bit machine
  287. ((package_list.map {|pkg| pkg.capitalize} << cls).join("::")).hash & (0.size == 8 ? 0x7fffffff : 0x3fffffff)
  288. end
  289. end
  290. name_components = other.name.to_s.split("::")
  291. other.qmf_class_name name_components.pop
  292. other.qmf_package_name name_components.join(".").downcase
  293. end
  294. end
  295. end