Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.fts.service.pdb

File PDBFTSRestClient.java

 

Coverage histogram

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

Code metrics

60
149
16
1
553
405
59
0.4
9.31
16
3.69

Classes

Class Line # Actions
PDBFTSRestClient 66 149 59
0.657777865.8%
 

Contributing tests

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