JAL-3829 avoid sending rubbish to PDBe search api
[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.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 import jalview.util.MessageManager;
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     if (refs != null && refs.size() != 0)
90     {
91       for (int ib = 0, nb = refs.size(); ib < nb; ib++)
92       {
93         DBRefEntry dbRef = refs.get(ib);
94         if (isValidSeqName(getDBRefId(dbRef))
95                 && queryBuilder.length() < MAX_QLENGTH)
96         {
97           if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT)
98                   && dbRef.isCanonical())
99           {
100             // TODO: pick best Uniprot accession
101             isUniProtRefsFound = true;
102             return getDBRefId(dbRef);
103
104           }
105         }
106       }
107     }
108     return null;
109   }
110
111   /**
112    * Ensures sequence ref names are not less than 3 characters and does not
113    * contain a database name
114    * 
115    * @param seqName
116    * @return
117    */
118   static boolean isValidSeqName(String seqName)
119   {
120     // System.out.println("seqName : " + seqName);
121     String ignoreList = "pdb,uniprot,swiss-prot";
122     if (seqName.length() < 3)
123     {
124       return false;
125     }
126     if (seqName.contains(":"))
127     {
128       return false;
129     }
130     seqName = seqName.toLowerCase();
131     for (String ignoredEntry : ignoreList.split(","))
132     {
133       if (seqName.contains(ignoredEntry))
134       {
135         return false;
136       }
137     }
138     return true;
139   }
140
141   static String getDBRefId(DBRefEntry dbRef)
142   {
143     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
144     return ref;
145   }
146
147   /**
148    * FTSRestClient specific query builder to recover associated structure data
149    * records for a sequence
150    * 
151    * @param seq
152    *          - seq to generate a query for
153    * @param wantedFields
154    *          - fields to retrieve
155    * @param selectedFilterOpt
156    *          - criterion for ranking results (e.g. resolution)
157    * @param b
158    *          - sort ascending or descending
159    * @return
160    * @throws Exception
161    */
162   public FTSRestResponse fetchStructuresMetaData(SequenceI seq,
163           Collection<FTSDataColumnI> wantedFields,
164           FilterOption selectedFilterOpt, boolean b) throws Exception
165   {
166     FTSRestResponse resultList;
167     if (selectedFilterOpt!=null && tdBeaconsFilter(selectedFilterOpt.getValue()))
168     {
169       FTSRestRequest tdbRequest = getTDBeaconsRequest(seq, wantedFields);
170       resultList = tdbRestClient.executeRequest(tdbRequest);
171
172       lastTdbRequest = tdbRequest;
173
174       // Query the PDB and add additional metadata
175       FTSRestResponse pdbResponse = fetchStructuresMetaDataFor(
176               getPDBQuerySource(), resultList);
177       FTSRestResponse joinedResp = joinResponses(resultList, pdbResponse);
178       return resultList;
179     }
180     // use the PDBFTS directly
181     resultList = getPDBQuerySource().fetchStructuresMetaData(seq,
182             wantedFields, selectedFilterOpt, b);
183     lastTdbRequest = getPDBQuerySource().lastPdbRequest;
184     lastPdbRequest = lastTdbRequest; // both queries the same - indicates we
185                                      // rank using PDBe
186     return resultList;
187
188   }
189
190   PDBStructureChooserQuerySource pdbQuerySource = null;
191
192   private PDBStructureChooserQuerySource getPDBQuerySource()
193   {
194     if (pdbQuerySource == null)
195     {
196       pdbQuerySource = new PDBStructureChooserQuerySource();
197     }
198     return pdbQuerySource;
199   }
200
201   private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
202           Collection<FTSDataColumnI> wantedFields)
203   {
204     FTSRestRequest pdbRequest = new FTSRestRequest();
205     pdbRequest.setAllowEmptySeq(false);
206     pdbRequest.setResponseSize(500);
207     pdbRequest.setWantedFields(wantedFields);
208     String query = buildQuery(seq);
209     if (query == null)
210     {
211       return null;
212     }
213     pdbRequest.setSearchTerm(query + ".json");
214     pdbRequest.setAssociatedSequence(seq);
215     return pdbRequest;
216   }
217
218   @Override
219   public List<FilterOption> getAvailableFilterOptions(String VIEWS_FILTER)
220   {
221     List<FilterOption> filters = getPDBQuerySource()
222             .getAvailableFilterOptions(VIEWS_FILTER);
223     tdBeaconsFilters = new LinkedHashSet<String>();
224     tdBeaconsFilters.addAll(defaultFilters);
225     filters.add(0, new FilterOption("Best 3D-Beacons Coverage",
226             FILTER_FIRST_BEST_COVERAGE, VIEWS_FILTER, false, this));
227     filters.add(1, new FilterOption("Multiple 3D-Beacons Coverage",
228             FILTER_TDBEACONS_COVERAGE, VIEWS_FILTER, true, this));
229
230     return filters;
231   }
232
233   @Override
234   public void updateAvailableFilterOptions(String VIEWS_FILTER,
235           List<FilterOption> xtantOptions, Collection<FTSData> tdbEntries)
236   {
237     if (tdbEntries !=null && lastTdbRequest != null)
238     {
239       int prov_idx = lastTdbRequest.getFieldIndex("Provider");
240
241       for (FTSData row : tdbEntries)
242       {
243         String provider = (String) row.getSummaryData()[prov_idx];
244         FilterOption providerOpt = new FilterOption("3DB Provider - " + provider,
245                 FILTER_SOURCE_PREFIX + provider, VIEWS_FILTER,
246                 false, this);
247         if (!xtantOptions.contains(providerOpt))
248         {
249           xtantOptions.add(1,
250                   providerOpt);
251           tdBeaconsFilters.add(FILTER_SOURCE_PREFIX+provider);
252
253         }
254       }
255     }
256
257   }
258
259   private boolean tdBeaconsFilter(String fieldToFilterBy)
260   {
261     return tdBeaconsFilters != null
262             && tdBeaconsFilters.contains(fieldToFilterBy);
263   }
264
265   private String remove_prefix(String fieldToFilterBy)
266   {
267     if (tdBeaconsFilters != null
268             && tdBeaconsFilters.contains(fieldToFilterBy)
269             && !defaultFilters.contains(fieldToFilterBy))
270     {
271       return fieldToFilterBy.substring(FILTER_SOURCE_PREFIX.length());
272     }
273     else
274     {
275       return null;
276     }
277   }
278
279   @Override
280   public boolean needsRefetch(FilterOption selectedFilterOpt)
281   {
282     return selectedFilterOpt==null || !tdBeaconsFilter(selectedFilterOpt.getValue())
283             && lastPdbRequest != lastTdbRequest;
284   }
285
286   /**
287    * FTSRestClient specific query builder to pick top ranked entry from a
288    * fetchStructuresMetaData query
289    * 
290    * @param seq
291    *          - seq to generate a query for
292    * @param wantedFields
293    *          - fields to retrieve
294    * @param selectedFilterOpt
295    *          - criterion for ranking results (e.g. resolution)
296    * @param b
297    *          - sort ascending or descending
298    * @return
299    * @throws Exception
300    */
301   public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
302           Collection<FTSData> collectedResults,
303           Collection<FTSDataColumnI> wantedFields, String fieldToFilterBy,
304           boolean b) throws Exception
305   {
306     if (fieldToFilterBy!=null && tdBeaconsFilter(fieldToFilterBy))
307     {
308       TDBResultAnalyser analyser = new TDBResultAnalyser(seq,
309               collectedResults, lastTdbRequest, fieldToFilterBy,
310               remove_prefix(fieldToFilterBy));
311
312       FTSRestResponse resultList = new FTSRestResponse();
313
314       List<FTSData> filteredResponse = analyser.getFilteredResponse();
315
316       List<FTSData> selectedStructures = analyser
317               .selectStructures(filteredResponse);
318       resultList.setNumberOfItemsFound(selectedStructures.size());
319       resultList.setSearchSummary(selectedStructures);
320       return resultList;
321     }
322     // Fall back to PDBe rankings
323     return getPDBQuerySource().selectFirstRankedQuery(seq, collectedResults,
324             wantedFields, fieldToFilterBy, b);
325   }
326
327   @Override
328   public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
329           List<SequenceI> selectedSeqsToView)
330   {
331     int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
332
333     PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
334     int count = 0;
335     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
336     int urlColumnIndex = restable.getColumn("Url").getModelIndex();
337     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
338     int categoryColumnIndex = restable.getColumn("Model Category")
339             .getModelIndex();
340     final int up_start_idx = restable.getColumn("Uniprot Start")
341             .getModelIndex();
342     final int up_end_idx = restable.getColumn("Uniprot End")
343             .getModelIndex();
344     int i = 0;
345
346     // bleugh!
347     Integer[] sellist = new Integer[selectedRows.length];
348     for (Integer row : selectedRows)
349     {
350       sellist[i++] = row;
351     }
352     // Sort rows by coverage
353     Arrays.sort(sellist, new Comparator<Integer>()
354     {
355       @Override
356       public int compare(Integer o1, Integer o2)
357       {
358         int o1_xt = ((Integer) restable.getValueAt(o1, up_end_idx))
359                 - (Integer) restable.getValueAt(o1, up_start_idx);
360         int o2_xt = ((Integer) restable.getValueAt(o2, up_end_idx))
361                 - (Integer) restable.getValueAt(o2, up_start_idx);
362         return o2_xt - o1_xt;
363       }
364     });
365
366     for (int row : sellist)
367     {
368       // unique id - could be a horrible hash
369
370       String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
371       String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
372       String typeColumn = restable.getValueAt(row, typeColumnIndex)
373               .toString();
374       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
375               refSeqColIndex);
376       selectedSeqsToView.add(selectedSeq);
377       PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
378       if (pdbEntry == null)
379       {
380         pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
381       }
382
383       if (pdbEntry == null)
384       {
385         pdbEntry = new PDBEntry();
386         pdbEntry.setId(pdbIdStr);
387         pdbEntry.setType(PDBEntry.Type.MMCIF);
388         if (!"PDBe".equalsIgnoreCase(typeColumn))
389         {
390           pdbEntry.setRetrievalUrl(urlStr);
391         }
392         selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
393       }
394       pdbEntriesToView[count++] = pdbEntry;
395     }
396     return pdbEntriesToView;
397   }
398
399   @Override
400   protected FTSRestRequest getLastFTSRequest()
401   {
402     return lastTdbRequest;
403   }
404
405   /**
406    * generate a query for PDBFTS to retrieve structure metadata
407    * 
408    * @param ftsRestRequest
409    * @param upResponse
410    * @return
411    */
412
413   public String buildPDBFTSQueryFor(FTSRestResponse upResponse)
414   {
415     List<String> pdbIds = new ArrayList<String>();
416     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
417     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
418     for (FTSData row : upResponse.getSearchSummary())
419     {
420       String id = (String) row.getSummaryData()[idx_modelId];
421       String provider = (String) row.getSummaryData()[idx_provider];
422       if ("PDBe".equalsIgnoreCase(provider))
423       {
424         pdbIds.add(id);
425       }
426     }
427     return String.join(" OR ", pdbIds).toString();
428   }
429
430   /**
431    * query PDBe for structure metadata
432    * 
433    * @param pdbquery
434    * @param upResponse
435    * @return FTSRestResponse via PDBStructureChooserQuerySource
436    */
437   public FTSRestResponse fetchStructuresMetaDataFor(
438           PDBStructureChooserQuerySource pdbquery,
439           FTSRestResponse upResponse) throws Exception
440   {
441
442     String pdb_Query = buildPDBFTSQueryFor(upResponse);
443     if (pdb_Query.length()==0)
444     {
445       return null;
446     }
447     FTSRestResponse resultList;
448     FTSRestRequest pdbRequest = new FTSRestRequest();
449     pdbRequest.setAllowEmptySeq(false);
450     pdbRequest.setResponseSize(500);
451     pdbRequest.setFieldToSearchBy("(");
452     // pdbRequest.setFieldToSortBy("pdb_id");
453     pdbRequest.setWantedFields(
454             pdbquery.getDocFieldPrefs().getStructureSummaryFields());
455     pdbRequest.setSearchTerm(pdb_Query + ")");
456     
457     resultList = pdbquery.executePDBFTSRestRequest(pdbRequest);
458
459     lastPdbRequest = pdbRequest;
460     return resultList;
461   }
462
463   public FTSRestResponse joinResponses(FTSRestResponse upResponse,
464           FTSRestResponse pdbResponse)
465   {
466     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
467     // join on
468     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
469     int pdbIdx = lastPdbRequest.getFieldIndex("PDB Id");
470     int pdbTitle_idx = lastPdbRequest.getFieldIndex("Title");
471     int tdbTitle_idx = getLastFTSRequest().getFieldIndex("Title");
472
473     List<FTSData> joinedRows = new ArrayList<FTSData>();
474     for (final FTSData row : upResponse.getSearchSummary())
475     {
476       String id = (String) row.getSummaryData()[idx_modelId];
477       String provider = (String) row.getSummaryData()[idx_provider];
478       if ("PDBe".equalsIgnoreCase(provider))
479       {
480         for (final FTSData pdbrow : pdbResponse.getSearchSummary())
481         {
482           String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
483           if (id.equalsIgnoreCase(pdbid))
484           {
485             row.getSummaryData()[tdbTitle_idx] = pdbrow
486                     .getSummaryData()[pdbTitle_idx];
487           }
488         }
489       }
490       else
491       {
492         row.getSummaryData()[tdbTitle_idx] = "Model from TDB";
493       }
494     }
495     return upResponse;
496   }
497
498 }