/* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojava.bio.seq.db.biosql; import java.io.StringReader; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.sql.DataSource; import org.biojava.bio.Annotation; import org.biojava.bio.BioError; import org.biojava.bio.BioException; import org.biojava.bio.BioRuntimeException; import org.biojava.bio.seq.Feature; import org.biojava.bio.seq.FeatureFilter; import org.biojava.bio.seq.FeatureHolder; import org.biojava.bio.seq.Sequence; import org.biojava.bio.seq.SequenceIterator; import org.biojava.bio.seq.SimpleFeatureHolder; import org.biojava.bio.seq.db.IDMaker; import org.biojava.bio.seq.db.IllegalIDException; import org.biojava.bio.seq.db.SequenceDB; import org.biojava.bio.seq.db.biosql.DBHelper.BioSequenceStyle; import org.biojava.bio.seq.io.OrganismParser; import org.biojava.bio.seq.io.ParseException; import org.biojava.bio.seq.io.SymbolTokenization; import org.biojava.bio.symbol.Alphabet; import org.biojava.bio.taxa.Taxon; import org.biojava.ontology.Ontology; import org.biojava.utils.AbstractChangeable; import org.biojava.utils.ChangeEvent; import org.biojava.utils.ChangeVetoException; import org.biojava.utils.JDBCPooledDataSource; import org.biojava.utils.cache.Cache; import org.biojava.utils.cache.FixedSizeCache; import org.biojava.utils.cache.WeakValueHashMap; /** * SequenceDB keyed off a BioSQL database. This is an almost-complete * implementation of the BioJava Sequence, SequenceDB, and Feature interfaces, * and can be used in a wide range of applications. * * Note: It now uses BioSQL schema version 1.0 (Post Singapore) * All previous versions are no longer supported. * * @author Thomas Down * @author Matthew Pocock * @author Simon Foote * @author Len Trigg * @author Mark Schreiber * @author Richard Holland * @deprecated Use hibernate and org.biojavax.bio.db.* * @since 1.3 */ public class BioSQLSequenceDB extends AbstractChangeable implements SequenceDB { private DataSource dataSource; private int dbid = -1; private String name; private IDMaker idmaker = new IDMaker.ByName(); private WeakValueHashMap sequencesByName = new WeakValueHashMap(); private WeakValueHashMap sequencesByID = new WeakValueHashMap(); private DBHelper helper; private FeaturesSQL featuresSQL; private OntologySQL ontologySQL; // private BioSQLChangeHub changeHub; private BioSQLEntryChangeHub entryChangeHub; private BioSQLEntryAnnotationChangeHub entryAnnotationChangeHub; private BioSQLFeatureChangeHub featureChangeHub; private BioSQLFeatureAnnotationChangeHub featureAnnotationChangeHub; private WeakValueHashMap featuresByID = new WeakValueHashMap(); private Cache tileCache = new FixedSizeCache(10); DataSource getDataSource() { return dataSource; } DBHelper getDBHelper() { return helper; } FeaturesSQL getFeaturesSQL() { return featuresSQL; } /* BioSQLChangeHub getChangeHub() { return changeHub; } */ BioSQLEntryChangeHub getEntryChangeHub() { return entryChangeHub; } BioSQLEntryAnnotationChangeHub getEntryAnnotationChangeHub() { return entryAnnotationChangeHub; } BioSQLFeatureChangeHub getFeatureChangeHub() { return featureChangeHub; } BioSQLFeatureAnnotationChangeHub getFeatureAnnotationChangeHub() { return featureAnnotationChangeHub; } /** * Connect to a BioSQL database. * * @param dbDriver A JDBC database driver. For example, com.jdbc.mysql.Driver * @param dbURL A JDBC database URL. For example, jdbc:postgresql://localhost/thomasd_biosql * @param dbUser The username to use when connecting to the database (or an empty string). * @param dbPass The password to use when connecting to the database (or an empty string). * @param biodatabase The identifier of a namespace within the physical BioSQL database. * @param create If the requested namespace doesn't exist, and this flag is true, * a new namespace will be created. * * @throws BioException if an error occurs communicating with the database */ public BioSQLSequenceDB(String dbDriver, String dbURL, String dbUser, String dbPass, String biodatabase, boolean create) throws BioException { try { dataSource = JDBCPooledDataSource.getDataSource(dbDriver, dbURL, dbUser, dbPass); } catch (Exception ex) { throw new BioException("Error getting datasource", ex); } this.initDb(biodatabase, create); } /** * Connect to a BioSQL database. * * @param dbURL A JDBC database URL. For example, jdbc:postgresql://localhost/thomasd_biosql * @param dbUser The username to use when connecting to the database (or an empty string). * @param dbPass The password to use when connecting to the database (or an empty string). * @param biodatabase The identifier of a namespace within the physical BioSQL database. * @param create If the requested namespace doesn't exist, and this flag is true, * a new namespace will be created. * * @throws BioException if an error occurs communicating with the database */ public BioSQLSequenceDB(String dbURL, String dbUser, String dbPass, String biodatabase, boolean create) throws BioException { try { Driver drv = DriverManager.getDriver(dbURL); dataSource = JDBCPooledDataSource.getDataSource(drv.getClass().getName(), dbURL, dbUser, dbPass); } catch (Exception ex) { throw new BioException("Error getting datasource", ex); } this.initDb(biodatabase, create); } public BioSQLSequenceDB(DataSource ds, String biodatabase, boolean create) throws BioException { dataSource = ds; this.initDb(biodatabase, create); } void initDb(String biodatabase, boolean create) throws BioException { // Create helpers entryChangeHub = new BioSQLEntryChangeHub(this); entryAnnotationChangeHub = new BioSQLEntryAnnotationChangeHub(this, entryChangeHub); featureChangeHub = new BioSQLFeatureChangeHub(this, entryChangeHub); featureAnnotationChangeHub = new BioSQLFeatureAnnotationChangeHub(this, featureChangeHub); Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // DBHelper needs to be initialized before checks and ontologies are created helper = DBHelper.getDBHelper(conn); // Check that BioSQL database schema is post-Singapore if (! isDbSchemaSupported()) { try {conn.close();} catch (SQLException ex3) {} throw new BioException("This database appears to be an old (pre-Singapore) BioSQL." + " If you need to access it, try an older BioJava snapshot (1.3pre1 or earlier)"); } if (! isBioentryPropertySupported()) { try {conn.close();} catch (SQLException ex3) {} throw new BioException("This database appears to be an old (pre-Cape-Town) BioSQL." + " If you need to access it, try an older BioJava snapshot"); } // Create adapters featuresSQL = new FeaturesSQL(this); try { //ontologySQL = new OntologySQL(dataSource, helper); ontologySQL = OntologySQL.getOntologySQL(dataSource, helper); } catch (SQLException ex) { try {conn.close();} catch (SQLException ex3) {} throw new BioException("Error accessing ontologies", ex); } PreparedStatement getID = conn.prepareStatement("select biodatabase_id from biodatabase where name = ?"); getID.setString(1, biodatabase); ResultSet rs = getID.executeQuery(); if (rs.next()) { dbid = rs.getInt(1); name = biodatabase; rs.close(); getID.close(); conn.close(); } else { rs.close(); getID.close(); if (create) { PreparedStatement createdb = conn.prepareStatement( "insert into biodatabase (name) values ( ? )"); createdb.setString(1, biodatabase); createdb.executeUpdate(); conn.commit(); createdb.close(); conn.close(); dbid = getDBHelper().getInsertID(conn, "biodatabase", "biodatabase_id"); } else { conn.close(); throw new BioException("Biodatabase " + biodatabase + " doesn't exist"); } } } catch (SQLException ex) { if (conn!=null) try {conn.close();} catch (SQLException ex3) {} throw new BioException("Error connecting to BioSQL database: " + ex.getMessage(), ex); } } public Ontology createOntology(String name, String description) throws Exception { return ontologySQL.createOntology(name, description); } public Ontology getOntology(String name) throws Exception { return ontologySQL.getOntology(name); } public Ontology addOntology(Ontology onto) throws Exception { return ontologySQL.addOntology(onto); } public String getName() { return name; } public void createDummySequence(String id, Alphabet alphabet, int length) throws ChangeVetoException, BioException { synchronized (this) { ChangeEvent cev = new ChangeEvent(this, SequenceDB.SEQUENCES, null); firePreChangeEvent(cev); _createDummySequence(id, alphabet, length); firePostChangeEvent(cev); } } private void _createDummySequence(String id, Alphabet seqAlpha, int length) throws ChangeVetoException, BioException { int version = 1; Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); PreparedStatement create_bioentry = conn.prepareStatement( "insert into bioentry " + "(biodatabase_id, name, accession, version, division) " + "values (?, ?, ?, ?, ?)"); create_bioentry.setInt(1, dbid); create_bioentry.setString(2, id); create_bioentry.setString(3, id); create_bioentry.setInt(4, version); create_bioentry.setString(5, "?"); create_bioentry.executeUpdate(); create_bioentry.close(); int bioentry_id = getDBHelper().getInsertID(conn, "bioentry", "bioentry_id"); PreparedStatement create_dummy = conn.prepareStatement("insert into biosequence " + " (bioentry_id, version, alphabet, length) " + "values (?, ?, ?, ?)"); create_dummy.setInt(1, bioentry_id); create_dummy.setInt(2, version); create_dummy.setString(3, seqAlpha.getName()); create_dummy.setInt(4, length); create_dummy.executeUpdate(); create_dummy.close(); //int dummy_id = getDBHelper().getInsertID(conn, "biosequence", "biosequence_id"); conn.commit(); conn.close(); } catch (SQLException ex) { boolean rolledback = false; if (conn != null) { try { conn.rollback(); rolledback = true; } catch (SQLException ex2) {} try {conn.close();} catch (SQLException ex3) {} } throw new BioRuntimeException("Error adding dummy sequence" + (rolledback ? " (rolled back successfully)" : ""), ex); } } public void addSequence(Sequence seq) throws ChangeVetoException, BioException { synchronized (this) { ChangeEvent cev = new ChangeEvent(this, SequenceDB.SEQUENCES, seq); firePreChangeEvent(cev); _addSequence(seq); firePostChangeEvent(cev); } } private void _addSequence(Sequence seq) throws ChangeVetoException, BioException { String seqName = idmaker.calcID(seq); int version = 1; Alphabet seqAlpha = seq.getAlphabet(); SymbolTokenization seqToke; try { seqToke = seqAlpha.getTokenization("token"); } catch (Exception ex) { throw new BioException("Can't store sequences in BioSQL unless they can be sensibly tokenized/detokenized", ex); } Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); //ResultSet rs; // // we will need this annotation bundle for various things // Annotation ann = seq.getAnnotation(); PreparedStatement create_bioentry = conn.prepareStatement( "insert into bioentry " + "(biodatabase_id, name, accession, version, division) " + "values (?, ?, ?, ?, ?)" ); create_bioentry.setInt(1, dbid); create_bioentry.setString(2, seqName); create_bioentry.setString(3, seqName); create_bioentry.setInt(4, version); create_bioentry.setString(5, "?"); create_bioentry.executeUpdate(); create_bioentry.close(); // System.err.println("Created bioentry"); int bioentry_id = getDBHelper().getInsertID(conn, "bioentry", "bioentry_id"); BioSequenceStyle bs = getDBHelper().getBioSequenceStyle(); // See if we are using CLOBs. if (bs==DBHelper.BIOSEQUENCE_ORACLECLOB) { PreparedStatement create_biosequence = conn.prepareStatement("insert into biosequence " + "(bioentry_id, version, length, seq, alphabet) " + "values (?, ?, ?, empty_clob(), ?)"); create_biosequence.setInt(1, bioentry_id); create_biosequence.setInt(2, version); create_biosequence.setInt(3, seq.length()); String seqstr = seqToke.tokenizeSymbolList(seq); create_biosequence.setString(4, seqAlpha.getName()); create_biosequence.executeUpdate(); create_biosequence.close(); // Now retrieve and update PreparedStatement retrieve_biosequence = conn.prepareStatement("select seq from biosequence " + "where bioentry_id = ? for update"); retrieve_biosequence.setInt(1, bioentry_id); ResultSet rs = retrieve_biosequence.executeQuery(); if (!rs.next()) throw new BioRuntimeException("Could not read newly inserted sequence!"); OracleDBHelper odh = (OracleDBHelper)getDBHelper(); odh.stringToClob(conn, rs, 1, seqstr); rs.close(); retrieve_biosequence.close(); } else { // BIOSEQUENCE_GENERIC PreparedStatement create_biosequence = conn.prepareStatement("insert into biosequence " + "(bioentry_id, version, length, seq, alphabet) " + "values (?, ?, ?, ?, ?)"); create_biosequence.setInt(1, bioentry_id); create_biosequence.setInt(2, version); create_biosequence.setInt(3, seq.length()); String seqstr = seqToke.tokenizeSymbolList(seq); create_biosequence.setCharacterStream(4, new StringReader(seqstr), seqstr.length()); create_biosequence.setString(5, seqAlpha.getName()); create_biosequence.executeUpdate(); create_biosequence.close(); } // System.err.println("Stored sequence"); // // Store the features // FeatureHolder features = seq; int num = features.countFeatures(); if (!isHierarchySupported()) { features = features.filter(FeatureFilter.all, true); if (features.countFeatures() != num) { System.err.println("*** Warning: feature hierarchy was lost when adding sequence to BioSQL"); } } getFeaturesSQL().persistFeatures(conn, bioentry_id, features); // System.err.println("Stored features"); // // Store generic properties // for (Iterator i = ann.asMap().entrySet().iterator(); i.hasNext(); ) { Map.Entry me = (Map.Entry) i.next(); Object key = me.getKey(); Object value = me.getValue(); persistBioentryProperty(conn, bioentry_id, key, value, false, true); } conn.commit(); conn.close(); } catch (SQLException ex) { boolean rolledback = false; if (conn != null) { try { conn.rollback(); rolledback = true; } catch (SQLException ex2) {} try {conn.close();} catch (SQLException ex3) {} } throw new BioRuntimeException( "Error adding sequence: " + seq.getName() + (rolledback ? " (rolled back successfully)" : ""), ex); } } public Sequence getSequence(String id) throws BioException { return getSequence(id, -1); } public Sequence getSequence(int bioentry_id) throws BioException { return getSequence(null, bioentry_id); } Sequence getSequence(String id, int bioentry_id) throws BioException { Sequence seq = null; if (id != null) { seq = (Sequence) sequencesByName.get(id); } else if (bioentry_id >= 0) { seq = (Sequence) sequencesByID.get(new Integer(bioentry_id)); } else { throw new BioError("Neither a name nor an internal ID was supplied"); } if (seq != null) { return seq; } Connection conn = null; try { conn = dataSource.getConnection(); if (bioentry_id < 0) { PreparedStatement get_bioentry = conn.prepareStatement("select bioentry.bioentry_id " + "from bioentry " + "where bioentry.accession = ? and " + " bioentry.biodatabase_id = ?"); get_bioentry.setString(1, id); get_bioentry.setInt(2, dbid); ResultSet rs = get_bioentry.executeQuery(); if (rs.next()) { bioentry_id = rs.getInt(1); } rs.close(); get_bioentry.close(); if (bioentry_id < 0) { conn.close(); throw new IllegalIDException("No bioentry with accession " + id); } } else { PreparedStatement get_accession = conn.prepareStatement("select bioentry.accession from bioentry where bioentry.bioentry_id = ? and bioentry.biodatabase_id = ?"); get_accession.setInt(1, bioentry_id); get_accession.setInt(2, dbid); ResultSet rs = get_accession.executeQuery(); if (rs.next()) { id = rs.getString(1); } rs.close(); get_accession.close(); if (id == null) { conn.close(); throw new IllegalIDException("No bioentry with internal ID " + bioentry_id); } } if (seq == null) { PreparedStatement get_biosequence = conn.prepareStatement("select alphabet, length " + "from biosequence " + "where bioentry_id = ?"); get_biosequence.setInt(1, bioentry_id); ResultSet rs = get_biosequence.executeQuery(); if (rs.next()) { // UC conversion required for lower-case alphabets from bioperl. // This is because BioSQL accepts both UC and LC. String molecule = rs.getString(1).toUpperCase(); int length = rs.getInt(2); if (rs.wasNull()) { length = -1; } seq = new BioSQLSequence(this, id, bioentry_id, molecule, length); } rs.close(); get_biosequence.close(); } if (seq == null && isAssemblySupported()) { PreparedStatement get_assembly = conn.prepareStatement("select assembly_id, length, molecule " + "from assembly " + "where bioentry_id = ?"); get_assembly.setInt(1, bioentry_id); ResultSet rs = get_assembly.executeQuery(); if (rs.next()) { int assembly_id = rs.getInt(1); int length = rs.getInt(2); String molecule = rs.getString(3); seq = new BioSQLAssembly(this, id, bioentry_id, assembly_id, molecule, length); } rs.close(); get_assembly.close(); } conn.close(); if (seq != null) { sequencesByName.put(id, seq); sequencesByID.put(new Integer(bioentry_id), seq); return seq; } } catch (SQLException ex) { if (conn!=null) try {conn.close();} catch (SQLException ex3) {} throw new BioException("Error accessing BioSQL tables", ex); } throw new BioException("BioEntry " + id + " exists with unknown sequence type"); } public void removeSequence(String id) throws ChangeVetoException, BioException { synchronized (this) { ChangeEvent cev = new ChangeEvent(this, SequenceDB.SEQUENCES, null); firePreChangeEvent(cev); _removeSequence(id); firePostChangeEvent(cev); } } private void _removeSequence(String id) throws BioException, ChangeVetoException { Sequence seq = (Sequence) sequencesByName.get(id); if (seq != null) { seq = null; // Don't want to be holding the reference ourselves! try { Thread.sleep(100L); System.gc(); } catch (Exception ex) { ex.printStackTrace(); } seq = (Sequence) sequencesByName.get(id); if (seq != null) { throw new BioException("There are still references to sequence with ID " + id + " from this database."); } } Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); PreparedStatement get_sequence = conn.prepareStatement("select bioentry.bioentry_id " + "from bioentry where accession = ? and biodatabase_id = ?" ); get_sequence.setString(1, id); get_sequence.setInt(2, dbid); ResultSet rs = get_sequence.executeQuery(); boolean exists; if ((exists = rs.next())) { // For MySQL4 (default is to use InnoDB tables), delete is via a CASCADE // so only have to delete from bioentry to get all references to that // bioentry deleted DBHelper.DeleteStyle dstyle = getDBHelper().getDeleteStyle(); int bioentry_id = rs.getInt(1); if (dstyle != DBHelper.DELETE_MYSQL4) { PreparedStatement delete_reference = conn.prepareStatement("delete from bioentry_reference where bioentry_id = ?"); delete_reference.setInt(1, bioentry_id); delete_reference.executeUpdate(); delete_reference.close(); String commentTableName = getCommentTableName(); if (commentTableName != null) { PreparedStatement delete_comment = conn.prepareStatement("delete from " + commentTableName + " where bioentry_id = ?"); delete_comment.setInt(1, bioentry_id); delete_comment.executeUpdate(); delete_comment.close(); } PreparedStatement delete_qv = conn.prepareStatement("delete from bioentry_qualifier_value where bioentry_id = ?"); delete_qv.setInt(1, bioentry_id); delete_qv.executeUpdate(); delete_qv.close(); ArrayList generic_ids = null; // default delete style will cache seqfeature_id's that need to be deleted PreparedStatement delete_locs; if (dstyle == DBHelper.DELETE_POSTGRESQL) { delete_locs = conn.prepareStatement("delete from location" + " where location.seqfeature_id = seqfeature.seqfeature_id and" + " seqfeature.bioentry_id = ?"); delete_locs.setInt(1, bioentry_id); delete_locs.executeUpdate(); delete_locs.close(); } else { delete_locs = conn.prepareStatement("delete from location where seqfeature_id = ?"); PreparedStatement get_seqfeats = conn.prepareStatement("select seqfeature_id" + " from seqfeature" + " where bioentry_id = ?" ); get_seqfeats.setInt(1, bioentry_id); ResultSet sfids = get_seqfeats.executeQuery(); generic_ids = new ArrayList(); while (sfids.next()) { int sfid = sfids.getInt(1); generic_ids.add(new Integer(sfid)); delete_locs.setInt(1, sfid); delete_locs.executeUpdate(); } sfids.close(); get_seqfeats.close(); } delete_locs.close(); PreparedStatement delete_fqv; if (dstyle == DBHelper.DELETE_POSTGRESQL) { delete_fqv = conn.prepareStatement("delete from seqfeature_qualifier_value" + " where seqfeature_qualifier_value.seqfeature_id = seqfeature.seqfeature_id" + " and seqfeature.bioentry_id = ?"); delete_fqv.setInt(1, bioentry_id); delete_fqv.executeUpdate(); } else { delete_fqv = conn.prepareStatement("delete from seqfeature_qualifier_value" + " where seqfeature_qualifier_value.seqfeature_id = ?"); for (int i = 0; i < generic_ids.size(); i++) { int sfid = ((Integer) generic_ids.get(i)).intValue(); delete_fqv.setInt(1, sfid); delete_fqv.executeUpdate(); } } delete_fqv.close(); PreparedStatement delete_rel; if (dstyle == DBHelper.DELETE_POSTGRESQL) { delete_rel = conn.prepareStatement("delete from seqfeature_relationship" + " where object_seqfeature_id = seqfeature.seqfeature_id" + " and seqfeature.bioentry_id = ?"); delete_rel.setInt(1, bioentry_id); delete_rel.executeUpdate(); } else { delete_rel = conn.prepareStatement("delete from seqfeature_relationship" + " where object_seqfeature_id = ?"); for (int i = 0; i < generic_ids.size(); i++) { int sfid = ((Integer) generic_ids.get(i)).intValue(); delete_rel.setInt(1, sfid); delete_rel.executeUpdate(); } } delete_rel.close(); PreparedStatement delete_features = conn.prepareStatement("delete from seqfeature " + " where bioentry_id = ?"); delete_features.setInt(1, bioentry_id); delete_features.executeUpdate(); delete_features.close(); PreparedStatement delete_biosequence = conn.prepareStatement("delete from biosequence where bioentry_id = ?"); delete_biosequence.setInt(1, bioentry_id); delete_biosequence.executeUpdate(); delete_biosequence.close(); } // End of if for non-MYSQL4 deletion // All DB types must delete the bioentry via its id // MySQL4 only needs to delete this, as it will cascade delete all references to it PreparedStatement delete_entry = conn.prepareStatement("delete from bioentry where bioentry_id = ?"); delete_entry.setInt(1, bioentry_id); int status = delete_entry.executeUpdate(); // Check that a deletion actually occurred, if not indicate so if (status < 1) { System.out.println("Bioentry (ID " + bioentry_id + ") failed to delete!!"); } delete_entry.close(); } rs.close(); get_sequence.close(); conn.commit(); conn.close(); if (!exists) { throw new IllegalIDException("Sequence " + id + " didn't exist"); } } catch (SQLException ex) { boolean rolledback = false; if (conn != null) { try { conn.rollback(); rolledback = true; } catch (SQLException ex2) {} try {conn.close();} catch (SQLException ex3) {} } throw new BioException("Error removing from BioSQL tables" + (rolledback ? " (rolled back successfully)" : ""), ex); } } public Set ids() { Connection conn = null; try { Set _ids = new HashSet(); conn = dataSource.getConnection(); PreparedStatement st = conn.prepareStatement("select bioentry.accession from bioentry where bioentry.biodatabase_id = ?"); st.setInt(1, dbid); ResultSet rs = st.executeQuery(); while (rs.next()) { _ids.add(rs.getString(1)); } rs.close(); st.close(); conn.close(); return Collections.unmodifiableSet(_ids); } catch (SQLException ex) { if (conn!=null) try {conn.close();} catch (SQLException ex3) {} throw new BioRuntimeException("Error reading from BioSQL tables", ex); } } // // Sequence support // void persistBioentryProperty(Connection conn, int bioentry_id, Object key, Object value, boolean removeFirst, boolean silent) throws SQLException { // Ought to check for special-case keys. (or just wait 'til the special case // tables get nuked :-) // ex. taxon, references, dbxrefs if (key.equals(OrganismParser.PROPERTY_ORGANISM)) { int taxon_id = TaxonSQL.putTaxon(conn, getDBHelper(), (Taxon) value); if (taxon_id != -1) { PreparedStatement set_taxon = conn.prepareStatement("update bioentry set taxon_id = ? " + " where bioentry_id = ?"); set_taxon.setInt(1, taxon_id); set_taxon.setInt(2, bioentry_id); set_taxon.executeUpdate(); set_taxon.close(); } } else { String keyString = key.toString(); if (!isBioentryPropertySupported()) { if (silent) { return; } else { throw new SQLException("Can't persist this property since the bioentry_qualifier_value table isn't available"); } } if (removeFirst) { int id = intern_ontology_term(conn, keyString); PreparedStatement remove_old_value = conn.prepareStatement("delete from bioentry_qualifier_value " + " where bioentry_id = ? and term_id = ?"); remove_old_value.setInt(1, bioentry_id); remove_old_value.setInt(2, id); remove_old_value.executeUpdate(); remove_old_value.close(); } if (value != null) { PreparedStatement insert_new; if (isSPASupported()) { insert_new = conn.prepareStatement("insert into bioentry_qualifier_value " + " (bioentry_id, term_id, value, rank) " + "values (?, intern_ontology_term( ? ), ?, ?)"); if (value instanceof Collection) { int cnt = 0; for (Iterator i = ((Collection) value).iterator(); i.hasNext(); ) { insert_new.setInt(1, bioentry_id); insert_new.setString(2, keyString); insert_new.setInt(4, ++cnt); insert_new.setString(3, i.next().toString()); insert_new.executeUpdate(); } } else { insert_new.setInt(1, bioentry_id); insert_new.setString(2, keyString); insert_new.setInt(3, 1); insert_new.setString(3, value.toString()); insert_new.executeUpdate(); } } else { insert_new = conn.prepareStatement("insert into bioentry_qualifier_value " + " (bioentry_id, term_id, rank, value) " + "values (?, ?, ?, ?)"); int termID = intern_ontology_term(conn, keyString); if (value instanceof Collection) { int cnt = 0; for (Iterator i = ((Collection) value).iterator(); i.hasNext(); ) { insert_new.setInt(1, bioentry_id); insert_new.setInt(2, termID); insert_new.setInt(3, ++cnt); insert_new.setString(4, i.next().toString()); insert_new.executeUpdate(); } } else { insert_new.setInt(1, bioentry_id); insert_new.setInt(2, termID); insert_new.setInt(3, 1); insert_new.setString(4, value.toString()); insert_new.executeUpdate(); } } insert_new.close(); } } } /** * Legacy method -- will go eventually */ int intern_ontology_term(Connection conn, String s) throws SQLException { Ontology legacy = ontologySQL.getLegacyOntology(); String ts = s.trim(); // Hack for schema change if (legacy.containsTerm(ts)) { return ontologySQL.termID(legacy.getTerm(ts)); // Same term but different case causes error when try to add it for MySQL // These hacks prevent it. ex. genbank can have ORGANISM & organism keys // Removed hack as if set term name to BINARY handles case correctly // } else if (legacy.containsTerm(ts.toLowerCase())) { // return ontologySQL.termID(legacy.getTerm(ts.toLowerCase())); // } else if (legacy.containsTerm(ts.toUpperCase())) { // return ontologySQL.termID(legacy.getTerm(ts.toUpperCase())); } else { try { return ontologySQL.termID(legacy.createTerm(ts, "")); } catch (Exception ex) { //ex.printStackTrace(); //System.err.println("Term: " + ts + " " + ex.getMessage()); throw (SQLException) new SQLException( "Couldn't create term '" + ts + "' for '" + s + "' in legacy ontology namespace" ).initCause(ex); } } } String getOntologyTerm(int termId) { return ontologySQL.termForID(termId).getName(); } private boolean hierarchyChecked = false; private boolean hierarchySupported = false; boolean isHierarchySupported() { if (!hierarchyChecked) { hierarchySupported = getDBHelper().containsTable(dataSource, "seqfeature_relationship"); hierarchyChecked = true; } return hierarchySupported; } private boolean assemblyChecked = false; private boolean assemblySupported = false; boolean isAssemblySupported() { if (!assemblyChecked) { assemblySupported = getDBHelper().containsTable(dataSource, "assembly"); assemblyChecked = true; } return assemblySupported; } private boolean bioentryPropertyChecked = false; private boolean bioentryPropertySupported = false; boolean isBioentryPropertySupported() { if (!bioentryPropertyChecked) { bioentryPropertySupported = getDBHelper().containsTable(dataSource, "bioentry_qualifier_value"); bioentryPropertyChecked = true; } return bioentryPropertySupported; } private boolean dbSchemaChecked = false; private boolean dbSchemaSupported = false; boolean isDbSchemaSupported() { if (!dbSchemaChecked) { dbSchemaSupported = getDBHelper().containsTable(dataSource, "location"); dbSchemaChecked = true; } return dbSchemaSupported; } private boolean commentTableNameChecked = false; private String commentTableName = null; // Get the name of the table used for comments. // "comment" isn't allowed in oracle (where comment is a reserved word) // Hilmar has said this table will be renamed to anncomment post-1.0 BioSQLe // We support both "comment" and "anncomment" String getCommentTableName() { if (!commentTableNameChecked) { if (getDBHelper().containsTable(dataSource, "comment")) { commentTableName = "comment"; } else if (getDBHelper().containsTable(dataSource, "anncomment")) { commentTableName = "anncomment"; } commentTableNameChecked = true; } return commentTableName; } private boolean spaChecked = false; private boolean spaSupported = false; boolean isSPASupported() { if (!spaChecked) { Connection conn = null; try { spaSupported = false; conn = dataSource.getConnection(); PreparedStatement ps = null; try { ps = conn.prepareStatement("select biosql_accelerators_level()"); ResultSet rs = ps.executeQuery(); if (rs.next()) { int level = rs.getInt(1); if (level >= 2) { spaSupported = true; // System.err.println("*** Accelerators present in the database: level " + level); } } rs.close(); } catch (SQLException ex) { } if (ps != null) { ps.close(); } conn.close(); spaChecked = true; } catch (SQLException ex) { if (conn!=null) try {conn.close();} catch (SQLException ex3) {} throw new BioRuntimeException(ex); } } return spaSupported; } private class SqlizedFilter { private List tables = new ArrayList(); private String filter; private int used_ot = 0; private int used_sfs = 0; //private int used_sqv = 0; SqlizedFilter(FeatureFilter ff) { filter = sqlizeFilter(ff, false); } private String sqlizeFilter(FeatureFilter ff, boolean negate) { if (ff instanceof FeatureFilter.ByType) { String type = ((FeatureFilter.ByType) ff).getType(); String tableName = "ot_" + (used_ot++); tables.add("term as " + tableName); return tableName + ".name " + eq(negate) + qw(type) + " and seqfeature.type_term_id = " + tableName + ".term_id"; } else if (ff instanceof FeatureFilter.BySource) { String source = ((FeatureFilter.BySource) ff).getSource(); String tableName = "sfs_" + (used_sfs++); tables.add("term as " + tableName); return tableName + ".name " + eq(negate) + qw(source) + " and seqfeature.source_term_id = " + tableName + ".term_id"; } else if (ff instanceof FeatureFilter.ByAnnotation) { return ""; /* FIXME disabled until Matthew works out what he's doing with AnnotationTypes FeatureFilter.ByAnnotation ffba = (FeatureFilter.ByAnnotation) ff; Object key = ffba.getKey(); Object value = ffba.getValue(); String keyString = key.toString(); String valueString = value.toString(); String otName = "ot_" + (used_ot++); tables.add("ontology_term as " + otName); String sqvName = "sqv_" + (used_sqv++); tables.add("seqfeature_qualifier_value as " + sqvName); */ // FIXME this doesn't actually do negate quite right -- it doesn't // match if the property isn't defined. Should do an outer join // to fix this. But for now, we'll just punt :-( /* if (negate) { return ""; } return sqvName + ".qualifier_value" + eq(negate) + qw(valueString) + " and " + sqvName + ".term_id = " + otName + ".term_id and " + otName + ".term_name = " + qw(keyString) + " and " + "seqfeature.seqfeature_id = " + sqvName + ".seqfeature_id"; */ } else if (ff instanceof FeatureFilter.And) { FeatureFilter.And and = (FeatureFilter.And) ff; FeatureFilter ff1 = and.getChild1(); FeatureFilter ff2 = and.getChild2(); String filter1 = sqlizeFilter(ff1, negate); String filter2 = sqlizeFilter(ff2, negate); if (filter1.length() > 0) { if (filter2.length() > 0) { return filter1 + " and " + filter2; } else { return filter1; } } else { if (filter2.length() > 0) { return filter2; } else { return ""; } } } else if (ff instanceof FeatureFilter.Not) { FeatureFilter child = ((FeatureFilter.Not) ff).getChild(); return sqlizeFilter(child, !negate); } else { return ""; } } private String eq(boolean negate) { if (negate) { return " <> "; } else { return "="; } } private String qw(String word) { return "'" + word + "'"; } public String getQuery() { StringBuffer query = new StringBuffer(); query.append("select bioentry.accession, seqfeature.seqfeature_id "); query.append(" from seqfeature, bioentry"); for (Iterator i = tables.iterator(); i.hasNext(); ) { query.append(", "); query.append((String) i.next()); } query.append(" where bioentry.bioentry_id = seqfeature.bioentry_id"); query.append(" and bioentry.biodatabase_id = ?"); if (filter.length() > 0) { query.append(" and "); query.append(filter); } query.append(" order by bioentry.accession"); return query.substring(0); } } private class FilterByInternalID implements FeatureFilter { private int id; public FilterByInternalID(int id) { this.id = id; } public boolean accept(Feature f) { if (! (f instanceof BioSQLFeature)) { return false; } int intID = ((BioSQLFeature) f)._getInternalID(); return (intID == id); } } public FeatureHolder filter(FeatureFilter ff) { Connection conn = null; try { SqlizedFilter sqf = new SqlizedFilter(ff); System.err.println("Doing BioSQL filter"); System.err.println(sqf.getQuery()); conn = dataSource.getConnection(); PreparedStatement get_features = conn.prepareStatement(sqf.getQuery()); get_features.setInt(1, dbid); ResultSet rs = get_features.executeQuery(); String lastAcc = ""; Sequence seq = null; SimpleFeatureHolder fh = new SimpleFeatureHolder(); while (rs.next()) { String accession = rs.getString(1); int fid = rs.getInt(2); System.err.println(accession + "\t" + fid); if (seq == null || ! lastAcc.equals(accession)) { seq = getSequence(accession); } FeatureHolder hereFeature = seq.filter(new FilterByInternalID(fid), true); Feature f = (Feature) hereFeature.features().next(); if (ff.accept(f)) { fh.addFeature(f); } } rs.close(); get_features.close(); conn.close(); return fh; } catch (SQLException ex) { if (conn!=null) try {conn.close();} catch (SQLException ex3) {} throw new BioRuntimeException("Error accessing BioSQL tables", ex); } catch (ChangeVetoException ex) { if (conn!=null) try {conn.close();} catch (SQLException ex3) {} throw new BioError("Assert failed: couldn't modify internal FeatureHolder", ex); } catch (BioException ex) { if (conn!=null) try {conn.close();} catch (SQLException ex3) {} throw new BioRuntimeException("Error fetching sequence", ex); } } // SequenceIterator here, 'cos AbstractSequenceDB grandfathers in AbstractChangable :-( public SequenceIterator sequenceIterator() { return new SequenceIterator() { private Iterator pID = ids().iterator(); public boolean hasNext() { return pID.hasNext(); } public Sequence nextSequence() throws BioException { return getSequence((String) pID.next()); } }; } // change support stuff void firePreChangeEvent(ChangeEvent cev) throws ChangeVetoException { getChangeSupport(cev.getType()).firePreChangeEvent(cev); } void firePostChangeEvent(ChangeEvent cev) { getChangeSupport(cev.getType()).firePostChangeEvent(cev); } // // Feature canonicalization // BioSQLFeature canonicalizeFeature(BioSQLFeature f, int feature_id) { // System.err.println("Canonicalizing feature at " + f.getLocation()); Integer key = new Integer(feature_id); BioSQLFeature oldFeature = (BioSQLFeature) featuresByID.get(key); if (oldFeature != null) { return oldFeature; } else { featuresByID.put(key, f); return f; } } private class SingleFeatureReceiver extends BioSQLFeatureReceiver { private Feature feature; private SingleFeatureReceiver() { super(BioSQLSequenceDB.this); } protected void deliverTopLevelFeature(Feature f) throws ParseException { if (feature == null) { feature = f; } else { throw new ParseException("Expecting only a single feature"); } } public Feature getFeature() { return feature; } } BioSQLFeature getFeatureByID(int feature_id) { Integer key = new Integer(feature_id); BioSQLFeature f = (BioSQLFeature) featuresByID.get(key); if (f != null) { return f; } try { SingleFeatureReceiver receiver = new SingleFeatureReceiver(); getFeaturesSQL().retrieveFeatures(-1, receiver, null, -1, feature_id); if (receiver.getFeature() == null) { throw new BioRuntimeException("Dangling internal_feature_id"); } else { featuresByID.put(key, (BioSQLFeature) receiver.getFeature()); return (BioSQLFeature) receiver.getFeature(); } } catch (SQLException ex) { throw new BioRuntimeException("Database error", ex); } catch (BioException ex) { throw new BioRuntimeException(ex); } } /* // // Dbxref canonicalization // BioSQLXRef canonicalizeXRef(BioSQLXRef r, int dbxref_id) { Integer key = new Integer(dbxref_id); BioSQLXRef oldXRef = (BioSQLXRef) XRefsByID.get(key); if (oldXRef != null) { return oldXRef; } else { featuresByID.put(key, r); return r; } } BioSQLXRef getXRefsByID(int dbxref_id) { Integer key = new Integer(dbxref_id); BioSQLFeature f = (BioSQLFeature) featuresByID.get(key); if (f != null) { return f; } try { SingleFeatureReceiver receiver = new SingleFeatureReceiver(); getFeaturesSQL().retrieveFeatures(-1, receiver, null, -1, feature_id); if (receiver.getFeature() == null) { throw new BioRuntimeException("Dangling internal_feature_id"); } else { featuresByID.put(key, (BioSQLFeature) receiver.getFeature()); return (BioSQLFeature) receiver.getFeature(); } } catch (SQLException ex) { throw new BioRuntimeException(ex, "Database error"); } catch (BioException ex) { throw new BioRuntimeException(ex); } } */ Cache getTileCache() { return tileCache; } }