Clover icon

Coverage Report

  1. Project Clover database Wed Nov 13 2024 16:21:17 GMT
  2. Package jalview.fts.service.pdb

File PDBFTSRestClient.java

 

Coverage histogram

../../../../img/srcFileCovDistChart7.png
29% of files have more coverage

Code metrics

62
151
16
1
558
407
60
0.4
9.44
16
3.75

Classes

Class Line # Actions
PDBFTSRestClient 64 151 60
0.6506550365.1%
 

Contributing tests

This file is covered by 45 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.fts.service.pdb;
22   
23    import java.io.File;
24    import java.io.FileInputStream;
25    import java.io.FileReader;
26    import java.net.URI;
27    import java.nio.CharBuffer;
28    import java.util.ArrayList;
29    import java.util.Collection;
30    import java.util.Iterator;
31    import java.util.List;
32    import java.util.Map;
33    import java.util.Objects;
34   
35    import javax.ws.rs.core.MediaType;
36   
37    import org.json.simple.parser.ParseException;
38   
39    import com.sun.jersey.api.client.Client;
40    import com.sun.jersey.api.client.ClientResponse;
41    import com.sun.jersey.api.client.WebResource;
42    import com.sun.jersey.api.client.config.DefaultClientConfig;
43   
44    import jalview.datamodel.SequenceI;
45    import jalview.fts.api.FTSData;
46    import jalview.fts.api.FTSDataColumnI;
47    import jalview.fts.api.FTSRestClientI;
48    import jalview.fts.api.StructureFTSRestClientI;
49    import jalview.fts.core.FTSDataColumnPreferences;
50    import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
51    import jalview.fts.core.FTSRestClient;
52    import jalview.fts.core.FTSRestRequest;
53    import jalview.fts.core.FTSRestResponse;
54    import jalview.fts.service.alphafold.AlphafoldRestClient;
55    import jalview.util.JSONUtils;
56    import jalview.util.MessageManager;
57    import jalview.util.Platform;
58   
59    /**
60    * A rest client for querying the Search endpoint of the PDB API
61    *
62    * @author tcnofoegbu
63    */
 
