001/* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */ 002/* 003 * BioJava development code 004 * 005 * This code may be freely distributed and modified under the 006 * terms of the GNU Lesser General Public Licence. This should 007 * be distributed with the code. If you do not have a copy, 008 * see: 009 * 010 * http://www.gnu.org/copyleft/lesser.html 011 * 012 * Copyright for this code is held jointly by the individual 013 * authors. These should be listed in @author doc comments. 014 * 015 * For more information on the BioJava project and its aims, 016 * or to join the biojava-l mailing list, visit the home page 017 * at: 018 * 019 * http://www.biojava.org/ 020 * 021 */ 022 023package org.biojava.bio.seq.db.biosql; 024import java.io.StringReader; 025import java.sql.Connection; 026import java.sql.Driver; 027import java.sql.DriverManager; 028import java.sql.PreparedStatement; 029import java.sql.ResultSet; 030import java.sql.SQLException; 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.Collections; 034import java.util.HashSet; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Map; 038import java.util.Set; 039 040import javax.sql.DataSource; 041 042import org.biojava.bio.Annotation; 043import org.biojava.bio.BioError; 044import org.biojava.bio.BioException; 045import org.biojava.bio.BioRuntimeException; 046import org.biojava.bio.seq.Feature; 047import org.biojava.bio.seq.FeatureFilter; 048import org.biojava.bio.seq.FeatureHolder; 049import org.biojava.bio.seq.Sequence; 050import org.biojava.bio.seq.SequenceIterator; 051import org.biojava.bio.seq.SimpleFeatureHolder; 052import org.biojava.bio.seq.db.IDMaker; 053import org.biojava.bio.seq.db.IllegalIDException; 054import org.biojava.bio.seq.db.SequenceDB; 055import org.biojava.bio.seq.db.biosql.DBHelper.BioSequenceStyle; 056import org.biojava.bio.seq.io.OrganismParser; 057import org.biojava.bio.seq.io.ParseException; 058import org.biojava.bio.seq.io.SymbolTokenization; 059import org.biojava.bio.symbol.Alphabet; 060import org.biojava.bio.taxa.Taxon; 061import org.biojava.ontology.Ontology; 062import org.biojava.utils.AbstractChangeable; 063import org.biojava.utils.ChangeEvent; 064import org.biojava.utils.ChangeVetoException; 065import org.biojava.utils.JDBCPooledDataSource; 066import org.biojava.utils.cache.Cache; 067import org.biojava.utils.cache.FixedSizeCache; 068import org.biojava.utils.cache.WeakValueHashMap; 069 070/** 071 * SequenceDB keyed off a BioSQL database. This is an almost-complete 072 * implementation of the BioJava Sequence, SequenceDB, and Feature interfaces, 073 * and can be used in a wide range of applications. 074 * 075 * Note: It now uses BioSQL schema version 1.0 (Post Singapore) 076 * All previous versions are no longer supported. 077 * 078 * @author Thomas Down 079 * @author Matthew Pocock 080 * @author Simon Foote 081 * @author Len Trigg 082 * @author Mark Schreiber 083 * @author Richard Holland 084 * @deprecated Use hibernate and org.biojavax.bio.db.* 085 * @since 1.3 086 */ 087public class BioSQLSequenceDB extends AbstractChangeable implements SequenceDB { 088 089 private DataSource dataSource; 090 private int dbid = -1; 091 private String name; 092 private IDMaker idmaker = new IDMaker.ByName(); 093 private WeakValueHashMap sequencesByName = new WeakValueHashMap(); 094 private WeakValueHashMap sequencesByID = new WeakValueHashMap(); 095 private DBHelper helper; 096 private FeaturesSQL featuresSQL; 097 private OntologySQL ontologySQL; 098// private BioSQLChangeHub changeHub; 099 private BioSQLEntryChangeHub entryChangeHub; 100 private BioSQLEntryAnnotationChangeHub entryAnnotationChangeHub; 101 private BioSQLFeatureChangeHub featureChangeHub; 102 private BioSQLFeatureAnnotationChangeHub featureAnnotationChangeHub; 103 private WeakValueHashMap featuresByID = new WeakValueHashMap(); 104 private Cache tileCache = new FixedSizeCache(10); 105 106 DataSource getDataSource() { 107 return dataSource; 108 } 109 110 DBHelper getDBHelper() { 111 return helper; 112 } 113 114 FeaturesSQL getFeaturesSQL() { 115 return featuresSQL; 116 } 117/* 118 BioSQLChangeHub getChangeHub() { 119 return changeHub; 120 } 121*/ 122 BioSQLEntryChangeHub getEntryChangeHub() { 123 return entryChangeHub; 124 } 125 126 BioSQLEntryAnnotationChangeHub getEntryAnnotationChangeHub() { 127 return entryAnnotationChangeHub; 128 } 129 130 BioSQLFeatureChangeHub getFeatureChangeHub() { 131 return featureChangeHub; 132 } 133 134 BioSQLFeatureAnnotationChangeHub getFeatureAnnotationChangeHub() { 135 return featureAnnotationChangeHub; 136 } 137 138 /** 139 * Connect to a BioSQL database. 140 * 141 * @param dbDriver A JDBC database driver. For example, <code>com.jdbc.mysql.Driver</code> 142 * @param dbURL A JDBC database URL. For example, <code>jdbc:postgresql://localhost/thomasd_biosql</code> 143 * @param dbUser The username to use when connecting to the database (or an empty string). 144 * @param dbPass The password to use when connecting to the database (or an empty string). 145 * @param biodatabase The identifier of a namespace within the physical BioSQL database. 146 * @param create If the requested namespace doesn't exist, and this flag is <code>true</code>, 147 * a new namespace will be created. 148 * 149 * @throws BioException if an error occurs communicating with the database 150 */ 151 152 public BioSQLSequenceDB(String dbDriver, 153 String dbURL, 154 String dbUser, 155 String dbPass, 156 String biodatabase, 157 boolean create) 158 throws BioException { 159 160 try { 161 dataSource = JDBCPooledDataSource.getDataSource(dbDriver, dbURL, dbUser, dbPass); 162 } catch (Exception ex) { 163 throw new BioException("Error getting datasource", ex); 164 } 165 this.initDb(biodatabase, create); 166 } 167 168 /** 169 * Connect to a BioSQL database. 170 * 171 * @param dbURL A JDBC database URL. For example, <code>jdbc:postgresql://localhost/thomasd_biosql</code> 172 * @param dbUser The username to use when connecting to the database (or an empty string). 173 * @param dbPass The password to use when connecting to the database (or an empty string). 174 * @param biodatabase The identifier of a namespace within the physical BioSQL database. 175 * @param create If the requested namespace doesn't exist, and this flag is <code>true</code>, 176 * a new namespace will be created. 177 * 178 * @throws BioException if an error occurs communicating with the database 179 */ 180 181 public BioSQLSequenceDB(String dbURL, 182 String dbUser, 183 String dbPass, 184 String biodatabase, 185 boolean create) 186 throws BioException { 187 188 try { 189 Driver drv = DriverManager.getDriver(dbURL); 190 dataSource = JDBCPooledDataSource.getDataSource(drv.getClass().getName(), dbURL, dbUser, dbPass); 191 } catch (Exception ex) { 192 throw new BioException("Error getting datasource", ex); 193 } 194 this.initDb(biodatabase, create); 195 } 196 197 public BioSQLSequenceDB(DataSource ds, 198 String biodatabase, 199 boolean create) 200 throws BioException { 201 202 dataSource = ds; 203 this.initDb(biodatabase, create); 204 } 205 206 void initDb(String biodatabase, boolean create) 207 throws BioException { 208 209 // Create helpers 210 entryChangeHub = new BioSQLEntryChangeHub(this); 211 entryAnnotationChangeHub = new BioSQLEntryAnnotationChangeHub(this, entryChangeHub); 212 featureChangeHub = new BioSQLFeatureChangeHub(this, entryChangeHub); 213 featureAnnotationChangeHub = new BioSQLFeatureAnnotationChangeHub(this, featureChangeHub); 214 215 Connection conn = null; 216 try { 217 conn = dataSource.getConnection(); 218 conn.setAutoCommit(false); 219 220 // DBHelper needs to be initialized before checks and ontologies are created 221 helper = DBHelper.getDBHelper(conn); 222 223 // Check that BioSQL database schema is post-Singapore 224 if (! isDbSchemaSupported()) { 225 try {conn.close();} catch (SQLException ex3) {} 226 throw new BioException("This database appears to be an old (pre-Singapore) BioSQL." 227 + " If you need to access it, try an older BioJava snapshot (1.3pre1 or earlier)"); 228 } 229 230 if (! isBioentryPropertySupported()) { 231 try {conn.close();} catch (SQLException ex3) {} 232 throw new BioException("This database appears to be an old (pre-Cape-Town) BioSQL." 233 + " If you need to access it, try an older BioJava snapshot"); 234 } 235 236 // Create adapters 237 featuresSQL = new FeaturesSQL(this); 238 try { 239 //ontologySQL = new OntologySQL(dataSource, helper); 240 ontologySQL = OntologySQL.getOntologySQL(dataSource, helper); 241 } catch (SQLException ex) { 242 try {conn.close();} catch (SQLException ex3) {} 243 throw new BioException("Error accessing ontologies", ex); 244 } 245 246 PreparedStatement getID = conn.prepareStatement("select biodatabase_id from biodatabase where name = ?"); 247 getID.setString(1, biodatabase); 248 ResultSet rs = getID.executeQuery(); 249 if (rs.next()) { 250 dbid = rs.getInt(1); 251 name = biodatabase; 252 rs.close(); 253 getID.close(); 254 conn.close(); 255 256 } else { 257 rs.close(); 258 getID.close(); 259 260 if (create) { 261 PreparedStatement createdb = conn.prepareStatement( 262 "insert into biodatabase (name) values ( ? )"); 263 createdb.setString(1, biodatabase); 264 createdb.executeUpdate(); 265 conn.commit(); 266 createdb.close(); 267 conn.close(); 268 dbid = getDBHelper().getInsertID(conn, "biodatabase", "biodatabase_id"); 269 } else { 270 conn.close(); 271 throw new BioException("Biodatabase " + biodatabase + " doesn't exist"); 272 } 273 } 274 } catch (SQLException ex) { 275 if (conn!=null) try {conn.close();} catch (SQLException ex3) {} 276 throw new BioException("Error connecting to BioSQL database: " + ex.getMessage(), ex); 277 } 278 } 279 280 public Ontology createOntology(String name, String description) 281 throws Exception 282 { 283 return ontologySQL.createOntology(name, description); 284 } 285 286 public Ontology getOntology(String name) 287 throws Exception 288 { 289 return ontologySQL.getOntology(name); 290 } 291 292 public Ontology addOntology(Ontology onto) 293 throws Exception 294 { 295 return ontologySQL.addOntology(onto); 296 } 297 298 public String getName() { 299 return name; 300 } 301 302 public void createDummySequence(String id, 303 Alphabet alphabet, 304 int length) 305 throws ChangeVetoException, BioException 306 { 307 synchronized (this) { 308 ChangeEvent cev = new ChangeEvent(this, SequenceDB.SEQUENCES, null); 309 firePreChangeEvent(cev); 310 _createDummySequence(id, alphabet, length); 311 firePostChangeEvent(cev); 312 } 313 } 314 315 private void _createDummySequence(String id, 316 Alphabet seqAlpha, 317 int length) 318 throws ChangeVetoException, BioException 319 { 320 int version = 1; 321 322 Connection conn = null; 323 try { 324 conn = dataSource.getConnection(); 325 conn.setAutoCommit(false); 326 327 PreparedStatement create_bioentry = conn.prepareStatement( 328 "insert into bioentry " + 329 "(biodatabase_id, name, accession, version, division) " + 330 "values (?, ?, ?, ?, ?)"); 331 create_bioentry.setInt(1, dbid); 332 create_bioentry.setString(2, id); 333 create_bioentry.setString(3, id); 334 create_bioentry.setInt(4, version); 335 create_bioentry.setString(5, "?"); 336 create_bioentry.executeUpdate(); 337 create_bioentry.close(); 338 339 int bioentry_id = getDBHelper().getInsertID(conn, "bioentry", "bioentry_id"); 340 341 PreparedStatement create_dummy = conn.prepareStatement("insert into biosequence " + 342 " (bioentry_id, version, alphabet, length) " + 343 "values (?, ?, ?, ?)"); 344 create_dummy.setInt(1, bioentry_id); 345 create_dummy.setInt(2, version); 346 create_dummy.setString(3, seqAlpha.getName()); 347 create_dummy.setInt(4, length); 348 create_dummy.executeUpdate(); 349 create_dummy.close(); 350 //int dummy_id = getDBHelper().getInsertID(conn, "biosequence", "biosequence_id"); 351 352 conn.commit(); 353 conn.close(); 354 } catch (SQLException ex) { 355 boolean rolledback = false; 356 if (conn != null) { 357 try { 358 conn.rollback(); 359 rolledback = true; 360 } catch (SQLException ex2) {} 361 try {conn.close();} catch (SQLException ex3) {} 362 } 363 throw new BioRuntimeException("Error adding dummy sequence" + 364 (rolledback ? " (rolled back successfully)" : ""), ex); 365 } 366 } 367 368 public void addSequence(Sequence seq) 369 throws ChangeVetoException, BioException 370 { 371 synchronized (this) { 372 ChangeEvent cev = new ChangeEvent(this, SequenceDB.SEQUENCES, seq); 373 firePreChangeEvent(cev); 374 _addSequence(seq); 375 firePostChangeEvent(cev); 376 } 377 } 378 379 private void _addSequence(Sequence seq) 380 throws ChangeVetoException, BioException { 381 String seqName = idmaker.calcID(seq); 382 int version = 1; 383 384 Alphabet seqAlpha = seq.getAlphabet(); 385 SymbolTokenization seqToke; 386 try { 387 seqToke = seqAlpha.getTokenization("token"); 388 } catch (Exception ex) { 389 throw new BioException("Can't store sequences in BioSQL unless they can be sensibly tokenized/detokenized", ex); 390 } 391 392 Connection conn = null; 393 try { 394 conn = dataSource.getConnection(); 395 conn.setAutoCommit(false); 396 //ResultSet rs; 397 398 // 399 // we will need this annotation bundle for various things 400 // 401 402 Annotation ann = seq.getAnnotation(); 403 404 PreparedStatement create_bioentry = conn.prepareStatement( 405 "insert into bioentry " + 406 "(biodatabase_id, name, accession, version, division) " + 407 "values (?, ?, ?, ?, ?)" 408 ); 409 create_bioentry.setInt(1, dbid); 410 create_bioentry.setString(2, seqName); 411 create_bioentry.setString(3, seqName); 412 create_bioentry.setInt(4, version); 413 create_bioentry.setString(5, "?"); 414 create_bioentry.executeUpdate(); 415 create_bioentry.close(); 416 417 // System.err.println("Created bioentry"); 418 419 int bioentry_id = getDBHelper().getInsertID(conn, "bioentry", "bioentry_id"); 420 421 BioSequenceStyle bs = getDBHelper().getBioSequenceStyle(); 422 423 // See if we are using CLOBs. 424 if (bs==DBHelper.BIOSEQUENCE_ORACLECLOB) { 425 PreparedStatement create_biosequence = conn.prepareStatement("insert into biosequence " + 426 "(bioentry_id, version, length, seq, alphabet) " + 427 "values (?, ?, ?, empty_clob(), ?)"); 428 create_biosequence.setInt(1, bioentry_id); 429 create_biosequence.setInt(2, version); 430 create_biosequence.setInt(3, seq.length()); 431 String seqstr = seqToke.tokenizeSymbolList(seq); 432 433 create_biosequence.setString(4, seqAlpha.getName()); 434 create_biosequence.executeUpdate(); 435 create_biosequence.close(); 436 437 // Now retrieve and update 438 PreparedStatement retrieve_biosequence = conn.prepareStatement("select seq from biosequence " + 439 "where bioentry_id = ? for update"); 440 retrieve_biosequence.setInt(1, bioentry_id); 441 ResultSet rs = retrieve_biosequence.executeQuery(); 442 if (!rs.next()) throw new BioRuntimeException("Could not read newly inserted sequence!"); 443 444 OracleDBHelper odh = (OracleDBHelper)getDBHelper(); 445 odh.stringToClob(conn, rs, 1, seqstr); 446 447 rs.close(); 448 retrieve_biosequence.close(); 449 450 } else { // BIOSEQUENCE_GENERIC 451 PreparedStatement create_biosequence = conn.prepareStatement("insert into biosequence " + 452 "(bioentry_id, version, length, seq, alphabet) " + 453 "values (?, ?, ?, ?, ?)"); 454 create_biosequence.setInt(1, bioentry_id); 455 create_biosequence.setInt(2, version); 456 create_biosequence.setInt(3, seq.length()); 457 String seqstr = seqToke.tokenizeSymbolList(seq); 458 459 create_biosequence.setCharacterStream(4, new StringReader(seqstr), seqstr.length()); 460 461 create_biosequence.setString(5, seqAlpha.getName()); 462 create_biosequence.executeUpdate(); 463 create_biosequence.close(); 464 } 465 466 467 // System.err.println("Stored sequence"); 468 469 // 470 // Store the features 471 // 472 473 FeatureHolder features = seq; 474 int num = features.countFeatures(); 475 if (!isHierarchySupported()) { 476 features = features.filter(FeatureFilter.all, true); 477 if (features.countFeatures() != num) { 478 System.err.println("*** Warning: feature hierarchy was lost when adding sequence to BioSQL"); 479 } 480 } 481 getFeaturesSQL().persistFeatures(conn, bioentry_id, features); 482 483 // System.err.println("Stored features"); 484 485 // 486 // Store generic properties 487 // 488 489 for (Iterator i = ann.asMap().entrySet().iterator(); i.hasNext(); ) { 490 Map.Entry me = (Map.Entry) i.next(); 491 Object key = me.getKey(); 492 Object value = me.getValue(); 493 persistBioentryProperty(conn, bioentry_id, key, value, false, true); 494 } 495 496 conn.commit(); 497 conn.close(); 498 } catch (SQLException ex) { 499 boolean rolledback = false; 500 if (conn != null) { 501 try { 502 conn.rollback(); 503 rolledback = true; 504 } catch (SQLException ex2) {} 505 try {conn.close();} catch (SQLException ex3) {} 506 } 507 throw new BioRuntimeException( 508 "Error adding sequence: " + seq.getName() + 509 (rolledback ? " (rolled back successfully)" : ""), ex); 510 } 511 } 512 513 514 public Sequence getSequence(String id) 515 throws BioException { 516 return getSequence(id, -1); 517 } 518 519 public Sequence getSequence(int bioentry_id) 520 throws BioException { 521 return getSequence(null, bioentry_id); 522 } 523 524 Sequence getSequence(String id, int bioentry_id) 525 throws BioException { 526 Sequence seq = null; 527 if (id != null) { 528 seq = (Sequence) sequencesByName.get(id); 529 } else if (bioentry_id >= 0) { 530 seq = (Sequence) sequencesByID.get(new Integer(bioentry_id)); 531 } else { 532 throw new BioError("Neither a name nor an internal ID was supplied"); 533 } 534 535 if (seq != null) { 536 return seq; 537 } 538 539 Connection conn = null; 540 try { 541 conn = dataSource.getConnection(); 542 543 if (bioentry_id < 0) { 544 PreparedStatement get_bioentry = conn.prepareStatement("select bioentry.bioentry_id " + 545 "from bioentry " + 546 "where bioentry.accession = ? and " + 547 " bioentry.biodatabase_id = ?"); 548 get_bioentry.setString(1, id); 549 get_bioentry.setInt(2, dbid); 550 ResultSet rs = get_bioentry.executeQuery(); 551 if (rs.next()) { 552 bioentry_id = rs.getInt(1); 553 } 554 rs.close(); 555 get_bioentry.close(); 556 557 if (bioentry_id < 0) { 558 conn.close(); 559 throw new IllegalIDException("No bioentry with accession " + id); 560 } 561 } else { 562 PreparedStatement get_accession = conn.prepareStatement("select bioentry.accession from bioentry where bioentry.bioentry_id = ? and bioentry.biodatabase_id = ?"); 563 get_accession.setInt(1, bioentry_id); 564 get_accession.setInt(2, dbid); 565 ResultSet rs = get_accession.executeQuery(); 566 if (rs.next()) { 567 id = rs.getString(1); 568 } 569 rs.close(); 570 get_accession.close(); 571 572 if (id == null) { 573 conn.close(); 574 throw new IllegalIDException("No bioentry with internal ID " + bioentry_id); 575 } 576 } 577 578 if (seq == null) { 579 PreparedStatement get_biosequence = conn.prepareStatement("select alphabet, length " + 580 "from biosequence " + 581 "where bioentry_id = ?"); 582 get_biosequence.setInt(1, bioentry_id); 583 ResultSet rs = get_biosequence.executeQuery(); 584 if (rs.next()) { 585 // UC conversion required for lower-case alphabets from bioperl. 586 // This is because BioSQL accepts both UC and LC. 587 String molecule = rs.getString(1).toUpperCase(); 588 int length = rs.getInt(2); 589 if (rs.wasNull()) { 590 length = -1; 591 } 592 seq = new BioSQLSequence(this, id, bioentry_id, molecule, length); 593 } 594 rs.close(); 595 get_biosequence.close(); 596 } 597 598 if (seq == null && isAssemblySupported()) { 599 PreparedStatement get_assembly = conn.prepareStatement("select assembly_id, length, molecule " + 600 "from assembly " + 601 "where bioentry_id = ?"); 602 get_assembly.setInt(1, bioentry_id); 603 ResultSet rs = get_assembly.executeQuery(); 604 if (rs.next()) { 605 int assembly_id = rs.getInt(1); 606 int length = rs.getInt(2); 607 String molecule = rs.getString(3); 608 seq = new BioSQLAssembly(this, id, bioentry_id, assembly_id, molecule, length); 609 } 610 rs.close(); 611 get_assembly.close(); 612 } 613 614 conn.close(); 615 616 if (seq != null) { 617 sequencesByName.put(id, seq); 618 sequencesByID.put(new Integer(bioentry_id), seq); 619 return seq; 620 } 621 } catch (SQLException ex) { 622 if (conn!=null) try {conn.close();} catch (SQLException ex3) {} 623 throw new BioException("Error accessing BioSQL tables", ex); 624 } 625 626 throw new BioException("BioEntry " + id + " exists with unknown sequence type"); 627 } 628 629 public void removeSequence(String id) 630 throws ChangeVetoException, BioException { 631 632 synchronized (this) { 633 ChangeEvent cev = new ChangeEvent(this, SequenceDB.SEQUENCES, null); 634 firePreChangeEvent(cev); 635 _removeSequence(id); 636 firePostChangeEvent(cev); 637 } 638 } 639 640 private void _removeSequence(String id) 641 throws BioException, ChangeVetoException 642 { 643 644 Sequence seq = (Sequence) sequencesByName.get(id); 645 646 if (seq != null) { 647 seq = null; // Don't want to be holding the reference ourselves! 648 try { 649 Thread.sleep(100L); 650 System.gc(); 651 } catch (Exception ex) { 652 ex.printStackTrace(); 653 } 654 seq = (Sequence) sequencesByName.get(id); 655 if (seq != null) { 656 throw new BioException("There are still references to sequence with ID " + id + " from this database."); 657 } 658 } 659 660 Connection conn = null; 661 try { 662 conn = dataSource.getConnection(); 663 conn.setAutoCommit(false); 664 PreparedStatement get_sequence = conn.prepareStatement("select bioentry.bioentry_id " + 665 "from bioentry where accession = ? and biodatabase_id = ?" 666 ); 667 get_sequence.setString(1, id); 668 get_sequence.setInt(2, dbid); 669 ResultSet rs = get_sequence.executeQuery(); 670 boolean exists; 671 if ((exists = rs.next())) { 672 673 // For MySQL4 (default is to use InnoDB tables), delete is via a CASCADE 674 // so only have to delete from bioentry to get all references to that 675 // bioentry deleted 676 DBHelper.DeleteStyle dstyle = getDBHelper().getDeleteStyle(); 677 int bioentry_id = rs.getInt(1); 678 679 if (dstyle != DBHelper.DELETE_MYSQL4) { 680 681 PreparedStatement delete_reference = conn.prepareStatement("delete from bioentry_reference where bioentry_id = ?"); 682 delete_reference.setInt(1, bioentry_id); 683 delete_reference.executeUpdate(); 684 delete_reference.close(); 685 686 String commentTableName = getCommentTableName(); 687 if (commentTableName != null) { 688 PreparedStatement delete_comment = conn.prepareStatement("delete from " + commentTableName 689 + " where bioentry_id = ?"); 690 delete_comment.setInt(1, bioentry_id); 691 delete_comment.executeUpdate(); 692 delete_comment.close(); 693 } 694 695 PreparedStatement delete_qv = conn.prepareStatement("delete from bioentry_qualifier_value where bioentry_id = ?"); 696 delete_qv.setInt(1, bioentry_id); 697 delete_qv.executeUpdate(); 698 delete_qv.close(); 699 700 ArrayList generic_ids = null; // default delete style will cache seqfeature_id's that need to be deleted 701 702 PreparedStatement delete_locs; 703 if (dstyle == DBHelper.DELETE_POSTGRESQL) { 704 delete_locs = conn.prepareStatement("delete from location" + 705 " where location.seqfeature_id = seqfeature.seqfeature_id and" + 706 " seqfeature.bioentry_id = ?"); 707 delete_locs.setInt(1, bioentry_id); 708 delete_locs.executeUpdate(); 709 delete_locs.close(); 710 } else { 711 delete_locs = conn.prepareStatement("delete from location where seqfeature_id = ?"); 712 713 PreparedStatement get_seqfeats = conn.prepareStatement("select seqfeature_id" 714 + " from seqfeature" 715 + " where bioentry_id = ?" 716 ); 717 get_seqfeats.setInt(1, bioentry_id); 718 ResultSet sfids = get_seqfeats.executeQuery(); 719 generic_ids = new ArrayList(); 720 while (sfids.next()) { 721 int sfid = sfids.getInt(1); 722 generic_ids.add(new Integer(sfid)); 723 delete_locs.setInt(1, sfid); 724 delete_locs.executeUpdate(); 725 } 726 sfids.close(); 727 get_seqfeats.close(); 728 } 729 delete_locs.close(); 730 731 PreparedStatement delete_fqv; 732 if (dstyle == DBHelper.DELETE_POSTGRESQL) { 733 delete_fqv = conn.prepareStatement("delete from seqfeature_qualifier_value" + 734 " where seqfeature_qualifier_value.seqfeature_id = seqfeature.seqfeature_id" + 735 " and seqfeature.bioentry_id = ?"); 736 delete_fqv.setInt(1, bioentry_id); 737 delete_fqv.executeUpdate(); 738 } else { 739 delete_fqv = conn.prepareStatement("delete from seqfeature_qualifier_value" 740 + " where seqfeature_qualifier_value.seqfeature_id = ?"); 741 for (int i = 0; i < generic_ids.size(); i++) { 742 int sfid = ((Integer) generic_ids.get(i)).intValue(); 743 delete_fqv.setInt(1, sfid); 744 delete_fqv.executeUpdate(); 745 } 746 } 747 delete_fqv.close(); 748 749 PreparedStatement delete_rel; 750 if (dstyle == DBHelper.DELETE_POSTGRESQL) { 751 delete_rel = conn.prepareStatement("delete from seqfeature_relationship" + 752 " where object_seqfeature_id = seqfeature.seqfeature_id" + 753 " and seqfeature.bioentry_id = ?"); 754 delete_rel.setInt(1, bioentry_id); 755 delete_rel.executeUpdate(); 756 } else { 757 delete_rel = conn.prepareStatement("delete from seqfeature_relationship" 758 + " where object_seqfeature_id = ?"); 759 for (int i = 0; i < generic_ids.size(); i++) { 760 int sfid = ((Integer) generic_ids.get(i)).intValue(); 761 delete_rel.setInt(1, sfid); 762 delete_rel.executeUpdate(); 763 } 764 } 765 delete_rel.close(); 766 767 PreparedStatement delete_features = conn.prepareStatement("delete from seqfeature " + 768 " where bioentry_id = ?"); 769 delete_features.setInt(1, bioentry_id); 770 delete_features.executeUpdate(); 771 delete_features.close(); 772 773 PreparedStatement delete_biosequence = 774 conn.prepareStatement("delete from biosequence where bioentry_id = ?"); 775 776 delete_biosequence.setInt(1, bioentry_id); 777 delete_biosequence.executeUpdate(); 778 delete_biosequence.close(); 779 } // End of if for non-MYSQL4 deletion 780 781 // All DB types must delete the bioentry via its id 782 // MySQL4 only needs to delete this, as it will cascade delete all references to it 783 PreparedStatement delete_entry = 784 conn.prepareStatement("delete from bioentry where bioentry_id = ?"); 785 delete_entry.setInt(1, bioentry_id); 786 int status = delete_entry.executeUpdate(); 787 // Check that a deletion actually occurred, if not indicate so 788 if (status < 1) { 789 System.out.println("Bioentry (ID " + bioentry_id + ") failed to delete!!"); 790 } 791 delete_entry.close(); 792 } 793 794 rs.close(); 795 get_sequence.close(); 796 797 conn.commit(); 798 conn.close(); 799 800 if (!exists) { 801 throw new IllegalIDException("Sequence " + id + " didn't exist"); 802 } 803 } catch (SQLException ex) { 804 boolean rolledback = false; 805 if (conn != null) { 806 try { 807 conn.rollback(); 808 rolledback = true; 809 } catch (SQLException ex2) {} 810 try {conn.close();} catch (SQLException ex3) {} 811 } 812 throw new BioException("Error removing from BioSQL tables" + 813 (rolledback ? " (rolled back successfully)" : ""), ex); 814 } 815 } 816 817 public Set ids() { 818 Connection conn = null; 819 try { 820 Set _ids = new HashSet(); 821 conn = dataSource.getConnection(); 822 823 PreparedStatement st = conn.prepareStatement("select bioentry.accession from bioentry where bioentry.biodatabase_id = ?"); 824 st.setInt(1, dbid); 825 ResultSet rs = st.executeQuery(); 826 while (rs.next()) { 827 _ids.add(rs.getString(1)); 828 } 829 rs.close(); 830 st.close(); 831 832 conn.close(); 833 return Collections.unmodifiableSet(_ids); 834 } catch (SQLException ex) { 835 if (conn!=null) try {conn.close();} catch (SQLException ex3) {} 836 throw new BioRuntimeException("Error reading from BioSQL tables", ex); 837 } 838 } 839 840 // 841 // Sequence support 842 // 843 844 845 void persistBioentryProperty(Connection conn, 846 int bioentry_id, 847 Object key, 848 Object value, 849 boolean removeFirst, 850 boolean silent) 851 throws SQLException 852 { 853 // Ought to check for special-case keys. (or just wait 'til the special case 854 // tables get nuked :-) 855 // ex. taxon, references, dbxrefs 856 857 if (key.equals(OrganismParser.PROPERTY_ORGANISM)) { 858 int taxon_id = TaxonSQL.putTaxon(conn, getDBHelper(), (Taxon) value); 859 if (taxon_id != -1) { 860 PreparedStatement set_taxon = conn.prepareStatement("update bioentry set taxon_id = ? " 861 + " where bioentry_id = ?"); 862 set_taxon.setInt(1, taxon_id); 863 set_taxon.setInt(2, bioentry_id); 864 set_taxon.executeUpdate(); 865 set_taxon.close(); 866 } 867 868 } else { 869 String keyString = key.toString(); 870 871 if (!isBioentryPropertySupported()) { 872 if (silent) { 873 return; 874 } else { 875 throw new SQLException("Can't persist this property since the bioentry_qualifier_value table isn't available"); 876 } 877 } 878 879 if (removeFirst) { 880 int id = intern_ontology_term(conn, keyString); 881 PreparedStatement remove_old_value = conn.prepareStatement("delete from bioentry_qualifier_value " + 882 " where bioentry_id = ? and term_id = ?"); 883 remove_old_value.setInt(1, bioentry_id); 884 remove_old_value.setInt(2, id); 885 remove_old_value.executeUpdate(); 886 remove_old_value.close(); 887 } 888 889 if (value != null) { 890 PreparedStatement insert_new; 891 if (isSPASupported()) { 892 insert_new = conn.prepareStatement("insert into bioentry_qualifier_value " + 893 " (bioentry_id, term_id, value, rank) " + 894 "values (?, intern_ontology_term( ? ), ?, ?)"); 895 if (value instanceof Collection) { 896 int cnt = 0; 897 for (Iterator i = ((Collection) value).iterator(); i.hasNext(); ) { 898 insert_new.setInt(1, bioentry_id); 899 insert_new.setString(2, keyString); 900 insert_new.setInt(4, ++cnt); 901 insert_new.setString(3, i.next().toString()); 902 insert_new.executeUpdate(); 903 } 904 } else { 905 insert_new.setInt(1, bioentry_id); 906 insert_new.setString(2, keyString); 907 insert_new.setInt(3, 1); 908 insert_new.setString(3, value.toString()); 909 insert_new.executeUpdate(); 910 } 911 } else { 912 insert_new = conn.prepareStatement("insert into bioentry_qualifier_value " + 913 " (bioentry_id, term_id, rank, value) " + 914 "values (?, ?, ?, ?)"); 915 int termID = intern_ontology_term(conn, keyString); 916 if (value instanceof Collection) { 917 int cnt = 0; 918 for (Iterator i = ((Collection) value).iterator(); i.hasNext(); ) { 919 insert_new.setInt(1, bioentry_id); 920 insert_new.setInt(2, termID); 921 insert_new.setInt(3, ++cnt); 922 insert_new.setString(4, i.next().toString()); 923 insert_new.executeUpdate(); 924 } 925 } else { 926 insert_new.setInt(1, bioentry_id); 927 insert_new.setInt(2, termID); 928 insert_new.setInt(3, 1); 929 insert_new.setString(4, value.toString()); 930 insert_new.executeUpdate(); 931 } 932 } 933 insert_new.close(); 934 } 935 } 936 } 937 938 /** 939 * Legacy method -- will go eventually 940 */ 941 942 int intern_ontology_term(Connection conn, String s) 943 throws SQLException 944 { 945 Ontology legacy = ontologySQL.getLegacyOntology(); 946 String ts = s.trim(); // Hack for schema change 947 if (legacy.containsTerm(ts)) { 948 return ontologySQL.termID(legacy.getTerm(ts)); 949 // Same term but different case causes error when try to add it for MySQL 950 // These hacks prevent it. ex. genbank can have ORGANISM & organism keys 951 // Removed hack as if set term name to BINARY handles case correctly 952// } else if (legacy.containsTerm(ts.toLowerCase())) { 953// return ontologySQL.termID(legacy.getTerm(ts.toLowerCase())); 954// } else if (legacy.containsTerm(ts.toUpperCase())) { 955// return ontologySQL.termID(legacy.getTerm(ts.toUpperCase())); 956 957 } else { 958 try { 959 return ontologySQL.termID(legacy.createTerm(ts, "")); 960 } catch (Exception ex) { 961 //ex.printStackTrace(); 962 //System.err.println("Term: " + ts + " " + ex.getMessage()); 963 throw (SQLException) new SQLException( 964 "Couldn't create term '" + ts + 965 "' for '" + s + "' in legacy ontology namespace" 966 ).initCause(ex); 967 } 968 } 969 } 970 971 String getOntologyTerm(int termId) { 972 return ontologySQL.termForID(termId).getName(); 973 } 974 975 private boolean hierarchyChecked = false; 976 private boolean hierarchySupported = false; 977 978 boolean isHierarchySupported() { 979 if (!hierarchyChecked) { 980 hierarchySupported = getDBHelper().containsTable(dataSource, "seqfeature_relationship"); 981 hierarchyChecked = true; 982 } 983 984 return hierarchySupported; 985 } 986 987 private boolean assemblyChecked = false; 988 private boolean assemblySupported = false; 989 990 boolean isAssemblySupported() { 991 if (!assemblyChecked) { 992 assemblySupported = getDBHelper().containsTable(dataSource, "assembly"); 993 assemblyChecked = true; 994 } 995 996 return assemblySupported; 997 } 998 999 1000 1001 private boolean bioentryPropertyChecked = false; 1002 private boolean bioentryPropertySupported = false; 1003 1004 boolean isBioentryPropertySupported() { 1005 if (!bioentryPropertyChecked) { 1006 bioentryPropertySupported = getDBHelper().containsTable(dataSource, "bioentry_qualifier_value"); 1007 bioentryPropertyChecked = true; 1008 } 1009 1010 return bioentryPropertySupported; 1011 } 1012 1013 1014 private boolean dbSchemaChecked = false; 1015 private boolean dbSchemaSupported = false; 1016 1017 boolean isDbSchemaSupported() { 1018 if (!dbSchemaChecked) { 1019 dbSchemaSupported = getDBHelper().containsTable(dataSource, "location"); 1020 dbSchemaChecked = true; 1021 } 1022 1023 return dbSchemaSupported; 1024 } 1025 1026 private boolean commentTableNameChecked = false; 1027 private String commentTableName = null; 1028 1029 // Get the name of the table used for comments. 1030 // "comment" isn't allowed in oracle (where comment is a reserved word) 1031 // Hilmar has said this table will be renamed to anncomment post-1.0 BioSQLe 1032 // We support both "comment" and "anncomment" 1033 String getCommentTableName() { 1034 if (!commentTableNameChecked) { 1035 if (getDBHelper().containsTable(dataSource, "comment")) { 1036 commentTableName = "comment"; 1037 } else if (getDBHelper().containsTable(dataSource, "anncomment")) { 1038 commentTableName = "anncomment"; 1039 } 1040 commentTableNameChecked = true; 1041 } 1042 return commentTableName; 1043 } 1044 1045 1046 1047 private boolean spaChecked = false; 1048 private boolean spaSupported = false; 1049 1050 boolean isSPASupported() { 1051 if (!spaChecked) { 1052 Connection conn = null; 1053 try { 1054 spaSupported = false; 1055 conn = dataSource.getConnection(); 1056 PreparedStatement ps = null; 1057 try { 1058 ps = conn.prepareStatement("select biosql_accelerators_level()"); 1059 ResultSet rs = ps.executeQuery(); 1060 if (rs.next()) { 1061 int level = rs.getInt(1); 1062 if (level >= 2) { 1063 spaSupported = true; 1064 // System.err.println("*** Accelerators present in the database: level " + level); 1065 } 1066 } 1067 rs.close(); 1068 } catch (SQLException ex) { 1069 } 1070 if (ps != null) { 1071 ps.close(); 1072 } 1073 conn.close(); 1074 1075 spaChecked = true; 1076 } catch (SQLException ex) { 1077 if (conn!=null) try {conn.close();} catch (SQLException ex3) {} 1078 throw new BioRuntimeException(ex); 1079 } 1080 } 1081 1082 return spaSupported; 1083 } 1084 1085 private class SqlizedFilter { 1086 private List tables = new ArrayList(); 1087 private String filter; 1088 private int used_ot = 0; 1089 private int used_sfs = 0; 1090 //private int used_sqv = 0; 1091 1092 SqlizedFilter(FeatureFilter ff) { 1093 filter = sqlizeFilter(ff, false); 1094 } 1095 1096 private String sqlizeFilter(FeatureFilter ff, boolean negate) { 1097 if (ff instanceof FeatureFilter.ByType) { 1098 String type = ((FeatureFilter.ByType) ff).getType(); 1099 String tableName = "ot_" + (used_ot++); 1100 tables.add("term as " + tableName); 1101 return tableName + ".name " + eq(negate) + qw(type) + " and seqfeature.type_term_id = " + tableName + ".term_id"; 1102 } else if (ff instanceof FeatureFilter.BySource) { 1103 String source = ((FeatureFilter.BySource) ff).getSource(); 1104 String tableName = "sfs_" + (used_sfs++); 1105 tables.add("term as " + tableName); 1106 return tableName + ".name " + eq(negate) + qw(source) + " and seqfeature.source_term_id = " + tableName + ".term_id"; 1107 } else if (ff instanceof FeatureFilter.ByAnnotation) { 1108 1109 return ""; 1110 1111 /* FIXME disabled until Matthew works out what he's doing with AnnotationTypes 1112 1113 FeatureFilter.ByAnnotation ffba = (FeatureFilter.ByAnnotation) ff; 1114 Object key = ffba.getKey(); 1115 Object value = ffba.getValue(); 1116 String keyString = key.toString(); 1117 String valueString = value.toString(); 1118 1119 String otName = "ot_" + (used_ot++); 1120 tables.add("ontology_term as " + otName); 1121 1122 String sqvName = "sqv_" + (used_sqv++); 1123 tables.add("seqfeature_qualifier_value as " + sqvName); 1124 1125 */ 1126 1127 // FIXME this doesn't actually do negate quite right -- it doesn't 1128 // match if the property isn't defined. Should do an outer join 1129 // to fix this. But for now, we'll just punt :-( 1130 1131 /* 1132 1133 if (negate) { 1134 return ""; 1135 } 1136 1137 return sqvName + ".qualifier_value" + eq(negate) + qw(valueString) + " and " + 1138 sqvName + ".term_id = " + otName + ".term_id and " + 1139 otName + ".term_name = " + qw(keyString) + " and " + 1140 "seqfeature.seqfeature_id = " + sqvName + ".seqfeature_id"; 1141 1142 */ 1143 1144 } else if (ff instanceof FeatureFilter.And) { 1145 FeatureFilter.And and = (FeatureFilter.And) ff; 1146 FeatureFilter ff1 = and.getChild1(); 1147 FeatureFilter ff2 = and.getChild2(); 1148 String filter1 = sqlizeFilter(ff1, negate); 1149 String filter2 = sqlizeFilter(ff2, negate); 1150 if (filter1.length() > 0) { 1151 if (filter2.length() > 0) { 1152 return filter1 + " and " + filter2; 1153 } else { 1154 return filter1; 1155 } 1156 } else { 1157 if (filter2.length() > 0) { 1158 return filter2; 1159 } else { 1160 return ""; 1161 } 1162 } 1163 } else if (ff instanceof FeatureFilter.Not) { 1164 FeatureFilter child = ((FeatureFilter.Not) ff).getChild(); 1165 return sqlizeFilter(child, !negate); 1166 } else { 1167 return ""; 1168 } 1169 } 1170 1171 private String eq(boolean negate) { 1172 if (negate) { 1173 return " <> "; 1174 } else { 1175 return "="; 1176 } 1177 } 1178 1179 private String qw(String word) { 1180 return "'" + word + "'"; 1181 } 1182 1183 public String getQuery() { 1184 StringBuffer query = new StringBuffer(); 1185 query.append("select bioentry.accession, seqfeature.seqfeature_id "); 1186 query.append(" from seqfeature, bioentry"); 1187 for (Iterator i = tables.iterator(); i.hasNext(); ) { 1188 query.append(", "); 1189 query.append((String) i.next()); 1190 } 1191 query.append(" where bioentry.bioentry_id = seqfeature.bioentry_id"); 1192 query.append(" and bioentry.biodatabase_id = ?"); 1193 if (filter.length() > 0) { 1194 query.append(" and "); 1195 query.append(filter); 1196 } 1197 query.append(" order by bioentry.accession"); 1198 1199 return query.substring(0); 1200 } 1201 } 1202 1203 private class FilterByInternalID implements FeatureFilter { 1204 private int id; 1205 1206 public FilterByInternalID(int id) { 1207 this.id = id; 1208 } 1209 1210 public boolean accept(Feature f) { 1211 if (! (f instanceof BioSQLFeature)) { 1212 return false; 1213 } 1214 1215 int intID = ((BioSQLFeature) f)._getInternalID(); 1216 return (intID == id); 1217 } 1218 } 1219 1220 public FeatureHolder filter(FeatureFilter ff) { 1221 Connection conn = null; 1222 try { 1223 SqlizedFilter sqf = new SqlizedFilter(ff); 1224 System.err.println("Doing BioSQL filter"); 1225 System.err.println(sqf.getQuery()); 1226 1227 conn = dataSource.getConnection(); 1228 PreparedStatement get_features = conn.prepareStatement(sqf.getQuery()); 1229 get_features.setInt(1, dbid); 1230 ResultSet rs = get_features.executeQuery(); 1231 1232 String lastAcc = ""; 1233 Sequence seq = null; 1234 SimpleFeatureHolder fh = new SimpleFeatureHolder(); 1235 1236 while (rs.next()) { 1237 String accession = rs.getString(1); 1238 int fid = rs.getInt(2); 1239 1240 System.err.println(accession + "\t" + fid); 1241 1242 if (seq == null || ! lastAcc.equals(accession)) { 1243 seq = getSequence(accession); 1244 } 1245 1246 FeatureHolder hereFeature = seq.filter(new FilterByInternalID(fid), true); 1247 Feature f = (Feature) hereFeature.features().next(); 1248 if (ff.accept(f)) { 1249 fh.addFeature(f); 1250 } 1251 } 1252 rs.close(); 1253 get_features.close(); 1254 conn.close(); 1255 1256 return fh; 1257 } catch (SQLException ex) { 1258 if (conn!=null) try {conn.close();} catch (SQLException ex3) {} 1259 throw new BioRuntimeException("Error accessing BioSQL tables", ex); 1260 } catch (ChangeVetoException ex) { 1261 if (conn!=null) try {conn.close();} catch (SQLException ex3) {} 1262 throw new BioError("Assert failed: couldn't modify internal FeatureHolder", ex); 1263 } catch (BioException ex) { 1264 if (conn!=null) try {conn.close();} catch (SQLException ex3) {} 1265 throw new BioRuntimeException("Error fetching sequence", ex); 1266 } 1267 } 1268 1269 // SequenceIterator here, 'cos AbstractSequenceDB grandfathers in AbstractChangable :-( 1270 1271 public SequenceIterator sequenceIterator() { 1272 return new SequenceIterator() { 1273 private Iterator pID = ids().iterator(); 1274 1275 public boolean hasNext() { 1276 return pID.hasNext(); 1277 } 1278 1279 public Sequence nextSequence() throws BioException { 1280 return getSequence((String) pID.next()); 1281 } 1282 }; 1283 } 1284 // change support stuff 1285 void firePreChangeEvent(ChangeEvent cev) 1286 throws ChangeVetoException 1287 { 1288 getChangeSupport(cev.getType()).firePreChangeEvent(cev); 1289 } 1290 1291 void firePostChangeEvent(ChangeEvent cev) 1292 { 1293 getChangeSupport(cev.getType()).firePostChangeEvent(cev); 1294 } 1295 1296 // 1297 // Feature canonicalization 1298 // 1299 1300 BioSQLFeature canonicalizeFeature(BioSQLFeature f, int feature_id) { 1301 // System.err.println("Canonicalizing feature at " + f.getLocation()); 1302 1303 Integer key = new Integer(feature_id); 1304 BioSQLFeature oldFeature = (BioSQLFeature) featuresByID.get(key); 1305 if (oldFeature != null) { 1306 return oldFeature; 1307 } else { 1308 featuresByID.put(key, f); 1309 return f; 1310 } 1311 } 1312 1313 private class SingleFeatureReceiver extends BioSQLFeatureReceiver { 1314 private Feature feature; 1315 1316 private SingleFeatureReceiver() { 1317 super(BioSQLSequenceDB.this); 1318 } 1319 1320 protected void deliverTopLevelFeature(Feature f) 1321 throws ParseException 1322 { 1323 if (feature == null) { 1324 feature = f; 1325 } else { 1326 throw new ParseException("Expecting only a single feature"); 1327 } 1328 } 1329 1330 public Feature getFeature() { 1331 return feature; 1332 } 1333 } 1334 1335 BioSQLFeature getFeatureByID(int feature_id) 1336 { 1337 Integer key = new Integer(feature_id); 1338 BioSQLFeature f = (BioSQLFeature) featuresByID.get(key); 1339 if (f != null) { 1340 return f; 1341 } 1342 1343 try { 1344 SingleFeatureReceiver receiver = new SingleFeatureReceiver(); 1345 getFeaturesSQL().retrieveFeatures(-1, receiver, null, -1, feature_id); 1346 if (receiver.getFeature() == null) { 1347 throw new BioRuntimeException("Dangling internal_feature_id"); 1348 } else { 1349 featuresByID.put(key, (BioSQLFeature) receiver.getFeature()); 1350 return (BioSQLFeature) receiver.getFeature(); 1351 } 1352 } catch (SQLException ex) { 1353 throw new BioRuntimeException("Database error", ex); 1354 } catch (BioException ex) { 1355 throw new BioRuntimeException(ex); 1356 } 1357 } 1358/* 1359 // 1360 // Dbxref canonicalization 1361 // 1362 BioSQLXRef canonicalizeXRef(BioSQLXRef r, int dbxref_id) { 1363 1364 Integer key = new Integer(dbxref_id); 1365 BioSQLXRef oldXRef = (BioSQLXRef) XRefsByID.get(key); 1366 if (oldXRef != null) { 1367 return oldXRef; 1368 } else { 1369 featuresByID.put(key, r); 1370 return r; 1371 } 1372 } 1373 1374 BioSQLXRef getXRefsByID(int dbxref_id) 1375 { 1376 Integer key = new Integer(dbxref_id); 1377 BioSQLFeature f = (BioSQLFeature) featuresByID.get(key); 1378 if (f != null) { 1379 return f; 1380 } 1381 1382 try { 1383 SingleFeatureReceiver receiver = new SingleFeatureReceiver(); 1384 getFeaturesSQL().retrieveFeatures(-1, receiver, null, -1, feature_id); 1385 if (receiver.getFeature() == null) { 1386 throw new BioRuntimeException("Dangling internal_feature_id"); 1387 } else { 1388 featuresByID.put(key, (BioSQLFeature) receiver.getFeature()); 1389 return (BioSQLFeature) receiver.getFeature(); 1390 } 1391 } catch (SQLException ex) { 1392 throw new BioRuntimeException(ex, "Database error"); 1393 } catch (BioException ex) { 1394 throw new BioRuntimeException(ex); 1395 } 1396 } 1397*/ 1398 1399 Cache getTileCache() { 1400 return tileCache; 1401 } 1402}