Merge pull request #2975 from octo/issue/2954
[collectd.git] / bindings / java / org / collectd / java / GenericJMXConfValue.java
1 /**
2  * collectd - bindings/java/org/collectd/java/GenericJMXConfValue.java
3  * Copyright (C) 2009       Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  */
26
27 package org.collectd.java;
28
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Collection;
32 import java.util.Set;
33 import java.util.Iterator;
34 import java.util.ArrayList;
35
36 import java.math.BigDecimal;
37 import java.math.BigInteger;
38
39 import javax.management.MBeanServerConnection;
40 import javax.management.ObjectName;
41 import javax.management.openmbean.OpenType;
42 import javax.management.openmbean.CompositeData;
43 import javax.management.openmbean.TabularData;
44 import javax.management.openmbean.InvalidKeyException;
45
46 import org.collectd.api.Collectd;
47 import org.collectd.api.DataSet;
48 import org.collectd.api.DataSource;
49 import org.collectd.api.ValueList;
50 import org.collectd.api.PluginData;
51 import org.collectd.api.OConfigValue;
52 import org.collectd.api.OConfigItem;
53
54 /**
55  * Representation of a &lt;value&nbsp;/&gt; block and query functionality.
56  *
57  * This class represents a &lt;value&nbsp;/&gt; block in the configuration. As
58  * such, the constructor takes an {@link org.collectd.api.OConfigValue} to
59  * construct an object of this class.
60  *
61  * The object can then be asked to query data from JMX and dispatch it to
62  * collectd.
63  *
64  * @see GenericJMXConfMBean
65  */
66 class GenericJMXConfValue
67 {
68   private String _ds_name;
69   private DataSet _ds;
70   private List<String> _attributes;
71   private String _instance_prefix;
72   private List<String> _instance_from;
73   private String _plugin_name;
74   private boolean _is_table;
75
76   /**
77    * Converts a generic (OpenType) object to a number.
78    *
79    * Returns null if a conversion is not possible or not implemented.
80    */
81   private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
82   {
83     if (obj instanceof String)
84     {
85       String str = (String) obj;
86       
87       try
88       {
89         if (ds_type == DataSource.TYPE_GAUGE)
90           return (new Double (str));
91         else
92           return (new Long (str));
93       }
94       catch (NumberFormatException e)
95       {
96         return (null);
97       }
98     }
99     else if (obj instanceof Byte)
100     {
101       return (new Byte ((Byte) obj));
102     }
103     else if (obj instanceof Short)
104     {
105       return (new Short ((Short) obj));
106     }
107     else if (obj instanceof Integer)
108     {
109       return (new Integer ((Integer) obj));
110     }
111     else if (obj instanceof Long)
112     {
113       return (new Long ((Long) obj));
114     }
115     else if (obj instanceof Float)
116     {
117       return (new Float ((Float) obj));
118     }
119     else if (obj instanceof Double)
120     {
121       return (new Double ((Double) obj));
122     }
123     else if (obj instanceof BigDecimal)
124     {
125       return (BigDecimal.ZERO.add ((BigDecimal) obj));
126     }
127     else if (obj instanceof BigInteger)
128     {
129       return (BigInteger.ZERO.add ((BigInteger) obj));
130     }
131
132     return (null);
133   } /* }}} Number genericObjectToNumber */
134
135   /**
136    * Converts a generic list to a list of numbers.
137    *
138    * Returns null if one or more objects could not be converted.
139    */
140   private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
141   {
142     List<Number> ret = new ArrayList<Number> ();
143     List<DataSource> dsrc = this._ds.getDataSources ();
144
145     assert (objects.size () == dsrc.size ());
146
147     for (int i = 0; i < objects.size (); i++)
148     {
149       Number n;
150
151       n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
152       if (n == null)
153         return (null);
154       ret.add (n);
155     }
156
157     return (ret);
158   } /* }}} List<Number> genericListToNumber */
159
160   /**
161    * Converts a list of CompositeData to a list of numbers.
162    *
163    * From each <em>CompositeData </em> the key <em>key</em> is received and all
164    * those values are converted to a number. If one of the
165    * <em>CompositeData</em> doesn't have the specified key or one returned
166    * object cannot converted to a number then the function will return null.
167    */
168   private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
169       String key)
170   {
171     List<Object> objects = new ArrayList<Object> ();
172
173     for (int i = 0; i < cdlist.size (); i++)
174     {
175       CompositeData cd;
176       Object value;
177
178       cd = cdlist.get (i);
179       try
180       {
181         value = cd.get (key);
182       }
183       catch (InvalidKeyException e)
184       {
185         return (null);
186       }
187       objects.add (value);
188     }
189
190     return (genericListToNumber (objects));
191   } /* }}} List<Number> genericCompositeToNumber */
192
193   private void submitTable (List<Object> objects, ValueList vl, /* {{{ */
194       String instancePrefix)
195   {
196     List<CompositeData> cdlist;
197     Set<String> keySet = null;
198     Iterator<String> keyIter;
199
200     cdlist = new ArrayList<CompositeData> ();
201     for (int i = 0; i < objects.size (); i++)
202     {
203       Object obj;
204
205       obj = objects.get (i);
206       if (obj instanceof CompositeData)
207       {
208         CompositeData cd;
209
210         cd = (CompositeData) obj;
211
212         if (i == 0)
213           keySet = cd.getCompositeType ().keySet ();
214
215         cdlist.add (cd);
216       }
217       else
218       {
219         Collectd.logError ("GenericJMXConfValue: At least one of the "
220             + "attributes was not of type `CompositeData', as required "
221             + "when table is set to `true'.");
222         return;
223       }
224     }
225
226     assert (keySet != null);
227
228     keyIter = keySet.iterator ();
229     while (keyIter.hasNext ())
230     {
231       String key;
232       List<Number> values;
233
234       key = keyIter.next ();
235       values = genericCompositeToNumber (cdlist, key);
236       if (values == null)
237       {
238         Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
239             + "numbers for key " + key + ". Most likely not all attributes "
240             + "have this key.");
241         continue;
242       }
243
244       if (instancePrefix == null)
245         vl.setTypeInstance (key);
246       else
247         vl.setTypeInstance (instancePrefix + key);
248       vl.setValues (values);
249
250       Collectd.dispatchValues (vl);
251     }
252   } /* }}} void submitTable */
253
254   private void submitScalar (List<Object> objects, ValueList vl, /* {{{ */
255       String instancePrefix)
256   {
257     List<Number> values;
258
259     values = genericListToNumber (objects);
260     if (values == null)
261     {
262       Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
263           + "objects to numbers.");
264       return;
265     }
266
267     if (instancePrefix == null)
268       vl.setTypeInstance ("");
269     else
270       vl.setTypeInstance (instancePrefix);
271     vl.setValues (values);
272
273     Collectd.dispatchValues (vl);
274   } /* }}} void submitScalar */
275
276   private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
277       List<String> attrName)
278   {
279     String key;
280     Object value;
281
282     key = attrName.remove (0);
283
284     try
285     {
286       value = parent.get (key);
287     }
288     catch (InvalidKeyException e)
289     {
290       return (null);
291     }
292
293     if (attrName.size () == 0)
294     {
295       return (value);
296     }
297     else
298     {
299       if (value instanceof CompositeData)
300         return (queryAttributeRecursive ((CompositeData) value, attrName));
301       else if (value instanceof TabularData)
302         return (queryAttributeRecursive ((TabularData) value, attrName));
303       else
304         return (null);
305     }
306   } /* }}} queryAttributeRecursive */
307
308   private Object queryAttributeRecursive (TabularData parent, /* {{{ */
309       List<String> attrName)
310   {
311     String key;
312     Object value = null;
313
314     key = attrName.remove (0);
315
316     @SuppressWarnings("unchecked")
317     Collection<CompositeData> table = (Collection<CompositeData>) parent.values();
318     for (CompositeData compositeData : table)
319     {
320       if (key.equals(compositeData.get("key")))
321       {
322         value = compositeData.get("value");
323       }
324     }
325     if (null == value)
326     {
327       return (null);
328     }
329
330     if (attrName.size () == 0)
331     {
332       return (value);
333     }
334     else
335     {
336       if (value instanceof CompositeData)
337         return (queryAttributeRecursive ((CompositeData) value, attrName));
338       else if (value instanceof TabularData)
339         return (queryAttributeRecursive ((TabularData) value, attrName));
340       else
341         return (null);
342     }
343   } /* }}} queryAttributeRecursive */
344
345   private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
346       ObjectName objName, String attrName)
347   {
348     List<String> attrNameList;
349     String key;
350     Object value;
351     String[] attrNameArray;
352
353     attrNameList = new ArrayList<String> ();
354
355     attrNameArray = attrName.split ("\\.");
356     key = attrNameArray[0];
357     for (int i = 1; i < attrNameArray.length; i++)
358       attrNameList.add (attrNameArray[i]);
359
360     try
361     {
362       try
363       {
364         value = conn.getAttribute (objName, key);
365       }
366       catch (javax.management.AttributeNotFoundException e)
367       {
368         value = conn.invoke (objName, key, /* args = */ null, /* types = */ null);
369       }
370     }
371     catch (Exception e)
372     {
373       Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
374           + e);
375       return (null);
376     }
377
378     if (attrNameList.size () == 0)
379     {
380       return (value);
381     }
382     else
383     {
384       if (value instanceof CompositeData)
385         return (queryAttributeRecursive((CompositeData) value, attrNameList));
386       else if (value instanceof TabularData)
387         return (queryAttributeRecursive((TabularData) value, attrNameList));
388       else if (value instanceof OpenType)
389       {
390         OpenType ot = (OpenType) value;
391         Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
392             + ot.getTypeName () + "\" is not yet implemented.");
393         return (null);
394       }
395       else
396       {
397         Collectd.logError ("GenericJMXConfValue: Received object of "
398             + "unknown class. " + attrName + " " + ((value == null)?"null":value.getClass().getName()));
399         return (null);
400       }
401     }
402   } /* }}} Object queryAttribute */
403
404   private String join (String separator, List<String> list) /* {{{ */
405   {
406     StringBuffer sb;
407
408     sb = new StringBuffer ();
409
410     for (int i = 0; i < list.size (); i++)
411     {
412       if (i > 0)
413         sb.append ("-");
414       sb.append (list.get (i));
415     }
416
417     return (sb.toString ());
418   } /* }}} String join */
419
420   private String getConfigString (OConfigItem ci) /* {{{ */
421   {
422     List<OConfigValue> values;
423     OConfigValue v;
424
425     values = ci.getValues ();
426     if (values.size () != 1)
427     {
428       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
429           + " configuration option needs exactly one string argument.");
430       return (null);
431     }
432
433     v = values.get (0);
434     if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
435     {
436       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
437           + " configuration option needs exactly one string argument.");
438       return (null);
439     }
440
441     return (v.getString ());
442   } /* }}} String getConfigString */
443
444   private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
445   {
446     List<OConfigValue> values;
447     OConfigValue v;
448     Boolean b;
449
450     values = ci.getValues ();
451     if (values.size () != 1)
452     {
453       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
454           + " configuration option needs exactly one boolean argument.");
455       return (null);
456     }
457
458     v = values.get (0);
459     if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
460     {
461       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
462           + " configuration option needs exactly one boolean argument.");
463       return (null);
464     }
465
466     return (new Boolean (v.getBoolean ()));
467   } /* }}} String getConfigBoolean */
468
469   /**
470    * Constructs a new value with the configured properties.
471    */
472   public GenericJMXConfValue (OConfigItem ci) /* {{{ */
473     throws IllegalArgumentException
474   {
475     List<OConfigItem> children;
476     Iterator<OConfigItem> iter;
477
478     this._ds_name = null;
479     this._ds = null;
480     this._attributes = new ArrayList<String> ();
481     this._instance_prefix = null;
482     this._instance_from = new ArrayList<String> ();
483     this._plugin_name = null;
484     this._is_table = false;
485
486     /*
487      * <Value>
488      *   Type "memory"
489      *   Table true|false
490      *   Attribute "HeapMemoryUsage"
491      *   Attribute "..."
492      *   :
493      *   # Type instance:
494      *   InstancePrefix "heap-"
495      * </Value>
496      */
497     children = ci.getChildren ();
498     iter = children.iterator ();
499     while (iter.hasNext ())
500     {
501       OConfigItem child = iter.next ();
502
503       if (child.getKey ().equalsIgnoreCase ("Type"))
504       {
505         String tmp = getConfigString (child);
506         if (tmp != null)
507           this._ds_name = tmp;
508       }
509       else if (child.getKey ().equalsIgnoreCase ("Table"))
510       {
511         Boolean tmp = getConfigBoolean (child);
512         if (tmp != null)
513           this._is_table = tmp.booleanValue ();
514       }
515       else if (child.getKey ().equalsIgnoreCase ("Attribute"))
516       {
517         String tmp = getConfigString (child);
518         if (tmp != null)
519           this._attributes.add (tmp);
520       }
521       else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
522       {
523         String tmp = getConfigString (child);
524         if (tmp != null)
525           this._instance_prefix = tmp;
526       }
527       else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
528       {
529         String tmp = getConfigString (child);
530         if (tmp != null)
531           this._instance_from.add (tmp);
532       }
533       else if (child.getKey ().equalsIgnoreCase ("PluginName"))
534       {
535         String tmp = getConfigString (child);
536         if (tmp != null)
537           this._plugin_name = tmp;
538       }
539       else
540         throw (new IllegalArgumentException ("Unknown option: "
541               + child.getKey ()));
542     }
543
544     if (this._ds_name == null)
545       throw (new IllegalArgumentException ("No data set was defined."));
546     else if (this._attributes.size () == 0)
547       throw (new IllegalArgumentException ("No attribute was defined."));
548   } /* }}} GenericJMXConfValue (OConfigItem ci) */
549
550   /**
551    * Query values via JMX according to the object's configuration and dispatch
552    * them to collectd.
553    *
554    * @param conn    Connection to the MBeanServer.
555    * @param objName Object name of the MBean to query.
556    * @param pd      Preset naming components. The members host, plugin and
557    *                plugin instance will be used.
558    */
559   public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
560       PluginData pd)
561   {
562     ValueList vl;
563     List<DataSource> dsrc;
564     List<Object> values;
565     List<String> instanceList;
566     String instancePrefix;
567
568     if (this._ds == null)
569     {
570       this._ds = Collectd.getDS (this._ds_name);
571       if (this._ds == null)
572       {
573         Collectd.logError ("GenericJMXConfValue: Unknown type: "
574             + this._ds_name);
575         return;
576       }
577     }
578
579     dsrc = this._ds.getDataSources ();
580     if (dsrc.size () != this._attributes.size ())
581     {
582       Collectd.logError ("GenericJMXConfValue.query: The data set "
583           + this._ds_name + " has " + this._ds.getDataSources ().size ()
584           + " data sources, but there were " + this._attributes.size ()
585           + " attributes configured. This doesn't match!");
586       this._ds = null;
587       return;
588     }
589
590     vl = new ValueList (pd);
591     vl.setType (this._ds_name);
592     if (this._plugin_name != null)
593     {
594       vl.setPlugin (this._plugin_name);
595     }
596
597     /*
598      * Build the instnace prefix from the fixed string prefix and the
599      * properties of the objName.
600      */
601     instanceList = new ArrayList<String> ();
602     for (int i = 0; i < this._instance_from.size (); i++)
603     {
604       String propertyName;
605       String propertyValue;
606
607       propertyName = this._instance_from.get (i);
608       propertyValue = objName.getKeyProperty (propertyName);
609       if (propertyValue == null)
610       {
611         Collectd.logError ("GenericJMXConfMBean: "
612             + "No such property in object name: " + propertyName);
613       }
614       else
615       {
616         instanceList.add (propertyValue);
617       }
618     }
619
620     if (this._instance_prefix != null)
621       instancePrefix = new String (this._instance_prefix
622           + join ("-", instanceList));
623     else
624       instancePrefix = join ("-", instanceList);
625
626     /*
627      * Build a list of `Object's which is then passed to `submitTable' and
628      * `submitScalar'.
629      */
630     values = new ArrayList<Object> ();
631     assert (dsrc.size () == this._attributes.size ());
632     for (int i = 0; i < this._attributes.size (); i++)
633     {
634       Object v;
635
636       v = queryAttribute (conn, objName, this._attributes.get (i));
637       if (v == null)
638       {
639         Collectd.logError ("GenericJMXConfValue.query: "
640             + "Querying attribute " + this._attributes.get (i) + " failed.");
641         return;
642       }
643
644       values.add (v);
645     }
646
647     if (this._is_table)
648       submitTable (values, vl, instancePrefix);
649     else
650       submitScalar (values, vl, instancePrefix);
651   } /* }}} void query */
652 } /* class GenericJMXConfValue */
653
654 /* vim: set sw=2 sts=2 et fdm=marker : */