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