17c1bc9a68b94ca698a2392abeee79da3531d4f6
[jalview.git] / src / jalview / ws / DBRefFetcher.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ws;
19
20 import jalview.analysis.AlignSeq;
21 import jalview.bin.Cache;
22 import jalview.datamodel.AlignmentI;
23 import jalview.datamodel.DBRefEntry;
24 import jalview.datamodel.DBRefSource;
25 import jalview.datamodel.Mapping;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.AlignFrame;
29 import jalview.gui.CutAndPasteTransfer;
30 import jalview.gui.Desktop;
31 import jalview.gui.IProgressIndicator;
32 import jalview.gui.OOMWarning;
33 import jalview.ws.dbsources.das.api.jalviewSourceI;
34 import jalview.ws.seqfetcher.DbSourceProxy;
35
36 import java.util.ArrayList;
37 import java.util.Enumeration;
38 import java.util.Hashtable;
39 import java.util.List;
40 import java.util.StringTokenizer;
41 import java.util.Vector;
42
43 import uk.ac.ebi.picr.model.UPEntry;
44
45 /**
46  * Implements a runnable for validating a sequence against external databases
47  * and then propagating references and features onto the sequence(s)
48  * 
49  * @author $author$
50  * @version $Revision$
51  */
52 public class DBRefFetcher implements Runnable
53 {
54   SequenceI[] dataset;
55
56   IProgressIndicator af;
57
58   CutAndPasteTransfer output = new CutAndPasteTransfer();
59
60   StringBuffer sbuffer = new StringBuffer();
61
62   boolean running = false;
63
64   /**
65    * picr client instance
66    */
67   uk.ac.ebi.www.picr.AccessionMappingService.AccessionMapperInterface picrClient = null;
68
69   // /This will be a collection of Vectors of sequenceI refs.
70   // The key will be the seq name or accession id of the seq
71   Hashtable seqRefs;
72
73   DbSourceProxy[] dbSources;
74
75   SequenceFetcher sfetcher;
76
77   private SequenceI[] alseqs;
78
79   public DBRefFetcher()
80   {
81   }
82
83   /**
84    * Creates a new SequenceFeatureFetcher object and fetches from the currently
85    * selected set of databases.
86    * 
87    * @param seqs
88    *          fetch references for these sequences
89    * @param af
90    *          the parent alignframe for progress bar monitoring.
91    */
92   public DBRefFetcher(SequenceI[] seqs, AlignFrame af)
93   {
94     this(seqs, af, null);
95   }
96
97   /**
98    * Creates a new SequenceFeatureFetcher object and fetches from the currently
99    * selected set of databases.
100    * 
101    * @param seqs
102    *          fetch references for these sequences
103    * @param af
104    *          the parent alignframe for progress bar monitoring.
105    * @param sources
106    *          array of database source strings to query references from
107    */
108   public DBRefFetcher(SequenceI[] seqs, AlignFrame af,
109           DbSourceProxy[] sources)
110   {
111     this.af = af;
112     alseqs = new SequenceI[seqs.length];
113     SequenceI[] ds = new SequenceI[seqs.length];
114     for (int i = 0; i < seqs.length; i++)
115     {
116       alseqs[i] = seqs[i];
117       if (seqs[i].getDatasetSequence() != null)
118         ds[i] = seqs[i].getDatasetSequence();
119       else
120         ds[i] = seqs[i];
121     }
122     this.dataset = ds;
123     // TODO Jalview 2.5 lots of this code should be in the gui package!
124     sfetcher = jalview.gui.SequenceFetcher.getSequenceFetcherSingleton(af);
125     if (sources == null)
126     {
127       // af.featureSettings_actionPerformed(null);
128       String[] defdb = null, otherdb = sfetcher
129               .getDbInstances(jalview.ws.dbsources.das.datamodel.DasSequenceSource.class);
130       List<DbSourceProxy> selsources = new ArrayList<DbSourceProxy>();
131       Vector dasselsrc = (af.featureSettings != null) ? af.featureSettings
132               .getSelectedSources() : new jalview.gui.DasSourceBrowser()
133               .getSelectedSources();
134       Enumeration<jalviewSourceI> en = dasselsrc.elements();
135       while (en.hasMoreElements())
136       {
137         jalviewSourceI src = en.nextElement();
138         List<DbSourceProxy> sp = src.getSequenceSourceProxies();
139         if (sp != null)
140         {
141           selsources.addAll(sp);
142           if (sp.size() > 1)
143           {
144             Cache.log.debug("Added many Db Sources for :" + src.getTitle());
145           }
146         }
147       }
148       // select appropriate databases based on alignFrame context.
149       if (af.getViewport().getAlignment().isNucleotide())
150       {
151         defdb = DBRefSource.DNACODINGDBS;
152       }
153       else
154       {
155         defdb = DBRefSource.PROTEINDBS;
156       }
157       List<DbSourceProxy> srces = new ArrayList<DbSourceProxy>();
158       for (String ddb : defdb)
159       {
160         List<DbSourceProxy> srcesfordb = sfetcher.getSourceProxy(ddb);
161         if (srcesfordb != null)
162         {
163           srces.addAll(srcesfordb);
164         }
165       }
166
167       // append the selected sequence sources to the default dbs
168       srces.addAll(selsources);
169       dbSources = srces.toArray(new DbSourceProxy[0]);
170     }
171     else
172     {
173       // we assume the caller knows what they're doing and ensured that all the
174       // db source names are valid
175       dbSources = sources;
176     }
177   }
178
179   /**
180    * retrieve all the das sequence sources and add them to the list of db
181    * sources to retrieve from
182    */
183   public void appendAllDasSources()
184   {
185     if (dbSources == null)
186     {
187       dbSources = new DbSourceProxy[0];
188     }
189     // append additional sources
190     DbSourceProxy[] otherdb = sfetcher
191             .getDbSourceProxyInstances(jalview.ws.dbsources.das.datamodel.DasSequenceSource.class);
192     if (otherdb != null && otherdb.length > 0)
193     {
194       DbSourceProxy[] newsrc = new DbSourceProxy[dbSources.length
195               + otherdb.length];
196       System.arraycopy(dbSources, 0, newsrc, 0, dbSources.length);
197       System.arraycopy(otherdb, 0, newsrc, dbSources.length, otherdb.length);
198       dbSources = newsrc;
199     }
200   }
201
202   /**
203    * start the fetcher thread
204    * 
205    * @param waitTillFinished
206    *          true to block until the fetcher has finished
207    */
208   public void fetchDBRefs(boolean waitTillFinished)
209   {
210     Thread thread = new Thread(this);
211     thread.start();
212     running = true;
213
214     if (waitTillFinished)
215     {
216       while (running)
217       {
218         try
219         {
220           Thread.sleep(500);
221         } catch (Exception ex)
222         {
223         }
224       }
225     }
226   }
227
228   /**
229    * The sequence will be added to a vector of sequences belonging to key which
230    * could be either seq name or dbref id
231    * 
232    * @param seq
233    *          SequenceI
234    * @param key
235    *          String
236    */
237   void addSeqId(SequenceI seq, String key)
238   {
239     key = key.toUpperCase();
240
241     Vector seqs;
242     if (seqRefs.containsKey(key))
243     {
244       seqs = (Vector) seqRefs.get(key);
245
246       if (seqs != null && !seqs.contains(seq))
247       {
248         seqs.addElement(seq);
249       }
250       else if (seqs == null)
251       {
252         seqs = new Vector();
253         seqs.addElement(seq);
254       }
255
256     }
257     else
258     {
259       seqs = new Vector();
260       seqs.addElement(seq);
261     }
262
263     seqRefs.put(key, seqs);
264   }
265
266   /**
267    * DOCUMENT ME!
268    */
269   public void run()
270   {
271     if (dbSources == null)
272     {
273       throw new Error("Implementation error. Must initialise dbSources");
274     }
275     running = true;
276     long startTime = System.currentTimeMillis();
277     af.setProgressBar("Fetching db refs", startTime);
278     try
279     {
280       if (Cache.getDefault("DBREFFETCH_USEPICR", false))
281       {
282         picrClient = new uk.ac.ebi.www.picr.AccessionMappingService.AccessionMapperServiceLocator()
283                 .getAccessionMapperPort();
284       }
285     } catch (Exception e)
286     {
287       System.err.println("Couldn't locate PICR service instance.\n");
288       e.printStackTrace();
289     }
290     int db = 0;
291     Vector sdataset = new Vector();
292     for (int s = 0; s < dataset.length; s++)
293     {
294       sdataset.addElement(dataset[s]);
295     }
296     while (sdataset.size() > 0 && db < dbSources.length)
297     {
298       int maxqlen = 1; // default number of queries made to at one time
299       System.err.println("Verifying against " + dbSources[db].getDbName());
300       boolean dn = false;
301
302       // iterate through db for each remaining un-verified sequence
303       SequenceI[] currSeqs = new SequenceI[sdataset.size()];
304       sdataset.copyInto(currSeqs);// seqs that are to be validated against
305       // dbSources[db]
306       Vector queries = new Vector(); // generated queries curSeq
307       seqRefs = new Hashtable();
308
309       int seqIndex = 0;
310
311       jalview.ws.seqfetcher.DbSourceProxy dbsource = dbSources[db];
312       {
313         // for moment, we dumbly iterate over all retrieval sources for a
314         // particular database
315         // TODO: introduce multithread multisource queries and logic to remove a
316         // query from other sources if any source for a database returns a
317         // record
318         if (dbsource.getDbSourceProperties().containsKey(
319                 DBRefSource.MULTIACC))
320         {
321           maxqlen = ((Integer) dbsource.getDbSourceProperties().get(
322                   DBRefSource.MULTIACC)).intValue();
323         }
324         else
325         {
326           maxqlen = 1;
327         }
328         while (queries.size() > 0 || seqIndex < currSeqs.length)
329         {
330           if (queries.size() > 0)
331           {
332             // Still queries to make for current seqIndex
333             StringBuffer queryString = new StringBuffer("");
334             int numq = 0, nqSize = (maxqlen > queries.size()) ? queries
335                     .size() : maxqlen;
336
337             while (queries.size() > 0 && numq < nqSize)
338             {
339               String query = (String) queries.elementAt(0);
340               if (dbsource.isValidReference(query))
341               {
342                 queryString.append((numq == 0) ? "" : dbsource
343                         .getAccessionSeparator());
344                 queryString.append(query);
345                 numq++;
346               }
347               // remove the extracted query string
348               queries.removeElementAt(0);
349             }
350             // make the queries and process the response
351             AlignmentI retrieved = null;
352             try
353             {
354               if (jalview.bin.Cache.log.isDebugEnabled())
355               {
356                 jalview.bin.Cache.log.debug("Querying "
357                         + dbsource.getDbName() + " with : '"
358                         + queryString.toString() + "'");
359               }
360               retrieved = dbsource.getSequenceRecords(queryString
361                       .toString());
362             } catch (Exception ex)
363             {
364               ex.printStackTrace();
365             } catch (OutOfMemoryError err)
366             {
367               new OOMWarning("retrieving database references ("
368                       + queryString.toString() + ")", err);
369             }
370             if (retrieved != null)
371             {
372               transferReferences(sdataset, dbsource.getDbSource(),
373                       retrieved);
374             }
375           }
376           else
377           {
378             // make some more strings for use as queries
379             for (int i = 0; (seqIndex < dataset.length) && (i < 50); seqIndex++, i++)
380             {
381               SequenceI sequence = dataset[seqIndex];
382               DBRefEntry[] uprefs = jalview.util.DBRefUtils.selectRefs(
383                       sequence.getDBRef(), new String[]
384                       { dbsource.getDbSource() }); // jalview.datamodel.DBRefSource.UNIPROT
385               // });
386               // check for existing dbrefs to use
387               if (uprefs != null && uprefs.length > 0)
388               {
389                 for (int j = 0; j < uprefs.length; j++)
390                 {
391                   addSeqId(sequence, uprefs[j].getAccessionId());
392                   queries.addElement(uprefs[j].getAccessionId()
393                           .toUpperCase());
394                 }
395               }
396               else
397               {
398                 // generate queries from sequence ID string
399                 StringTokenizer st = new StringTokenizer(
400                         sequence.getName(), "|");
401                 while (st.hasMoreTokens())
402                 {
403                   String token = st.nextToken();
404                   UPEntry[] presp = null;
405                   if (picrClient != null)
406                   {
407                     // resolve the string against PICR to recover valid IDs
408                     try
409                     {
410                       presp = picrClient.getUPIForAccession(token, null,
411                               picrClient.getMappedDatabaseNames(), null,
412                               true);
413                     } catch (Exception e)
414                     {
415                       System.err.println("Exception with Picr for '"
416                               + token + "'\n");
417                       e.printStackTrace();
418                     }
419                   }
420                   if (presp != null && presp.length > 0)
421                   {
422                     for (int id = 0; id < presp.length; id++)
423                     {
424                       // construct sequences from response if sequences are
425                       // present, and do a transferReferences
426                       // otherwise transfer non sequence x-references directly.
427                     }
428                     System.out
429                             .println("Validated ID against PICR... (for what its worth):"
430                                     + token);
431                     addSeqId(sequence, token);
432                     queries.addElement(token.toUpperCase());
433                   }
434                   else
435                   {
436                     // if ()
437                     // System.out.println("Not querying source with token="+token+"\n");
438                     addSeqId(sequence, token);
439                     queries.addElement(token.toUpperCase());
440                   }
441                 }
442               }
443             }
444           }
445         }
446       }
447       // advance to next database
448       db++;
449     } // all databases have been queries.
450     if (sbuffer.length() > 0)
451     {
452       output.setText("Your sequences have been verified against known sequence databases. Some of the ids have been\n"
453               + "altered, most likely the start/end residue will have been updated.\n"
454               + "Save your alignment to maintain the updated id.\n\n"
455               + sbuffer.toString());
456       Desktop.addInternalFrame(output, "Sequence names updated ", 600, 300);
457       // The above is the dataset, we must now find out the index
458       // of the viewed sequence
459
460     }
461
462     af.setProgressBar("DBRef search completed", startTime);
463     // promptBeforeBlast();
464
465     running = false;
466
467   }
468
469   /**
470    * Verify local sequences in seqRefs against the retrieved sequence database
471    * records.
472    * 
473    */
474   void transferReferences(Vector sdataset, String dbSource,
475           AlignmentI retrievedAl) // File
476   // file)
477   {
478     if (retrievedAl == null || retrievedAl.getHeight() == 0)
479     {
480       return;
481     }
482     SequenceI[] retrieved = recoverDbSequences(retrievedAl
483             .getSequencesArray());
484     SequenceI sequence = null;
485     boolean transferred = false;
486     StringBuffer messages = new StringBuffer();
487
488     // Vector entries = new Uniprot().getUniprotEntries(file);
489
490     int i, iSize = retrieved.length; // entries == null ? 0 : entries.size();
491     // UniprotEntry entry;
492     for (i = 0; i < iSize; i++)
493     {
494       SequenceI entry = retrieved[i]; // (UniprotEntry) entries.elementAt(i);
495
496       // Work out which sequences this sequence matches,
497       // taking into account all accessionIds and names in the file
498       Vector sequenceMatches = new Vector();
499       // look for corresponding accession ids
500       DBRefEntry[] entryRefs = jalview.util.DBRefUtils.selectRefs(
501               entry.getDBRef(), new String[]
502               { dbSource });
503       if (entryRefs == null)
504       {
505         System.err
506                 .println("Dud dbSource string ? no entryrefs selected for "
507                         + dbSource + " on " + entry.getName());
508         continue;
509       }
510       for (int j = 0; j < entryRefs.length; j++)
511       {
512         String accessionId = entryRefs[j].getAccessionId(); // .getAccession().elementAt(j).toString();
513         // match up on accessionId
514         if (seqRefs.containsKey(accessionId.toUpperCase()))
515         {
516           Vector seqs = (Vector) seqRefs.get(accessionId);
517           for (int jj = 0; jj < seqs.size(); jj++)
518           {
519             sequence = (SequenceI) seqs.elementAt(jj);
520             if (!sequenceMatches.contains(sequence))
521             {
522               sequenceMatches.addElement(sequence);
523             }
524           }
525         }
526       }
527       if (sequenceMatches.size() == 0)
528       {
529         // failed to match directly on accessionId==query so just compare all
530         // sequences to entry
531         Enumeration e = seqRefs.keys();
532         while (e.hasMoreElements())
533         {
534           Vector sqs = (Vector) seqRefs.get(e.nextElement());
535           if (sqs != null && sqs.size() > 0)
536           {
537             Enumeration sqe = sqs.elements();
538             while (sqe.hasMoreElements())
539             {
540               sequenceMatches.addElement(sqe.nextElement());
541             }
542           }
543         }
544       }
545       // look for corresponding names
546       // this is uniprot specific ?
547       // could be useful to extend this so we try to find any 'significant'
548       // information in common between two sequence objects.
549       /*
550        * DBRefEntry[] entryRefs =
551        * jalview.util.DBRefUtils.selectRefs(entry.getDBRef(), new String[] {
552        * dbSource }); for (int j = 0; j < entry.getName().size(); j++) { String
553        * name = entry.getName().elementAt(j).toString(); if
554        * (seqRefs.containsKey(name)) { Vector seqs = (Vector) seqRefs.get(name);
555        * for (int jj = 0; jj < seqs.size(); jj++) { sequence = (SequenceI)
556        * seqs.elementAt(jj); if (!sequenceMatches.contains(sequence)) {
557        * sequenceMatches.addElement(sequence); } } } }
558        */
559       // sequenceMatches now contains the set of all sequences associated with
560       // the returned db record
561       String entrySeq = entry.getSequenceAsString().toUpperCase();
562       for (int m = 0; m < sequenceMatches.size(); m++)
563       {
564         sequence = (SequenceI) sequenceMatches.elementAt(m);
565         // only update start and end positions and shift features if there are
566         // no existing references
567         // TODO: test for legacy where uniprot or EMBL refs exist but no
568         // mappings are made (but content matches retrieved set)
569         boolean updateRefFrame = sequence.getDBRef() == null
570                 || sequence.getDBRef().length == 0;
571         // verify sequence against the entry sequence
572
573         String nonGapped = AlignSeq.extractGaps("-. ",
574                 sequence.getSequenceAsString()).toUpperCase();
575
576         int absStart = entrySeq.indexOf(nonGapped);
577         int mapStart = entry.getStart();
578         jalview.datamodel.Mapping mp;
579
580         if (absStart == -1)
581         {
582           // Is local sequence contained in dataset sequence?
583           absStart = nonGapped.indexOf(entrySeq);
584           if (absStart == -1)
585           { // verification failed.
586             messages.append(sequence.getName()
587                     + " SEQUENCE NOT %100 MATCH \n");
588             continue;
589           }
590           transferred = true;
591           sbuffer.append(sequence.getName() + " HAS " + absStart
592                   + " PREFIXED RESIDUES COMPARED TO " + dbSource + "\n");
593           //
594           // + " - ANY SEQUENCE FEATURES"
595           // + " HAVE BEEN ADJUSTED ACCORDINGLY \n");
596           // absStart = 0;
597           // create valid mapping between matching region of local sequence and
598           // the mapped sequence
599           mp = new Mapping(null, new int[]
600           { sequence.getStart() + absStart,
601               sequence.getStart() + absStart + entrySeq.length() - 1 },
602                   new int[]
603                   { entry.getStart(),
604                       entry.getStart() + entrySeq.length() - 1 }, 1, 1);
605           updateRefFrame = false; // mapping is based on current start/end so
606           // don't modify start and end
607         }
608         else
609         {
610           transferred = true;
611           // update start and end of local sequence to place it in entry's
612           // reference frame.
613           // apply identity map map from whole of local sequence to matching
614           // region of database
615           // sequence
616           mp = null; // Mapping.getIdentityMap();
617           // new Mapping(null,
618           // new int[] { absStart+sequence.getStart(),
619           // absStart+sequence.getStart()+entrySeq.length()-1},
620           // new int[] { entry.getStart(), entry.getEnd() }, 1, 1);
621           // relocate local features for updated start
622           if (updateRefFrame)
623           {
624             if (sequence.getSequenceFeatures() != null)
625             {
626               SequenceFeature[] sf = sequence.getSequenceFeatures();
627               int start = sequence.getStart();
628               int end = sequence.getEnd();
629               int startShift = 1 - absStart - start; // how much the features
630                                                      // are
631               // to be shifted by
632               for (int sfi = 0; sfi < sf.length; sfi++)
633               {
634                 if (sf[sfi].getBegin() >= start && sf[sfi].getEnd() <= end)
635                 {
636                   // shift feature along by absstart
637                   sf[sfi].setBegin(sf[sfi].getBegin() + startShift);
638                   sf[sfi].setEnd(sf[sfi].getEnd() + startShift);
639                 }
640               }
641             }
642           }
643         }
644
645         System.out.println("Adding dbrefs to " + sequence.getName()
646                 + " from " + dbSource + " sequence : " + entry.getName());
647         sequence.transferAnnotation(entry, mp);
648         // unknownSequences.remove(sequence);
649         int absEnd = absStart + nonGapped.length();
650         absStart += 1;
651         if (updateRefFrame)
652         {
653           // finally, update local sequence reference frame if we're allowed
654           sequence.setStart(absStart);
655           sequence.setEnd(absEnd);
656           // search for alignment sequences to update coordinate frame for
657           for (int alsq = 0; alsq < alseqs.length; alsq++)
658           {
659             if (alseqs[alsq].getDatasetSequence() == sequence)
660             {
661               String ngAlsq = AlignSeq.extractGaps("-. ",
662                       alseqs[alsq].getSequenceAsString()).toUpperCase();
663               int oldstrt = alseqs[alsq].getStart();
664               alseqs[alsq].setStart(sequence.getSequenceAsString()
665                       .toUpperCase().indexOf(ngAlsq)
666                       + sequence.getStart());
667               if (oldstrt != alseqs[alsq].getStart())
668               {
669                 alseqs[alsq].setEnd(ngAlsq.length()
670                         + alseqs[alsq].getStart() - 1);
671               }
672             }
673           }
674           // TODO: search for all other references to this dataset sequence, and
675           // update start/end
676           // TODO: update all AlCodonMappings which involve this alignment
677           // sequence (e.g. Q30167 cdna translation from exon2 product (vamsas
678           // demo)
679         }
680         // and remove it from the rest
681         // TODO: decide if we should remove annotated sequence from set
682         sdataset.remove(sequence);
683         // TODO: should we make a note of sequences that have received new DB
684         // ids, so we can query all enabled DAS servers for them ?
685       }
686     }
687     if (!transferred)
688     {
689       // report the ID/sequence mismatches
690       sbuffer.append(messages);
691     }
692   }
693
694   /**
695    * loop thru and collect additional sequences in Map.
696    * 
697    * @param sequencesArray
698    * @return
699    */
700   private SequenceI[] recoverDbSequences(SequenceI[] sequencesArray)
701   {
702     Vector nseq = new Vector();
703     for (int i = 0; sequencesArray != null && i < sequencesArray.length; i++)
704     {
705       nseq.addElement(sequencesArray[i]);
706       DBRefEntry dbr[] = sequencesArray[i].getDBRef();
707       jalview.datamodel.Mapping map = null;
708       for (int r = 0; (dbr != null) && r < dbr.length; r++)
709       {
710         if ((map = dbr[r].getMap()) != null)
711         {
712           if (map.getTo() != null && !nseq.contains(map.getTo()))
713           {
714             nseq.addElement(map.getTo());
715           }
716         }
717       }
718     }
719     if (nseq.size() > 0)
720     {
721       sequencesArray = new SequenceI[nseq.size()];
722       nseq.toArray(sequencesArray);
723     }
724     return sequencesArray;
725   }
726 }