JAL-3829 insert title for structures from PDBe
[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     
161     // Query the PDB and add additional metadata
162     FTSRestResponse pdbResponse = fetchStructuresMetaDataFor(getPDBQuerySource(), resultList);
163     FTSRestResponse joinedResp = joinResponses(resultList,
164             pdbResponse);
165     return resultList;
166   }
167
168   PDBStructureChooserQuerySource pdbQuerySource=null;
169   private PDBStructureChooserQuerySource getPDBQuerySource()
170   {
171     if (pdbQuerySource==null)
172     {
173       pdbQuerySource = new PDBStructureChooserQuerySource();
174     }
175     return pdbQuerySource;
176   }
177
178   private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
179           Collection<FTSDataColumnI> wantedFields)
180   {
181     FTSRestRequest pdbRequest = new FTSRestRequest();
182     pdbRequest.setAllowEmptySeq(false);
183     pdbRequest.setResponseSize(500);
184     pdbRequest.setWantedFields(wantedFields);
185     String query = buildQuery(seq);
186     if (query == null)
187     {
188       return null;
189     }
190     pdbRequest.setSearchTerm(query + ".json");
191     pdbRequest.setAssociatedSequence(seq);
192     return pdbRequest;
193   }
194
195   @Override
196   public List<FilterOption> getAvailableFilterOptions(String VIEWS_FILTER)
197   {
198     List<FilterOption> filters = new ArrayList<FilterOption>();
199     filters.add(
200             new FilterOption(MessageManager.getString("label.best_quality"),
201                     "overall_quality", VIEWS_FILTER, false));
202     filters.add(new FilterOption(
203             MessageManager.getString("label.best_resolution"), "resolution",
204             VIEWS_FILTER, false));
205     filters.add(new FilterOption(
206             MessageManager.getString("label.most_protein_chain"),
207             "number_of_protein_chains", VIEWS_FILTER, false));
208     filters.add(new FilterOption(
209             MessageManager.getString("label.most_bound_molecules"),
210             "number_of_bound_molecules", VIEWS_FILTER, false));
211     filters.add(new FilterOption(
212             MessageManager.getString("label.most_polymer_residues"),
213             "number_of_polymer_residues", VIEWS_FILTER, true));
214
215     return filters;
216   }
217
218   /**
219    * FTSRestClient specific query builder to pick top ranked entry from a
220    * fetchStructuresMetaData query
221    * 
222    * @param seq
223    *          - seq to generate a query for
224    * @param wantedFields
225    *          - fields to retrieve
226    * @param selectedFilterOpt
227    *          - criterion for ranking results (e.g. resolution)
228    * @param b
229    *          - sort ascending or descending
230    * @return
231    * @throws Exception
232    */
233   public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
234           Collection<FTSData> collectedResults,
235           Collection<FTSDataColumnI> wantedFields, String fieldToFilterBy,
236           boolean b) throws Exception
237   {
238
239     TDBResultAnalyser analyser= new TDBResultAnalyser(seq,collectedResults, lastTdbRequest);
240     
241     
242     FTSRestResponse resultList = new FTSRestResponse();
243
244     List<FTSData> filteredResponse = analyser.getFilteredResponse();
245     
246     List<FTSData> selectedStructures = analyser.selectStructures(filteredResponse);
247     resultList.setNumberOfItemsFound(selectedStructures.size());
248     resultList.setSearchSummary(selectedStructures);
249     return resultList;
250   }
251
252   @Override
253   public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
254           List<SequenceI> selectedSeqsToView)
255   {
256     int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
257
258     PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
259     int count = 0;
260     int idColumnIndex = restable.getColumn("Model id").getModelIndex();
261     int urlColumnIndex = restable.getColumn("Url").getModelIndex();
262     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
263     int categoryColumnIndex = restable.getColumn("Model Category")
264             .getModelIndex();
265     final int up_start_idx = restable.getColumn("Uniprot Start").getModelIndex();
266     final int up_end_idx = restable.getColumn("Uniprot End").getModelIndex();
267     int i=0;
268     
269     // bleugh!
270     Integer[] sellist = new Integer[selectedRows.length];
271     for (Integer row: selectedRows)
272     {
273       sellist[i++] = row;
274     }
275     // Sort rows by coverage
276     Arrays.sort(sellist,new Comparator<Integer>()
277     {
278       @Override
279       public int compare(Integer o1, Integer o2)
280       {
281         int o1_xt = ((Integer)restable.getValueAt(o1, up_end_idx)) - (Integer)restable.getValueAt(o1, up_start_idx);
282         int o2_xt = ((Integer)restable.getValueAt(o2, up_end_idx)) - (Integer)restable.getValueAt(o2, up_start_idx);
283         return o2_xt-o1_xt;
284       }
285     });
286
287     
288     for (int row : sellist)
289     {
290       // unique id - could be a horrible hash
291
292       String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
293       String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
294       String typeColumn = restable.getValueAt(row, typeColumnIndex)
295               .toString();
296       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
297               refSeqColIndex);
298       selectedSeqsToView.add(selectedSeq);
299       PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
300       if (pdbEntry == null)
301       {
302         pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
303       }
304
305       if (pdbEntry == null)
306       {
307         pdbEntry = new PDBEntry();
308         pdbEntry.setId(pdbIdStr);
309         pdbEntry.setType(PDBEntry.Type.MMCIF);
310         if (!"PDBe".equalsIgnoreCase(typeColumn))
311         {
312           pdbEntry.setRetrievalUrl(urlStr);
313         }
314         selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
315       }
316       pdbEntriesToView[count++] = pdbEntry;
317     }
318     return pdbEntriesToView;
319   }
320
321   @Override
322   protected FTSRestRequest getLastFTSRequest()
323   {
324     return lastTdbRequest;
325   }
326
327   /**
328    * generate a query for PDBFTS to retrieve structure metadata
329    * 
330    * @param ftsRestRequest
331    * @param upResponse
332    * @return
333    */
334
335   public String buildPDBFTSQueryFor(FTSRestResponse upResponse)
336   {
337     List<String> pdbIds = new ArrayList<String>();
338     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
339     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
340     for (FTSData row : upResponse.getSearchSummary())
341     {
342       String id = (String) row.getSummaryData()[idx_modelId];
343       String provider = (String) row.getSummaryData()[idx_provider];
344       if ("PDBe".equalsIgnoreCase(provider))
345       {
346         pdbIds.add(id);
347       }
348     }
349     return String.join(" OR ", pdbIds).toString();
350   }
351
352   /**
353    * query PDBe for structure metadata
354    * 
355    * @param pdbquery
356    * @param upResponse
357    * @return FTSRestResponse via PDBStructureChooserQuerySource
358    */
359   public FTSRestResponse fetchStructuresMetaDataFor(
360           PDBStructureChooserQuerySource pdbquery,
361           FTSRestResponse upResponse) throws Exception
362   {
363
364     String pdb_Query = buildPDBFTSQueryFor(upResponse);
365
366     FTSRestResponse resultList;
367     FTSRestRequest pdbRequest = new FTSRestRequest();
368     pdbRequest.setAllowEmptySeq(false);
369     pdbRequest.setResponseSize(500);
370     pdbRequest.setFieldToSearchBy("(");
371     // pdbRequest.setFieldToSortBy("pdb_id");
372     pdbRequest.setWantedFields(
373             pdbquery.getDocFieldPrefs().getStructureSummaryFields());
374     pdbRequest.setSearchTerm(pdb_Query + ")");
375     resultList = pdbquery.executePDBFTSRestRequest(pdbRequest);
376
377     lastPdbRequest = pdbRequest;
378     return resultList;
379   }
380
381   public FTSRestResponse joinResponses(FTSRestResponse upResponse,
382           FTSRestResponse pdbResponse)
383   {
384     int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
385     // join on
386     int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
387     int pdbIdx = lastPdbRequest.getFieldIndex("PDB Id");
388     int pdbTitle_idx = lastPdbRequest.getFieldIndex("Title");
389     int tdbTitle_idx = getLastFTSRequest().getFieldIndex("Title");
390     
391     List<FTSData> joinedRows = new ArrayList<FTSData>();
392     for (final FTSData row : upResponse.getSearchSummary())
393     {
394       String id = (String) row.getSummaryData()[idx_modelId];
395       String provider = (String) row.getSummaryData()[idx_provider];
396       if ("PDBe".equalsIgnoreCase(provider))
397       {
398         for (final FTSData pdbrow : pdbResponse.getSearchSummary())
399         {
400           String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
401           if (id.equalsIgnoreCase(pdbid))
402           {
403             row.getSummaryData()[tdbTitle_idx] = pdbrow
404                     .getSummaryData()[pdbTitle_idx];
405           }
406         }
407       } else {
408         row.getSummaryData()[tdbTitle_idx] = "Model from TDB";
409       }
410     }
411     return upResponse;
412   }
413
414 }