JAL-3829 belt and braces check to make sure there is a pdbe response available to...
[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.Collections;
7 import java.util.Comparator;
8 import java.util.HashSet;
9 import java.util.LinkedHashSet;
10 import java.util.List;
11 import java.util.Locale;
12 import java.util.Set;
13
14 import javax.swing.JTable;
15
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.TDBeaconsFTSRestClient;
28 import jalview.jbgui.FilterOption;
29 import jalview.util.MessageManager;
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   private static int MAX_QLENGTH = 7820;
49
50   protected FTSRestRequest lastTdbRequest;
51
52   protected FTSRestClientI tdbRestClient;
53
54   private FTSRestRequest lastPdbRequest;
55
56   public ThreeDBStructureChooserQuerySource()
57   {
58     defaultFilters = new LinkedHashSet<String>();
59     defaultFilters.add(FILTER_TDBEACONS_COVERAGE);
60     defaultFilters.add(FILTER_FIRST_BEST_COVERAGE);
61
62     tdbRestClient = TDBeaconsFTSRestClient.getInstance();
63     docFieldPrefs = new FTSDataColumnPreferences(
64             PreferenceSource.STRUCTURE_CHOOSER,
65             TDBeaconsFTSRestClient.getInstance());
66   }
67
68   /**
69    * Builds a query string for a given sequences using its DBRef entries 3d
70    * Beacons is only useful for uniprot IDs
71    * 
72    * @param seq
73    *          the sequences to build a query for
74    * @return the built query string
75    */
76
77   public String buildQuery(SequenceI seq)
78   {
79     boolean isPDBRefsFound = false;
80     boolean isUniProtRefsFound = false;
81     StringBuilder queryBuilder = new StringBuilder();
82     Set<String> seqRefs = new LinkedHashSet<>();
83
84     /*
85      * note PDBs as DBRefEntry so they are not duplicated in query
86      */
87     Set<String> pdbids = new HashSet<>();
88
89     List<DBRefEntry> refs = seq.getDBRefs();
90     if (refs != null && refs.size() != 0)
91     {
92       for (int ib = 0, nb = refs.size(); ib < nb; ib++)
93       {
94         DBRefEntry dbRef = refs.get(ib);
95         if (isValidSeqName(getDBRefId(dbRef))
96                 && queryBuilder.length() < MAX_QLENGTH)
97         {
98           if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT)
99                   && dbRef.isCanonical())
100           {
101             // TODO: pick best Uniprot accession
102             isUniProtRefsFound = true;
103             return getDBRefId(dbRef);
104
105           }
106         }
107       }
108     }
109     return null;
110   }
111
112   /**
113    * Ensures sequence ref names are not less than 3 characters and does not
114    * contain a database name
115    * 
116    * @param seqName
117    * @return
118    */
119   static boolean isValidSeqName(String seqName)
120   {
121     // System.out.println("seqName : " + seqName);
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();
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 && tdBeaconsFilter(selectedFilterOpt.getValue()))
169     {
170       FTSRestRequest tdbRequest = getTDBeaconsRequest(seq, wantedFields);
171       resultList = tdbRestClient.executeRequest(tdbRequest);
172
173       lastTdbRequest = tdbRequest;
174
175       // Query the PDB and add additional metadata
176       FTSRestResponse pdbResponse = fetchStructuresMetaDataFor(
177               getPDBQuerySource(), resultList);
178       FTSRestResponse joinedResp = joinResponses(resultList, pdbResponse);
179       return resultList;
180     }
181     // use the PDBFTS directly
182     resultList = getPDBQuerySource().fetchStructuresMetaData(seq,
183             wantedFields, selectedFilterOpt, b);
184     lastTdbRequest = getPDBQuerySource().lastPdbRequest;
185     lastPdbRequest = lastTdbRequest; // both queries the same - indicates we
186                                      // rank using PDBe
187     return resultList;
188
189   }
190
191   PDBStructureChooserQuerySource pdbQuerySource = null;
192
193   private PDBStructureChooserQuerySource getPDBQuerySource()
194   {
195     if (pdbQuerySource == null)
196     {
197       pdbQuerySource = new PDBStructureChooserQuerySource();
198     }
199     return pdbQuerySource;
200   }
201
202   private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
203           Collection<FTSDataColumnI> wantedFields)
204   {
205     FTSRestRequest pdbRequest = new FTSRestRequest();
206     pdbRequest.setAllowEmptySeq(false);
207     pdbRequest.setResponseSize(500);
208     pdbRequest.setWantedFields(wantedFields);
209     String query = buildQuery(seq);
210     if (query == null)
211     {
212       return null;
213     }
214     pdbRequest.setSearchTerm(query + ".json");
215     pdbRequest.setAssociatedSequence(seq);
216     return pdbRequest;
217   }
218
219   @Override
220   public List<FilterOption> getAvailableFilterOptions(String VIEWS_FILTER)
221   {
222     List<FilterOption> filters = getPDBQuerySource()
223             .getAvailableFilterOptions(VIEWS_FILTER);
224     tdBeaconsFilters = new LinkedHashSet<String>();
225     tdBeaconsFilters.addAll(defaultFilters);
226     filters.add(0, new FilterOption("Best 3D-Beacons Coverage",
227             FILTER_FIRST_BEST_COVERAGE, VIEWS_FILTER, false, this));
228     filters.add(1, new FilterOption("Multiple 3D-Beacons Coverage",
229             FILTER_TDBEACONS_COVERAGE, VIEWS_FILTER, true, this));
230
231     return filters;
232   }
233
234   @Override
235   public void updateAvailableFilterOptions(String VIEWS_FILTER,
236           List<FilterOption> xtantOptions, Collection<FTSData> tdbEntries)
237   {
238     if (tdbEntries !=null && lastTdbRequest != null)
239     {
240       int prov_idx = lastTdbRequest.getFieldIndex("Provider");
241
242       for (FTSData row : tdbEntries)
243       {
244         String provider = (String) row.getSummaryData()[prov_idx];
245         FilterOption providerOpt = new FilterOption("3DB Provider - " + provider,
246                 FILTER_SOURCE_PREFIX + provider, VIEWS_FILTER,
247                 false, this);
248         if (!xtantOptions.contains(providerOpt))
249         {
250           xtantOptions.add(1,
251                   providerOpt);
252           tdBeaconsFilters.add(FILTER_SOURCE_PREFIX+provider);
253
254         }
255       }
256     }
257
258   }
259
260   private boolean tdBeaconsFilter(String fieldToFilterBy)
261   {
262     return tdBeaconsFilters != null
263             && tdBeaconsFilters.contains(fieldToFilterBy);
264   }
265
266   private String remove_prefix(String fieldToFilterBy)
267   {
268     if (tdBeaconsFilters != null
269             && tdBeaconsFilters.contains(fieldToFilterBy)
270             && !defaultFilters.contains(fieldToFilterBy))
271     {
272       return fieldToFilterBy.substring(FILTER_SOURCE_PREFIX.length());
273     }
274     else
275     {
276       return null;
277     }
278   }
279
280   @Override
281   public boolean needsRefetch(FilterOption selectedFilterOpt)
282   {
283     return selectedFilterOpt==null || !tdBeaconsFilter(selectedFilterOpt.getValue())
284             && lastPdbRequest != lastTdbRequest;
285   }
286
287   /**
288    * FTSRestClient specific query builder to pick top ranked entry from a
289    * fetchStructuresMetaData query
290    * 
291    * @param seq
292    *          - seq to generate a query for
293    * @param wantedFields
294    *          - fields to retrieve
295    * @param selectedFilterOpt
296    *          - criterion for ranking results (e.g. resolution)
297    * @param b
298    *          - sort ascending or descending
299    * @return
300    * @throws Exception
301    */
302   public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
303           Collection<FTSData> collectedResults,
304           Collection<FTSDataColumnI> wantedFields, String fieldToFilterBy,
305           boolean b) throws Exception
306   {
307     if (fieldToFilterBy!=null && tdBeaconsFilter(fieldToFilterBy))
308     {
309       TDBResultAnalyser analyser = new TDBResultAnalyser(seq,
310               collectedResults, lastTdbRequest, fieldToFilterBy,
311               remove_prefix(fieldToFilterBy));
312
313       FTSRestResponse resultList = new FTSRestResponse();
314
315       List<FTSData> filteredResponse = analyser.getFilteredResponse();
316
317       List<FTSData> selectedStructures = analyser
318               .selectStructures(filteredResponse);
319       resultList.setNumberOfItemsFound(selectedStructures.size());
320       resultList.setSearchSummary(selectedStructures);
321       return resultList;
322     }
323     // Fall back to PDBe rankings
324     return getPDBQuerySource().selectFirstRankedQuery(seq, collectedResults,
325             wantedFields, fieldToFilterBy, b);
326   }
327
328   @Override
329   public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
330           List<SequenceI> selectedSeqsToView)
331   {
332     int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
333
334     PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
335     int count = 0;
336     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
337     int urlColumnIndex = restable.getColumn("Url").getModelIndex();
338     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
339     int categoryColumnIndex = restable.getColumn("Model Category")
340             .getModelIndex();
341     final int up_start_idx = restable.getColumn("Uniprot Start")
342             .getModelIndex();
343     final int up_end_idx = restable.getColumn("Uniprot End")
344             .getModelIndex();
345     int i = 0;
346
347     // bleugh!
348     Integer[] sellist = new Integer[selectedRows.length];
349     for (Integer row : selectedRows)
350     {
351       sellist[i++] = row;
352     }
353     // Sort rows by coverage
354     Arrays.sort(sellist, new Comparator<Integer>()
355     {
356       @Override
357       public int compare(Integer o1, Integer o2)
358       {
359         int o1_xt = ((Integer) restable.getValueAt(o1, up_end_idx))
360                 - (Integer) restable.getValueAt(o1, up_start_idx);
361         int o2_xt = ((Integer) restable.getValueAt(o2, up_end_idx))
362                 - (Integer) restable.getValueAt(o2, up_start_idx);
363         return o2_xt - o1_xt;
364       }
365     });
366
367     for (int row : sellist)
368     {
369       // unique id - could be a horrible hash
370
371       String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
372       String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
373       String typeColumn = restable.getValueAt(row, typeColumnIndex)
374               .toString();
375       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
376               refSeqColIndex);
377       selectedSeqsToView.add(selectedSeq);
378       PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
379       if (pdbEntry == null)
380       {
381         pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
382       }
383
384       if (pdbEntry == null)
385       {
386         pdbEntry = new PDBEntry();
387         pdbEntry.setId(pdbIdStr);
388         boolean hasCif = urlStr.toLowerCase(Locale.ENGLISH).endsWith("cif");
389         boolean probablyPdb = urlStr.toLowerCase(Locale.ENGLISH).contains("pdb");
390         pdbEntry.setType(hasCif ? PDBEntry.Type.MMCIF : probablyPdb ? PDBEntry.Type.PDB : PDBEntry.Type.FILE);
391         if (!"PDBe".equalsIgnoreCase(typeColumn))
392         {
393           pdbEntry.setRetrievalUrl(urlStr);
394         }
395         selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
396       }
397       pdbEntriesToView[count++] = pdbEntry;
398     }
399     return pdbEntriesToView;
400   }
401
402   @Override
403   protected FTSRestRequest getLastFTSRequest()
404   {
405     return lastTdbRequest;
406   }
407
408   /**
409    * generate a query for PDBFTS to retrieve structure metadata
410    * 
411    * @param ftsRestRequest
412    * @param upResponse
413    * @return
414    */
415
416   public String buildPDBFTSQueryFor(FTSRestResponse upResponse)
417   {
418     List<String> pdbIds = new ArrayList<String>();
419     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
420     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
421     for (FTSData row : upResponse.getSearchSummary())
422     {
423       String id = (String) row.getSummaryData()[idx_modelId];
424       String provider = (String) row.getSummaryData()[idx_provider];
425       if ("PDBe".equalsIgnoreCase(provider))
426       {
427         pdbIds.add(id);
428       }
429     }
430     return String.join(" OR ", pdbIds).toString();
431   }
432
433   /**
434    * query PDBe for structure metadata
435    * 
436    * @param pdbquery
437    * @param upResponse
438    * @return FTSRestResponse via PDBStructureChooserQuerySource
439    */
440   public FTSRestResponse fetchStructuresMetaDataFor(
441           PDBStructureChooserQuerySource pdbquery,
442           FTSRestResponse upResponse) throws Exception
443   {
444
445     String pdb_Query = buildPDBFTSQueryFor(upResponse);
446     if (pdb_Query.length()==0)
447     {
448       return null;
449     }
450     FTSRestResponse resultList;
451     FTSRestRequest pdbRequest = new FTSRestRequest();
452     pdbRequest.setAllowEmptySeq(false);
453     pdbRequest.setResponseSize(500);
454     pdbRequest.setFieldToSearchBy("(");
455     // pdbRequest.setFieldToSortBy("pdb_id");
456     pdbRequest.setWantedFields(
457             pdbquery.getDocFieldPrefs().getStructureSummaryFields());
458     pdbRequest.setSearchTerm(pdb_Query + ")");
459     
460     resultList = pdbquery.executePDBFTSRestRequest(pdbRequest);
461
462     lastPdbRequest = pdbRequest;
463     return resultList;
464   }
465
466   public FTSRestResponse joinResponses(FTSRestResponse upResponse,
467           FTSRestResponse pdbResponse)
468   {
469     boolean hasPdbResp = lastPdbRequest != null;
470
471     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
472     // join on
473     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
474     int pdbIdx = hasPdbResp ? lastPdbRequest.getFieldIndex("PDB Id") : -1;
475     int pdbTitle_idx = hasPdbResp ? lastPdbRequest.getFieldIndex("Title")
476             : -1;
477     int tdbTitle_idx = getLastFTSRequest().getFieldIndex("Title");
478
479     List<FTSData> joinedRows = new ArrayList<FTSData>();
480     for (final FTSData row : upResponse.getSearchSummary())
481     {
482       String id = (String) row.getSummaryData()[idx_modelId];
483       String provider = (String) row.getSummaryData()[idx_provider];
484       if ("PDBe".equalsIgnoreCase(provider))
485       {
486         if (!hasPdbResp)
487         {
488           System.out.println(
489                   "Warning: seems like we couldn't get to the PDBe search interface.");
490         }
491         else
492         {
493           for (final FTSData pdbrow : pdbResponse.getSearchSummary())
494           {
495             String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
496             if (id.equalsIgnoreCase(pdbid))
497             {
498               row.getSummaryData()[tdbTitle_idx] = pdbrow
499                       .getSummaryData()[pdbTitle_idx];
500             }
501           }
502         }
503
504       }
505       else
506       {
507         row.getSummaryData()[tdbTitle_idx] = "Model from TDB";
508       }
509     }
510     return upResponse;
511   }
512
513 }