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