Merge branch 'ff/genericjmx'
[collectd.git] / bindings / java / org / collectd / java / GenericJMXConfValue.java
1 /*
2  * collectd/java - org/collectd/java/GenericJMXConfValue.java
3  * Copyright (C) 2009  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  */
21
22 package org.collectd.java;
23
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.Iterator;
28 import java.util.ArrayList;
29
30 import java.math.BigDecimal;
31 import java.math.BigInteger;
32
33 import javax.management.MBeanServerConnection;
34 import javax.management.ObjectName;
35 import javax.management.openmbean.OpenType;
36 import javax.management.openmbean.CompositeData;
37 import javax.management.openmbean.InvalidKeyException;
38
39 import org.collectd.api.Collectd;
40 import org.collectd.api.DataSet;
41 import org.collectd.api.DataSource;
42 import org.collectd.api.ValueList;
43 import org.collectd.api.PluginData;
44 import org.collectd.api.OConfigValue;
45 import org.collectd.api.OConfigItem;
46
47 /**
48  * Representation of a &lt;value&nbsp;/&gt; block and query functionality.
49  *
50  * This class represents a &lt;value&nbsp;/&gt; block in the configuration. As
51  * such, the constructor takes an {@link org.collectd.api.OConfigValue} to
52  * construct an object of this class.
53  *
54  * The object can then be asked to query data from JMX and dispatch it to
55  * collectd.
56  *
57  * @see GenericJMXConfMBean
58  */
59 class GenericJMXConfValue
60 {
61   private String _ds_name;
62   private DataSet _ds;
63   private List<String> _attributes;
64   private String _instance_prefix;
65   private boolean _is_table;
66
67   /**
68    * Converts a generic (OpenType) object to a number.
69    *
70    * Returns null if a conversion is not possible or not implemented.
71    */
72   private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
73   {
74     if (obj instanceof String)
75     {
76       String str = (String) obj;
77       
78       try
79       {
80         if (ds_type == DataSource.TYPE_GAUGE)
81           return (new Double (str));
82         else
83           return (new Long (str));
84       }
85       catch (NumberFormatException e)
86       {
87         return (null);
88       }
89     }
90     else if (obj instanceof Byte)
91     {
92       return (new Byte ((Byte) obj));
93     }
94     else if (obj instanceof Short)
95     {
96       return (new Short ((Short) obj));
97     }
98     else if (obj instanceof Integer)
99     {
100       return (new Integer ((Integer) obj));
101     }
102     else if (obj instanceof Long)
103     {
104       return (new Long ((Long) obj));
105     }
106     else if (obj instanceof Float)
107     {
108       return (new Float ((Float) obj));
109     }
110     else if (obj instanceof Double)
111     {
112       return (new Double ((Double) obj));
113     }
114     else if (obj instanceof BigDecimal)
115     {
116       return (BigDecimal.ZERO.add ((BigDecimal) obj));
117     }
118     else if (obj instanceof BigInteger)
119     {
120       return (BigInteger.ZERO.add ((BigInteger) obj));
121     }
122
123     return (null);
124   } /* }}} Number genericObjectToNumber */
125
126   private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
127   {
128     List<Number> ret = new ArrayList<Number> ();
129     List<DataSource> dsrc = this._ds.getDataSources ();
130
131     assert (objects.size () == dsrc.size ());
132
133     for (int i = 0; i < objects.size (); i++)
134     {
135       Number n;
136
137       n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
138       if (n == null)
139         return (null);
140       ret.add (n);
141     }
142
143     return (ret);
144   } /* }}} List<Number> genericListToNumber */
145
146   private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
147       String key)
148   {
149     List<Object> objects = new ArrayList<Object> ();
150
151     for (int i = 0; i < cdlist.size (); i++)
152     {
153       CompositeData cd;
154       Object value;
155
156       cd = cdlist.get (i);
157       try
158       {
159         value = cd.get (key);
160       }
161       catch (InvalidKeyException e)
162       {
163         return (null);
164       }
165       objects.add (value);
166     }
167
168     return (genericListToNumber (objects));
169   } /* }}} List<Number> genericCompositeToNumber */
170
171   private void submitTable (List<Object> objects, ValueList vl) /* {{{ */
172   {
173     List<CompositeData> cdlist;
174     Set<String> keySet = null;
175     Iterator<String> keyIter;
176
177     cdlist = new ArrayList<CompositeData> ();
178     for (int i = 0; i < objects.size (); i++)
179     {
180       Object obj;
181
182       obj = objects.get (i);
183       if (obj instanceof CompositeData)
184       {
185         CompositeData cd;
186
187         cd = (CompositeData) obj;
188
189         if (i == 0)
190           keySet = cd.getCompositeType ().keySet ();
191
192         cdlist.add (cd);
193       }
194       else
195       {
196         Collectd.logError ("GenericJMXConfValue: At least one of the "
197             + "attributes was not of type `CompositeData', as required "
198             + "when table is set to `true'.");
199         return;
200       }
201     }
202
203     assert (keySet != null);
204
205     keyIter = keySet.iterator ();
206     while (keyIter.hasNext ())
207     {
208       String key;
209       List<Number> values;
210
211       key = keyIter.next ();
212       values = genericCompositeToNumber (cdlist, key);
213       if (values == null)
214       {
215         Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
216             + "numbers for key " + key + ". Most likely not all attributes "
217             + "have this key.");
218         continue;
219       }
220
221       if (this._instance_prefix == null)
222         vl.setTypeInstance (key);
223       else
224         vl.setTypeInstance (this._instance_prefix + key);
225       vl.setValues (values);
226
227       Collectd.dispatchValues (vl);
228     }
229   } /* }}} void submitTable */
230
231   private void submitScalar (List<Object> objects, ValueList vl) /* {{{ */
232   {
233     List<Number> values;
234
235     values = genericListToNumber (objects);
236     if (values == null)
237     {
238       Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
239           + "objects to numbers.");
240       return;
241     }
242
243     if (this._instance_prefix == null)
244       vl.setTypeInstance ("");
245     else
246       vl.setTypeInstance (this._instance_prefix);
247     vl.setValues (values);
248
249     Collectd.dispatchValues (vl);
250   } /* }}} void submitScalar */
251
252   private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
253       List<String> attrName)
254   {
255     String key;
256     Object value;
257
258     key = attrName.remove (0);
259
260     try
261     {
262       value = parent.get (key);
263     }
264     catch (InvalidKeyException e)
265     {
266       return (null);
267     }
268
269     if (attrName.size () == 0)
270     {
271       return (value);
272     }
273     else
274     {
275       if (value instanceof CompositeData)
276         return (queryAttributeRecursive ((CompositeData) value, attrName));
277       else
278         return (null);
279     }
280   } /* }}} queryAttributeRecursive */
281
282   private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
283       ObjectName objName, String attrName)
284   {
285     List<String> attrNameList;
286     String key;
287     Object value;
288     String[] attrNameArray;
289
290     attrNameList = new ArrayList<String> ();
291
292     attrNameArray = attrName.split ("\\.");
293     key = attrNameArray[0];
294     for (int i = 1; i < attrNameArray.length; i++)
295       attrNameList.add (attrNameArray[i]);
296
297     try
298     {
299       value = conn.getAttribute (objName, key);
300     }
301     catch (Exception e)
302     {
303       Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
304           + e);
305       return (null);
306     }
307
308     if (attrNameList.size () == 0)
309     {
310       return (value);
311     }
312     else
313     {
314       if (value instanceof CompositeData)
315         return (queryAttributeRecursive((CompositeData) value, attrNameList));
316       else if (value instanceof OpenType)
317       {
318         OpenType ot = (OpenType) value;
319         Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
320             + ot.getTypeName () + "\" is not yet implemented.");
321         return (null);
322       }
323       else
324       {
325         Collectd.logError ("GenericJMXConfValue: Received object of "
326             + "unknown class.");
327         return (null);
328       }
329     }
330   } /* }}} Object queryAttribute */
331
332   private String getConfigString (OConfigItem ci) /* {{{ */
333   {
334     List<OConfigValue> values;
335     OConfigValue v;
336
337     values = ci.getValues ();
338     if (values.size () != 1)
339     {
340       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
341           + " configuration option needs exactly one string argument.");
342       return (null);
343     }
344
345     v = values.get (0);
346     if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
347     {
348       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
349           + " configuration option needs exactly one string argument.");
350       return (null);
351     }
352
353     return (v.getString ());
354   } /* }}} String getConfigString */
355
356   private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
357   {
358     List<OConfigValue> values;
359     OConfigValue v;
360     Boolean b;
361
362     values = ci.getValues ();
363     if (values.size () != 1)
364     {
365       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
366           + " configuration option needs exactly one boolean argument.");
367       return (null);
368     }
369
370     v = values.get (0);
371     if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
372     {
373       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
374           + " configuration option needs exactly one boolean argument.");
375       return (null);
376     }
377
378     return (new Boolean (v.getBoolean ()));
379   } /* }}} String getConfigBoolean */
380
381   /**
382    * Constructs a new value with the configured properties.
383    */
384   public GenericJMXConfValue (OConfigItem ci) /* {{{ */
385     throws IllegalArgumentException
386   {
387     List<OConfigItem> children;
388     Iterator<OConfigItem> iter;
389
390     this._ds_name = null;
391     this._ds = null;
392     this._attributes = new ArrayList<String> ();
393     this._instance_prefix = null;
394     this._is_table = false;
395
396     /*
397      * <Value>
398      *   Type "memory"
399      *   Table true|false
400      *   Attribute "HeapMemoryUsage"
401      *   Attribute "..."
402      *   :
403      *   # Type instance:
404      *   InstancePrefix "heap-"
405      * </Value>
406      */
407     children = ci.getChildren ();
408     iter = children.iterator ();
409     while (iter.hasNext ())
410     {
411       OConfigItem child = iter.next ();
412
413       if (child.getKey ().equalsIgnoreCase ("Type"))
414       {
415         String tmp = getConfigString (child);
416         if (tmp != null)
417           this._ds_name = tmp;
418       }
419       else if (child.getKey ().equalsIgnoreCase ("Table"))
420       {
421         Boolean tmp = getConfigBoolean (child);
422         if (tmp != null)
423           this._is_table = tmp.booleanValue ();
424       }
425       else if (child.getKey ().equalsIgnoreCase ("Attribute"))
426       {
427         String tmp = getConfigString (child);
428         if (tmp != null)
429           this._attributes.add (tmp);
430       }
431       else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
432       {
433         String tmp = getConfigString (child);
434         if (tmp != null)
435           this._instance_prefix = tmp;
436       }
437       else
438         throw (new IllegalArgumentException ("Unknown option: "
439               + child.getKey ()));
440     }
441
442     if (this._ds_name == null)
443       throw (new IllegalArgumentException ("No data set was defined."));
444     else if (this._attributes.size () == 0)
445       throw (new IllegalArgumentException ("No attribute was defined."));
446   } /* }}} GenericJMXConfValue (OConfigItem ci) */
447
448   /**
449    * Query values via JMX according to the object's configuration and dispatch
450    * them to collectd.
451    *
452    * @param conn Connection to the MBeanServer.
453    * @param objName Object name of the MBean to query.
454    * @param pd      Preset naming components. The members host, plugin and
455    *                plugin instance will be used.
456    */
457   public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
458       PluginData pd)
459   {
460     ValueList vl;
461     List<DataSource> dsrc;
462     List<Object> values;
463
464     if (this._ds == null)
465     {
466       this._ds = Collectd.getDS (this._ds_name);
467       if (this._ds == null)
468       {
469         Collectd.logError ("GenericJMXConfValue: Unknown type: "
470             + this._ds_name);
471         return;
472       }
473     }
474
475     dsrc = this._ds.getDataSources ();
476     if (dsrc.size () != this._attributes.size ())
477     {
478       Collectd.logError ("GenericJMXConfValue.query: The data set "
479           + this._ds_name + " has " + this._ds.getDataSources ().size ()
480           + " data sources, but there were " + this._attributes.size ()
481           + " attributes configured. This doesn't match!");
482       this._ds = null;
483       return;
484     }
485
486     vl = new ValueList (pd);
487     vl.setType (this._ds_name);
488     vl.setTypeInstance (this._instance_prefix);
489
490     values = new ArrayList<Object> ();
491
492     assert (dsrc.size () == this._attributes.size ());
493     for (int i = 0; i < this._attributes.size (); i++)
494     {
495       Object v;
496
497       v = queryAttribute (conn, objName, this._attributes.get (i));
498       if (v == null)
499       {
500         Collectd.logError ("GenericJMXConfValue.query: "
501             + "Querying attribute " + this._attributes.get (i) + " failed.");
502         return;
503       }
504
505       values.add (v);
506     }
507
508     if (this._is_table)
509       submitTable (values, vl);
510     else
511       submitScalar (values, vl);
512   } /* }}} void query */
513 }
514
515 /* vim: set sw=2 sts=2 et fdm=marker : */