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