JAL-3956 - PDBEntry objects constructed from 3D-Beacons have authoritative IDs overri...
[jalview.git] / src / jalview / gui / structurechooser / ThreeDBStructureChooserQuerySource.java
1 package jalview.gui.structurechooser;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.Comparator;
7 import java.util.HashSet;
8 import java.util.LinkedHashSet;
9 import java.util.List;
10 import java.util.Locale;
11 import java.util.Set;
12
13 import javax.swing.JTable;
14
15 import jalview.bin.Console;
16 import jalview.datamodel.DBRefEntry;
17 import jalview.datamodel.DBRefSource;
18 import jalview.datamodel.PDBEntry;
19 import jalview.datamodel.SequenceI;
20 import jalview.fts.api.FTSData;
21 import jalview.fts.api.FTSDataColumnI;
22 import jalview.fts.api.FTSRestClientI;
23 import jalview.fts.core.FTSDataColumnPreferences;
24 import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
25 import jalview.fts.core.FTSRestRequest;
26 import jalview.fts.core.FTSRestResponse;
27 import jalview.fts.service.threedbeacons.TDB_FTSData;
28 import jalview.fts.service.threedbeacons.TDBeaconsFTSRestClient;
29 import jalview.jbgui.FilterOption;
30
31 /**
32  * logic for querying the 3DBeacons API for structures of sequences
33  * 
34  * @author jprocter
35  */
36 public class ThreeDBStructureChooserQuerySource
37         extends StructureChooserQuerySource
38 {
39
40   private Set<String> tdBeaconsFilters = null, defaultFilters = null;
41
42   public static final String FILTER_TDBEACONS_COVERAGE = "3d_beacons_coverage";
43
44   public static final String FILTER_FIRST_BEST_COVERAGE = "3d_beacons_first_best_coverage";
45
46   private static final String FILTER_SOURCE_PREFIX = "only_";
47
48   protected FTSRestRequest lastTdbRequest;
49
50   protected FTSRestClientI tdbRestClient;
51
52   private FTSRestRequest lastPdbRequest;
53
54   public ThreeDBStructureChooserQuerySource()
55   {
56     defaultFilters = new LinkedHashSet<String>();
57     defaultFilters.add(FILTER_TDBEACONS_COVERAGE);
58     defaultFilters.add(FILTER_FIRST_BEST_COVERAGE);
59
60     tdbRestClient = TDBeaconsFTSRestClient.getInstance();
61     docFieldPrefs = new FTSDataColumnPreferences(
62             PreferenceSource.STRUCTURE_CHOOSER,
63             TDBeaconsFTSRestClient.getInstance());
64   }
65
66   /**
67    * Builds a query string for a given sequences using its DBRef entries 3d
68    * Beacons is only useful for uniprot IDs
69    * 
70    * @param seq
71    *          the sequences to build a query for
72    * @return the built query string
73    */
74
75   public String buildQuery(SequenceI seq)
76   {
77     List<DBRefEntry> refs = seq.getDBRefs();
78     int ib = checkUniprotRefs(refs);
79     if (ib > -1)
80     {
81       return getDBRefId(refs.get(ib));
82     }
83     return null;
84   }
85
86   /**
87    * Searches DBRefEntry for uniprot refs
88    * 
89    * @param seq
90    * @return -2 if no uniprot refs, -1 if no canonical ref., otherwise index of
91    *         Uniprot canonical DBRefEntry
92    */
93   public static int checkUniprotRefs(List<DBRefEntry> refs)
94   {
95     boolean hasUniprot = false;
96     if (refs != null && refs.size() != 0)
97     {
98       for (int ib = 0, nb = refs.size(); ib < nb; ib++)
99       {
100         DBRefEntry dbRef = refs.get(ib);
101         if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
102         {
103           hasUniprot = true;
104           if (dbRef.isCanonical())
105           {
106             return ib;
107           }
108         }
109       }
110     }
111     return hasUniprot ? -1 : -2;
112   }
113
114   /**
115    * Ensures sequence ref names are not less than 3 characters and does not
116    * contain a database name
117    * 
118    * @param seqName
119    * @return
120    */
121   static boolean isValidSeqName(String seqName)
122   {
123     String ignoreList = "pdb,uniprot,swiss-prot";
124     if (seqName.length() < 3)
125     {
126       return false;
127     }
128     if (seqName.contains(":"))
129     {
130       return false;
131     }
132     seqName = seqName.toLowerCase(Locale.ROOT);
133     for (String ignoredEntry : ignoreList.split(","))
134     {
135       if (seqName.contains(ignoredEntry))
136       {
137         return false;
138       }
139     }
140     return true;
141   }
142
143   static String getDBRefId(DBRefEntry dbRef)
144   {
145     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
146     return ref;
147   }
148
149   /**
150    * FTSRestClient specific query builder to recover associated structure data
151    * records for a sequence
152    * 
153    * @param seq
154    *          - seq to generate a query for
155    * @param wantedFields
156    *          - fields to retrieve
157    * @param selectedFilterOpt
158    *          - criterion for ranking results (e.g. resolution)
159    * @param b
160    *          - sort ascending or descending
161    * @return
162    * @throws Exception
163    */
164   public FTSRestResponse fetchStructuresMetaData(SequenceI seq,
165           Collection<FTSDataColumnI> wantedFields,
166           FilterOption selectedFilterOpt, boolean b) throws Exception
167   {
168     FTSRestResponse resultList;
169     if (selectedFilterOpt != null
170             && tdBeaconsFilter(selectedFilterOpt.getValue()))
171     {
172       FTSRestRequest tdbRequest = getTDBeaconsRequest(seq, wantedFields);
173       resultList = tdbRestClient.executeRequest(tdbRequest);
174
175       lastTdbRequest = tdbRequest;
176       if (resultList != null)
177       { // Query the PDB and add additional metadata
178         FTSRestResponse pdbResponse = fetchStructuresMetaDataFor(
179                 getPDBQuerySource(), resultList);
180
181         resultList = joinResponses(resultList, pdbResponse);
182       }
183       return resultList;
184     }
185     // use the PDBFTS directly
186     resultList = getPDBQuerySource().fetchStructuresMetaData(seq,
187             wantedFields, selectedFilterOpt, b);
188     lastTdbRequest = getPDBQuerySource().lastPdbRequest;
189     lastPdbRequest = lastTdbRequest; // both queries the same - indicates we
190     // rank using PDBe
191     return resultList;
192
193   }
194
195   PDBStructureChooserQuerySource pdbQuerySource = null;
196
197   private PDBStructureChooserQuerySource getPDBQuerySource()
198   {
199     if (pdbQuerySource == null)
200     {
201       pdbQuerySource = new PDBStructureChooserQuerySource();
202     }
203     return pdbQuerySource;
204   }
205
206   private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
207           Collection<FTSDataColumnI> wantedFields)
208   {
209     FTSRestRequest pdbRequest = new FTSRestRequest();
210     pdbRequest.setAllowEmptySeq(false);
211     pdbRequest.setResponseSize(500);
212     pdbRequest.setWantedFields(wantedFields);
213     String query = buildQuery(seq);
214     if (query == null)
215     {
216       return null;
217     }
218     pdbRequest.setSearchTerm(query + ".json");
219     pdbRequest.setAssociatedSequence(seq);
220     return pdbRequest;
221   }
222
223   @Override
224   public List<FilterOption> getAvailableFilterOptions(String VIEWS_FILTER)
225   {
226     List<FilterOption> filters = getPDBQuerySource()
227             .getAvailableFilterOptions(VIEWS_FILTER);
228     tdBeaconsFilters = new LinkedHashSet<String>();
229     tdBeaconsFilters.addAll(defaultFilters);
230     filters.add(0, new FilterOption("Best 3D-Beacons Coverage",
231             FILTER_FIRST_BEST_COVERAGE, VIEWS_FILTER, false, this));
232     filters.add(1, new FilterOption("Multiple 3D-Beacons Coverage",
233             FILTER_TDBEACONS_COVERAGE, VIEWS_FILTER, true, this));
234
235     return filters;
236   }
237
238   @Override
239   public void updateAvailableFilterOptions(String VIEWS_FILTER,
240           List<FilterOption> xtantOptions, Collection<FTSData> tdbEntries)
241   {
242     if (tdbEntries != null && lastTdbRequest != null)
243     {
244       boolean hasPDBe = false;
245       for (FTSData _row : tdbEntries)
246       {
247         // tdb returns custom object
248         TDB_FTSData row = (TDB_FTSData) _row;
249         String provider = (String) row.getProvider();
250         FilterOption providerOpt = new FilterOption(
251                 "3DB Provider - " + provider,
252                 FILTER_SOURCE_PREFIX + provider, VIEWS_FILTER, false, this);
253         if (!xtantOptions.contains(providerOpt))
254         {
255           xtantOptions.add(1, providerOpt);
256           tdBeaconsFilters.add(FILTER_SOURCE_PREFIX + provider);
257           if ("PDBe".equalsIgnoreCase(provider))
258           {
259             hasPDBe = true;
260           }
261         }
262       }
263       if (!hasPDBe)
264       {
265         // remove the PDBe options from the available filters
266         int op = 0;
267         while (op < xtantOptions.size())
268         {
269           FilterOption filter = xtantOptions.get(op);
270           if (filter
271                   .getQuerySource() instanceof PDBStructureChooserQuerySource)
272           {
273             xtantOptions.remove(op);
274           }
275           else
276           {
277             op++;
278           }
279         }
280       }
281     }
282
283   }
284
285   private boolean tdBeaconsFilter(String fieldToFilterBy)
286   {
287     return tdBeaconsFilters != null
288             && tdBeaconsFilters.contains(fieldToFilterBy);
289   }
290
291   private String remove_prefix(String fieldToFilterBy)
292   {
293     if (tdBeaconsFilters != null
294             && tdBeaconsFilters.contains(fieldToFilterBy)
295             && !defaultFilters.contains(fieldToFilterBy))
296     {
297       return fieldToFilterBy.substring(FILTER_SOURCE_PREFIX.length());
298     }
299     else
300     {
301       return null;
302     }
303   }
304
305   @Override
306   public boolean needsRefetch(FilterOption selectedFilterOpt)
307   {
308     return selectedFilterOpt == null
309             || !tdBeaconsFilter(selectedFilterOpt.getValue())
310                     && lastPdbRequest != lastTdbRequest;
311   }
312
313   /**
314    * FTSRestClient specific query builder to pick top ranked entry from a
315    * fetchStructuresMetaData query
316    * 
317    * @param seq
318    *          - seq to generate a query for
319    * @param wantedFields
320    *          - fields to retrieve
321    * @param selectedFilterOpt
322    *          - criterion for ranking results (e.g. resolution)
323    * @param b
324    *          - sort ascending or descending
325    * @return
326    * @throws Exception
327    */
328   public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
329           Collection<FTSData> collectedResults,
330           Collection<FTSDataColumnI> wantedFields, String fieldToFilterBy,
331           boolean b) throws Exception
332   {
333     if (fieldToFilterBy != null && tdBeaconsFilter(fieldToFilterBy))
334     {
335       TDBResultAnalyser analyser = new TDBResultAnalyser(seq,
336               collectedResults, lastTdbRequest, fieldToFilterBy,
337               remove_prefix(fieldToFilterBy));
338
339       FTSRestResponse resultList = new FTSRestResponse();
340
341       List<FTSData> filteredResponse = analyser.getFilteredResponse();
342
343       List<FTSData> selectedStructures = analyser
344               .selectStructures(filteredResponse);
345       resultList.setNumberOfItemsFound(selectedStructures.size());
346       resultList.setSearchSummary(selectedStructures);
347       return resultList;
348     }
349     // Fall back to PDBe rankings
350     return getPDBQuerySource().selectFirstRankedQuery(seq, collectedResults,
351             wantedFields, fieldToFilterBy, b);
352   }
353
354   @Override
355   public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
356           List<SequenceI> selectedSeqsToView)
357   {
358     int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
359
360     PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
361     int count = 0;
362     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
363     int urlColumnIndex = restable.getColumn("Url").getModelIndex();
364     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
365     int humanUrl = restable.getColumn("Page URL").getModelIndex();
366     int modelformat = restable.getColumn("Model Format").getModelIndex();
367     final int up_start_idx = restable.getColumn("Uniprot Start")
368             .getModelIndex();
369     final int up_end_idx = restable.getColumn("Uniprot End")
370             .getModelIndex();
371     int i = 0;
372
373     // bleugh!
374     Integer[] sellist = new Integer[selectedRows.length];
375     for (Integer row : selectedRows)
376     {
377       sellist[i++] = row;
378     }
379     // Sort rows by coverage
380     Arrays.sort(sellist, new Comparator<Integer>()
381     {
382       @Override
383       public int compare(Integer o1, Integer o2)
384       {
385         int o1_xt = ((Integer) restable.getValueAt(o1, up_end_idx))
386                 - (Integer) restable.getValueAt(o1, up_start_idx);
387         int o2_xt = ((Integer) restable.getValueAt(o2, up_end_idx))
388                 - (Integer) restable.getValueAt(o2, up_start_idx);
389         return o2_xt - o1_xt;
390       }
391     });
392
393     for (int row : sellist)
394     {
395       // unique id - could be a horrible hash
396
397       String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
398       String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
399       String typeColumn = restable.getValueAt(row, typeColumnIndex)
400               .toString();
401       String modelPage = humanUrl < 1 ? null
402               : (String) restable.getValueAt(row, humanUrl);
403       String strucFormat = restable.getValueAt(row, modelformat).toString();
404
405       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
406               refSeqColIndex);
407       selectedSeqsToView.add(selectedSeq);
408       PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
409       if (pdbEntry == null)
410       {
411         pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
412       }
413
414       if (pdbEntry == null)
415       {
416         pdbEntry = new PDBEntry();
417         pdbEntry.setId(pdbIdStr);
418         pdbEntry.setAuthoritative(true);
419         try
420         {
421           pdbEntry.setType(PDBEntry.Type.valueOf(strucFormat));
422         } catch (Exception q)
423         {
424           Console.warn("Unknown filetype for 3D Beacons Model from: "
425                   + strucFormat + " - " + pdbIdStr + " - " + modelPage);
426         }
427
428         if (!"PDBe".equalsIgnoreCase(typeColumn))
429         {
430           pdbEntry.setRetrievalUrl(urlStr);
431         }
432         pdbEntry.setProvider(typeColumn);
433         pdbEntry.setProviderPage(modelPage);
434         selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
435       }
436       pdbEntriesToView[count++] = pdbEntry;
437     }
438     return pdbEntriesToView;
439   }
440
441   @Override
442   protected FTSRestRequest getLastFTSRequest()
443   {
444     return lastTdbRequest;
445   }
446
447   /**
448    * generate a query for PDBFTS to retrieve structure metadata
449    * 
450    * @param ftsRestRequest
451    * @param upResponse
452    * @return
453    */
454
455   public String buildPDBFTSQueryFor(FTSRestResponse upResponse)
456   {
457     Set<String> pdbIds = new HashSet<String>();
458     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
459     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
460     for (FTSData row : upResponse.getSearchSummary())
461     {
462       String id = (String) row.getSummaryData()[idx_modelId];
463       String provider = (String) row.getSummaryData()[idx_provider];
464       if ("PDBe".equalsIgnoreCase(provider))
465       {
466         pdbIds.add(id);
467       }
468     }
469     return String.join(" OR ", pdbIds).toString();
470   }
471
472   /**
473    * query PDBe for structure metadata
474    * 
475    * @param pdbquery
476    * @param upResponse
477    * @return FTSRestResponse via PDBStructureChooserQuerySource
478    */
479   public FTSRestResponse fetchStructuresMetaDataFor(
480           PDBStructureChooserQuerySource pdbquery,
481           FTSRestResponse upResponse) throws Exception
482   {
483
484     String pdb_Query = buildPDBFTSQueryFor(upResponse);
485     if (pdb_Query.length() == 0)
486     {
487       return null;
488     }
489     FTSRestResponse resultList;
490     FTSRestRequest pdbRequest = new FTSRestRequest();
491     pdbRequest.setAllowEmptySeq(false);
492     pdbRequest.setResponseSize(500);
493     pdbRequest.setFieldToSearchBy("(");
494     // pdbRequest.setFieldToSortBy("pdb_id");
495     pdbRequest.setWantedFields(
496             pdbquery.getDocFieldPrefs().getStructureSummaryFields());
497     pdbRequest.setSearchTerm(pdb_Query + ")");
498
499     resultList = pdbquery.executePDBFTSRestRequest(pdbRequest);
500
501     lastPdbRequest = pdbRequest;
502     return resultList;
503   }
504
505   public FTSRestResponse joinResponses(FTSRestResponse upResponse,
506           FTSRestResponse pdbResponse)
507   {
508     boolean hasPdbResp = lastPdbRequest != null;
509
510     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
511     // join on
512     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
513     int pdbIdx = hasPdbResp ? lastPdbRequest.getFieldIndex("PDB Id") : -1;
514     int pdbTitle_idx = hasPdbResp ? lastPdbRequest.getFieldIndex("Title")
515             : -1;
516     int tdbTitle_idx = getLastFTSRequest().getFieldIndex("Title");
517
518     for (final FTSData row : upResponse.getSearchSummary())
519     {
520       String id = (String) row.getSummaryData()[idx_modelId];
521       String provider = (String) row.getSummaryData()[idx_provider];
522       if ("PDBe".equalsIgnoreCase(provider))
523       {
524         if (!hasPdbResp)
525         {
526           System.out.println(
527                   "Warning: seems like we couldn't get to the PDBe search interface.");
528         }
529         else
530         {
531           for (final FTSData pdbrow : pdbResponse.getSearchSummary())
532           {
533             String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
534             if (id.equalsIgnoreCase(pdbid))
535             {
536               row.getSummaryData()[tdbTitle_idx] = pdbrow
537                       .getSummaryData()[pdbTitle_idx];
538             }
539           }
540         }
541
542       }
543       else
544       {
545         row.getSummaryData()[tdbTitle_idx] = "Model from TDB";
546       }
547     }
548     return upResponse;
549   }
550
551   public TDB_FTSData getFTSDataFor(JTable restable, int selectedRow,
552           Collection<FTSData> discoveredStructuresSet)
553   {
554     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
555
556     String modelId = (String) restable.getValueAt(selectedRow,
557             idColumnIndex);
558     for (FTSData row : discoveredStructuresSet)
559     {
560       if (row instanceof TDB_FTSData
561               && ((TDB_FTSData) row).getModelId().equals(modelId))
562       {
563         return ((TDB_FTSData) row);
564       }
565     }
566     return null;
567   }
568
569 }