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