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