001/* 002 * Copyright (c) 2008-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2015-10-28 21:02:57 +0000 (Wed, 28 Oct 2015) $' 007 * '$Revision: 34135 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.kepler.util.sql; 031 032import java.io.File; 033import java.io.FilenameFilter; 034import java.io.IOException; 035import java.sql.Connection; 036import java.sql.SQLException; 037import java.sql.Statement; 038import java.util.Arrays; 039import java.util.HashMap; 040import java.util.LinkedList; 041import java.util.List; 042import java.util.Map; 043import java.util.regex.Matcher; 044import java.util.regex.Pattern; 045 046import org.apache.tools.ant.taskdefs.Copy; 047import org.kepler.build.modules.Module; 048import org.kepler.build.modules.ModuleTree; 049import org.kepler.build.util.Version; 050import org.kepler.configuration.ConfigurationManager; 051import org.kepler.configuration.ConfigurationProperty; 052import org.kepler.configuration.ConfigurationUtilities; 053import org.kepler.objectmanager.cache.CacheManager; 054import org.kepler.util.DotKeplerManager; 055 056import ptolemy.util.MessageHandler; 057 058/** 059 * 060 * A factory to instantiate and enumerate database types. 061 * 062 * @author Daniel Crawl 063 * @version $Id: DatabaseFactory.java 34135 2015-10-28 21:02:57Z crawl $ 064 * 065 */ 066 067public class DatabaseFactory 068{ 069 /** Convenience method to get connection to default kepler DB, 070 * which is currently HSQL. 071 * 072 * FIXME this method should be deleted 073 * 074 * @return 075 * @throws SQLException 076 * @throws ClassNotFoundException 077 */ 078 public static Connection getDBConnection() 079 throws SQLException, ClassNotFoundException 080 { 081 082 ConfigurationProperty commonProperty = ConfigurationManager 083 .getInstance().getProperty( 084 ConfigurationManager.getModule("common")); 085 List<ConfigurationProperty> hsqlList = commonProperty.findProperties( 086 "sqlEngineName", "hsql", true); 087 ConfigurationProperty hsqlProp = (ConfigurationProperty) hsqlList 088 .get(0); 089 090 String username = hsqlProp.getProperty("userName").getValue(); 091 String password = hsqlProp.getProperty("password").getValue(); 092 String url = hsqlProp.getProperty("url").getValue(); 093 String dbPort = hsqlProp.getProperty("port").getValue(); 094 095 String dbAlias = hsqlProp.getProperty("dbName").getValue(); 096 097 DatabaseType dbType = getType("HSQL"); 098 099 String dbName = dbAlias; 100 101 if(dbType.needAbsolutePathForName() && 102 !dbAlias.startsWith("/")) 103 { 104 dbName = DotKeplerManager.getInstance().getCacheDirString() 105 + "cachedata" + File.separator + dbAlias; 106 } 107 108 String fullUrl = null; 109 110 if (url != null && url.endsWith("/")) 111 { 112 fullUrl = url.substring(0, url.length()-1); 113 } 114 else 115 { 116 fullUrl = url; 117 } 118 119 String randomPort; 120 try 121 { 122 // NOTE: the third argument should be the hostname 123 // we use the url since there is not a separate entry 124 // for host name in the configuration file. 125 // the url may specify to directly access the file, but 126 // _getRandomPort will chose a random port. this isn't 127 // too bad since the port will not actually be used 128 // (since it's configured to directly access the file). 129 randomPort = dbType._getPort(dbPort, dbName, url); 130 } 131 catch (IOException e) 132 { 133 throw new SQLException("Error chosing random port.", e); 134 } 135 136 if (randomPort != null && fullUrl != null) 137 { 138 fullUrl = fullUrl.concat(":" + randomPort); 139 } 140 141 if(fullUrl != null) 142 { 143 fullUrl = fullUrl.concat("/" + dbAlias + ";filepath=hsqldb:file:" + dbName); 144 } 145 146 // if the url is null, then set the url to directly access the file 147 // instead of starting the database server. 148 if(fullUrl == null) 149 { 150 fullUrl = "jdbc:hsqldb:file:" + dbName; 151 } 152 153 // must load the driver 154 try { 155 Class.forName(dbType._getDriverName()).newInstance(); 156 } catch (InstantiationException e) { 157 throw new SQLException("Error loading HSQL driver: " + e.getMessage()); 158 } catch (IllegalAccessException e) { 159 throw new SQLException("Error loading HSQL driver: " + e.getMessage()); 160 } 161 162 //System.out.println("fullUrl = " + fullUrl); 163 164 Connection conn = dbType._getConnection(fullUrl, randomPort, 165 dbName, username, password); 166 167 // set the schema name; 168 final String schemaName = CacheManager.getDatabaseSchemaName(); 169 Statement statement = null; 170 try { 171 statement = conn.createStatement(); 172 // try to set the schema 173 try { 174 statement.execute("SET SCHEMA " + schemaName); 175 } catch(SQLException e) { 176 // see if the exception looks like this schema does not exist 177 if(e.getMessage() != null && e.getMessage().startsWith("invalid schema name:")) { 178 // create the schema and set it 179 statement.execute("CREATE SCHEMA " + schemaName + " AUTHORIZATION DBA"); 180 statement.execute("SET SCHEMA " + schemaName); 181 } else { 182 throw e; 183 } 184 } 185 } finally { 186 if(statement != null) { 187 statement.close(); 188 } 189 } 190 191 return conn; 192 } 193 194 /** Connect to a database from a ConfigurationProperty. */ 195 public static DatabaseType getConnectedDatabaseType( 196 ConfigurationProperty dbProperty) throws SQLException 197 { 198 // get a map of the parameters 199 Map<String,String> map = ConfigurationUtilities.getPairsMap(dbProperty); 200 return getConnectedDatabaseType(map, dbProperty.getModule().getStemName()); 201 } 202 203 /** Get a connected database from connection parameters. */ 204 public static DatabaseType getConnectedDatabaseType( 205 Map<String,String> parameters, String moduleName) throws SQLException 206 { 207 // get the type 208 String typeStr = parameters.get(Parameter.TYPE.getName()); 209 if(typeStr == null) 210 { 211 new Exception().printStackTrace(System.out); 212 throw new SQLException("Could not find the " + 213 Parameter.TYPE.getName() + " property."); 214 } 215 216 DatabaseType dbType = getType(typeStr); 217 if(dbType == null) 218 { 219 throw new SQLException("Unsupported type of database: " + 220 typeStr); 221 } 222 223 Map<String,String> connectParameters = parameters; 224 225 // see if a jdbc url was specified 226 String jdbcURLStr = parameters.get(Parameter.JDBC_URL.getName()); 227 if(jdbcURLStr == null || jdbcURLStr.length() == 0) 228 { 229 // check path name 230 final String dbName = parameters.get(Parameter.NAME.getName()); 231 232 if(dbType.needAbsolutePathForName()) 233 { 234 File dbFileName = new File(dbName); 235 // see if it's a relative path 236 if(!dbFileName.isAbsolute()) 237 { 238 String dbPathStr; 239 240 // prepend the absolute path to the persistent module 241 // directory for owning module 242 243 ModuleTree modules = ModuleTree.instance(); 244 Module module = modules.getModuleByStemName(moduleName); 245 246 // make sure we found the module. 247 if(module == null) 248 { 249 throw new SQLException("Could not find module: " + 250 moduleName + " Is it in modules.txt?"); 251 } 252 253 // see if we are running a released module 254 if(Version.isVersioned(module.getName())) 255 { 256 Version version = Version.fromVersionString(module.getName()); 257 //System.out.println("version = " + version.getMajor() + "." + version.getMinor()); 258 259 dbPathStr = DotKeplerManager.getInstance(). 260 getPersistentModuleDirectory(moduleName) + 261 File.separator + "db-" + 262 version.getMajor() + "." + version.getMinor() + 263 File.separator + dbName; 264 265 // see if it exists 266 String dbFullName = dbPathStr + "." + dbType.getPrimaryFileExtension(); 267 dbFileName = new File(dbFullName); 268 if(!dbFileName.exists()) 269 { 270 System.out.println("missing db for " + dbPathStr); 271 272 // find previous version, if any 273 String previousNameStr = _findPreviousVersion(module, 274 dbName + "." + dbType.getPrimaryFileExtension()); 275 276 // see if there was a previous version 277 if(previousNameStr != null) 278 { 279 // upgrade 280 System.out.println("going to copy previous version in " + previousNameStr); 281 File oldDir = new File(DotKeplerManager.getInstance(). 282 getPersistentModuleDirectory(moduleName) + 283 File.separator + previousNameStr); 284 285 // determine the files to copy 286 String[] filesToCopy = oldDir.list(new FilenameFilter() { 287 public boolean accept(File dir, String name) { 288 return name.startsWith(dbName); 289 } 290 }); 291 292 File dbDestDir = new File(dbPathStr).getParentFile(); 293 // make the destination directory if it does not exist. 294 if(!dbDestDir.exists()) 295 { 296 dbDestDir.mkdir(); 297 } 298 299 // copy the files 300 for(String fileStr : filesToCopy) 301 { 302 File srcFile = new File(oldDir, fileStr); 303 File destFile = new File(dbDestDir, fileStr); 304 System.out.println("going to copy " + srcFile + " to " + destFile); 305 Copy copy = new Copy(); 306 copy.setFile(srcFile); 307 copy.setTofile(destFile); 308 copy.execute(); 309 } 310 } 311 } 312 } 313 else 314 { 315 // trunk 316 //System.out.println("on trunk."); 317 318 dbPathStr = DotKeplerManager.getInstance(). 319 getPersistentModuleDirectory(moduleName) 320 + File.separator + dbName; 321 } 322 323 // make a copy and replace the name. 324 connectParameters = new HashMap<String,String>(parameters); 325 connectParameters.put(Parameter.NAME.getName(), dbPathStr); 326 } 327 } 328 } 329 330 // make the connection 331 dbType.connect(connectParameters); 332 333 return dbType; 334 } 335 336 /** Get the DatabaseType from a name. */ 337 public static DatabaseType getType(String name) 338 { 339 DatabaseType retval = null; 340 341 if(name.equals("MySQL")) 342 { 343 retval = new MySQL(); 344 } 345 else if(name.equals("HSQL")) 346 { 347 retval = new HSQL(); 348 } 349 else if(name.equals("PostgreSQL")) 350 { 351 retval = new PostgreSQL(); 352 } 353 else if(name.equals("Oracle")) 354 { 355 retval = new Oracle(); 356 } 357 return retval; 358 } 359 360 /** Get all the type names. */ 361 public static List<String> getNames() 362 { 363 List<String> names = new LinkedList<String>(); 364 names.add("MySQL"); 365 names.add("HSQL"); 366 names.add("PostgreSQL"); 367 names.add("Oracle"); 368 return names; 369 } 370 371 /** Shut down the server for the kepler cache database. */ 372 public static void shutdownCacheServer() { 373 Connection connection = null; 374 try { 375 connection = getDBConnection(); 376 } catch (Exception e) { 377 MessageHandler.error("Error stopping .kepler cache database.", e); 378 return; 379 } 380 381 try { 382 Statement statement = null; 383 try { 384 statement = connection.createStatement(); 385 statement.execute("SHUTDOWN"); 386 } finally { 387 if(statement != null) { 388 statement.close(); 389 } 390 } 391 } catch (SQLException e) { 392 MessageHandler.error("Error stopping .kepler cache database.", e); 393 } 394 395 // read the database name from the configuration 396 397 ConfigurationProperty commonProperty = ConfigurationManager 398 .getInstance().getProperty( 399 ConfigurationManager.getModule("common")); 400 List<ConfigurationProperty> hsqlList = commonProperty.findProperties( 401 "sqlEngineName", "hsql", true); 402 ConfigurationProperty hsqlProp = (ConfigurationProperty) hsqlList 403 .get(0); 404 405 String dbName = hsqlProp.getProperty("dbName").getValue(); 406 407 DatabaseType dbType = getType("HSQL"); 408 409 // get the database path 410 411 if(dbType.needAbsolutePathForName() && 412 !dbName.startsWith("/")) 413 { 414 dbName = DotKeplerManager.getInstance().getCacheDirString() 415 + "cachedata" + File.separator + dbName; 416 } 417 418 // remove the port file 419 HSQL._deletePortFile(dbName); 420 421 } 422 423 public enum Parameter 424 { 425 /** User name of database connection. */ 426 USER("DB User Name"), 427 428 /** Password for database connection. */ 429 PASSWD("Password"), 430 431 /** Database host. */ 432 HOST("DB Host"), 433 434 /** Name of database (i.e., schema or sid). */ 435 NAME("DB Name"), 436 437 /** Type of database. */ 438 TYPE("DB Type"), 439 440 /** Port of database. */ 441 PORT("DB Port"), 442 443 /** Name of table prefix. */ 444 TABLEPREFIX("DB Table Prefix"), 445 446 /** JDBC URL. This is optional; use to override other parameters. */ 447 JDBC_URL("JDBC URL"), 448 449 /** If false, do not create any indexes in schema. 450 * NOTE: this has only been tested for MySQL. 451 */ 452 CREATE_INDEXES("Create Indexes"); 453 454 455 Parameter(String name) 456 { 457 _name = name; 458 } 459 460 /** Get the name of the parameter. */ 461 public String getName() 462 { 463 return _name; 464 } 465 466 /** Find the Parameter from a name. */ 467 public static Parameter getType(String name) 468 { 469 for(Parameter parameter : values()) 470 { 471 if(parameter.getName().equals(name)) 472 { 473 return parameter; 474 } 475 } 476 return null; 477 } 478 479 private String _name; 480 } 481 482 /////////////////////////////////////////////////////////////////// 483 //// private methods ////// 484 485 /** Returns the directory containing the least oldest version of the 486 * database. If no such directory exists, returns null. 487 */ 488 private static String _findPreviousVersion(Module module, 489 final String dbName) 490 { 491 File moduleDir = DotKeplerManager.getInstance().getPersistentModuleDirectory(module.getStemName()); 492 493 // get a list of possible directories 494 String[] choices = moduleDir.list(new FilenameFilter() { 495 public boolean accept(File dir, String name) { 496 //System.out.println("checking " + name); 497 // see if the name matches the pattern 498 if(_DB_PATTERN.matcher(name).matches()) { 499 File dbFile = new File(dir.getAbsolutePath() + 500 File.separator + name + File.separator + dbName); 501 // make sure the primary database file exists. 502 return dbFile.exists(); 503 } 504 return false; 505 } 506 }); 507 Arrays.sort(choices); 508 509 // find the closest version 510 String closestDir = null; 511 String moduleVersion = "db-" + 512 Version.fromVersionString(module.getName()).getMajor() + "." + 513 Version.fromVersionString(module.getName()).getMinor(); 514 515 for(String choice : choices) 516 { 517 if(moduleVersion.compareTo(choice) > 0) 518 { 519 //System.out.println(choice + " is before " + moduleVersion); 520 closestDir = _compareTwoVersions(moduleVersion, closestDir, choice); 521 } 522 } 523 524 //System.out.println("closest dir is " + closestDir); 525 526 return closestDir; 527 } 528 529 /** Check if a version is closer to another version. 530 * @param dbName The string against which to test. 531 * @param closest The currently nearest version to dbName. 532 * @param choice A version string to test. 533 * @return If closest has the nearest version to dbName, returns closest, otherwise 534 * returns choice. 535 */ 536 private static String _compareTwoVersions(String dbName, String closest, 537 String choice) 538 { 539 String retval = null; 540 541 // if there is no currently nearest value, select choice 542 if(closest == null) 543 { 544 retval = choice; 545 } 546 else 547 { 548 // compute the version sum of each 549 550 Matcher dbMatcher = _DB_PATTERN.matcher(dbName); 551 dbMatcher.matches(); 552 int dbSum = _computeVersionSum(Integer.valueOf(dbMatcher.group(1)), 553 Integer.valueOf(dbMatcher.group(2))); 554 555 Matcher closestMatcher = _DB_PATTERN.matcher(closest); 556 closestMatcher.matches(); 557 int closestSum = _computeVersionSum(Integer.valueOf(closestMatcher.group(1)), 558 Integer.valueOf(closestMatcher.group(2))); 559 560 Matcher choiceMatcher = _DB_PATTERN.matcher(choice); 561 choiceMatcher.matches(); 562 int choiceSum = _computeVersionSum(Integer.valueOf(choiceMatcher.group(1)), 563 Integer.valueOf(choiceMatcher.group(2))); 564 565 // see which sum is smaller 566 if(dbSum - choiceSum < dbSum - closestSum) 567 { 568 retval = choice; 569 } 570 else 571 { 572 retval = closest; 573 } 574 575 } 576 577 return retval; 578 } 579 580 /** Compute a version sum from major and minor numbers. */ 581 private static int _computeVersionSum(int major, int minor) 582 { 583 return (major * 10000) + minor; 584 } 585 586 /////////////////////////////////////////////////////////////////// 587 //// private variables //// 588 589 /** Pattern of directory name containing database. */ 590 private final static Pattern _DB_PATTERN = Pattern.compile("db\\-(\\d+)\\.(\\d+)"); 591}