1 /*
2 * Copyright (C) 2008 - 2011 Vivien Malerba <malerba@gnome-db.org>
3 * Copyright (C) 2010 David King <davidk@openismus.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #define __GDA_JDBC_LIBMAIN__
22
23 #include <glib/gi18n-lib.h>
24 #include <gmodule.h>
25 #include <libgda/gda-log.h>
26 #include <libgda/gda-server-provider-extra.h>
27 #include <libgda/binreloc/gda-binreloc.h>
28 #include "gda-jdbc.h"
29 #include "gda-jdbc-provider.h"
30 #include "jni-wrapper.h"
31 #include "jni-globals.h"
32 #include <unistd.h>
33 #include <sys/wait.h>
34
35 #ifdef G_OS_WIN32
36 #include <windows.h>
37 #include <winreg.h>
38 #define EXPORT __declspec(dllexport)
39 #else
40 #define EXPORT
41 #endif
42
43 static GModule *jvm_handle = NULL;
44
45 /* JVM's symbols */
46 static GMutex vm_create;
47 static jint (*__CreateJavaVM) (JavaVM **pvm, void **penv, void *args) = NULL;
48 JavaVM *_jdbc_provider_java_vm = NULL;
49
50
51 static gchar *module_path = NULL;
52
53 EXPORT const gchar **plugin_get_sub_names (void);
54 EXPORT const gchar *plugin_get_sub_description (const gchar *name);
55 EXPORT gchar *plugin_get_sub_dsn_spec (const gchar *name);
56 EXPORT GdaServerProvider *plugin_create_sub_provider (const gchar *name);
57
58 /* locate and load JAVA virtual machine */
59 static gboolean load_jvm ();
60
61 /* get the name of the database server type from the JDBC driver name */
62 static const gchar *get_database_name_from_driver_name (const gchar *driver_name);
63
64
65 /* JDBC drivers installed */
66 typedef struct {
67 const gchar *name;
68 const gchar *native_db;
69 gchar *descr;
70 } JdbcDriver;
71 static GHashTable *jdbc_drivers_hash = NULL; /* key = name, value = JdbcDriver pointer */
72 static gchar **sub_names = NULL;
73 static gint sub_nb; /* size of sub_names */
74
75 /* Functions executed when calling g_module_open() and g_module_close()
76 *
77 * The GModule is made resident here because I haven't found a way to completely unload the JVM, maybe
78 * http://forums.sun.com/thread.jspa?threadID=5321076
79 * would be the solution...
80 */
81 EXPORT const gchar *
g_module_check_init(G_GNUC_UNUSED GModule * module)82 g_module_check_init (G_GNUC_UNUSED GModule *module)
83 {
84 //g_module_make_resident (module);
85 return NULL;
86 }
87
88 EXPORT void
g_module_unload(G_GNUC_UNUSED GModule * module)89 g_module_unload (G_GNUC_UNUSED GModule *module)
90 {
91 if (! __CreateJavaVM) {
92 g_free (module_path);
93 module_path = NULL;
94 }
95
96 /*
97 __CreateJavaVM = NULL;
98 jni_wrapper_destroy_vm (_jdbc_provider_java_vm);
99 _jdbc_provider_java_vm = NULL;
100 if (jvm_handle) {
101 g_module_close (jvm_handle);
102 jvm_handle = NULL;
103 }
104 */
105 }
106
107 /*
108 * Normal plugin functions
109 */
110 EXPORT void
plugin_init(const gchar * real_path)111 plugin_init (const gchar *real_path)
112 {
113 if (real_path)
114 module_path = g_strdup (real_path);
115 }
116
117 static gboolean in_forked = FALSE;
118 static gchar **try_getting_drivers_list_forked (gboolean *out_forked_ok);
119 static void describe_driver_names (void);
120
121 EXPORT const gchar **
plugin_get_sub_names(void)122 plugin_get_sub_names (void)
123 {
124 if (sub_names)
125 return (const gchar**) sub_names;
126
127 if (! in_forked) {
128 gboolean forked_ok;
129 sub_names = try_getting_drivers_list_forked (&forked_ok);
130 if (forked_ok) {
131 if (sub_names)
132 describe_driver_names ();
133 return (const gchar**) sub_names;
134 }
135 }
136
137 if (! __CreateJavaVM && !load_jvm ())
138 return NULL;
139
140 GError *error = NULL;
141 GValue *lvalue;
142 JNIEnv *env;
143 jclass cls;
144
145 if ((*_jdbc_provider_java_vm)->AttachCurrentThread (_jdbc_provider_java_vm,
146 (void**) &env, NULL) < 0) {
147 g_warning ("Could not attach JAVA virtual machine's current thread");
148 return NULL;
149 }
150
151 cls = jni_wrapper_class_get (env, "GdaJProvider", &error);
152 if (!cls) {
153 g_warning (_("Can't get list of installed JDBC drivers: %s"),
154 error && error->message ? error->message : _("No detail"));
155 if (error)
156 g_error_free (error);
157
158 (*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
159 return NULL;
160 }
161 lvalue = jni_wrapper_method_call (env, GdaJProvider__getDrivers, NULL, NULL, NULL, &error);
162 if (!lvalue) {
163 g_warning (_("Can't get list of installed JDBC drivers: %s"),
164 error && error->message ? error->message : _("No detail"));
165 if (error)
166 g_error_free (error);
167
168 (*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
169 return NULL;
170 }
171 if (!gda_value_is_null (lvalue)) {
172 sub_names = g_strsplit (g_value_get_string (lvalue), ":", 0);
173 gda_value_free (lvalue);
174
175 describe_driver_names ();
176
177 (*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
178 return (const gchar **) sub_names;
179 }
180 else {
181 g_free (lvalue);
182 (*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
183 return NULL;
184 }
185 }
186
187 /*
188 * fills the jdbc_drivers_hash hash table
189 */
190 static void
describe_driver_names(void)191 describe_driver_names (void)
192 {
193 gint i;
194
195 if (jdbc_drivers_hash)
196 g_hash_table_destroy (jdbc_drivers_hash);
197 sub_nb = g_strv_length (sub_names);
198 jdbc_drivers_hash = g_hash_table_new (g_str_hash, g_str_equal);
199 for (i = 0; i < sub_nb; i++) {
200 JdbcDriver *dr = g_new0 (JdbcDriver, 1);
201 dr->name = sub_names [i];
202 dr->native_db = get_database_name_from_driver_name (sub_names [i]);
203 if (dr->native_db)
204 dr->descr = g_strdup_printf ("Provider to access %s databases using JDBC",
205 dr->native_db);
206 else
207 dr->descr = g_strdup_printf ("Provider to access databases using JDBC's %s driver",
208 dr->name);
209 g_hash_table_insert (jdbc_drivers_hash, (gchar*) dr->name, dr);
210 }
211 }
212
213 /*
214 * Tries to load the JVM in a forked child to avoid
215 * keeping the JVM loaded all the time
216 *
217 * if it succedded running a forked child, then @out_forked_ok is set to %TRUE
218 */
219 static gchar **
try_getting_drivers_list_forked(gboolean * out_forked_ok)220 try_getting_drivers_list_forked (gboolean *out_forked_ok)
221 {
222 g_assert (out_forked_ok);
223 *out_forked_ok = FALSE;
224 #ifdef G_OS_UNIX
225 int pipes[2] = { -1, -1 };
226 pid_t pid;
227
228 if (pipe (pipes) < 0)
229 return NULL;
230
231 pid = fork ();
232 if (pid < 0) {
233 close (pipes [0]);
234 close (pipes [1]);
235 return NULL;
236 }
237
238 if (pid == 0) {
239 /* in child */
240 const gchar **retval;
241 const gchar **ptr;
242 GString *string = NULL;
243
244 close (pipes [0]);
245 in_forked = TRUE;
246 retval = plugin_get_sub_names ();
247 for (ptr = retval; ptr && *ptr; ptr++) {
248 if (!string)
249 string = g_string_new ("");
250 else
251 g_string_append_c (string, ':');
252 g_string_append (string, *ptr);
253 }
254 if (string) {
255 write (pipes [1], string->str, strlen (string->str));
256 g_string_free (string, TRUE);
257 }
258 close (pipes [1]);
259 exit (0);
260 }
261 else {
262 /* in parent */
263 gchar **retval;
264 GString *string;
265 char buf;
266 close (pipes [1]);
267 string = g_string_new ("");
268 while (read(pipes[0], &buf, 1) > 0)
269 g_string_append_c (string, buf);
270 close (pipes [0]);
271 wait (NULL);
272 /*g_print ("Read from child [%s]\n", string->str);*/
273 retval = g_strsplit (string->str, ":", -1);
274 g_string_free (string, TRUE);
275 *out_forked_ok = TRUE;
276
277 return retval;
278 }
279 #else
280 /* not supported */
281 return NULL;
282 #endif
283 }
284
285
286 EXPORT const gchar *
plugin_get_sub_description(const gchar * name)287 plugin_get_sub_description (const gchar *name)
288 {
289 JdbcDriver *dr;
290 dr = g_hash_table_lookup (jdbc_drivers_hash, name);
291 if (dr)
292 return dr->descr;
293 else
294 return NULL;
295 }
296
297 EXPORT gchar *
plugin_get_sub_dsn_spec(const gchar * name)298 plugin_get_sub_dsn_spec (const gchar *name)
299 {
300 gchar *ret, *dir, *tmp;
301
302 dir = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, NULL);
303 tmp = g_strdup_printf ("jdbc_specs_%s_dsn.xml", name);
304 ret = gda_server_provider_load_file_contents (module_path, dir, tmp);
305 g_free (tmp);
306 if (!ret)
307 ret = gda_server_provider_load_file_contents (module_path, dir, "jdbc_specs_dsn.xml");
308 g_free (dir);
309 return ret;
310 }
311
312 EXPORT GdaServerProvider *
plugin_create_sub_provider(const gchar * name)313 plugin_create_sub_provider (const gchar *name)
314 {
315 /* server creation */
316 GdaServerProvider *prov;
317
318 if (! __CreateJavaVM && !load_jvm ())
319 return NULL;
320 else {
321 JNIEnv *env;
322 jclass cls;
323
324 if ((*_jdbc_provider_java_vm)->AttachCurrentThread (_jdbc_provider_java_vm,
325 (void**) &env, NULL) < 0) {
326 (*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
327 if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
328 g_warning ("Could not attach JAVA virtual machine's current thread");
329 return NULL;
330 }
331
332 cls = jni_wrapper_class_get (env, "GdaJProvider", NULL);
333 (*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
334 if (!cls) {
335 if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
336 g_warning ("Could not find the GdaJProvider class");
337 return NULL;
338 }
339 }
340
341 prov = gda_jdbc_provider_new (name, NULL);
342 g_object_set_data ((GObject *) prov, "GDA_PROVIDER_DIR", module_path);
343 return prov;
344 }
345
346 #ifdef G_OS_WIN32
347 /* Look up a key's value in the registry. */
348 static gchar *
win32_lookup_registry_key(const gchar * keyname,const gchar * name)349 win32_lookup_registry_key (const gchar *keyname, const gchar *name)
350 {
351 gchar *retval;
352 DWORD size;
353 DWORD type;
354 LONG res;
355 HKEY reg_key = (HKEY) INVALID_HANDLE_VALUE;
356
357 res = RegOpenKeyExA (HKEY_LOCAL_MACHINE, keyname, 0, KEY_READ, ®_key);
358 if (res != ERROR_SUCCESS) {
359 reg_key = (HKEY) INVALID_HANDLE_VALUE;
360 return NULL;
361 }
362
363 size = 128;
364 retval = g_malloc (sizeof (gchar) * size);
365
366 res = RegQueryValueExA (reg_key, name, 0, &type, retval, &size);
367 if (res == ERROR_MORE_DATA && type == REG_SZ) {
368 retval = (gchar *) g_realloc (retval, sizeof (gchar) * size);
369 res = RegQueryValueExA (reg_key, name, 0, &type, retval, &size);
370 }
371
372 if (type != REG_SZ || res != ERROR_SUCCESS) {
373 g_free (retval);
374 retval = NULL;
375 }
376
377 RegCloseKey (reg_key);
378
379 return retval;
380 }
381
382 #endif
383
384 static gboolean find_jvm_in_dir (const gchar *dir_name);
385 static gboolean
load_jvm()386 load_jvm ()
387 {
388 gboolean jvm_found = FALSE;
389 const gchar *env;
390
391 g_mutex_lock (&vm_create);
392 if (_jdbc_provider_java_vm) {
393 g_mutex_unlock (&vm_create);
394 return TRUE;
395 }
396
397 #ifdef G_OS_WIN32
398 gchar *tmp1;
399 const gchar *loc = "SOFTWARE\\\JavaSoft\\Java Runtime Environment";
400 tmp1 = win32_lookup_registry_key (loc, "CurrentVersion");
401 if (tmp1) {
402 gchar *kn, *tmp2;
403 kn = g_strdup_printf ("%s\\%s", loc, tmp1);
404 g_free (tmp1);
405 tmp2 = win32_lookup_registry_key (kn, "JavaHome");
406 if (tmp2) {
407 gchar *tmp3;
408 tmp3 = g_build_filename (tmp2, "bin", "client", NULL);
409 g_free (tmp2);
410 if (find_jvm_in_dir (tmp3))
411 jvm_found = TRUE;
412 }
413 }
414 if (!jvm_found) {
415 loc = "SOFTWARE\\\JavaSoft\\Java Development Kit";
416 tmp1 = win32_lookup_registry_key (loc, "CurrentVersion");
417 if (tmp1) {
418 gchar *kn, *tmp2;
419 kn = g_strdup_printf ("%s\\%s", loc, tmp1);
420 g_free (tmp1);
421 tmp2 = win32_lookup_registry_key (kn, "JavaHome");
422 if (tmp2) {
423 gchar *tmp3;
424 tmp3 = g_build_filename (tmp2, "jre", "bin", "client", NULL);
425 g_free (tmp2);
426 if (find_jvm_in_dir (tmp3))
427 jvm_found = TRUE;
428 }
429 }
430 }
431
432 #else
433 /* first, use LD_LIBRARY_PATH */
434 env = g_getenv ("LD_LIBRARY_PATH");
435 if (env) {
436 gchar **array;
437 gint i;
438 array = g_strsplit (env, ":", 0);
439 for (i = 0; array[i]; i++) {
440 if (find_jvm_in_dir (array [i])) {
441 jvm_found = TRUE;
442 break;
443 }
444 }
445 g_strfreev (array);
446 }
447 if (jvm_found)
448 goto out;
449
450 /* then use the compile time JVM_PATH */
451 if (JVM_PATH) {
452 gchar **array;
453 gint i;
454 array = g_strsplit (JVM_PATH, ":", 0);
455 for (i = 0; array[i]; i++) {
456 if (find_jvm_in_dir (array [i])) {
457 jvm_found = TRUE;
458 break;
459 }
460 }
461 g_strfreev (array);
462 }
463 #endif
464 if (jvm_found)
465 goto out;
466
467 /* at last use module_path */
468 if (find_jvm_in_dir (module_path))
469 jvm_found = TRUE;
470
471 out:
472 if (jvm_found) {
473 gchar *path;
474 GError *error = NULL;
475 path = g_build_filename (module_path, "gdaprovider-5.0.jar", NULL);
476 jni_wrapper_create_vm (&_jdbc_provider_java_vm, __CreateJavaVM, module_path, path, &error);
477 if (!_jdbc_provider_java_vm) {
478 if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
479 g_warning (_("Can't create JAVA virtual machine: %s"),
480 error && error->message ? error->message : _("No detail"));
481 jvm_found = FALSE;
482 }
483 }
484 else {
485 __CreateJavaVM = NULL;
486 if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
487 g_warning (_("Could not find the JVM runtime (libjvm.so), JDBC provider is unavailable."));
488 }
489
490 g_mutex_unlock (&vm_create);
491 return jvm_found;
492 }
493
494 static gboolean
find_jvm_in_dir(const gchar * dir_name)495 find_jvm_in_dir (const gchar *dir_name)
496 {
497 GDir *dir;
498 GError *err = NULL;
499 const gchar *name;
500
501 if (jvm_handle) {
502 g_module_close (jvm_handle);
503 jvm_handle = NULL;
504 }
505
506 /* read the plugin directory */
507 #ifdef GDA_DEBUG_NO
508 g_print ("Looking for JVM in %s\n", dir_name);
509 #endif
510 dir = g_dir_open (dir_name, 0, &err);
511 if (err) {
512 gda_log_error (err->message);
513 g_error_free (err);
514 return FALSE;
515 }
516
517 while ((name = g_dir_read_name (dir))) {
518 if (!g_str_has_suffix (name, "." G_MODULE_SUFFIX))
519 continue;
520 if (!g_strrstr (name, "jvm"))
521 continue;
522
523 gchar *path;
524
525 path = g_build_path (G_DIR_SEPARATOR_S, dir_name, name, NULL);
526 jvm_handle = g_module_open (path, G_MODULE_BIND_LAZY);
527 g_free (path);
528 if (!jvm_handle) {
529 /*g_warning (_("Error: %s"), g_module_error ());*/
530 continue;
531 }
532
533 if (g_module_symbol (jvm_handle, "JNI_CreateJavaVM", (gpointer *) &__CreateJavaVM)) {
534 /* JVM found */
535 #ifdef GDA_DEBUG_NO
536 path = g_build_path (G_DIR_SEPARATOR_S, dir_name, name, NULL);
537 g_print ("JVM found as: '%s'\n", path);
538 g_free (path);
539 #endif
540 break;
541 }
542 else {
543 g_module_close (jvm_handle);
544 jvm_handle = NULL;
545 }
546
547 }
548 /* free memory */
549 g_dir_close (dir);
550 return jvm_handle ? TRUE : FALSE;
551 }
552
553 static const gchar *
get_database_name_from_driver_name(const gchar * driver_name)554 get_database_name_from_driver_name (const gchar *driver_name)
555 {
556 typedef struct {
557 gchar *jdbc_name;
558 gchar *db_name;
559 } Corresp;
560
561 static Corresp carray[] = {
562 {"COM.cloudscape.core.JDBCDriver", "Cloudscape"},
563 {"RmiJdbc.RJDriver", "Cloudscape"},
564 {"COM.ibm.db2.jdbc.app.DB2Driver", "DB2"},
565 {"org.firebirdsql.jdbc.FBDriver", "Firebird"},
566 {"hSql.hDriver", "Hypersonic SQL"},
567 {"org.hsql.jdbcDriver", "Hypersonic SQL"},
568 {"com.informix.jdbc.IfxDriver", "Informix"},
569 {"jdbc.idbDriver", "InstantDB"},
570 {"org.enhydra.instantdb.jdbc.idbDriver", "InstantDB"},
571 {"interbase.interclient.Driver", "Interbase"},
572 {"ids.sql.IDSDriver", "IDS Server"},
573 {"com.mysql.jdbc.Driver", "MySQL"},
574 {"org.gjt.mm.mysql.Driver", "MySQL"},
575 {"oracle.jdbc.driver.OracleDriver", "Oracle"},
576 {"oracle.jdbc.OracleDriver", "Oracle"},
577 {"com.pointbase.jdbc.jdbcUniversalDriver", "PointBase"},
578 {"org.postgresql.Driver", "PostgreSQL"},
579 {"postgresql.Driver", "v6.5 and earlier"},
580 {"com.microsoft.sqlserver.jdbc.SQLServerDriver", "SqlServer"},
581 {"weblogic.jdbc.mssqlserver4.Driver", "SqlServer"},
582 {"com.ashna.jturbo.driver.Driver", "SqlServer"},
583 {"com.inet.tds.TdsDriver", "SqlServer"},
584 {"com.sybase.jdbc.SybDriver", "Sybase"},
585 {"com.sybase.jdbc2.jdbc.SybDriver", "Sybase"}
586 };
587
588 gsize i;
589 for (i = 0; i < sizeof (carray) / sizeof (Corresp); i++) {
590 if (!strcmp (carray[i].jdbc_name, driver_name))
591 return carray[i].db_name;
592 }
593
594 return NULL;
595 }
596