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}