JAL-3691 automatic insertion of Locale.ROOT to toUpperCase() and toLowerCase() and...
[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.TDBeaconsFTSRestClient;
27 import jalview.jbgui.FilterOption;
28
29 /**
30  * logic for querying the 3DBeacons API for structures of sequences
31  * 
32  * @author jprocter
33  */
34 public class ThreeDBStructureChooserQuerySource
35         extends StructureChooserQuerySource
36 {
37
38   private Set<String> tdBeaconsFilters = null, defaultFilters = null;
39
40   public static final String FILTER_TDBEACONS_COVERAGE = "3d_beacons_coverage";
41
42   public static final String FILTER_FIRST_BEST_COVERAGE = "3d_beacons_first_best_coverage";
43
44   private static final String FILTER_SOURCE_PREFIX = "only_";
45
46   private static int MAX_QLENGTH = 7820;
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     boolean isPDBRefsFound = false;
78     boolean isUniProtRefsFound = false;
79     StringBuilder queryBuilder = new StringBuilder();
80     Set<String> seqRefs = new LinkedHashSet<>();
81
82     /*
83      * note PDBs as DBRefEntry so they are not duplicated in query
84      */
85     Set<String> pdbids = new HashSet<>();
86
87     List<DBRefEntry> refs = seq.getDBRefs();
88     int ib = checkUniprotRefs(refs);
89     if (ib>-1)
90     {
91       return getDBRefId(refs.get(ib));
92     }
93     return null;
94   }
95
96   /**
97    * Searches DBRefEntry for uniprot refs
98    * @param seq
99    * @return -2 if no uniprot refs, -1 if no canonical ref., otherwise index of Uniprot canonical DBRefEntry 
100    */
101   public static int checkUniprotRefs(List<DBRefEntry> refs)
102   {
103     boolean hasUniprot = false;
104     if (refs != null && refs.size() != 0)
105     {
106       for (int ib = 0, nb = refs.size(); ib < nb; ib++)
107       {
108         DBRefEntry dbRef = refs.get(ib);
109         if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
110         {
111           hasUniprot = true;
112           if (dbRef.isCanonical())
113           {
114             return ib;
115           }
116         }
117       }
118     }
119     return hasUniprot ? -1 : -2;
120   }
121
122   /**
123    * Ensures sequence ref names are not less than 3 characters and does not
124    * contain a database name
125    * 
126    * @param seqName
127    * @return
128    */
129   static boolean isValidSeqName(String seqName)
130   {
131     // System.out.println("seqName : " + seqName);
132     String ignoreList = "pdb,uniprot,swiss-prot";
133     if (seqName.length() < 3)
134     {
135       return false;
136     }
137     if (seqName.contains(":"))
138     {
139       return false;
140     }
141     seqName = seqName.toLowerCase(Locale.ROOT);
142     for (String ignoredEntry : ignoreList.split(","))
143     {
144       if (seqName.contains(ignoredEntry))
145       {
146         return false;
147       }
148     }
149     return true;
150   }
151
152   static String getDBRefId(DBRefEntry dbRef)
153   {
154     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
155     return ref;
156   }
157
158   /**
159    * FTSRestClient specific query builder to recover associated structure data
160    * records for a sequence
161    * 
162    * @param seq
163    *          - seq to generate a query for
164    * @param wantedFields
165    *          - fields to retrieve
166    * @param selectedFilterOpt
167    *          - criterion for ranking results (e.g. resolution)
168    * @param b
169    *          - sort ascending or descending
170    * @return
171    * @throws Exception
172    */
173   public FTSRestResponse fetchStructuresMetaData(SequenceI seq,
174           Collection<FTSDataColumnI> wantedFields,
175           FilterOption selectedFilterOpt, boolean b) throws Exception
176   {
177     FTSRestResponse resultList;
178     if (selectedFilterOpt != null
179             && tdBeaconsFilter(selectedFilterOpt.getValue()))
180     {
181       FTSRestRequest tdbRequest = getTDBeaconsRequest(seq, wantedFields);
182       resultList = tdbRestClient.executeRequest(tdbRequest);
183       
184       lastTdbRequest = tdbRequest;
185       if (resultList!=null)
186       { // Query the PDB and add additional metadata
187         FTSRestResponse pdbResponse = fetchStructuresMetaDataFor(
188                 getPDBQuerySource(), resultList);
189         FTSRestResponse joinedResp = joinResponses(resultList, pdbResponse);
190       }
191       return resultList;
192     }
193     // use the PDBFTS directly
194     resultList = getPDBQuerySource().fetchStructuresMetaData(seq,
195             wantedFields, selectedFilterOpt, b);
196     lastTdbRequest = getPDBQuerySource().lastPdbRequest;
197     lastPdbRequest = lastTdbRequest; // both queries the same - indicates we
198     // rank using PDBe
199     return resultList;
200
201   }
202
203   PDBStructureChooserQuerySource pdbQuerySource = null;
204
205   private PDBStructureChooserQuerySource getPDBQuerySource()
206   {
207     if (pdbQuerySource == null)
208     {
209       pdbQuerySource = new PDBStructureChooserQuerySource();
210     }
211     return pdbQuerySource;
212   }
213
214   private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
215           Collection<FTSDataColumnI> wantedFields)
216   {
217     FTSRestRequest pdbRequest = new FTSRestRequest();
218     pdbRequest.setAllowEmptySeq(false);
219     pdbRequest.setResponseSize(500);
220     pdbRequest.setWantedFields(wantedFields);
221     String query = buildQuery(seq);
222     if (query == null)
223     {
224       return null;
225     }
226     pdbRequest.setSearchTerm(query + ".json");
227     pdbRequest.setAssociatedSequence(seq);
228     return pdbRequest;
229   }
230
231   @Override
232   public List<FilterOption> getAvailableFilterOptions(String VIEWS_FILTER)
233   {
234     List<FilterOption> filters = getPDBQuerySource()
235             .getAvailableFilterOptions(VIEWS_FILTER);
236     tdBeaconsFilters = new LinkedHashSet<String>();
237     tdBeaconsFilters.addAll(defaultFilters);
238     filters.add(0, new FilterOption("Best 3D-Beacons Coverage",
239             FILTER_FIRST_BEST_COVERAGE, VIEWS_FILTER, false, this));
240     filters.add(1, new FilterOption("Multiple 3D-Beacons Coverage",
241             FILTER_TDBEACONS_COVERAGE, VIEWS_FILTER, true, this));
242
243     return filters;
244   }
245
246   @Override
247   public void updateAvailableFilterOptions(String VIEWS_FILTER,
248           List<FilterOption> xtantOptions, Collection<FTSData> tdbEntries)
249   {
250     if (tdbEntries != null && lastTdbRequest != null)
251     {
252       int prov_idx = lastTdbRequest.getFieldIndex("Provider");
253       boolean hasPDBe=false;
254       for (FTSData row : tdbEntries)
255       {
256         String provider = (String) row.getSummaryData()[prov_idx];
257         FilterOption providerOpt = new FilterOption(
258                 "3DB Provider - " + provider,
259                 FILTER_SOURCE_PREFIX + provider, VIEWS_FILTER, false, this);
260         if (!xtantOptions.contains(providerOpt))
261         {
262           xtantOptions.add(1, providerOpt);
263           tdBeaconsFilters.add(FILTER_SOURCE_PREFIX + provider);
264           if ("PDBe".equalsIgnoreCase(provider))
265           {
266             hasPDBe=true;
267           }
268         }
269       }
270       if (!hasPDBe)
271       {
272         // remove the PDBe options from the available filters
273         int op=0;
274         while (op<xtantOptions.size())
275         {
276           FilterOption filter = xtantOptions.get(op);
277           if (filter.getQuerySource() instanceof PDBStructureChooserQuerySource)
278           {
279             xtantOptions.remove(op);
280           } else {
281             op++;
282           }
283         }
284       }
285     }
286
287   }
288
289   private boolean tdBeaconsFilter(String fieldToFilterBy)
290   {
291     return tdBeaconsFilters != null
292             && tdBeaconsFilters.contains(fieldToFilterBy);
293   }
294
295   private String remove_prefix(String fieldToFilterBy)
296   {
297     if (tdBeaconsFilters != null
298             && tdBeaconsFilters.contains(fieldToFilterBy)
299             && !defaultFilters.contains(fieldToFilterBy))
300     {
301       return fieldToFilterBy.substring(FILTER_SOURCE_PREFIX.length());
302     }
303     else
304     {
305       return null;
306     }
307   }
308
309   @Override
310   public boolean needsRefetch(FilterOption selectedFilterOpt)
311   {
312     return selectedFilterOpt == null
313             || !tdBeaconsFilter(selectedFilterOpt.getValue())
314                     && lastPdbRequest != lastTdbRequest;
315   }
316
317   /**
318    * FTSRestClient specific query builder to pick top ranked entry from a
319    * fetchStructuresMetaData query
320    * 
321    * @param seq
322    *          - seq to generate a query for
323    * @param wantedFields
324    *          - fields to retrieve
325    * @param selectedFilterOpt
326    *          - criterion for ranking results (e.g. resolution)
327    * @param b
328    *          - sort ascending or descending
329    * @return
330    * @throws Exception
331    */
332   public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
333           Collection<FTSData> collectedResults,
334           Collection<FTSDataColumnI> wantedFields, String fieldToFilterBy,
335           boolean b) throws Exception
336   {
337     if (fieldToFilterBy != null && tdBeaconsFilter(fieldToFilterBy))
338     {
339       TDBResultAnalyser analyser = new TDBResultAnalyser(seq,
340               collectedResults, lastTdbRequest, fieldToFilterBy,
341               remove_prefix(fieldToFilterBy));
342
343       FTSRestResponse resultList = new FTSRestResponse();
344
345       List<FTSData> filteredResponse = analyser.getFilteredResponse();
346
347       List<FTSData> selectedStructures = analyser
348               .selectStructures(filteredResponse);
349       resultList.setNumberOfItemsFound(selectedStructures.size());
350       resultList.setSearchSummary(selectedStructures);
351       return resultList;
352     }
353     // Fall back to PDBe rankings
354     return getPDBQuerySource().selectFirstRankedQuery(seq, collectedResults,
355             wantedFields, fieldToFilterBy, b);
356   }
357
358   @Override
359   public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
360           List<SequenceI> selectedSeqsToView)
361   {
362     int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
363
364     PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
365     int count = 0;
366     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
367     int urlColumnIndex = restable.getColumn("Url").getModelIndex();
368     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
369     int humanUrl = restable.getColumn("Page URL").getModelIndex();
370     int categoryColumnIndex = restable.getColumn("Model Category")
371             .getModelIndex();
372     final int up_start_idx = restable.getColumn("Uniprot Start")
373             .getModelIndex();
374     final int up_end_idx = restable.getColumn("Uniprot End")
375             .getModelIndex();
376     int i = 0;
377
378     // bleugh!
379     Integer[] sellist = new Integer[selectedRows.length];
380     for (Integer row : selectedRows)
381     {
382       sellist[i++] = row;
383     }
384     // Sort rows by coverage
385     Arrays.sort(sellist, new Comparator<Integer>()
386     {
387       @Override
388       public int compare(Integer o1, Integer o2)
389       {
390         int o1_xt = ((Integer) restable.getValueAt(o1, up_end_idx))
391                 - (Integer) restable.getValueAt(o1, up_start_idx);
392         int o2_xt = ((Integer) restable.getValueAt(o2, up_end_idx))
393                 - (Integer) restable.getValueAt(o2, up_start_idx);
394         return o2_xt - o1_xt;
395       }
396     });
397
398     for (int row : sellist)
399     {
400       // unique id - could be a horrible hash
401
402       String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
403       String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
404       String typeColumn = restable.getValueAt(row, typeColumnIndex)
405               .toString();
406       String modelPage = humanUrl < 1 ? null
407               : (String) restable.getValueAt(row, humanUrl);
408       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
409               refSeqColIndex);
410       selectedSeqsToView.add(selectedSeq);
411       PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
412       if (pdbEntry == null)
413       {
414         pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
415       }
416
417       if (pdbEntry == null)
418       {
419         pdbEntry = new PDBEntry();
420         pdbEntry.setId(pdbIdStr);
421         boolean hasCif = urlStr.toLowerCase(Locale.ENGLISH).endsWith("cif");
422         boolean probablyPdb = urlStr.toLowerCase(Locale.ENGLISH)
423                 .contains("pdb");
424         pdbEntry.setType(hasCif ? PDBEntry.Type.MMCIF
425                 : probablyPdb ? PDBEntry.Type.PDB : PDBEntry.Type.FILE);
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     List<FTSData> joinedRows = new ArrayList<FTSData>();
517     for (final FTSData row : upResponse.getSearchSummary())
518     {
519       String id = (String) row.getSummaryData()[idx_modelId];
520       String provider = (String) row.getSummaryData()[idx_provider];
521       if ("PDBe".equalsIgnoreCase(provider))
522       {
523         if (!hasPdbResp)
524         {
525           System.out.println(
526                   "Warning: seems like we couldn't get to the PDBe search interface.");
527         }
528         else
529         {
530           for (final FTSData pdbrow : pdbResponse.getSearchSummary())
531           {
532             String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
533             if (id.equalsIgnoreCase(pdbid))
534             {
535               row.getSummaryData()[tdbTitle_idx] = pdbrow
536                       .getSummaryData()[pdbTitle_idx];
537             }
538           }
539         }
540
541       }
542       else
543       {
544         row.getSummaryData()[tdbTitle_idx] = "Model from TDB";
545       }
546     }
547     return upResponse;
548   }
549
550 }