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 */
27 package org.collectd.java;
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;
36 import java.math.BigDecimal;
37 import java.math.BigInteger;
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;
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;
54 /**
55 * Representation of a <value /> block and query functionality.
56 *
57 * This class represents a <value /> 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;
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;
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 }
132 return (null);
133 } /* }}} Number genericObjectToNumber */
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 ();
145 assert (objects.size () == dsrc.size ());
147 for (int i = 0; i < objects.size (); i++)
148 {
149 Number n;
151 n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
152 if (n == null)
153 return (null);
154 ret.add (n);
155 }
157 return (ret);
158 } /* }}} List<Number> genericListToNumber */
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> ();
173 for (int i = 0; i < cdlist.size (); i++)
174 {
175 CompositeData cd;
176 Object value;
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 }
190 return (genericListToNumber (objects));
191 } /* }}} List<Number> genericCompositeToNumber */
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;
200 cdlist = new ArrayList<CompositeData> ();
201 for (int i = 0; i < objects.size (); i++)
202 {
203 Object obj;
205 obj = objects.get (i);
206 if (obj instanceof CompositeData)
207 {
208 CompositeData cd;
210 cd = (CompositeData) obj;
212 if (i == 0)
213 keySet = cd.getCompositeType ().keySet ();
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 }
226 assert (keySet != null);
228 keyIter = keySet.iterator ();
229 while (keyIter.hasNext ())
230 {
231 String key;
232 List<Number> values;
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 }
244 if (instancePrefix == null)
245 vl.setTypeInstance (key);
246 else
247 vl.setTypeInstance (instancePrefix + key);
248 vl.setValues (values);
250 Collectd.dispatchValues (vl);
251 }
252 } /* }}} void submitTable */
254 private void submitScalar (List<Object> objects, ValueList vl, /* {{{ */
255 String instancePrefix)
256 {
257 List<Number> values;
259 values = genericListToNumber (objects);
260 if (values == null)
261 {
262 Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
263 + "objects to numbers.");
264 return;
265 }
267 if (instancePrefix == null)
268 vl.setTypeInstance ("");
269 else
270 vl.setTypeInstance (instancePrefix);
271 vl.setValues (values);
273 Collectd.dispatchValues (vl);
274 } /* }}} void submitScalar */
276 private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
277 List<String> attrName)
278 {
279 String key;
280 Object value;
282 key = attrName.remove (0);
284 try
285 {
286 value = parent.get (key);
287 }
288 catch (InvalidKeyException e)
289 {
290 return (null);
291 }
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 */
308 private Object queryAttributeRecursive (TabularData parent, /* {{{ */
309 List<String> attrName)
310 {
311 String key;
312 Object value = null;
314 key = attrName.remove (0);
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 }
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 */
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;
353 attrNameList = new ArrayList<String> ();
355 attrNameArray = attrName.split ("\\.");
356 key = attrNameArray[0];
357 for (int i = 1; i < attrNameArray.length; i++)
358 attrNameList.add (attrNameArray[i]);
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 }
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 */
404 private String join (String separator, List<String> list) /* {{{ */
405 {
406 StringBuffer sb;
408 sb = new StringBuffer ();
410 for (int i = 0; i < list.size (); i++)
411 {
412 if (i > 0)
413 sb.append ("-");
414 sb.append (list.get (i));
415 }
417 return (sb.toString ());
418 } /* }}} String join */
420 private String getConfigString (OConfigItem ci) /* {{{ */
421 {
422 List<OConfigValue> values;
423 OConfigValue v;
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 }
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 }
441 return (v.getString ());
442 } /* }}} String getConfigString */
444 private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
445 {
446 List<OConfigValue> values;
447 OConfigValue v;
448 Boolean b;
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 }
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 }
466 return (new Boolean (v.getBoolean ()));
467 } /* }}} String getConfigBoolean */
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;
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;
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 ();
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 }
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) */
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;
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 }
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 }
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 }
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;
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 }
620 if (this._instance_prefix != null)
621 instancePrefix = new String (this._instance_prefix
622 + join ("-", instanceList));
623 else
624 instancePrefix = join ("-", instanceList);
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;
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 }
644 values.add (v);
645 }
647 if (this._is_table)
648 submitTable (values, vl, instancePrefix);
649 else
650 submitScalar (values, vl, instancePrefix);
651 } /* }}} void query */
652 } /* class GenericJMXConfValue */
654 /* vim: set sw=2 sts=2 et fdm=marker : */