64    public class PDBFTSRestClient extends FTSRestClient
65    implements StructureFTSRestClientI
66    {
67   
68    private static FTSRestClientI instance = null;
69   
70    public static final String PDB_SEARCH_ENDPOINT = "https://www.ebi.ac.uk/pdbe/search/pdb/select?";
71   
 
72  5 toggle protected PDBFTSRestClient()
73    {
74    }
75   
76    /**
77    * Takes a PDBRestRequest object and returns a response upon execution
78    *
79    * @param pdbRestRequest
80    * the PDBRestRequest instance to be processed
81    * @return the pdbResponse object for the given request
82    * @throws Exception
83    */
 
84  34 toggle @SuppressWarnings({ "unused", "unchecked" })
85    @Override
86    public FTSRestResponse executeRequest(FTSRestRequest pdbRestRequest)
87    throws Exception
88    {
89  34 try
90    {
91  34 String wantedFields = getDataColumnsFieldsAsCommaDelimitedString(
92    pdbRestRequest.getWantedFields());
93  34 int responseSize = (pdbRestRequest.getResponseSize() == 0)
94    ? getDefaultResponsePageSize()
95    : pdbRestRequest.getResponseSize();
96  34 int offSet = pdbRestRequest.getOffSet();
97  34 String sortParam = null;
98  34 if (pdbRestRequest.getFieldToSortBy() == null
99    || pdbRestRequest.getFieldToSortBy().trim().isEmpty())
100    {
101  32 sortParam = "";
102    }
103    else
104    {
105  2 if (pdbRestRequest.getFieldToSortBy()
106    .equalsIgnoreCase("Resolution"))
107    {
108  0 sortParam = pdbRestRequest.getFieldToSortBy()
109  0 + (pdbRestRequest.isAscending() ? " asc" : " desc");
110    }
111    else
112    {
113  2 sortParam = pdbRestRequest.getFieldToSortBy()
114  2 + (pdbRestRequest.isAscending() ? " desc" : " asc");
115    }
116    }
117   
118  34 String facetPivot = (pdbRestRequest.getFacetPivot() == null
119    || pdbRestRequest.getFacetPivot().isEmpty()) ? ""
120    : pdbRestRequest.getFacetPivot();
121  34 String facetPivotMinCount = String
122    .valueOf(pdbRestRequest.getFacetPivotMinCount());
123   
124  34 String query = pdbRestRequest.getFieldToSearchBy()
125    + pdbRestRequest.getSearchTerm()
126  34 + (pdbRestRequest.isAllowEmptySeq() ? ""
127    : " AND molecule_sequence:['' TO *]")
128  34 + (pdbRestRequest.isAllowUnpublishedEntries() ? ""
129    : " AND status:REL");
130   
131    // Build request parameters for the REST Request
132   
133    // BH 2018 the trick here is to coerce the classes in Javascript to be
134    // different from the ones in Java yet still allow this to be correct for
135    // Java
136  34 Client client;
137  34 Class<ClientResponse> clientResponseClass;
138  34 if (Platform.isJS())
139    {
140    // JavaScript only -- coerce types to Java types for Java
141  0 client = (Client) (Object) new jalview.javascript.web.Client();
142  0 clientResponseClass = (Class<ClientResponse>) (Object) jalview.javascript.web.ClientResponse.class;
143    }
144    else
145    /**
146    * Java only
147    *
148    * @j2sIgnore
149    */
150    {
151  34 client = Client.create(new DefaultClientConfig());
152  34 clientResponseClass = ClientResponse.class;
153    }
154   
155  34 WebResource webResource;
156  34 if (pdbRestRequest.isFacet())
157    {
158  0 webResource = client.resource(PDB_SEARCH_ENDPOINT)
159    .queryParam("wt", "json").queryParam("fl", wantedFields)
160    .queryParam("rows", String.valueOf(responseSize))
161    .queryParam("q", query)
162    .queryParam("start", String.valueOf(offSet))
163    .queryParam("sort", sortParam).queryParam("facet", "true")
164    .queryParam("facet.pivot", facetPivot)
165    .queryParam("facet.pivot.mincount", facetPivotMinCount);
166    }
167    else
168    {
169  34 webResource = client.resource(PDB_SEARCH_ENDPOINT)
170    .queryParam("wt", "json").queryParam("fl", wantedFields)
171    .queryParam("rows", String.valueOf(responseSize))
172    .queryParam("start", String.valueOf(offSet))
173    .queryParam("q", query).queryParam("sort", sortParam);
174    }
175   
176  34 URI uri = webResource.getURI();
177   
178  34 jalview.bin.Console.outPrintln(uri);
179  34 ClientResponse clientResponse = null;
180  34 int responseStatus = -1;
181    // Get the JSON string from the response object or directly from the
182    // client (JavaScript)
183  34 Map<String, Object> jsonObj = null;
184  34 String responseString = null;
185   
186  34 jalview.bin.Console
187    .outPrintln("query >>>>>>> " + pdbRestRequest.toString());
188   
189  34 if (!isMocked())
190    {
191    // Execute the REST request
192  0 clientResponse = webResource.accept(MediaType.APPLICATION_JSON)
193    .get(clientResponseClass);
194  0 responseStatus = clientResponse.getStatus();
195    }
196    else
197    {
198    // mock response
199  34 if (mockQueries.containsKey(uri.toString()))
200    {
201  32 responseStatus = 200;
202    }
203    else
204    {
205    // FIXME - may cause unexpected exceptions for callers when mocked
206  2 responseStatus = 400;
207    }
208    }
209   
210    // Check the response status and report exception if one occurs
211  34 switch (responseStatus)
212    {
213  32 case 200:
214   
215  32 if (isMocked())
216    {
217  32 responseString = mockQueries.get(uri.toString());
218    }
219    else
220    {
221  0 if (Platform.isJS())
222    {
223  0 jsonObj = clientResponse.getEntity(Map.class);
224    }
225    else
226    {
227  0 responseString = clientResponse.getEntity(String.class);
228    }
229    }
230  32 break;
231  2 case 400:
232  2 throw new Exception(isMocked() ? "400 response (Mocked)"
233    : parseJsonExceptionString(responseString));
234  0 default:
235  0 throw new Exception(
236    getMessageByHTTPStatusCode(responseStatus, "PDB"));
237    }
238   
239    // Process the response and return the result to the caller.
240  32 return parsePDBJsonResponse(responseString, jsonObj, pdbRestRequest);
241    } catch (Exception e)
242    {
243  2 if (e.getMessage() == null)
244    {
245  0 throw (e);
246    }
247  2 String exceptionMsg = e.getMessage();
248  2 if (exceptionMsg.contains("SocketException"))
249    {
250    // No internet connection
251  0 throw new Exception(MessageManager.getString(
252    "exception.unable_to_detect_internet_connection"));
253    }
254  2 else if (exceptionMsg.contains("UnknownHostException"))
255    {
256    // The server 'www.ebi.ac.uk' is unreachable
257  0 throw new Exception(MessageManager.formatMessage(
258    "exception.fts_server_unreachable", "PDB Solr"));
259    }
260    else
261    {
262  2 throw e;
263    }
264    }
265    }
266   
267    /**
268    * Process error response from PDB server if/when one occurs.
269    *
270    * @param jsonResponse
271    * the JSON string containing error message from the server
272    * @return the processed error message from the JSON string
273    */
 
274  0 toggle @SuppressWarnings("unchecked")
275    public static String parseJsonExceptionString(String jsonErrorResponse)
276    {
277  0 StringBuilder errorMessage = new StringBuilder(
278    "\n============= PDB Rest Client RunTime error =============\n");
279   
280    // {
281    // "responseHeader":{
282    // "status":0,
283    // "QTime":0,
284    // "params":{
285    // "q":"(text:q93xj9_soltu) AND molecule_sequence:['' TO *] AND status:REL",
286    // "fl":"pdb_id,title,experimental_method,resolution",
287    // "start":"0",
288    // "sort":"overall_quality desc",
289    // "rows":"500",
290    // "wt":"json"}},
291    // "response":{"numFound":1,"start":0,"docs":[
292    // {
293    // "experimental_method":["X-ray diffraction"],
294    // "pdb_id":"4zhp",
295    // "resolution":2.46,
296    // "title":"The crystal structure of Potato ferredoxin I with 2Fe-2S
297    // cluster"}]
298    // }}
299    //
300  0 try
301    {
302  0 Map<String, Object> jsonObj = (Map<String, Object>) JSONUtils
303    .parse(jsonErrorResponse);
304  0 Map<String, Object> errorResponse = (Map<String, Object>) jsonObj
305    .get("error");
306   
307  0 Map<String, Object> responseHeader = (Map<String, Object>) jsonObj
308    .get("responseHeader");
309  0 Map<String, Object> paramsObj = (Map<String, Object>) responseHeader
310    .get("params");
311  0 String status = responseHeader.get("status").toString();
312  0 String message = errorResponse.get("msg").toString();
313  0 String query = paramsObj.get("q").toString();
314  0 String fl = paramsObj.get("fl").toString();
315   
316  0 errorMessage.append("Status: ").append(status).append("\n");
317  0 errorMessage.append("Message: ").append(message).append("\n");
318  0 errorMessage.append("query: ").append(query).append("\n");
319  0 errorMessage.append("fl: ").append(fl).append("\n");
320   
321    } catch (ParseException e)
322    {
323  0 e.printStackTrace();
324    }
325  0 return errorMessage.toString();
326    }
327   
328    /**
329    * Parses the JSON response string from PDB REST API. The response is dynamic
330    * hence, only fields specifically requested for in the 'wantedFields'
331    * parameter is fetched/processed
332    *
333    * @param pdbJsonResponseString
334    * the JSON string to be parsed
335    * @param pdbRestRequest
336    * the request object which contains parameters used to process the
337    * JSON string
338    * @return
339    */
 
340  0 toggle public static FTSRestResponse parsePDBJsonResponse(
341    String pdbJsonResponseString, FTSRestRequest pdbRestRequest)
342    {
343  0 return parsePDBJsonResponse(pdbJsonResponseString,
344    (Map<String, Object>) null, pdbRestRequest);
345    }
346   
 
347  32 toggle @SuppressWarnings("unchecked")
348    public static FTSRestResponse parsePDBJsonResponse(
349    String pdbJsonResponseString, Map<String, Object> jsonObj,
350    FTSRestRequest pdbRestRequest)
351    {
352  32 FTSRestResponse searchResult = new FTSRestResponse();
353  32 List<FTSData> result = null;
354  32 try
355    {
356  32 if (jsonObj == null)
357    {
358  32 jsonObj = (Map<String, Object>) JSONUtils
359    .parse(pdbJsonResponseString);
360    }
361  32 Map<String, Object> pdbResponse = (Map<String, Object>) jsonObj
362    .get("response");
363  32 String queryTime = ((Map<String, Object>) jsonObj
364    .get("responseHeader")).get("QTime").toString();
365  32 int numFound = Integer
366    .valueOf(pdbResponse.get("numFound").toString());
367  32 List<Object> docs = (List<Object>) pdbResponse.get("docs");
368   
369  32 result = new ArrayList<FTSData>();
370  32 if (numFound > 0)
371    {
372   
373  8278 for (Iterator<Object> docIter = docs.iterator(); docIter.hasNext();)
374    {
375  8246 Map<String, Object> doc = (Map<String, Object>) docIter.next();
376  8246 result.add(getFTSData(doc, pdbRestRequest));
377    }
378    }
379    // this is the total number found by the query,
380    // rather than the set returned in SearchSummary
381  32 searchResult.setNumberOfItemsFound(numFound);
382  32 searchResult.setResponseTime(queryTime);
383  32 searchResult.setSearchSummary(result);
384   
385    } catch (ParseException e)
386    {
387  0 e.printStackTrace();
388    }
389  32 return searchResult;
390    }
391   
 
392  8246 toggle public static FTSData getFTSData(Map<String, Object> pdbJsonDoc,
393    FTSRestRequest request)
394    {
395   
396  8246 String primaryKey = null;
397   
398  8246 Object[] summaryRowData;
399   
400  8246 SequenceI associatedSequence;
401   
402  8246 Collection<FTSDataColumnI> diplayFields = request.getWantedFields();
403  8246 SequenceI associatedSeq = request.getAssociatedSequence();
404  8246 int colCounter = 0;
405  8246 summaryRowData = new Object[(associatedSeq != null)
406    ? diplayFields.size() + 1
407    : diplayFields.size()];
408  8246 if (associatedSeq != null)
409    {
410  0 associatedSequence = associatedSeq;
411  0 summaryRowData[0] = associatedSequence;
412  0 colCounter = 1;
413    }
414   
415  8246 for (FTSDataColumnI field : diplayFields)
416    {
417    // jalview.bin.Console.outPrintln("Field " + field);
418  32984 String fieldData = (pdbJsonDoc.get(field.getCode()) == null) ? ""
419    : pdbJsonDoc.get(field.getCode()).toString();
420    // jalview.bin.Console.outPrintln("Field Data : " + fieldData);
421  32984 if (field.isPrimaryKeyColumn())
422    {
423  8246 primaryKey = fieldData;
424  8246 summaryRowData[colCounter++] = primaryKey;
425    }
426  24738 else if (fieldData == null || fieldData.isEmpty())
427    {
428  480 summaryRowData[colCounter++] = null;
429    }
430    else
431    {
432  24258 try
433    {
434  24258 summaryRowData[colCounter++] = (field.getDataType()
435    .getDataTypeClass() == Integer.class)
436    ? Integer.valueOf(fieldData)
437  24258 : (field.getDataType()
438    .getDataTypeClass() == Double.class)
439    ? Double.valueOf(fieldData)
440    : sanitiseData(fieldData);
441    } catch (Exception e)
442    {
443  0 e.printStackTrace();
444  0 jalview.bin.Console.outPrintln("offending value:" + fieldData);
445    }
446    }
447    }
448   
449  8246 final String primaryKey1 = primaryKey;
450   
451  8246 final Object[] summaryRowData1 = summaryRowData;
452  8246 return new FTSData()
453    {
 
454  11201663 toggle @Override
455    public Object[] getSummaryData()
456    {
457  11201663 return summaryRowData1;
458    }
459   
 
460  0 toggle @Override
461    public Object getPrimaryKey()
462    {
463  0 return primaryKey1;
464    }
465   
466    /**
467    * Returns a string representation of this object;
468    */
 
469  0 toggle @Override
470    public String toString()
471    {
472  0 StringBuilder summaryFieldValues = new StringBuilder();
473  0 for (Object summaryField : summaryRowData1)
474    {
475  0 summaryFieldValues.append(
476  0 summaryField == null ? " " : summaryField.toString())
477    .append("\t");
478    }
479  0 return summaryFieldValues.toString();
480    }
481   
482    /**
483    * Returns hash code value for this object
484    */
 
485  0 toggle @Override
486    public int hashCode()
487    {
488  0 return Objects.hash(primaryKey1, this.toString());
489    }
490   
 
491  0 toggle @Override
492    public boolean equals(Object that)
493    {
494  0 return this.toString().equals(that.toString());
495    }
496    };
497    }
498   
 
499  16492 toggle private static String sanitiseData(String data)
500    {
501  16492 String cleanData = data.replaceAll("\\[\"", "").replaceAll("\\]\"", "")
502    .replaceAll("\\[", "").replaceAll("\\]", "")
503    .replaceAll("\",\"", ", ").replaceAll("\"", "");
504  16492 return cleanData;
505    }
506   
 
507  5 toggle @Override
508    public String getColumnDataConfigFileName()
509    {
510  5 return "/fts/pdb_data_columns.txt";
511    }
512   
 
513  163 toggle public static FTSRestClientI getInstance()
514    {
515  163 if (instance == null)
516    {
517  5 instance = new PDBFTSRestClient();
518    }
519  163 return instance;
520    }
521   
522    private Collection<FTSDataColumnI> allDefaultDisplayedStructureDataColumns;
523   
 
524  68 toggle @Override
525    public Collection<FTSDataColumnI> getAllDefaultDisplayedStructureDataColumns()
526    {
527  68 if (allDefaultDisplayedStructureDataColumns == null
528    || allDefaultDisplayedStructureDataColumns.isEmpty())
529    {
530  5 allDefaultDisplayedStructureDataColumns = new ArrayList<>();
531  5 allDefaultDisplayedStructureDataColumns
532    .addAll(super.getAllDefaultDisplayedFTSDataColumns());
533    }
534  68 return allDefaultDisplayedStructureDataColumns;
535    }
536   
 
537  69 toggle @Override
538    public String[] getPreferencesColumnsFor(PreferenceSource source)
539    {
540  69 String[] columnNames = null;
541  69 switch (source)
542    {
543  1 case SEARCH_SUMMARY:
544  1 columnNames = new String[] { "", "Display", "Group" };
545  1 break;
546  68 case STRUCTURE_CHOOSER:
547  68 columnNames = new String[] { "", "Display", "Group" };
548  68 break;
549  0 case PREFERENCES:
550  0 columnNames = new String[] { "PDB Field", "Show in search summary",
551    "Show in structure summary" };
552  0 break;
553  0 default:
554  0 break;
555    }
556  69 return columnNames;
557    }
558    }