JAL-3829 allow client-side selection of structures for a sequence by passing in disco...
[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 static int MAX_QLENGTH = 7820;
40
41   protected FTSRestRequest lastTdbRequest;
42
43   protected FTSRestClientI tdbRestClient;
44
45   private FTSRestRequest lastPdbRequest;
46
47   public ThreeDBStructureChooserQuerySource()
48   {
49     tdbRestClient = TDBeaconsFTSRestClient.getInstance();
50     docFieldPrefs = new FTSDataColumnPreferences(
51             PreferenceSource.STRUCTURE_CHOOSER,
52             TDBeaconsFTSRestClient.getInstance());
53
54   }
55
56   /**
57    * Builds a query string for a given sequences using its DBRef entries 3d
58    * Beacons is only useful for uniprot IDs
59    * 
60    * @param seq
61    *          the sequences to build a query for
62    * @return the built query string
63    */
64
65   public String buildQuery(SequenceI seq)
66   {
67     boolean isPDBRefsFound = false;
68     boolean isUniProtRefsFound = false;
69     StringBuilder queryBuilder = new StringBuilder();
70     Set<String> seqRefs = new LinkedHashSet<>();
71
72     /*
73      * note PDBs as DBRefEntry so they are not duplicated in query
74      */
75     Set<String> pdbids = new HashSet<>();
76
77     List<DBRefEntry> refs = seq.getDBRefs();
78     if (refs != null && refs.size() != 0)
79     {
80       for (int ib = 0, nb = refs.size(); ib < nb; ib++)
81       {
82         DBRefEntry dbRef = refs.get(ib);
83         if (isValidSeqName(getDBRefId(dbRef))
84                 && queryBuilder.length() < MAX_QLENGTH)
85         {
86           if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT)
87                   && dbRef.isCanonical())
88           {
89             // TODO: pick best Uniprot accession
90             isUniProtRefsFound = true;
91             return getDBRefId(dbRef);
92
93           }
94         }
95       }
96     }
97     return null;
98   }
99
100   /**
101    * Ensures sequence ref names are not less than 3 characters and does not
102    * contain a database name
103    * 
104    * @param seqName
105    * @return
106    */
107   static boolean isValidSeqName(String seqName)
108   {
109     // System.out.println("seqName : " + seqName);
110     String ignoreList = "pdb,uniprot,swiss-prot";
111     if (seqName.length() < 3)
112     {
113       return false;
114     }
115     if (seqName.contains(":"))
116     {
117       return false;
118     }
119     seqName = seqName.toLowerCase();
120     for (String ignoredEntry : ignoreList.split(","))
121     {
122       if (seqName.contains(ignoredEntry))
123       {
124         return false;
125       }
126     }
127     return true;
128   }
129
130   static String getDBRefId(DBRefEntry dbRef)
131   {
132     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
133     return ref;
134   }
135
136   /**
137    * FTSRestClient specific query builder to recover associated structure data
138    * records for a sequence
139    * 
140    * @param seq
141    *          - seq to generate a query for
142    * @param wantedFields
143    *          - fields to retrieve
144    * @param selectedFilterOpt
145    *          - criterion for ranking results (e.g. resolution)
146    * @param b
147    *          - sort ascending or descending
148    * @return
149    * @throws Exception
150    */
151   public FTSRestResponse fetchStructuresMetaData(SequenceI seq,
152           Collection<FTSDataColumnI> wantedFields,
153           FilterOption selectedFilterOpt, boolean b) throws Exception
154   {
155     FTSRestResponse resultList;
156     FTSRestRequest tdbRequest = getTDBeaconsRequest(seq, wantedFields);
157     resultList = tdbRestClient.executeRequest(tdbRequest);
158
159     lastTdbRequest = tdbRequest;
160     return resultList;
161   }
162
163   private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
164           Collection<FTSDataColumnI> wantedFields)
165   {
166     FTSRestRequest pdbRequest = new FTSRestRequest();
167     pdbRequest.setAllowEmptySeq(false);
168     pdbRequest.setResponseSize(500);
169     pdbRequest.setWantedFields(wantedFields);
170     String query = buildQuery(seq);
171     if (query == null)
172     {
173       return null;
174     }
175     pdbRequest.setSearchTerm(query + ".json");
176     pdbRequest.setAssociatedSequence(seq);
177     return pdbRequest;
178   }
179
180   @Override
181   public List<FilterOption> getAvailableFilterOptions(String VIEWS_FILTER)
182   {
183     List<FilterOption> filters = new ArrayList<FilterOption>();
184     filters.add(
185             new FilterOption(MessageManager.getString("label.best_quality"),
186                     "overall_quality", VIEWS_FILTER, false));
187     filters.add(new FilterOption(
188             MessageManager.getString("label.best_resolution"), "resolution",
189             VIEWS_FILTER, false));
190     filters.add(new FilterOption(
191             MessageManager.getString("label.most_protein_chain"),
192             "number_of_protein_chains", VIEWS_FILTER, false));
193     filters.add(new FilterOption(
194             MessageManager.getString("label.most_bound_molecules"),
195             "number_of_bound_molecules", VIEWS_FILTER, false));
196     filters.add(new FilterOption(
197             MessageManager.getString("label.most_polymer_residues"),
198             "number_of_polymer_residues", VIEWS_FILTER, true));
199
200     return filters;
201   }
202
203   /**
204    * model categories - update as needed. warnings output if unknown types
205    * encountered.
206    * 
207    * Order denotes 'trust'
208    */
209   private static List<String> EXP_CATEGORIES = Arrays
210           .asList(new String[]
211           { "EXPERIMENTALLY DETERMINED", "DEEP LEARNING", "TEMPLATE-BASED" });
212
213   /**
214    * FTSRestClient specific query builder to pick top ranked entry from a
215    * fetchStructuresMetaData query
216    * 
217    * @param seq
218    *          - seq to generate a query for
219    * @param wantedFields
220    *          - fields to retrieve
221    * @param selectedFilterOpt
222    *          - criterion for ranking results (e.g. resolution)
223    * @param b
224    *          - sort ascending or descending
225    * @return
226    * @throws Exception
227    */
228   public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
229           Collection<FTSData> collectedResults,
230           Collection<FTSDataColumnI> wantedFields, String fieldToFilterBy,
231           boolean b) throws Exception
232   {
233
234     List<FTSData> filteredResponse = new ArrayList<FTSData>();
235     final int idx_ups = lastTdbRequest.getFieldIndex("Uniprot Start");
236     final int idx_upe = lastTdbRequest.getFieldIndex("Uniprot End");
237     final int idx_mcat = lastTdbRequest.getFieldIndex("Model Category");
238     final int idx_mqual = lastTdbRequest.getFieldIndex("Qmean");
239     final int idx_resol = lastTdbRequest.getFieldIndex("Resolution");
240
241     // ignore anything outside the sequence region
242     for (FTSData row : collectedResults)
243     {
244       int up_s = (Integer) row.getSummaryData()[idx_ups];
245       int up_e = (Integer) row.getSummaryData()[idx_upe];
246
247       if (seq == row.getSummaryData()[0] && up_e > seq.getStart()
248               && up_s < seq.getEnd())
249       {
250         filteredResponse.add(row);
251       }
252     }
253     // sort according to decreasing length,
254     // increasing start
255     Collections.sort(filteredResponse, new Comparator<FTSData>()
256     {
257
258       private final int scoreCategory(String cat)
259       {
260         // TODO: make quicker
261         int idx = EXP_CATEGORIES.indexOf(cat.toUpperCase());
262         if (idx == -1)
263         {
264           System.out.println("Unknown category: '" + cat + "'");
265         }
266         return -EXP_CATEGORIES.size() - idx;
267       }
268
269       @Override
270       public int compare(FTSData o1, FTSData o2)
271       {
272         int o1_s = (Integer) o1.getSummaryData()[idx_ups];
273         int o1_e = (Integer) o1.getSummaryData()[idx_upe];
274         int o1_cat = scoreCategory((String) o1.getSummaryData()[idx_mcat]);
275         int o2_s = (Integer) o2.getSummaryData()[idx_ups];
276         int o2_e = (Integer) o2.getSummaryData()[idx_upe];
277         int o2_cat = scoreCategory((String) o2.getSummaryData()[idx_mcat]);
278
279         if (o1_cat == o2_cat)
280         {
281           if (o1_s == o2_s)
282           {
283             int o1_xtent = o1_e - o1_s;
284             int o2_xtent = o2_e - o2_s;
285             if (o1_xtent == o2_xtent)
286             {
287               if (o1_cat == scoreCategory(EXP_CATEGORIES.get(0)))
288               {
289                 // experimental structures, so rank on quality
290                 double o1_res = (Double) o1.getSummaryData()[idx_resol];
291                 double o2_res = (Double) o2.getSummaryData()[idx_resol];
292                 return (o2_res < o1_res) ? 1 : (o2_res == o1_res) ? 0 : -1;
293               }
294               else
295               {
296                 // models, so rank on qmean
297                 float o1_mq = (Float) o1.getSummaryData()[idx_mqual];
298                 float o2_mq = (Float) o2.getSummaryData()[idx_mqual];
299                 return (o2_mq < o1_mq) ? 1 : (o2_mq == o1_mq) ? 0 : -1;
300               }
301             }
302             else
303             {
304               return o1_xtent - o2_xtent;
305             }
306           }
307           else
308           {
309             return o1_s - o2_s;
310           }
311         }
312         else
313         {
314           return o2_cat - o1_cat;
315         }
316       }
317
318       @Override
319       public boolean equals(Object obj)
320       {
321         return super.equals(obj);
322       }
323     });
324     FTSRestResponse resultList = new FTSRestResponse();
325     resultList.setNumberOfItemsFound(filteredResponse.size());
326     resultList.setSearchSummary(filteredResponse);
327     return resultList;
328   }
329
330   @Override
331   public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
332           List<SequenceI> selectedSeqsToView)
333   {
334     int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
335
336     PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
337     int count = 0;
338     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
339     int urlColumnIndex = restable.getColumn("Url").getModelIndex();
340     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
341     int categoryColumnIndex = restable.getColumn("Model Category")
342             .getModelIndex();
343
344     for (int row : selectedRows)
345     {
346       // unique id - could be a horrible hash
347
348       String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
349       String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
350       String typeColumn = restable.getValueAt(row, typeColumnIndex)
351               .toString();
352       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
353               refSeqColIndex);
354       selectedSeqsToView.add(selectedSeq);
355       PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
356       if (pdbEntry == null)
357       {
358         pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
359       }
360
361       if (pdbEntry == null)
362       {
363         pdbEntry = new PDBEntry();
364         pdbEntry.setId(pdbIdStr);
365         pdbEntry.setType(PDBEntry.Type.MMCIF);
366         if (!"PDBe".equalsIgnoreCase(typeColumn))
367         {
368           pdbEntry.setRetrievalUrl(urlStr);
369         }
370         selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
371       }
372       pdbEntriesToView[count++] = pdbEntry;
373     }
374     return pdbEntriesToView;
375   }
376
377   @Override
378   protected FTSRestRequest getLastFTSRequest()
379   {
380     return lastTdbRequest;
381   }
382
383   /**
384    * generate a query for PDBFTS to retrieve structure metadata
385    * 
386    * @param ftsRestRequest
387    * @param upResponse
388    * @return
389    */
390
391   public String buildPDBFTSQueryFor(FTSRestResponse upResponse)
392   {
393     List<String> pdbIds = new ArrayList<String>();
394     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
395     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
396     for (FTSData row : upResponse.getSearchSummary())
397     {
398       String id = (String) row.getSummaryData()[idx_modelId];
399       String provider = (String) row.getSummaryData()[idx_provider];
400       if ("PDBe".equalsIgnoreCase(provider))
401       {
402         pdbIds.add(id);
403       }
404     }
405     return String.join(" OR ", pdbIds).toString();
406   }
407
408   /**
409    * query PDBe for structure metadata
410    * 
411    * @param pdbquery
412    * @param upResponse
413    * @return FTSRestResponse via PDBStructureChooserQuerySource
414    */
415   public FTSRestResponse fetchStructuresMetaDataFor(
416           PDBStructureChooserQuerySource pdbquery,
417           FTSRestResponse upResponse) throws Exception
418   {
419
420     String pdb_Query = buildPDBFTSQueryFor(upResponse);
421
422     FTSRestResponse resultList;
423     FTSRestRequest pdbRequest = new FTSRestRequest();
424     pdbRequest.setAllowEmptySeq(false);
425     pdbRequest.setResponseSize(500);
426     pdbRequest.setFieldToSearchBy("(");
427     // pdbRequest.setFieldToSortBy("pdb_id");
428     pdbRequest.setWantedFields(
429             pdbquery.getDocFieldPrefs().getStructureSummaryFields());
430     pdbRequest.setSearchTerm(pdb_Query + ")");
431     resultList = pdbquery.executePDBFTSRestRequest(pdbRequest);
432
433     lastPdbRequest = pdbRequest;
434     return resultList;
435   }
436
437   public FTSRestResponse joinResponses(FTSRestResponse upResponse,
438           FTSRestResponse pdbResponse)
439   {
440     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
441     // join on
442     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
443     int pdbIdx = lastPdbRequest.getFieldIndex("pdb_id");
444     for (FTSData row : upResponse.getSearchSummary())
445     {
446       String id = (String) row.getSummaryData()[idx_modelId];
447       String provider = (String) row.getSummaryData()[idx_provider];
448       if ("PDBe".equalsIgnoreCase(provider))
449       {
450         for (FTSData pdbrow : pdbResponse.getSearchSummary())
451         {
452           String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
453           if (id.equalsIgnoreCase(pdbid))
454           {
455             // often multiple entries per PDB ID so we bail after first
456             // get wanted fields
457             // append to FTSRestResponse array
458           }
459         }
460       }
461     }
462     // TODO Auto-generated method stub
463     return null;
464   }
465
466 }