d64d643d436f2c85be3d89db6068622026344906
[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 final String FILTER_TDBEACONS_COVERAGE = "3d_beacons_coverage";
40
41   private static int MAX_QLENGTH = 7820;
42
43   protected FTSRestRequest lastTdbRequest;
44
45   protected FTSRestClientI tdbRestClient;
46
47   private FTSRestRequest lastPdbRequest;
48
49   public ThreeDBStructureChooserQuerySource()
50   {
51     tdbRestClient = TDBeaconsFTSRestClient.getInstance();
52     docFieldPrefs = new FTSDataColumnPreferences(
53             PreferenceSource.STRUCTURE_CHOOSER,
54             TDBeaconsFTSRestClient.getInstance());
55
56   }  
57
58   /**
59    * Builds a query string for a given sequences using its DBRef entries 3d
60    * Beacons is only useful for uniprot IDs
61    * 
62    * @param seq
63    *          the sequences to build a query for
64    * @return the built query string
65    */
66
67   public String buildQuery(SequenceI seq)
68   {
69     boolean isPDBRefsFound = false;
70     boolean isUniProtRefsFound = false;
71     StringBuilder queryBuilder = new StringBuilder();
72     Set<String> seqRefs = new LinkedHashSet<>();
73
74     /*
75      * note PDBs as DBRefEntry so they are not duplicated in query
76      */
77     Set<String> pdbids = new HashSet<>();
78
79     List<DBRefEntry> refs = seq.getDBRefs();
80     if (refs != null && refs.size() != 0)
81     {
82       for (int ib = 0, nb = refs.size(); ib < nb; ib++)
83       {
84         DBRefEntry dbRef = refs.get(ib);
85         if (isValidSeqName(getDBRefId(dbRef))
86                 && queryBuilder.length() < MAX_QLENGTH)
87         {
88           if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT)
89                   && dbRef.isCanonical())
90           {
91             // TODO: pick best Uniprot accession
92             isUniProtRefsFound = true;
93             return getDBRefId(dbRef);
94
95           }
96         }
97       }
98     }
99     return null;
100   }
101
102   /**
103    * Ensures sequence ref names are not less than 3 characters and does not
104    * contain a database name
105    * 
106    * @param seqName
107    * @return
108    */
109   static boolean isValidSeqName(String seqName)
110   {
111     // System.out.println("seqName : " + seqName);
112     String ignoreList = "pdb,uniprot,swiss-prot";
113     if (seqName.length() < 3)
114     {
115       return false;
116     }
117     if (seqName.contains(":"))
118     {
119       return false;
120     }
121     seqName = seqName.toLowerCase();
122     for (String ignoredEntry : ignoreList.split(","))
123     {
124       if (seqName.contains(ignoredEntry))
125       {
126         return false;
127       }
128     }
129     return true;
130   }
131
132   static String getDBRefId(DBRefEntry dbRef)
133   {
134     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
135     return ref;
136   }
137
138   /**
139    * FTSRestClient specific query builder to recover associated structure data
140    * records for a sequence
141    * 
142    * @param seq
143    *          - seq to generate a query for
144    * @param wantedFields
145    *          - fields to retrieve
146    * @param selectedFilterOpt
147    *          - criterion for ranking results (e.g. resolution)
148    * @param b
149    *          - sort ascending or descending
150    * @return
151    * @throws Exception
152    */
153   public FTSRestResponse fetchStructuresMetaData(SequenceI seq,
154           Collection<FTSDataColumnI> wantedFields,
155           FilterOption selectedFilterOpt, boolean b) throws Exception
156   {
157     FTSRestResponse resultList;
158     if (tdBeaconsFilter(selectedFilterOpt.getValue())) {
159     FTSRestRequest tdbRequest = getTDBeaconsRequest(seq, wantedFields);
160     resultList = tdbRestClient.executeRequest(tdbRequest);
161
162     lastTdbRequest = tdbRequest;
163     
164     // Query the PDB and add additional metadata
165     FTSRestResponse pdbResponse = fetchStructuresMetaDataFor(getPDBQuerySource(), resultList);
166     FTSRestResponse joinedResp = joinResponses(resultList,
167             pdbResponse);
168     return resultList;
169     } 
170     // use the PDBFTS directly
171     resultList = getPDBQuerySource().fetchStructuresMetaData(seq, wantedFields, selectedFilterOpt, b);
172     lastTdbRequest = getPDBQuerySource().lastPdbRequest;
173     lastPdbRequest = lastTdbRequest; // both queries the same - indicates we rank using PDBe
174     return resultList;
175     
176   }
177
178   PDBStructureChooserQuerySource pdbQuerySource=null;
179   private PDBStructureChooserQuerySource getPDBQuerySource()
180   {
181     if (pdbQuerySource==null)
182     {
183       pdbQuerySource = new PDBStructureChooserQuerySource();
184     }
185     return pdbQuerySource;
186   }
187
188   private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
189           Collection<FTSDataColumnI> wantedFields)
190   {
191     FTSRestRequest pdbRequest = new FTSRestRequest();
192     pdbRequest.setAllowEmptySeq(false);
193     pdbRequest.setResponseSize(500);
194     pdbRequest.setWantedFields(wantedFields);
195     String query = buildQuery(seq);
196     if (query == null)
197     {
198       return null;
199     }
200     pdbRequest.setSearchTerm(query + ".json");
201     pdbRequest.setAssociatedSequence(seq);
202     return pdbRequest;
203   }
204
205   @Override
206   public List<FilterOption> getAvailableFilterOptions(String VIEWS_FILTER)
207   {
208     List<FilterOption> filters = getPDBQuerySource().getAvailableFilterOptions(VIEWS_FILTER);
209     
210     filters.add(0,
211             new FilterOption("3D-Beacons Coverage",
212                     FILTER_TDBEACONS_COVERAGE, VIEWS_FILTER, true, this));
213     return filters;
214   }
215   private boolean tdBeaconsFilter(String fieldToFilterBy)
216   {
217     return FILTER_TDBEACONS_COVERAGE.equals(fieldToFilterBy);
218   }
219
220   @Override
221   public boolean needsRefetch(FilterOption selectedFilterOpt)
222   {
223     return tdBeaconsFilter(selectedFilterOpt.getValue()) && lastPdbRequest!=lastTdbRequest;
224   }
225   /**
226    * FTSRestClient specific query builder to pick top ranked entry from a
227    * fetchStructuresMetaData query
228    * 
229    * @param seq
230    *          - seq to generate a query for
231    * @param wantedFields
232    *          - fields to retrieve
233    * @param selectedFilterOpt
234    *          - criterion for ranking results (e.g. resolution)
235    * @param b
236    *          - sort ascending or descending
237    * @return
238    * @throws Exception
239    */
240   public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
241           Collection<FTSData> collectedResults,
242           Collection<FTSDataColumnI> wantedFields, String fieldToFilterBy,
243           boolean b) throws Exception
244   {
245     if (tdBeaconsFilter(fieldToFilterBy))
246     {
247       TDBResultAnalyser analyser = new TDBResultAnalyser(seq,
248               collectedResults, lastTdbRequest);
249
250       FTSRestResponse resultList = new FTSRestResponse();
251
252       List<FTSData> filteredResponse = analyser.getFilteredResponse();
253
254       List<FTSData> selectedStructures = analyser
255               .selectStructures(filteredResponse);
256       resultList.setNumberOfItemsFound(selectedStructures.size());
257       resultList.setSearchSummary(selectedStructures);
258       return resultList;
259     }
260     // Fall back to PDBe rankings
261     return getPDBQuerySource().selectFirstRankedQuery(seq, collectedResults, wantedFields, fieldToFilterBy, b);
262   }
263
264
265   @Override
266   public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
267           List<SequenceI> selectedSeqsToView)
268   {
269     int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
270
271     PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
272     int count = 0;
273     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
274     int urlColumnIndex = restable.getColumn("Url").getModelIndex();
275     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
276     int categoryColumnIndex = restable.getColumn("Model Category")
277             .getModelIndex();
278     final int up_start_idx = restable.getColumn("Uniprot Start").getModelIndex();
279     final int up_end_idx = restable.getColumn("Uniprot End").getModelIndex();
280     int i=0;
281     
282     // bleugh!
283     Integer[] sellist = new Integer[selectedRows.length];
284     for (Integer row: selectedRows)
285     {
286       sellist[i++] = row;
287     }
288     // Sort rows by coverage
289     Arrays.sort(sellist,new Comparator<Integer>()
290     {
291       @Override
292       public int compare(Integer o1, Integer o2)
293       {
294         int o1_xt = ((Integer)restable.getValueAt(o1, up_end_idx)) - (Integer)restable.getValueAt(o1, up_start_idx);
295         int o2_xt = ((Integer)restable.getValueAt(o2, up_end_idx)) - (Integer)restable.getValueAt(o2, up_start_idx);
296         return o2_xt-o1_xt;
297       }
298     });
299
300     
301     for (int row : sellist)
302     {
303       // unique id - could be a horrible hash
304
305       String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
306       String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
307       String typeColumn = restable.getValueAt(row, typeColumnIndex)
308               .toString();
309       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
310               refSeqColIndex);
311       selectedSeqsToView.add(selectedSeq);
312       PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
313       if (pdbEntry == null)
314       {
315         pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
316       }
317
318       if (pdbEntry == null)
319       {
320         pdbEntry = new PDBEntry();
321         pdbEntry.setId(pdbIdStr);
322         pdbEntry.setType(PDBEntry.Type.MMCIF);
323         if (!"PDBe".equalsIgnoreCase(typeColumn))
324         {
325           pdbEntry.setRetrievalUrl(urlStr);
326         }
327         selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
328       }
329       pdbEntriesToView[count++] = pdbEntry;
330     }
331     return pdbEntriesToView;
332   }
333
334   @Override
335   protected FTSRestRequest getLastFTSRequest()
336   {
337     return lastTdbRequest;
338   }
339
340   /**
341    * generate a query for PDBFTS to retrieve structure metadata
342    * 
343    * @param ftsRestRequest
344    * @param upResponse
345    * @return
346    */
347
348   public String buildPDBFTSQueryFor(FTSRestResponse upResponse)
349   {
350     List<String> pdbIds = new ArrayList<String>();
351     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
352     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
353     for (FTSData row : upResponse.getSearchSummary())
354     {
355       String id = (String) row.getSummaryData()[idx_modelId];
356       String provider = (String) row.getSummaryData()[idx_provider];
357       if ("PDBe".equalsIgnoreCase(provider))
358       {
359         pdbIds.add(id);
360       }
361     }
362     return String.join(" OR ", pdbIds).toString();
363   }
364
365   /**
366    * query PDBe for structure metadata
367    * 
368    * @param pdbquery
369    * @param upResponse
370    * @return FTSRestResponse via PDBStructureChooserQuerySource
371    */
372   public FTSRestResponse fetchStructuresMetaDataFor(
373           PDBStructureChooserQuerySource pdbquery,
374           FTSRestResponse upResponse) throws Exception
375   {
376
377     String pdb_Query = buildPDBFTSQueryFor(upResponse);
378
379     FTSRestResponse resultList;
380     FTSRestRequest pdbRequest = new FTSRestRequest();
381     pdbRequest.setAllowEmptySeq(false);
382     pdbRequest.setResponseSize(500);
383     pdbRequest.setFieldToSearchBy("(");
384     // pdbRequest.setFieldToSortBy("pdb_id");
385     pdbRequest.setWantedFields(
386             pdbquery.getDocFieldPrefs().getStructureSummaryFields());
387     pdbRequest.setSearchTerm(pdb_Query + ")");
388     resultList = pdbquery.executePDBFTSRestRequest(pdbRequest);
389
390     lastPdbRequest = pdbRequest;
391     return resultList;
392   }
393
394   public FTSRestResponse joinResponses(FTSRestResponse upResponse,
395           FTSRestResponse pdbResponse)
396   {
397     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
398     // join on
399     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
400     int pdbIdx = lastPdbRequest.getFieldIndex("PDB Id");
401     int pdbTitle_idx = lastPdbRequest.getFieldIndex("Title");
402     int tdbTitle_idx = getLastFTSRequest().getFieldIndex("Title");
403     
404     List<FTSData> joinedRows = new ArrayList<FTSData>();
405     for (final FTSData row : upResponse.getSearchSummary())
406     {
407       String id = (String) row.getSummaryData()[idx_modelId];
408       String provider = (String) row.getSummaryData()[idx_provider];
409       if ("PDBe".equalsIgnoreCase(provider))
410       {
411         for (final FTSData pdbrow : pdbResponse.getSearchSummary())
412         {
413           String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
414           if (id.equalsIgnoreCase(pdbid))
415           {
416             row.getSummaryData()[tdbTitle_idx] = pdbrow
417                     .getSummaryData()[pdbTitle_idx];
418           }
419         }
420       } else {
421         row.getSummaryData()[tdbTitle_idx] = "Model from TDB";
422       }
423     }
424     return upResponse;
425   }
426
427 }