fetch database references for all EBI db + selected das sequence sources or a specifi...
[jalview.git] / src / jalview / ws / DBRefFetcher.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)\r
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  * \r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  * \r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  * \r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.ws;\r
20 \r
21 import java.io.*;\r
22 import java.util.*;\r
23 \r
24 import org.biojava.dasobert.dasregistry.DasSource;\r
25 import org.exolab.castor.mapping.*;\r
26 import org.exolab.castor.xml.*;\r
27 import jalview.analysis.*;\r
28 import jalview.datamodel.*;\r
29 import jalview.datamodel.Mapping;\r
30 import jalview.gui.*;\r
31 import jalview.ws.dbsources.Uniprot;\r
32 import jalview.ws.ebi.EBIFetchClient;\r
33 \r
34 /**\r
35  * Implements a runnable for validating a sequence against external databases\r
36  * and then propagating references and features onto the sequence(s)\r
37  * \r
38  * @author $author$\r
39  * @version $Revision$\r
40  */\r
41 public class DBRefFetcher implements Runnable\r
42 {\r
43   SequenceI[] dataset;\r
44 \r
45   IProgressIndicator af;\r
46 \r
47   CutAndPasteTransfer output = new CutAndPasteTransfer();\r
48 \r
49   StringBuffer sbuffer = new StringBuffer();\r
50 \r
51   boolean running = false;\r
52 \r
53   // /This will be a collection of Vectors of sequenceI refs.\r
54   // The key will be the seq name or accession id of the seq\r
55   Hashtable seqRefs;\r
56 \r
57   String[] dbSources;\r
58 \r
59   SequenceFetcher sfetcher;\r
60 \r
61   public DBRefFetcher()\r
62   {\r
63   }\r
64 \r
65   /**\r
66    * Creates a new SequenceFeatureFetcher object and fetches from the\r
67    * currently selected set of databases.\r
68    * \r
69    * @param seqs\r
70    *                fetch references for these sequences\r
71    * @param af\r
72    *                the parent alignframe for progress bar monitoring.\r
73    */\r
74   public DBRefFetcher(SequenceI[] seqs, AlignFrame af)\r
75   {\r
76     this(seqs, af, null);\r
77   }\r
78   /**\r
79    * Creates a new SequenceFeatureFetcher object and fetches from the\r
80    * currently selected set of databases.\r
81    * \r
82    * @param seqs\r
83    *                fetch references for these sequences\r
84    * @param af\r
85    *                the parent alignframe for progress bar monitoring.\r
86    * @param sources array of database source strings to query references from\r
87    */\r
88   public DBRefFetcher(SequenceI[] seqs, AlignFrame af, String[] sources)\r
89   {\r
90     this.af = af;\r
91     SequenceI[] ds = new SequenceI[seqs.length];\r
92     for (int i = 0; i < seqs.length; i++)\r
93     {\r
94       if (seqs[i].getDatasetSequence() != null)\r
95         ds[i] = seqs[i].getDatasetSequence();\r
96       else\r
97         ds[i] = seqs[i];\r
98     }\r
99     this.dataset = ds;\r
100     // TODO Jalview 2.5 lots of this code should be in the gui package!\r
101     sfetcher = jalview.gui.SequenceFetcher.getSequenceFetcherSingleton(af);\r
102     if (sources==null)\r
103     {\r
104       // af.featureSettings_actionPerformed(null);\r
105       String[] defdb=null,otherdb = sfetcher.getDbInstances(jalview.ws.dbsources.DasSequenceSource.class);\r
106       Vector selsources = new Vector(), dasselsrc= (af.featureSettings!=null) ? af.featureSettings.getSelectedSources()\r
107               : new jalview.gui.DasSourceBrowser().getSelectedSources();\r
108       Enumeration en = dasselsrc.elements();\r
109       while (en.hasMoreElements())\r
110       {\r
111         DasSource src = (DasSource) en.nextElement();\r
112         selsources.addElement(src.getNickname());\r
113       }\r
114       int osel = 0;\r
115       for (int o=0;otherdb!=null && o<otherdb.length;o++)\r
116       {\r
117         if (!selsources.contains(otherdb[o]))\r
118         {\r
119           otherdb[o] = null;\r
120         } else {\r
121           osel++;\r
122         }\r
123       }\r
124       // select appropriate databases based on alignFrame context.\r
125       if (af.getViewport().getAlignment().isNucleotide())\r
126       {\r
127         defdb = DBRefSource.DNACODINGDBS;\r
128       }\r
129       else\r
130       {\r
131         defdb = DBRefSource.PROTEINDBS;\r
132       }\r
133       // append the selected sequence sources to the default dbs \r
134       dbSources = new String[defdb.length+osel];\r
135       System.arraycopy(defdb, 0, dbSources, 0, defdb.length);\r
136       for (int o=0,op=defdb.length; otherdb!=null && o<otherdb.length; o++)\r
137       {\r
138         if (otherdb[o]!=null)\r
139         {\r
140           dbSources[op++] = otherdb[o];\r
141         }\r
142       }\r
143     } else {\r
144       // we assume the caller knows what they're doing and ensured that all the db source names are valid\r
145       dbSources = sources;\r
146     }\r
147   }\r
148   /**\r
149    * retrieve all the das sequence sources and add them to the list of db sources to retrieve from \r
150    */\r
151   public void appendAllDasSources()\r
152   {\r
153     if (dbSources == null)\r
154     {\r
155       dbSources = new String[] {};\r
156     }\r
157     // append additional sources\r
158     String[] otherdb = sfetcher.getDbInstances(jalview.ws.dbsources.DasSequenceSource.class);\r
159     if (otherdb!=null && otherdb.length>0)\r
160     {\r
161       String[] newsrc = new String[dbSources.length+otherdb.length];\r
162       System.arraycopy(dbSources, 0, newsrc,0,dbSources.length);\r
163       System.arraycopy(otherdb, 0, newsrc,dbSources.length, otherdb.length);\r
164       dbSources = newsrc;\r
165     }\r
166   }\r
167   /**\r
168    * start the fetcher thread\r
169    * \r
170    * @param waitTillFinished\r
171    *                true to block until the fetcher has finished\r
172    */\r
173   public void fetchDBRefs(boolean waitTillFinished)\r
174   {\r
175     Thread thread = new Thread(this);\r
176     thread.start();\r
177     running = true;\r
178 \r
179     if (waitTillFinished)\r
180     {\r
181       while (running)\r
182       {\r
183         try\r
184         {\r
185           Thread.sleep(500);\r
186         } catch (Exception ex)\r
187         {\r
188         }\r
189       }\r
190     }\r
191   }\r
192 \r
193   /**\r
194    * The sequence will be added to a vector of sequences belonging to key which\r
195    * could be either seq name or dbref id\r
196    * \r
197    * @param seq\r
198    *                SequenceI\r
199    * @param key\r
200    *                String\r
201    */\r
202   void addSeqId(SequenceI seq, String key)\r
203   {\r
204     key = key.toUpperCase();\r
205 \r
206     Vector seqs;\r
207     if (seqRefs.containsKey(key))\r
208     {\r
209       seqs = (Vector) seqRefs.get(key);\r
210 \r
211       if (seqs != null && !seqs.contains(seq))\r
212       {\r
213         seqs.addElement(seq);\r
214       }\r
215       else if (seqs == null)\r
216       {\r
217         seqs = new Vector();\r
218         seqs.addElement(seq);\r
219       }\r
220 \r
221     }\r
222     else\r
223     {\r
224       seqs = new Vector();\r
225       seqs.addElement(seq);\r
226     }\r
227 \r
228     seqRefs.put(key, seqs);\r
229   }\r
230 \r
231   /**\r
232    * DOCUMENT ME!\r
233    */\r
234   public void run()\r
235   {\r
236     if (dbSources == null)\r
237     {\r
238       throw new Error("Implementation error. Must initialise dbSources");\r
239     }\r
240     long startTime = System.currentTimeMillis();\r
241     af.setProgressBar("Fetching db refs", startTime);\r
242     running = true;\r
243     int db = 0;\r
244     Vector sdataset = new Vector();\r
245     for (int s = 0; s < dataset.length; s++)\r
246     {\r
247       sdataset.addElement(dataset[s]);\r
248     }\r
249     while (sdataset.size() > 0 && db < dbSources.length)\r
250     {\r
251       int maxqlen = 1; // default number of queries made to at one time\r
252       System.err.println("Verifying against " + dbSources[db]);\r
253       jalview.ws.seqfetcher.DbSourceProxy dbsource = sfetcher\r
254               .getSourceProxy(dbSources[db]);\r
255       if (dbsource == null)\r
256       {\r
257         System.err.println("No proxy for " + dbSources[db]);\r
258         db++;\r
259         continue;\r
260       }\r
261       if (dbsource.getDbSourceProperties()\r
262               .containsKey(DBRefSource.MULTIACC))\r
263       {\r
264         maxqlen = ((Integer) dbsource.getDbSourceProperties().get(\r
265                 DBRefSource.MULTIACC)).intValue();\r
266       }\r
267       else\r
268       {\r
269         maxqlen = 1;\r
270       }\r
271       // iterate through db for each remaining un-verified sequence\r
272       SequenceI[] currSeqs = new SequenceI[sdataset.size()];\r
273       sdataset.copyInto(currSeqs);// seqs that are to be validated against\r
274       // dbSources[db]\r
275       Vector queries = new Vector(); // generated queries curSeq\r
276       seqRefs = new Hashtable();\r
277 \r
278       int seqIndex = 0;\r
279 \r
280       while (queries.size() > 0 || seqIndex < currSeqs.length)\r
281       {\r
282         if (queries.size() > 0)\r
283         {\r
284           // Still queries to make for current seqIndex\r
285           StringBuffer queryString = new StringBuffer("");\r
286           int numq=0,nqSize = (maxqlen > queries.size()) ? queries.size()\r
287                   : maxqlen;\r
288           \r
289           while (queries.size()>0 && numq < nqSize)\r
290           {\r
291             String query = (String) queries.elementAt(0);\r
292             if (dbsource.isValidReference(query))\r
293             {\r
294               queryString.append((numq == 0) ? "" : dbsource\r
295                       .getAccessionSeparator());\r
296               queryString.append(query);\r
297               numq++;\r
298             }\r
299             // remove the extracted query string\r
300             queries.removeElementAt(0);\r
301           }\r
302           // make the queries and process the response\r
303           AlignmentI retrieved = null;\r
304           try\r
305           {\r
306             if (jalview.bin.Cache.log.isDebugEnabled())\r
307             {\r
308               jalview.bin.Cache.log.debug("Querying "+dbsource.getDbName()+" with : '"+queryString.toString()+"'");\r
309             }\r
310             retrieved = dbsource.getSequenceRecords(queryString.toString());\r
311           } catch (Exception ex)\r
312           {\r
313             ex.printStackTrace();\r
314           } catch (OutOfMemoryError err)\r
315           {\r
316             new OOMWarning("retrieving database references ("\r
317                     + queryString.toString() + ")", err);\r
318           }\r
319           if (retrieved != null)\r
320           {\r
321             transferReferences(sdataset, dbSources[db], retrieved);\r
322           }\r
323         }\r
324         else\r
325         {\r
326           // make some more strings for use as queries\r
327           for (int i = 0; (seqIndex < dataset.length) && (i < 50); seqIndex++, i++)\r
328           {\r
329             SequenceI sequence = dataset[seqIndex];\r
330             DBRefEntry[] uprefs = jalview.util.DBRefUtils.selectRefs(\r
331                     sequence.getDBRef(), new String[]\r
332                     { dbSources[db] }); // jalview.datamodel.DBRefSource.UNIPROT\r
333             // });\r
334             // check for existing dbrefs to use\r
335             if (uprefs != null && uprefs.length>0)\r
336             {\r
337               for (int j = 0; j < uprefs.length; j++)\r
338               {\r
339                 addSeqId(sequence, uprefs[j].getAccessionId());\r
340                 queries\r
341                         .addElement(uprefs[j].getAccessionId()\r
342                                 .toUpperCase());\r
343               }\r
344             }\r
345             else\r
346             {\r
347               // generate queries from sequence ID string\r
348               StringTokenizer st = new StringTokenizer(sequence.getName(),\r
349                       "|");\r
350               while (st.hasMoreTokens())\r
351               {\r
352                 String token = st.nextToken();\r
353                 addSeqId(sequence, token);\r
354                 queries.addElement(token.toUpperCase());\r
355               }\r
356             }\r
357           }\r
358         }\r
359       }\r
360       // advance to next database\r
361       db++;\r
362     } // all databases have been queries.\r
363     if (sbuffer.length() > 0)\r
364     {\r
365       output\r
366               .setText("Your sequences have been verified against known sequence databases. Some of the ids have been\n"\r
367                       + "altered, most likely the start/end residue will have been updated.\n"\r
368                       + "Save your alignment to maintain the updated id.\n\n"\r
369                       + sbuffer.toString());\r
370       Desktop.addInternalFrame(output, "Sequence names updated ", 600, 300);\r
371       // The above is the dataset, we must now find out the index\r
372       // of the viewed sequence\r
373 \r
374     }\r
375 \r
376     af.setProgressBar("DBRef search completed", startTime);\r
377     // promptBeforeBlast();\r
378 \r
379     running = false;\r
380 \r
381   }\r
382 \r
383   /**\r
384    * Verify local sequences in seqRefs against the retrieved sequence database\r
385    * records.\r
386    * \r
387    */\r
388   void transferReferences(Vector sdataset, String dbSource,\r
389           AlignmentI retrievedAl) // File\r
390   // file)\r
391   {\r
392 \r
393     if (retrievedAl == null || retrievedAl.getHeight() == 0)\r
394     {\r
395       return;\r
396     }\r
397     SequenceI[] retrieved = retrievedAl.getSequencesArray();\r
398     SequenceI sequence = null;\r
399 \r
400     // Vector entries = new Uniprot().getUniprotEntries(file);\r
401 \r
402     int i, iSize = retrieved.length; // entries == null ? 0 : entries.size();\r
403     // UniprotEntry entry;\r
404     for (i = 0; i < iSize; i++)\r
405     {\r
406       SequenceI entry = retrieved[i]; // (UniprotEntry) entries.elementAt(i);\r
407 \r
408       // Work out which sequences this sequence matches,\r
409       // taking into account all accessionIds and names in the file\r
410       Vector sequenceMatches = new Vector();\r
411       // look for corresponding accession ids\r
412       DBRefEntry[] entryRefs = jalview.util.DBRefUtils.selectRefs(entry\r
413               .getDBRef(), new String[]\r
414       { dbSource });\r
415       for (int j = 0; j < entryRefs.length; j++)\r
416       {\r
417         String accessionId = entryRefs[j].getAccessionId(); // .getAccession().elementAt(j).toString();\r
418         // match up on accessionId\r
419         if (seqRefs.containsKey(accessionId.toUpperCase()))\r
420         {\r
421           Vector seqs = (Vector) seqRefs.get(accessionId);\r
422           for (int jj = 0; jj < seqs.size(); jj++)\r
423           {\r
424             sequence = (SequenceI) seqs.elementAt(jj);\r
425             if (!sequenceMatches.contains(sequence))\r
426             {\r
427               sequenceMatches.addElement(sequence);\r
428             }\r
429           }\r
430         }\r
431       }\r
432       if (sequenceMatches.size() == 0)\r
433       {\r
434         // failed to match directly on accessionId==query so just compare all\r
435         // sequences to entry\r
436         Enumeration e = seqRefs.keys();\r
437         while (e.hasMoreElements())\r
438         {\r
439           Vector sqs = (Vector) seqRefs.get(e.nextElement());\r
440           if (sqs != null && sqs.size() > 0)\r
441           {\r
442             Enumeration sqe = sqs.elements();\r
443             while (sqe.hasMoreElements())\r
444             {\r
445               sequenceMatches.addElement(sqe.nextElement());\r
446             }\r
447           }\r
448         }\r
449       }\r
450       // look for corresponding names\r
451       // this is uniprot specific ?\r
452       // could be useful to extend this so we try to find any 'significant'\r
453       // information in common between two sequence objects.\r
454       /*\r
455        * DBRefEntry[] entryRefs =\r
456        * jalview.util.DBRefUtils.selectRefs(entry.getDBRef(), new String[] {\r
457        * dbSource }); for (int j = 0; j < entry.getName().size(); j++) { String\r
458        * name = entry.getName().elementAt(j).toString(); if\r
459        * (seqRefs.containsKey(name)) { Vector seqs = (Vector) seqRefs.get(name);\r
460        * for (int jj = 0; jj < seqs.size(); jj++) { sequence = (SequenceI)\r
461        * seqs.elementAt(jj); if (!sequenceMatches.contains(sequence)) {\r
462        * sequenceMatches.addElement(sequence); } } } }\r
463        */\r
464       // sequenceMatches now contains the set of all sequences associated with\r
465       // the returned db record\r
466       String entrySeq = entry.getSequenceAsString().toUpperCase();\r
467       for (int m = 0; m < sequenceMatches.size(); m++)\r
468       {\r
469         sequence = (SequenceI) sequenceMatches.elementAt(m);\r
470         // only update start and end positions and shift features if there are\r
471         // no existing references\r
472         // TODO: test for legacy where uniprot or EMBL refs exist but no\r
473         // mappings are made (but content matches retrieved set)\r
474         boolean updateRefFrame = sequence.getDBRef() == null\r
475                 || sequence.getDBRef().length == 0;\r
476         // verify sequence against the entry sequence\r
477 \r
478         String nonGapped = AlignSeq.extractGaps("-. ",\r
479                 sequence.getSequenceAsString()).toUpperCase();\r
480 \r
481         int absStart = entrySeq.indexOf(nonGapped);\r
482         int mapStart = entry.getStart();\r
483         jalview.datamodel.Mapping mp;\r
484 \r
485         if (absStart == -1)\r
486         {\r
487           // Is local sequence contained in dataset sequence?\r
488           absStart = nonGapped.indexOf(entrySeq);\r
489           if (absStart == -1)\r
490           { // verification failed.\r
491             sbuffer.append(sequence.getName()\r
492                     + " SEQUENCE NOT %100 MATCH \n");\r
493             continue;\r
494           }\r
495 \r
496           sbuffer.append(sequence.getName() + " HAS " + absStart\r
497                   + " PREFIXED RESIDUES COMPARED TO " + dbSource + "\n");\r
498           //\r
499           // + " - ANY SEQUENCE FEATURES"\r
500           // + " HAVE BEEN ADJUSTED ACCORDINGLY \n");\r
501           // absStart = 0;\r
502           // create valid mapping between matching region of local sequence and\r
503           // the mapped sequence\r
504           mp = new Mapping(null, new int[]\r
505           { sequence.getStart() + absStart,\r
506               sequence.getStart() + absStart + entrySeq.length() - 1 },\r
507                   new int[]\r
508                   { entry.getStart(),\r
509                       entry.getStart() + entrySeq.length() - 1 }, 1, 1);\r
510           updateRefFrame = false; // mapping is based on current start/end so\r
511           // don't modify start and end\r
512         }\r
513         else\r
514         {\r
515           // update start and end of local sequence to place it in entry's\r
516           // reference frame.\r
517           // apply identity map map from whole of local sequence to matching\r
518           // region of database\r
519           // sequence\r
520           mp = null; // Mapping.getIdentityMap();\r
521           // new Mapping(null,\r
522           // new int[] { absStart+sequence.getStart(),\r
523           // absStart+sequence.getStart()+entrySeq.length()-1},\r
524           // new int[] { entry.getStart(), entry.getEnd() }, 1, 1);\r
525           // relocate local features for updated start\r
526           if (updateRefFrame && sequence.getSequenceFeatures() != null)\r
527           {\r
528             SequenceFeature[] sf = sequence.getSequenceFeatures();\r
529             int start = sequence.getStart();\r
530             int end = sequence.getEnd();\r
531             int startShift = 1 - absStart - start; // how much the features are\r
532             // to be shifted by\r
533             for (int sfi = 0; sfi < sf.length; sfi++)\r
534             {\r
535               if (sf[sfi].getBegin() >= start && sf[sfi].getEnd() <= end)\r
536               {\r
537                 // shift feature along by absstart\r
538                 sf[sfi].setBegin(sf[sfi].getBegin() + startShift);\r
539                 sf[sfi].setEnd(sf[sfi].getEnd() + startShift);\r
540               }\r
541             }\r
542           }\r
543         }\r
544 \r
545         System.out.println("Adding dbrefs to " + sequence.getName()\r
546                 + " from " + dbSource + " sequence : " + entry.getName());\r
547         sequence.transferAnnotation(entry, mp);\r
548         // unknownSequences.remove(sequence);\r
549         int absEnd = absStart + nonGapped.length();\r
550         absStart += 1;\r
551         if (updateRefFrame)\r
552         {\r
553           // finally, update local sequence reference frame if we're allowed\r
554           sequence.setStart(absStart);\r
555           sequence.setEnd(absEnd);\r
556         }\r
557         // and remove it from the rest\r
558         // TODO: decide if we should remove annotated sequence from set\r
559         sdataset.remove(sequence);\r
560       }\r
561     }\r
562   }\r
563 }\r