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