java plugin: Add an early prototype of a Java binding, similar to the Perl plugin.
[collectd.git] / src / java.c
1 /**
2  * collectd - src/java.c
3  * Copyright (C) 2009  Florian octo Forster
4  * Copyright (C) 2008  Justo Alonso Achaques
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Florian octo Forster <octo at verplant.org>
21  *   Justo Alonso Achaques <justo.alonso at gmail.com>
22  **/
23
24 #include "collectd.h"
25 #include "plugin.h"
26 #include "common.h"
27
28 #include <pthread.h>
29 #include <jni.h>
30
31 #if !defined(JNI_VERSION_1_2)
32 # error "Need JNI 1.2 compatible interface!"
33 #endif
34
35 /*
36  * Types
37  */
38 struct java_plugin_s /* {{{ */
39 {
40   char *class_name;
41   jclass class_ptr;
42   jobject object_ptr;
43
44 #define CJNI_FLAG_ENABLED 0x0001
45   int flags;
46
47   jmethodID method_init;
48   jmethodID method_read;
49   jmethodID method_shutdown;
50 };
51 typedef struct java_plugin_s java_plugin_t;
52 /* }}} */
53
54 /*
55  * Global variables
56  */
57 static JavaVM *jvm = NULL;
58
59 static java_plugin_t java_plugins[] =
60 {
61   { "org.collectd.java.Foobar", NULL, NULL, 0, NULL, NULL, NULL }
62 };
63 static size_t java_plugins_num = sizeof (java_plugins) / sizeof (java_plugins[0]);
64
65 /* 
66  * Conversion functons
67  *
68  * - jtoc_*: From Java to C
69  * - ctoj_*: From C to Java
70  */
71 static int jtoc_string (JNIEnv *jvm_env, /* {{{ */
72     char *buffer, size_t buffer_size,
73     jclass class_ptr, jobject object_ptr, const char *method_name)
74 {
75   jmethodID method_id;
76   jobject string_obj;
77   const char *c_str;
78
79   method_id = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
80       method_name, "()Ljava/lang/String;");
81   if (method_id == NULL)
82   {
83     ERROR ("java plugin: jtoc_string: Cannot find method `String %s ()'.",
84         method_name);
85     return (-1);
86   }
87
88   string_obj = (*jvm_env)->CallObjectMethod (jvm_env, object_ptr, method_id);
89   if (string_obj == NULL)
90   {
91     ERROR ("java plugin: jtoc_string: CallObjectMethod (%s) failed.",
92         method_name);
93     return (-1);
94   }
95
96   c_str = (*jvm_env)->GetStringUTFChars (jvm_env, string_obj, 0);
97   if (c_str == NULL)
98   {
99     ERROR ("java plugin: jtoc_string: GetStringUTFChars failed.");
100     (*jvm_env)->DeleteLocalRef (jvm_env, string_obj);
101     return (-1);
102   }
103
104   DEBUG ("java plugin: jtoc_string: ->%s() = %s", method_name, c_str);
105
106   sstrncpy (buffer, c_str, buffer_size);
107
108   (*jvm_env)->ReleaseStringUTFChars (jvm_env, string_obj, c_str);
109   (*jvm_env)->DeleteLocalRef (jvm_env, string_obj);
110
111   return (0);
112 } /* }}} int jtoc_string */
113
114 static int jtoc_long (JNIEnv *jvm_env, /* {{{ */
115     jlong *ret_value,
116     jclass class_ptr, jobject object_ptr, const char *method_name)
117 {
118   jmethodID method_id;
119
120   method_id = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
121       method_name, "()J");
122   if (method_id == NULL)
123   {
124     ERROR ("java plugin: jtoc_string: Cannot find method `long %s ()'.",
125         method_name);
126     return (-1);
127   }
128
129   *ret_value = (*jvm_env)->CallLongMethod (jvm_env, object_ptr, method_id);
130
131   DEBUG ("java plugin: jtoc_long: ->%s() = %li",
132       method_name, (long int) *ret_value);
133
134   return (0);
135 } /* }}} int jtoc_long */
136
137 static int jtoc_double (JNIEnv *jvm_env, /* {{{ */
138     jdouble *ret_value,
139     jclass class_ptr, jobject object_ptr, const char *method_name)
140 {
141   jmethodID method_id;
142
143   method_id = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
144       method_name, "()D");
145   if (method_id == NULL)
146   {
147     ERROR ("java plugin: jtoc_string: Cannot find method `double %s ()'.",
148         method_name);
149     return (-1);
150   }
151
152   *ret_value = (*jvm_env)->CallDoubleMethod (jvm_env, object_ptr, method_id);
153
154   DEBUG ("java plugin: jtoc_double: ->%s() = %g",
155       method_name, (double) *ret_value);
156
157   return (0);
158 } /* }}} int jtoc_double */
159
160 static int jtoc_value (JNIEnv *jvm_env, /* {{{ */
161     value_t *ret_value, int ds_type, jobject object_ptr)
162 {
163   jclass class_ptr;
164   int status;
165
166   class_ptr = (*jvm_env)->GetObjectClass (jvm_env, object_ptr);
167
168   if (ds_type == DS_TYPE_COUNTER)
169   {
170     jlong tmp_long;
171
172     status = jtoc_long (jvm_env, &tmp_long,
173         class_ptr, object_ptr, "longValue");
174     if (status != 0)
175     {
176       ERROR ("java plugin: jtoc_value: "
177           "jtoc_long failed.");
178       return (-1);
179     }
180     (*ret_value).counter = (counter_t) tmp_long;
181   }
182   else
183   {
184     jdouble tmp_double;
185
186     status = jtoc_double (jvm_env, &tmp_double,
187         class_ptr, object_ptr, "doubleValue");
188     if (status != 0)
189     {
190       ERROR ("java plugin: jtoc_value: "
191           "jtoc_double failed.");
192       return (-1);
193     }
194     (*ret_value).gauge = (gauge_t) tmp_double;
195   }
196
197   return (0);
198 } /* }}} int jtoc_value */
199
200 static int jtoc_values_array (JNIEnv *jvm_env, /* {{{ */
201     const data_set_t *ds, value_list_t *vl,
202     jclass class_ptr, jobject object_ptr)
203 {
204   jmethodID m_getvalues;
205   jmethodID m_toarray;
206   jobject o_list;
207   jobjectArray o_number_array;
208
209   value_t *values;
210   int values_num;
211   int i;
212
213   values_num = ds->ds_num;
214
215   values = NULL;
216   o_number_array = NULL;
217   o_list = NULL;
218
219 #define BAIL_OUT(status) \
220   free (values); \
221   if (o_number_array != NULL) \
222     (*jvm_env)->DeleteLocalRef (jvm_env, o_number_array); \
223   if (o_list != NULL) \
224     (*jvm_env)->DeleteLocalRef (jvm_env, o_list); \
225   return (status);
226
227   /* Call: List<Number> ValueList.getValues () */
228   m_getvalues = (*jvm_env)->GetMethodID (jvm_env, class_ptr,
229       "getValues", "()Ljava/util/List;");
230   if (m_getvalues == NULL)
231   {
232     ERROR ("java plugin: jtoc_values_array: "
233         "Cannot find method `List getValues ()'.");
234     BAIL_OUT (-1);
235   }
236
237   o_list = (*jvm_env)->CallObjectMethod (jvm_env, object_ptr, m_getvalues);
238   if (o_list == NULL)
239   {
240     ERROR ("java plugin: jtoc_values_array: "
241         "CallObjectMethod (getValues) failed.");
242     BAIL_OUT (-1);
243   }
244
245   /* Call: Number[] List.toArray () */
246   m_toarray = (*jvm_env)->GetMethodID (jvm_env,
247       (*jvm_env)->GetObjectClass (jvm_env, o_list),
248       "toArray", "()[Ljava/lang/Object;");
249   if (m_toarray == NULL)
250   {
251     ERROR ("java plugin: jtoc_values_array: "
252         "Cannot find method `Object[] toArray ()'.");
253     BAIL_OUT (-1);
254   }
255
256   o_number_array = (*jvm_env)->CallObjectMethod (jvm_env, o_list, m_toarray);
257   if (o_number_array == NULL)
258   {
259     ERROR ("java plugin: jtoc_values_array: "
260         "CallObjectMethod (toArray) failed.");
261     BAIL_OUT (-1);
262   }
263
264   values = calloc (values_num, sizeof (value_t));
265   if (values == NULL)
266   {
267     ERROR ("java plugin: jtoc_values_array: calloc failed.");
268     BAIL_OUT (-1);
269   }
270
271   for (i = 0; i < values_num; i++)
272   {
273     jobject o_number;
274     int status;
275
276     o_number = (*jvm_env)->GetObjectArrayElement (jvm_env,
277         o_number_array, (jsize) i);
278     if (o_number == NULL)
279     {
280       ERROR ("java plugin: jtoc_values_array: "
281           "GetObjectArrayElement (%i) failed.", i);
282       BAIL_OUT (-1);
283     }
284
285     status = jtoc_value (jvm_env, values + i, ds->ds[i].type, o_number);
286     if (status != 0)
287     {
288       ERROR ("java plugin: jtoc_values_array: "
289           "jtoc_value (%i) failed.", i);
290       BAIL_OUT (-1);
291     }
292   } /* for (i = 0; i < values_num; i++) */
293
294   vl->values = values;
295   vl->values_len = values_num;
296
297 #undef BAIL_OUT
298   (*jvm_env)->DeleteLocalRef (jvm_env, o_number_array);
299   (*jvm_env)->DeleteLocalRef (jvm_env, o_list);
300   return (0);
301 } /* }}} int jtoc_values_array */
302
303 static int jtoc_value_list (JNIEnv *jvm_env, value_list_t *vl, /* {{{ */
304     jobject object_ptr)
305 {
306   jclass class_ptr;
307   int status;
308   jlong tmp_long;
309   const data_set_t *ds;
310
311   class_ptr = (*jvm_env)->GetObjectClass (jvm_env, object_ptr);
312   if (class_ptr == NULL)
313   {
314     ERROR ("java plugin: jtoc_value_list: GetObjectClass failed.");
315     return (-1);
316   }
317
318 #define SET_STRING(buffer,method) do { \
319   status = jtoc_string (jvm_env, buffer, sizeof (buffer), \
320       class_ptr, object_ptr, method); \
321   if (status != 0) { \
322     ERROR ("java plugin: jtoc_value_list: jtoc_string (%s) failed.", \
323         method); \
324     return (-1); \
325   } } while (0)
326
327   SET_STRING(vl->type, "getType");
328
329   ds = plugin_get_ds (vl->type);
330   if (ds == NULL)
331   {
332     ERROR ("java plugin: jtoc_value_list: Data-set `%s' is not defined. "
333         "Please consult the types.db(5) manpage for mor information.",
334         vl->type);
335     return (-1);
336   }
337
338   SET_STRING(vl->host, "getHost");
339   SET_STRING(vl->plugin, "getPlugin");
340   SET_STRING(vl->plugin_instance, "getPluginInstance");
341   SET_STRING(vl->type_instance, "getTypeInstance");
342
343 #undef SET_STRING
344
345   status = jtoc_long (jvm_env, &tmp_long, class_ptr, object_ptr, "getTime");
346   if (status != 0)
347   {
348     ERROR ("java plugin: jtoc_value_list: jtoc_string (getTime) failed.");
349     return (-1);
350   }
351   vl->time = (time_t) tmp_long;
352
353   status = jtoc_long (jvm_env, &tmp_long,
354       class_ptr, object_ptr, "getInterval");
355   if (status != 0)
356   {
357     ERROR ("java plugin: jtoc_value_list: jtoc_string (getInterval) failed.");
358     return (-1);
359   }
360   vl->interval = (int) tmp_long;
361
362   status = jtoc_values_array (jvm_env, ds, vl, class_ptr, object_ptr);
363   if (status != 0)
364   {
365     ERROR ("java plugin: jtoc_value_list: jtoc_values_array failed.");
366     return (-1);
367   }
368
369   return (0);
370 } /* }}} int jtoc_value_list */
371
372 /* 
373  * Functions accessible from Java
374  */
375 static jint JNICALL cjni_api_dispatch_values (JNIEnv *jvm_env, /* {{{ */
376     jobject this, jobject java_vl)
377 {
378   value_list_t vl = VALUE_LIST_INIT;
379   int status;
380
381   DEBUG ("cjni_api_dispatch_values: java_vl = %p;", (void *) java_vl);
382
383   status = jtoc_value_list (jvm_env, &vl, java_vl);
384   if (status != 0)
385   {
386     ERROR ("java plugin: cjni_api_dispatch_values: jtoc_value_list failed.");
387     return (-1);
388   }
389
390   plugin_dispatch_values (&vl);
391
392   sfree (vl.values);
393
394   return (0);
395 } /* }}} jint cjni_api_dispatch_values */
396
397 static JNINativeMethod jni_api_functions[] =
398 {
399   { "DispatchValues", "(Lorg/collectd/protocol/ValueList;)I", cjni_api_dispatch_values }
400 };
401 static size_t jni_api_functions_num = sizeof (jni_api_functions)
402   / sizeof (jni_api_functions[0]);
403
404 /*
405  * Functions
406  */
407 static int cjni_init_one_plugin (JNIEnv *jvm_env, java_plugin_t *jp) /* {{{ */
408 {
409   jmethodID constructor_id;
410   int status;
411
412   jp->class_ptr = (*jvm_env)->FindClass (jvm_env, jp->class_name);
413   if (jp->class_ptr == NULL)
414   {
415     ERROR ("cjni_init_one_plugin: FindClass (%s) failed.",
416         jp->class_name);
417     return (-1);
418   }
419
420   constructor_id = (*jvm_env)->GetMethodID (jvm_env, jp->class_ptr,
421       "<init>", "()V");
422   if (constructor_id == NULL)
423   {
424     ERROR ("cjni_init_one_plugin: Could not find the constructor for `%s'.",
425         jp->class_name);
426     return (-1);
427   }
428
429   jp->object_ptr = (*jvm_env)->NewObject (jvm_env, jp->class_ptr,
430       constructor_id);
431   if (jp->object_ptr == NULL)
432   {
433     ERROR ("cjni_init_one_plugin: Could create a new `%s' object.",
434         jp->class_name);
435     return (-1);
436   }
437
438   jp->method_init = (*jvm_env)->GetMethodID (jvm_env, jp->class_ptr,
439       "Init", "()I");
440   DEBUG ("jp->method_init = %p;", (void *) jp->method_init);
441   jp->method_read = (*jvm_env)->GetMethodID (jvm_env, jp->class_ptr,
442       "Read", "()I");
443   DEBUG ("jp->method_read = %p;", (void *) jp->method_read);
444   jp->method_shutdown = (*jvm_env)->GetMethodID (jvm_env, jp->class_ptr,
445       "Shutdown", "()I");
446   DEBUG ("jp->method_shutdown = %p;", (void *) jp->method_shutdown);
447
448   status = (*jvm_env)->CallIntMethod (jvm_env, jp->object_ptr,
449       jp->method_init);
450   if (status != 0)
451   {
452     ERROR ("cjni_init_one_plugin: Initializing `%s' object failed "
453         "with status %i.", jp->class_name, status);
454     return (-1);
455   }
456   jp->flags |= CJNI_FLAG_ENABLED;
457
458   return (0);
459 } /* }}} int cjni_init_one_plugin */
460
461 static int cjni_init_plugins (JNIEnv *jvm_env) /* {{{ */
462 {
463   size_t j;
464
465   for (j = 0; j < java_plugins_num; j++)
466     cjni_init_one_plugin (jvm_env, &java_plugins[j]);
467
468   return (0);
469 } /* }}} int cjni_init_plugins */
470
471 static int cjni_init_native (JNIEnv *jvm_env) /* {{{ */
472 {
473   jclass api_class_ptr;
474   int status;
475
476   api_class_ptr = (*jvm_env)->FindClass (jvm_env, "org.collectd.java.CollectdAPI");
477   if (api_class_ptr == NULL)
478   {
479     ERROR ("cjni_init_native: Cannot find API class `org.collectd.java.CollectdAPI'.");
480     return (-1);
481   }
482
483   status = (*jvm_env)->RegisterNatives (jvm_env, api_class_ptr,
484       jni_api_functions, (jint) jni_api_functions_num);
485   if (status != 0)
486   {
487     ERROR ("cjni_init_native: RegisterNatives failed with status %i.", status);
488     return (-1);
489   }
490
491   return (0);
492 } /* }}} int cjni_init_native */
493
494 static int cjni_init (void) /* {{{ */
495 {
496   JNIEnv *jvm_env;
497   JavaVMInitArgs vm_args;
498   JavaVMOption vm_options[2];
499
500   int status;
501
502   if (jvm != NULL)
503     return (0);
504
505   jvm_env = NULL;
506
507   memset (&vm_args, 0, sizeof (vm_args));
508   vm_args.version = JNI_VERSION_1_2;
509   vm_args.options = vm_options;
510   vm_args.nOptions = sizeof (vm_options) / sizeof (vm_options[0]);
511
512   vm_args.options[0].optionString = "-verbose:jni";
513   vm_args.options[1].optionString = "-Djava.class.path=/home/octo/collectd/bindings/java";
514
515   status = JNI_CreateJavaVM (&jvm, (void **) &jvm_env, (void **) &vm_args);
516   if (status != 0)
517   {
518     ERROR ("cjni_init: JNI_CreateJavaVM failed with status %i.",
519         status);
520     return (-1);
521   }
522   assert (jvm != NULL);
523   assert (jvm_env != NULL);
524
525   /* Call RegisterNatives */
526   status = cjni_init_native (jvm_env);
527   if (status != 0)
528   {
529     ERROR ("cjni_init: cjni_init_native failed.");
530     return (-1);
531   }
532
533   cjni_init_plugins (jvm_env);
534
535   return (0);
536 } /* }}} int cjni_init */
537
538 static int cjni_read_one_plugin (JNIEnv *jvm_env, java_plugin_t *jp) /* {{{ */
539 {
540   int status;
541
542   if ((jp == NULL)
543       || ((jp->flags & CJNI_FLAG_ENABLED) == 0)
544       || (jp->method_read == NULL))
545     return (0);
546
547   DEBUG ("java plugin: Calling: %s.Read()", jp->class_name);
548
549   status = (*jvm_env)->CallIntMethod (jvm_env, jp->object_ptr,
550       jp->method_read);
551   if (status != 0)
552   {
553     ERROR ("cjni_read_one_plugin: Calling `Read' on an `%s' object failed "
554         "with status %i.", jp->class_name, status);
555     return (-1);
556   }
557
558   return (0);
559 } /* }}} int cjni_read_one_plugin */
560
561 static int cjni_read_plugins (JNIEnv *jvm_env) /* {{{ */
562 {
563   size_t j;
564
565   for (j = 0; j < java_plugins_num; j++)
566     cjni_read_one_plugin (jvm_env, &java_plugins[j]);
567
568   return (0);
569 } /* }}} int cjni_read_plugins */
570
571 static int cjni_read (void) /* {{{ */
572 {
573   JNIEnv *jvm_env;
574   JavaVMAttachArgs args;
575   int status;
576
577   if (jvm == NULL)
578   {
579     ERROR ("java plugin: cjni_read: jvm == NULL");
580     return (-1);
581   }
582
583   jvm_env = NULL;
584   memset (&args, 0, sizeof (args));
585   args.version = JNI_VERSION_1_2;
586
587   status = (*jvm)->AttachCurrentThread (jvm, (void **) &jvm_env, &args);
588   if (status != 0)
589   {
590     ERROR ("java plugin: cjni_read: AttachCurrentThread failed with status %i.",
591         status);
592     return (-1);
593   }
594
595   cjni_read_plugins (jvm_env);
596
597   status = (*jvm)->DetachCurrentThread (jvm);
598   if (status != 0)
599   {
600     ERROR ("java plugin: cjni_read: DetachCurrentThread failed with status %i.",
601         status);
602     return (-1);
603   }
604
605   return (0);
606 } /* }}} int cjni_read */
607
608 static int cjni_shutdown_one_plugin (JNIEnv *jvm_env, /* {{{ */
609     java_plugin_t *jp)
610 {
611   int status;
612
613   if ((jp == NULL)
614       || ((jp->flags & CJNI_FLAG_ENABLED) == 0)
615       || (jp->method_shutdown == NULL))
616     return (0);
617
618   status = (*jvm_env)->CallIntMethod (jvm_env, jp->object_ptr,
619       jp->method_shutdown);
620   if (status != 0)
621   {
622     ERROR ("cjni_shutdown_one_plugin: Destroying an `%s' object failed "
623         "with status %i.", jp->class_name, status);
624     return (-1);
625   }
626   jp->flags &= ~CJNI_FLAG_ENABLED;
627
628   return (0);
629 } /* }}} int cjni_shutdown_one_plugin */
630
631 static int cjni_shutdown_plugins (JNIEnv *jvm_env) /* {{{ */
632 {
633   size_t j;
634
635   for (j = 0; j < java_plugins_num; j++)
636     cjni_shutdown_one_plugin (jvm_env, &java_plugins[j]);
637
638   return (0);
639 } /* }}} int cjni_shutdown_plugins */
640
641 static int cjni_shutdown (void) /* {{{ */
642 {
643   JNIEnv *jvm_env;
644   JavaVMAttachArgs args;
645   int status;
646
647   if (jvm == NULL)
648     return (0);
649
650   jvm_env = NULL;
651   memset (&args, 0, sizeof (args));
652   args.version = JNI_VERSION_1_2;
653
654   status = (*jvm)->AttachCurrentThread (jvm, (void **) &jvm_env, &args);
655   if (status != 0)
656   {
657     ERROR ("java plugin: cjni_read: AttachCurrentThread failed with status %i.",
658         status);
659     return (-1);
660   }
661
662   cjni_shutdown_plugins (jvm_env);
663
664   (*jvm)->DestroyJavaVM (jvm);
665   jvm = NULL;
666   jvm_env = NULL;
667
668   return (0);
669 } /* }}} int cjni_shutdown */
670
671 void module_register (void)
672 {
673   plugin_register_init ("java", cjni_init);
674   plugin_register_read ("java", cjni_read);
675   plugin_register_shutdown ("java", cjni_shutdown);
676 } /* void module_register (void) */
677
678 /* vim: set sw=2 sts=2 et fdm=marker : */