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 * 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 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 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 ** 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 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 ** 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 * 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 * 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 * 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 * 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 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 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 * 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