jmesnil / jmx4r

a JMX library for JRuby

This URL has Read+Write access

jmx4r / lib / dynamic_mbean.rb
100644 275 lines (241 sloc) 10.485 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# Copyright (c) 2008 Thomas E Enebo <enebo@acm.org>
#--
# taken from the 'jmx' gems in jruby-extras
 
module JMX
  import javax.management.MBeanParameterInfo
  import javax.management.MBeanOperationInfo
  import javax.management.MBeanAttributeInfo
  import javax.management.MBeanInfo
 
  # Module that is used to bridge java to ruby and ruby to java types.
  module JavaTypeAware
    # Current list of types we understand If it's not in this list we are
    # assuming that we are going to convert to a java.object
    SIMPLE_TYPES = {
      :boolean => ['java.lang.Boolean', lambda {|param| param}],
      :byte => ['java.lang.Byte', lambda {|param| param.to_i}],
      :int => ['java.lang.Integer', lambda {|param| param.to_i}],
      :long => ['java.lang.Long', lambda {|param| param.to_i}],
      :float => ['java.lang.Float', lambda {|param| param.to_f}],
      :double => ['java.lang.Double', lambda {|param| param.to_f}],
      :list => ['java.util.ArrayList', lambda {|param| param.to_a}],
      :map => ['java.util.HashMap', lambda {|param| param}],
      :set => ['java.util.HashSet', lambda {|param| param}],
      :string => ['java.lang.String', lambda {|param| param.to_s}],
      :void => ['java.lang.Void', lambda {|param| nil}]
    }
 
    def to_java_type(type_name)
      SIMPLE_TYPES[type_name][0] || type_name
    end
    #TODO: I'm not sure this is strictly needed, but funky things can happen if you
    # are expecting your attributes (from the ruby side) to be ruby types and they are java types.
    def to_ruby(type_name)
      SIMPLE_TYPES[type_name][1] || lambda {|param| param}
    end
  end
 
  class Parameter
    include JavaTypeAware
 
    def initialize(type, name, description)
      @type, @name, @description = type, name, description
    end
 
    def to_jmx
      MBeanParameterInfo.new @name.to_s, to_java_type(@type), @description
    end
  end
 
  class Operation < Struct.new(:description, :parameters, :return_type, :name, :impact)
    include JavaTypeAware
 
    def initialize(description)
      super
      self.parameters, self.impact, self.description = [], MBeanOperationInfo::UNKNOWN, description
    end
 
    def to_jmx
      java_parameters = parameters.map { |parameter| parameter.to_jmx }
      MBeanOperationInfo.new name.to_s, description, java_parameters.to_java(javax.management.MBeanParameterInfo), to_java_type(return_type), impact
    end
  end
 
  class Attribute < Struct.new(:name, :type, :description, :is_reader, :is_writer, :is_iser)
    include JavaTypeAware
 
    def initialize(name, type, description, is_rdr, is_wrtr)
      super
      self.description, self.type, self.name = description, type, name
      self.is_reader,self.is_writer, self.is_iser = is_rdr, is_wrtr, false
    end
 
    def to_jmx
      MBeanAttributeInfo.new(name.to_s, to_java_type(type), description, is_reader, is_writer, is_iser)
    end
  end
 
  # Creators of Ruby based MBeans must inherit this
  # class (<tt>DynamicMBean</tt>) in their own bean classes and then register them with a JMX mbean server.
  # Here is an example:
  # class MyMBean < DynamicMBean
  # rw_attribute :status, :string, "Status information for this process"
  #
  # operation "Shutdown this process"
  # parameter :string, "user_name", "Name of user requesting shutdown"
  # returns :string
  # def shutdown(user_name)
  # "shutdown requests more time"
  # end
  # end
  # Once you have defined your bean class you can start declaring attributes and operations.
  # Attributes come in three flavors: read, write, and read write. Simmilar to the <tt>attr*</tt>
  # helpers, there are helpers that are used to create management attributes. Use +r_attribute+,
  # +w_attribute+, and +rw_attribute+ to declare attributes, and the +operation+, +returns+,
  # and +parameter+ helpers to define a management operation.
  # Creating attributes with the *_attribute convention ALSO creates ruby accessors
  # (it invokes the attr_accessor/attr_reader/attr_writer ruby helpers) to create ruby methods
  # like: user_name= and username. So in your ruby code you can treat the attributes
  # as "regular" ruby accessors
  class DynamicMBean
    import javax.management.MBeanOperationInfo
    import javax.management.MBeanAttributeInfo
    import javax.management.DynamicMBean
    import javax.management.MBeanInfo
    include JMX::JavaTypeAware
  
    #NOTE this will not be needed when JRuby-3164 is fixed.
    def self.inherited(cls)
      cls.send(:include, DynamicMBean)
    end
  
    # TODO: preserve any original method_added?
    # TODO: Error handling here when it all goes wrong?
    def self.method_added(name) #:nodoc:
      return if Thread.current[:op].nil?
      Thread.current[:op].name = name
      operations << Thread.current[:op].to_jmx
      Thread.current[:op] = nil
    end
  
    def self.attributes #:nodoc:
      Thread.current[:attrs] ||= []
    end
  
    def self.operations #:nodoc:
      Thread.current[:ops] ||= []
    end
  
    # the <tt>rw_attribute</tt> method is used to declare a JMX read write attribute.
    # see the +JavaSimpleTypes+ module for more information about acceptable types
    # usage:
    # rw_attribute :attribute_name, :string, "Description displayed in a JMX console"
    #
    # The name and type parameters are mandatory
    # The description parameter is optional (defaults to the same value than the env: ruby: No such file or directory
    # name parameter in that case)
    # --
    # methods used to create an attribute. They are modeled on the attrib_accessor
    # patterns of creating getters and setters in ruby
    #++
    def self.rw_attribute(name, type, description=nil)
      description ||= name.to_s
      attributes << JMX::Attribute.new(name, type, description, true, true).to_jmx
      attr_accessor name
      #create a "java" oriented accessor method
      define_method("jmx_get_#{name.to_s.downcase}") do
        begin
          #attempt conversion
          java_type = to_java_type(type)
          value = eval "#{java_type}.new(@#{name.to_s})"
        rescue
          #otherwise turn it into a java Object type for now.
          value = eval "Java.ruby_to_java(@#{name.to_s})"
        end
        attribute = javax.management.Attribute.new(name.to_s, value)
      end
  
      define_method("jmx_set_#{name.to_s.downcase}") do |value|
        blck = to_ruby(type)
        send "#{name.to_s}=", blck.call(value)
      end
    end
  
    # the <tt>r_attribute</tt> method is used to declare a JMX read only attribute.
    # see the +JavaSimpleTypes+ module for more information about acceptable types
    # usage:
    # r_attribute :attribute_name, :string, "Description displayed in a JMX console"
    def self.r_attribute(name, type, description)
      attributes << JMX::Attribute.new(name, type, description, true, false).to_jmx
      attr_reader name
      #create a "java" oriented accessor method
      define_method("jmx_get_#{name.to_s.downcase}") do
        begin
          #attempt conversion
          java_type = to_java_type(type)
          value = eval "#{java_type}.new(@#{name.to_s})"
        rescue
          #otherwise turn it into a java Object type for now.
          value = eval "Java.ruby_to_java(@#{name.to_s})"
        end
        attribute = javax.management.Attribute.new(name.to_s, value)
      end
    end
  
    # the <tt>w_attribute</tt> method is used to declare a JMX write only attribute.
    # see the +JavaSimpleTypes+ module for more information about acceptable types
    # usage:
    # w_attribute :attribute_name, :string, "Description displayed in a JMX console"
    def self.w_attribute(name, type, description)
      attributes << JMX::Attribute.new(name, type, description, false, true).to_jmx
      attr_writer name
      define_method("jmx_set_#{name.to_s.downcase}") do |value|
        blck = to_ruby(type)
        eval "@#{name.to_s} = #{blck.call(value)}"
      end
    end
  
    # Use the operation method to declare the start of an operation
    # It takes as an optional argument the description for the operation
    # Example:
    # operation "Used to start the service"
    # def start
    # end
    #--
    # Last operation wins if more than one
    #++
    def self.operation(description=nil)
      # Wait to error check until method_added so we can know method name
      Thread.current[:op] = JMX::Operation.new description
    end
  
    # Used to declare a parameter (you can declare more than one in succession) that
    # is associated with the currently declared operation.
    # The type is mandatory, the name and description are optional.
    # Example:
    # operation "Used to update the name of a service"
    # parameter :string, "name", "Set the new name of the service"
    # def start(name)
    # ...
    # end
    def self.parameter(type, name=nil, description=nil)
      Thread.current[:op].parameters << JMX::Parameter.new(type, name, description)
    end
  
    # Used to declare the return type of the operation
    # operation "Used to update the name of a service"
    # parameter :string, "name", "Set the new name of the service"
    # returns :void
    # def do_stuff
    # ...
    # end
    def self.returns(type)
      Thread.current[:op].return_type = type
    end
  
    def initialize(description="")
      name = self.class.to_s
      operations = self.class.operations.to_java(MBeanOperationInfo)
      attributes = self.class.attributes.to_java(MBeanAttributeInfo)
      @info = MBeanInfo.new name, description, attributes, nil, operations, nil
    end
  
    # Retrieve the value of the requested attribute
    def getAttribute(attribute)
      send("jmx_get_"+attribute.downcase).value
    end
  
    def getAttributes(attributes)
      attrs = javax.management.AttributeList.new
      attributes.each { |attribute| send("jmx_get_"+attribute.downcase) }
      attrs
    end
  
    def getMBeanInfo; @info; end
  
    def invoke(actionName, params=nil, signature=nil)
      send(actionName, *params)
    end
  
    def setAttribute(attribute)
      send("jmx_set_#{attribute.name.downcase}", attribute.value)
    end
  
    def setAttributes(attributes)
      attributes.each { |attribute| setAttribute attribute}
    end
  
    def to_s; toString; end
    def inspect; toString; end
    def toString; "#@info.class_name: #@info.description"; end
  end
  
end