Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
PDBFTSRestClient | 64 | 151 | 60 |
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 | 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 | @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 | @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 | 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 | @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 | 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 | @Override |
455 | public Object[] getSummaryData() | |
456 | { | |
457 | 11201663 | return summaryRowData1; |
458 | } | |
459 | ||
460 | 0 | @Override |
461 | public Object getPrimaryKey() | |
462 | { | |
463 | 0 | return primaryKey1; |
464 | } | |
465 | ||
466 | /** | |
467 | * Returns a string representation of this object; | |
468 | */ | |
469 | 0 | @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 | @Override |
486 | public int hashCode() | |
487 | { | |
488 | 0 | return Objects.hash(primaryKey1, this.toString()); |
489 | } | |
490 | ||
491 | 0 | @Override |
492 | public boolean equals(Object that) | |
493 | { | |
494 | 0 | return this.toString().equals(that.toString()); |
495 | } | |
496 | }; | |
497 | } | |
498 | ||
499 | 16492 | 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 | @Override |
508 | public String getColumnDataConfigFileName() | |
509 | { | |
510 | 5 | return "/fts/pdb_data_columns.txt"; |
511 | } | |
512 | ||
513 | 163 | 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 | @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 | @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 | } |