001/* 002 * Copyright (c) 2002-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2012-06-15 21:28:15 +0000 (Fri, 15 Jun 2012) $' 007 * '$Revision: 29958 $' 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.geon; 031 032import java.sql.Connection; 033import java.sql.DatabaseMetaData; 034import java.sql.DriverManager; 035import java.sql.ResultSet; 036import java.sql.SQLException; 037import java.util.Iterator; 038import java.util.List; 039 040import ptolemy.actor.IOPort; 041import ptolemy.actor.TypedAtomicActor; 042import ptolemy.actor.TypedIOPort; 043import ptolemy.data.RecordToken; 044import ptolemy.data.StringToken; 045import ptolemy.data.Token; 046import ptolemy.data.expr.StringParameter; 047import ptolemy.data.type.BaseType; 048import ptolemy.data.type.RecordType; 049import ptolemy.data.type.Type; 050import ptolemy.kernel.CompositeEntity; 051import ptolemy.kernel.Entity; 052import ptolemy.kernel.Port; 053import ptolemy.kernel.util.Attribute; 054import ptolemy.kernel.util.IllegalActionException; 055import ptolemy.kernel.util.NameDuplicationException; 056import util.DBUtil; 057 058////////////////////////////////////////////////////////////////////////// 059//// OpenDBConnection 060/** 061 * This actor opens a database connection using the database format, database 062 * URL, username and password, and sends a reference to it. 063 * 064 * @author Efrat Jaeger 065 * @version $Id: OpenDBConnection.java 29958 2012-06-15 21:28:15Z crawl $ 066 * @since Ptolemy II 3.0.2 067 */ 068public class OpenDBConnection extends TypedAtomicActor { 069 070 /** 071 * Construct an actor with the given container and name. 072 * 073 * @param container 074 * The container. 075 * @param name 076 * The name of this actor. 077 * @exception IllegalActionException 078 * If the actor cannot be contained by the proposed 079 * container. 080 * @exception NameDuplicationException 081 * If the container already has an actor with this name. 082 */ 083 084 public OpenDBConnection(CompositeEntity container, String name) 085 throws NameDuplicationException, IllegalActionException { 086 087 super(container, name); 088 089 trigger = new TypedIOPort(this, "trigger", true, false); 090 trigger.setMultiport(true); 091 092 dbcon = new TypedIOPort(this, "dbcon", false, true); 093 // Set the type constraint. 094 dbcon.setTypeEquals(DBConnectionToken.DBCONNECTION); 095 096 // catalog = new StringAttribute(this, "catalog"); 097 // catalog.setExpression(""); 098 // _catalog = _none; 099 100 databaseFormat = new StringParameter(this, "databaseFormat"); 101 databaseFormat.setDisplayName("database format"); 102 databaseFormat.setExpression("Oracle"); 103 databaseFormat.addChoice("Oracle"); 104 databaseFormat.addChoice("DB2"); 105 databaseFormat.addChoice("Local MS Access"); 106 databaseFormat.addChoice("MS SQL Server"); 107 databaseFormat.addChoice("PostgreSQL"); 108 databaseFormat.addChoice("MySQL"); 109 databaseFormat.addChoice("HSQL"); 110 databaseFormat.addChoice("SQLite"); 111 _dbFormat = _DBType._ORCL; 112 113 // driverName = new StringAttribute(this, "driverName"); 114 databaseURL = new StringParameter(this, "databaseURL"); 115 116 username = new StringParameter(this, "username"); 117 password = new StringParameter(this, "password"); 118 119 dbParams = new TypedIOPort(this, "dbParams", false, true); 120 dbParams.setTypeEquals(getDBParamsType()); 121 122 _attachText("_iconDescription", "<svg>\n" 123 + "<ellipse cx=\"0\" cy=\"-30\" " + "rx=\"20\" ry=\"10\"/>\n" 124 + "<line x1=\"20\" y1=\"0\" " + "x2=\"20\" y2=\"-30\"/>\n" 125 + "<line x1=\"-20\" y1=\"0\" " + "x2=\"-20\" y2=\"-30\"/>\n" 126 + "<line x1=\"-20\" y1=\"0\" " + "x2=\"20\" y2=\"0\"/>\n" 127 + "</svg>\n"); 128 129 } 130 131 // ///////////////////////////////////////////////////////////////// 132 // // ports and parameters //// 133 134 /** 135 * Input trigger: if connected, actor will only run once token is available. 136 */ 137 public TypedIOPort trigger; 138 139 /** A reference to a db connection */ 140 public TypedIOPort dbcon; 141 142 /** A record containing parameters to create a db connection. */ 143 public TypedIOPort dbParams; 144 145 // public StringAttribute catalog; 146 public StringParameter databaseFormat; 147 // public StringAttribute driverName; 148 public StringParameter databaseURL; 149 public StringParameter username; 150 public StringParameter password; 151 152 public String strFileOrURL; 153 154 // ///////////////////////////////////////////////////////////////// 155 // // public methods //// 156 157 /** 158 * Callback for changes in attribute values. If the dbFormat has changed, 159 * points to the new driver path. At any change in the connection 160 * attributes, tries to connect to the referenced database to get the schema 161 * and send it to connected actors. 162 * 163 * @param at 164 * The attribute that changed. 165 * @exception IllegalActionException 166 * If the offsets array is not nondecreasing and nonnegative. 167 */ 168 public void attributeChanged(Attribute at) throws IllegalActionException { 169 if (at == databaseFormat) { 170 String dbFormat = databaseFormat.stringValue(); 171 _driverName = DBUtil.get(dbFormat.trim().toLowerCase()); 172 173 if (dbFormat.equals("Oracle")) { 174 _dbFormat = _DBType._ORCL; 175 } else if (dbFormat.equals("DB2")) { 176 _dbFormat = _DBType._DB2; 177 } else if (dbFormat.equals("Local MS Access")) { 178 _dbFormat = _DBType._LACCS; 179 } else if (dbFormat.equals("MS SQL Server")) { 180 _dbFormat = _DBType._MSSQL; 181 } else if (dbFormat.equals("PostgreSQL")) { 182 _dbFormat = _DBType._PGSQL; 183 } else if (dbFormat.equals("MySQL")) { 184 _dbFormat = _DBType._MYSQL; 185 } else if (dbFormat.equals("HSQL")) { 186 _dbFormat = _DBType._HSQL; 187 } else if (dbFormat.equals("SQLite")){ 188 _dbFormat = _DBType._SQLite; 189 }else { 190 throw new IllegalActionException(this, 191 "No jdbc driver within the system for " + dbFormat); 192 } 193 if (!_driverName.equals(this._prevDriver)) { 194 _setDBURL(); 195 _prevDriver = _driverName; 196 _getAndSendSchema(); 197 } 198 } else if (at == databaseURL) { 199 _setDBURL(); 200 if (!_databaseURL.equals(_prevDBURL)) { 201 _prevDBURL = _databaseURL; 202 _getAndSendSchema(); 203 } 204 } else if (at == username) { 205 _username = username.stringValue(); 206 if (!_username.equals(_prevUser)) { 207 _prevUser = _username; 208 _getAndSendSchema(); 209 } 210 } else if (at == password) { 211 _password = password.stringValue(); 212 if (!_password.equals(_prevPasswd)) { 213 _prevPasswd = _password; 214 _getAndSendSchema(); 215 } 216 217 } else { 218 super.attributeChanged(at); 219 } 220 } 221 222 /** 223 * When connecting the dbcon port, trigger the connectionsChanged of the 224 * connected actor. 225 */ 226 public void connectionsChanged(Port port) { 227 super.connectionsChanged(port); 228 if (port == dbcon) { 229 List<?> conPortsList = dbcon.connectedPortList(); 230 Iterator<?> conPorts = conPortsList.iterator(); 231 while (conPorts.hasNext()) { 232 IOPort p = (IOPort) conPorts.next(); 233 if (p.isInput()) { 234 Entity container = (Entity) p.getContainer(); 235 container.connectionsChanged(p); 236 } 237 } 238 } 239 } 240 241 /** 242 * Connect to a database outputs a reference to the DB connection. 243 */ 244 245 public void fire() throws IllegalActionException { 246 247 // consume tokens on trigger port if connected. 248 for (int i = 0; i < trigger.getWidth(); i++) { 249 if (trigger.hasToken(i)) { 250 trigger.get(i); 251 } 252 } 253 254 try { 255 256 // String _username = username.stringValue(); 257 // String _password = password.stringValue(); 258 259 // _setDBURL(); 260 261 Connection con = _connect("fire"); 262 dbcon.broadcast(new DBConnectionToken(con)); 263 264 RecordToken token = _createParamsToken(_driverName, _databaseURL, 265 _username, _password); 266 dbParams.broadcast(token); 267 268 } catch (Exception ex) { 269 throw new IllegalActionException(this, ex, 270 "fire exception DB connection"); 271 } 272 } 273 274 /** 275 * postfiring the actor. 276 * 277 */ 278 public boolean postfire() { 279 return false; 280 } 281 282 /** 283 * Returns the database schema. This function is called from the dbcon port 284 * connected actors. 285 */ 286 public String sendSchemaToConnected() throws IllegalActionException { 287 if (_schema.equals("")) { 288 _schema = _getSchema(); 289 } 290 return _schema; 291 } 292 293 /** Get the type of database parameter token. */ 294 public static Type getDBParamsType() { 295 return new RecordType(_paramsLabels, new Type[] { BaseType.STRING, 296 BaseType.STRING, BaseType.STRING, BaseType.STRING }); 297 } 298 299 /** Get a JDBC Connection from a database parameter token. */ 300 public static Connection getConnection(RecordToken params) 301 throws IllegalActionException { 302 303 try { 304 305 Connection retval = null; 306 String driver = ((StringToken) params.get("driver")).stringValue(); 307 308 try { 309 Class.forName(driver).newInstance(); 310 } catch (ClassNotFoundException e) { 311 throw new IllegalActionException("The JDBC class " + driver 312 + " was not found on the classpath. One " 313 + "possible reason for this error is " 314 + "the JDBC jar is missing. Several JDBC " 315 + "jars are released under licenses " 316 + "incompatible with Kepler's license such " 317 + "as the Oracle JDBC jar. In " 318 + "these cases, the jar must be downloaded " 319 + "manually and placed in core/lib/jar/dbdrivers."); 320 } 321 322 String user = _getParamField(params, "user"); 323 String passwd = _getParamField(params, "password"); 324 String url = _getParamField(params, "url"); 325 326 if (user.length() == 0 && passwd.length() == 0) { 327 retval = DriverManager.getConnection(url); 328 } else { 329 retval = DriverManager.getConnection(url, user, passwd); 330 } 331 332 return retval; 333 334 } catch (InstantiationException e) { 335 throw new IllegalActionException(e.getClass().getName() + ": " 336 + e.getMessage()); 337 } catch (IllegalAccessException e) { 338 throw new IllegalActionException(e.getClass().getName() + ": " 339 + e.getMessage()); 340 } catch (SQLException e) { 341 throw new IllegalActionException(e.getClass().getName() + ": " 342 + e.getMessage()); 343 } 344 } 345 346 // ///////////////////////////////////////////////////////////////// 347 // // private methods //// 348 349 /** 350 * Connecting to the database and returning a database connection reference. 351 * The context is either "fire" or "other", if unable to connect from a 352 * "fire" context throws an exception (for connecting from other context it 353 * might be the case that not all the connection attributes have already 354 * been set). 355 * 356 * @param context 357 * * @throws IllegalActionException 358 */ 359 private Connection _connect(String context) throws IllegalActionException { 360 Connection retval = null; 361 try { 362 RecordToken token = _createParamsToken(_driverName, _databaseURL, 363 _username, _password); 364 retval = getConnection(token); 365 } catch (IllegalActionException e) { 366 if (context.equals("fire")) 367 throw new IllegalActionException(this, e.getCause(), e.getMessage()); 368 } 369 370 return retval; 371 } 372 373 /** 374 * Called from attributeChanged. Once a connection attribute has been 375 * changed tries to connect to the database, get the schema and forward it 376 * to dbcon port connected actors. 377 * 378 * @throws IllegalActionException 379 */ 380 private void _getAndSendSchema() throws IllegalActionException { 381 _getSchema(); 382 383 // send schema to connected 384 if (!_schema.equals("")) 385 connectionsChanged(dbcon); 386 } 387 388 /** 389 * Gets the database schema in an XML format readable by the query builder. 390 * 391 * @param con 392 * - the database connection object. 393 */ 394 private void _getDBSchema(Connection con) { 395 StringBuffer schema = new StringBuffer(); 396 schema.append("<schema>\n"); 397 StringBuffer table = new StringBuffer(); 398 String prevTable = ""; 399 String prevSchema = ""; 400 int numTables = 0; 401 int numFields = 0; 402 403 try { 404 DatabaseMetaData dmd = con.getMetaData(); 405 ResultSet schemas; 406 407 String subname = null; 408 409 if (_dbFormat == _DBType._MYSQL) { 410 schemas = dmd.getCatalogs(); 411 subname = _databaseURL 412 .substring(_databaseURL.lastIndexOf("/") + 1); 413 } else 414 schemas = dmd.getSchemas(); 415 416 while (schemas.next()) { 417 String schemaName = schemas.getString(1); 418 419 // ignore certain schemas 420 if (_dbFormat == _DBType._ORCL) { 421 // system schema will be the same as username. 422 if (!schemaName.toLowerCase().equals(_username)) 423 continue; 424 } else if (_dbFormat == _DBType._PGSQL) { 425 // system schemas 426 if (schemaName.startsWith("pg_catalog") 427 || schemaName.startsWith("information_schema")) 428 continue; 429 } else if (_dbFormat == _DBType._MYSQL) { 430 // skip schemas with a different subname 431 if (!schemaName.equals(subname)) 432 continue; 433 } else if (schemaName.toLowerCase().startsWith("sys")) { 434 // system schemas 435 continue; 436 } 437 438 ResultSet rs = dmd.getColumns(null, schemaName, "%", "%"); 439 440 // ResultSetMetaData md = rs.getMetaData(); 441 while (rs.next()) { 442 443 // String schemaName = rs.getString(2); 444 String tableName = rs.getString(3); 445 String columnName = rs.getString(4); 446 String columnType = rs.getString(6); 447 448 if (tableName.equals("")) 449 continue; 450 if (!tableName.equals(prevTable) 451 || !schemaName.equals(prevSchema)) { 452 // new table, closing a previous one if exists 453 if (numFields > 0) { 454 table.append(" </table>\n"); 455 numTables++; 456 schema.append(table.toString()); 457 } 458 459 table = new StringBuffer(); 460 table.append(" <table name=\""); 461 if (!schemaName.toLowerCase().equals("null")) { 462 table.append(schemaName + "."); 463 } 464 table.append(tableName + "\">\n"); 465 numFields = 0; 466 prevTable = tableName; 467 prevSchema = schemaName; 468 } 469 if (columnName.equals("")) 470 continue; 471 else { 472 table.append(" <field name=\"" + columnName 473 + "\" dataType=\"" + columnType + "\"/>\n"); 474 numFields++; 475 } 476 } 477 } 478 table.append(" </table>\n"); 479 if (numFields > 0) { 480 numTables++; 481 schema.append(table.toString()); 482 } 483 schema.append("</schema>"); 484 if (numTables > 0) { 485 _schema = schema.toString(); 486 } 487 } catch (SQLException ex) { 488 _schema = ""; 489 System.out.println("SQLException: " + ex.getMessage()); 490 } 491 } 492 493 /** 494 * Connects to the database and get the schema. 495 * 496 * @return database schema string 497 * @throws IllegalActionException 498 */ 499 private String _getSchema() throws IllegalActionException { 500 Connection con = _connect("other"); 501 if (con != null) { 502 _getDBSchema(con); 503 try { 504 con.close(); 505 } catch (SQLException e) { 506 con = null; 507 System.out.println("SQLException closing connection: " 508 + e.getMessage()); 509 } 510 if (!_schema.equals("")) 511 return _schema; 512 } 513 return ""; 514 } 515 516 /** 517 * Set the absolute database URL depending on the database driver. 518 * 519 * @throws IllegalActionException 520 */ 521 private void _setDBURL() throws IllegalActionException { 522 _databaseURL = databaseURL.stringValue(); 523 switch (_dbFormat) { 524 case _ORCL: 525 if (!_databaseURL.trim().startsWith("jdbc:oracle:thin:@")) { 526 if (_databaseURL.trim().startsWith("jdbc:oracle:")) {// a 527 // different 528 // driver 529 // type 530 // is 531 // spcified. 532 int ind = _databaseURL.indexOf("@"); 533 if (ind > -1) { 534 _databaseURL = "jdbc:oracle:thin:@" 535 + _databaseURL.substring(ind); 536 } else 537 throw new IllegalActionException(this, 538 "Illegal database URL: " + _databaseURL); 539 } else { 540 _databaseURL = "jdbc:oracle:thin:@" + _databaseURL; 541 } 542 } 543 break; 544 case _DB2: 545 if (!_databaseURL.trim().startsWith("jdbc:db2:")) { 546 _databaseURL = "jdbc:db2:" + _databaseURL; 547 } 548 break; 549 case _LACCS: 550 if (!_databaseURL.trim().startsWith("jdbc:odbc:")) { 551 _databaseURL = "jdbc:odbc:" + _databaseURL; 552 } 553 break; 554 case _MSSQL: 555 if (!_databaseURL.trim().startsWith("jdbc:sqlserver:")) { 556 _databaseURL = "jdbc:sqlserver:" + _databaseURL; 557 } 558 _databaseURL = _databaseURL + ";User=" + _username + ";Password=" 559 + _password; 560 // _username = null; _password = null; 561 break; 562 case _PGSQL: 563 if (!_databaseURL.trim().startsWith("jdbc:postgresql:")) { 564 _databaseURL = "jdbc:postgresql:" + _databaseURL; 565 } 566 break; 567 case _MYSQL: 568 if (!_databaseURL.trim().startsWith("jdbc:mysql:")) { 569 _databaseURL = "jdbc:mysql:" + _databaseURL; 570 } 571 break; 572 case _HSQL: 573 if (!_databaseURL.trim().startsWith("jdbc:hsqldb:")) { 574 _databaseURL = "jdbc:hsqldb:" + _databaseURL; 575 } 576 break; 577 case _SQLite: 578 if (!_databaseURL.trim().startsWith("jdbc:sqlite:")) 579 { 580 _databaseURL = "jdbc:sqlite:" + _databaseURL; 581 } 582 break; 583 default: 584 System.out.println(databaseFormat.getExpression() 585 + " is not supported"); 586 } 587 } 588 589 /** Get the value of a field in a database parameter token. */ 590 private static String _getParamField(RecordToken record, String name) { 591 StringToken token = (StringToken) record.get(name); 592 if (token == null) { 593 return ""; 594 } else { 595 return token.stringValue(); 596 } 597 } 598 599 /** Create a database parameter token. */ 600 private static RecordToken _createParamsToken(String driver, String url, 601 String user, String passwd) throws IllegalActionException { 602 603 return new RecordToken(_paramsLabels, new Token[] { 604 new StringToken(driver), new StringToken(url), 605 new StringToken(user), new StringToken(passwd) }); 606 } 607 608 // ///////////////////////////////////////////////////////////////// 609 // // private variables //// 610 611 // An indicator for the db format. 612 private _DBType _dbFormat; 613 private String _databaseURL = ""; 614 private String _driverName = ""; 615 private String _username = ""; 616 private String _password = ""; 617 618 // Saving previous values to track changes. 619 private String _prevDriver = ""; 620 private String _prevDBURL = ""; 621 private String _prevUser = ""; 622 private String _prevPasswd = ""; 623 624 // the database schema 625 private String _schema = ""; 626 627 // database types 628 private enum _DBType { 629 _ORCL, _DB2, _LACCS, _MSSQL, _PGSQL, _MYSQL, _HSQL, _SQLite 630 }; 631 632 /** The database parameter token labels. */ 633 private static String[] _paramsLabels = new String[] { "driver", "url", 634 "user", "password" }; 635}