Clover icon

Coverage Report

  1. Project Clover database Fri Nov 1 2024 11:46:37 GMT
  2. Package jalview.fts.service.uniprot

File UniProtFTSRestClient.java

 

Coverage histogram

../../../../img/srcFileCovDistChart0.png
59% of files have more coverage

Code metrics

64
141
24
1
556
377
74
0.52
5.88
24
3.08

Classes

Class Line # Actions
UniProtFTSRestClient 75 141 74
0.00%
 

Contributing tests

No tests hitting this source file were found.

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   
22    package jalview.fts.service.uniprot;
23   
24    import java.net.MalformedURLException;
25    import java.net.URL;
26    import java.util.ArrayList;
27    import java.util.Collection;
28    import java.util.List;
29    import java.util.Objects;
30   
31    import javax.ws.rs.core.MediaType;
32   
33    import com.sun.jersey.api.client.Client;
34    import com.sun.jersey.api.client.ClientResponse;
35    import com.sun.jersey.api.client.WebResource;
36    import com.sun.jersey.api.client.config.DefaultClientConfig;
37   
38    import jalview.bin.Cache;
39    import jalview.bin.Console;
40    import jalview.fts.api.FTSData;
41    import jalview.fts.api.FTSDataColumnI;
42    import jalview.fts.core.FTSRestClient;
43    import jalview.fts.core.FTSRestRequest;
44    import jalview.fts.core.FTSRestResponse;
45    import jalview.util.ChannelProperties;
46    import jalview.util.MessageManager;
47    import jalview.util.Platform;
48   
49    /*
50    * 2022-07-20 bsoares
51    * See https://issues.jalview.org/browse/JAL-4036
52    * The new Uniprot API is not dissimilar to the old one, but has some important changes.
53    * Some group names have changed slightly, some old groups have gone and there are quite a few new groups.
54    *
55    * Most changes are mappings of old column ids to new field ids. There are a handful of old
56    * columns not mapped to new fields, and new fields without an old column.
57    * [aside: not all possible columns were listed in the resources/fts/uniprot_data_columns.txt file.
58    * These were presumably additions after the file was created]
59    * For existing/mapped fields, the same preferences found in the resource file have been migrated to
60    * the new file with the new field name, id and group.
61    *
62    * The new mapped groups and files are stored and read from resources/fts/uniprot_data_columns-2022.txt.
63    *
64    * There is now no "sort" query string parameter.
65    *
66    * See https://www.uniprot.org/help/api_queries
67    *
68    * SIGNIFICANT CHANGE: Pagination is no longer performed using a record offset, but with a "cursor"
69    * query string parameter that is not really a cursor. The value is an opaque string that is passed (or
70    * rather a whole URL is passed) in the "Link" header of the HTTP response of the previous page.
71    * Where such a link is passed it is put into the cursors ArrayList.
72    * There are @Overridden methods in UniprotFTSPanel.
73    */
74   
 
75    public class UniProtFTSRestClient extends FTSRestClient
76    {
77    private static final String DEFAULT_UNIPROT_DOMAIN = "https://rest.uniprot.org";
78   
79    private static final String USER_AGENT = ChannelProperties
80    .getProperty("app_name", "Jalview") + " "
81    + Cache.getDefault("VERSION", "Unknown") + " "
82    + UniProtFTSRestClient.class.toString() + " help@jalview.org";
83   
 
84  0 toggle static
85    {
86  0 Platform.addJ2SDirectDatabaseCall(DEFAULT_UNIPROT_DOMAIN);
87    }
88   
89    private static UniProtFTSRestClient instance = null;
90   
91    public final String uniprotSearchEndpoint;
92   
 
93  0 toggle public UniProtFTSRestClient()
94    {
95  0 super();
96  0 this.clearCursors();
97  0 uniprotSearchEndpoint = Cache.getDefault("UNIPROT_2022_DOMAIN",
98    DEFAULT_UNIPROT_DOMAIN) + "/uniprotkb/search";
99    }
100   
 
101  0 toggle @SuppressWarnings("unchecked")
102    @Override
103    public FTSRestResponse executeRequest(FTSRestRequest uniprotRestRequest)
104    throws Exception
105    {
106  0 return executeRequest(uniprotRestRequest, null);
107    }
108   
 
109  0 toggle public FTSRestResponse executeRequest(FTSRestRequest uniprotRestRequest,
110    String cursor) throws Exception
111    {
112  0 try
113    {
114  0 String wantedFields = getDataColumnsFieldsAsCommaDelimitedString(
115    uniprotRestRequest.getWantedFields());
116  0 int responseSize = (uniprotRestRequest.getResponseSize() == 0)
117    ? getDefaultResponsePageSize()
118    : uniprotRestRequest.getResponseSize();
119   
120  0 int offSet = uniprotRestRequest.getOffSet();
121  0 String query;
122  0 if (isAdvancedQuery(uniprotRestRequest.getSearchTerm()))
123    {
124  0 query = uniprotRestRequest.getSearchTerm();
125    }
126    else
127    {
128  0 query = uniprotRestRequest.getFieldToSearchBy().equalsIgnoreCase(
129    "Search All") ? uniprotRestRequest.getSearchTerm()
130    // + " or mnemonic:"
131    // + uniprotRestRequest.getSearchTerm()
132    : uniprotRestRequest.getFieldToSearchBy() + ":"
133    + uniprotRestRequest.getSearchTerm();
134    }
135   
136    // BH 2018 the trick here is to coerce the classes in Javascript to be
137    // different from the ones in Java yet still allow this to be correct for
138    // Java
139  0 Client client;
140  0 Class<ClientResponse> clientResponseClass;
141  0 if (Platform.isJS())
142    {
143    // JavaScript only -- coerce types to Java types for Java
144  0 client = (Client) (Object) new jalview.javascript.web.Client();
145  0 clientResponseClass = (Class<ClientResponse>) (Object) jalview.javascript.web.ClientResponse.class;
146    }
147    else
148    /**
149    * Java only
150    *
151    * @j2sIgnore
152    */
153    {
154    // Java only
155  0 client = Client.create(new DefaultClientConfig());
156  0 clientResponseClass = ClientResponse.class;
157    }
158   
159  0 WebResource webResource = null;
160  0 webResource = client.resource(uniprotSearchEndpoint)
161    .queryParam("format", "tsv")
162    .queryParam("fields", wantedFields)
163    .queryParam("size", String.valueOf(responseSize))
164    /* 2022 new api has no "sort"
165    * .queryParam("sort", "score")
166    */
167    .queryParam("query", query);
168  0 if (offSet != 0 && cursor != null && cursor.length() > 0)
169    // 2022 new api does not do pagination with an offset, it requires a
170    // "cursor" parameter with a key (given for the next page).
171    // (see https://www.uniprot.org/help/pagination)
172    {
173  0 webResource = webResource.queryParam("cursor", cursor);
174    }
175  0 Console.debug(
176    "Uniprot FTS Request: " + webResource.getURI().toString());
177    // Execute the REST request
178  0 WebResource.Builder wrBuilder = webResource
179    .accept(MediaType.TEXT_PLAIN);
180  0 if (!Platform.isJS())
181    /**
182    * Java only
183    *
184    * @j2sIgnore
185    */
186    {
187  0 wrBuilder.header("User-Agent", USER_AGENT);
188    }
189  0 ClientResponse clientResponse = wrBuilder.get(clientResponseClass);
190   
191  0 if (!Platform.isJS())
192    /**
193    * Java only
194    *
195    * @j2sIgnore
196    */
197    {
198  0 if (clientResponse.getHeaders().containsKey("Link"))
199    {
200    // extract the URL from the 'Link: <URL>; ref="stuff"' header
201  0 String linkHeader = clientResponse.getHeaders().get("Link")
202    .get(0);
203  0 if (linkHeader.indexOf("<") > -1)
204    {
205  0 String temp = linkHeader.substring(linkHeader.indexOf("<") + 1);
206  0 if (temp.indexOf(">") > -1)
207    {
208  0 String nextUrl = temp.substring(0, temp.indexOf(">"));
209    // then get the cursor value from the query string parameters
210  0 String nextCursor = getQueryParam("cursor", nextUrl);
211  0 setCursor(cursorPage + 1, nextCursor);
212    }
213    }
214    }
215    }
216   
217  0 String uniProtTabDelimittedResponseString = clientResponse
218    .getEntity(String.class);
219    // Make redundant objects eligible for garbage collection to conserve
220    // memory
221    // jalview.bin.Console.outPrintln(">>>>> response : "
222    // + uniProtTabDelimittedResponseString);
223  0 if (clientResponse.getStatus() != 200)
224    {
225  0 String errorMessage = getMessageByHTTPStatusCode(
226    clientResponse.getStatus(), "Uniprot");
227  0 throw new Exception(errorMessage);
228   
229    }
230  0 int xTotalResults = 0;
231  0 if (Platform.isJS())
232    {
233  0 xTotalResults = 1;
234    }
235    else
236    {
237    // new Uniprot API is not including a "X-Total-Results" header when
238    // there
239    // are 0 results
240  0 List<String> resultsHeaders = clientResponse.getHeaders()
241    .get("X-Total-Results");
242  0 if (resultsHeaders != null && resultsHeaders.size() >= 1)
243    {
244  0 xTotalResults = Integer.valueOf(resultsHeaders.get(0));
245    }
246    }
247  0 clientResponse = null;
248  0 client = null;
249  0 return parseUniprotResponse(uniProtTabDelimittedResponseString,
250    uniprotRestRequest, xTotalResults);
251    } catch (Exception e)
252    {
253  0 Console.warn("Problem with the query: " + e.getMessage());
254  0 Console.debug("Exception stacktrace:", e);
255  0 String exceptionMsg = e.getMessage();
256  0 if (exceptionMsg.contains("SocketException"))
257    {
258    // No internet connection
259  0 throw new Exception(MessageManager.getString(
260    "exception.unable_to_detect_internet_connection"));
261    }
262  0 else if (exceptionMsg.contains("UnknownHostException"))
263    {
264    // The server 'http://www.uniprot.org' is unreachable
265  0 throw new Exception(MessageManager.formatMessage(
266    "exception.fts_server_unreachable", "Uniprot"));
267    }
268    else
269    {
270  0 throw e;
271    }
272    }
273    }
274   
 
275  0 toggle public boolean isAdvancedQuery(String query)
276    {
277  0 if (query.contains(" AND ") || query.contains(" OR ")
278    || query.contains(" NOT ") || query.contains(" ! ")
279    || query.contains(" || ") || query.contains(" && ")
280    || query.contains(":") || query.contains("-"))
281    {
282  0 return true;
283    }
284  0 return false;
285    }
286   
 
287  0 toggle public FTSRestResponse parseUniprotResponse(
288    String uniProtTabDelimittedResponseString,
289    FTSRestRequest uniprotRestRequest, int xTotalResults)
290    {
291  0 FTSRestResponse searchResult = new FTSRestResponse();
292  0 List<FTSData> result = null;
293  0 if (uniProtTabDelimittedResponseString == null
294    || uniProtTabDelimittedResponseString.trim().isEmpty())
295    {
296  0 searchResult.setNumberOfItemsFound(0);
297  0 return searchResult;
298    }
299  0 String[] foundDataRow = uniProtTabDelimittedResponseString.split("\n");
300  0 if (foundDataRow != null && foundDataRow.length > 0)
301    {
302  0 result = new ArrayList<>();
303  0 boolean firstRow = true;
304  0 for (String dataRow : foundDataRow)
305    {
306    // The first data row is usually the header data. This should be
307    // filtered out from the rest of the data See: JAL-2485
308  0 if (firstRow)
309    {
310  0 firstRow = false;
311  0 continue;
312    }
313    // jalview.bin.Console.outPrintln(dataRow);
314  0 result.add(getFTSData(dataRow, uniprotRestRequest));
315    }
316  0 searchResult.setNumberOfItemsFound(xTotalResults);
317  0 searchResult.setSearchSummary(result);
318    }
319  0 return searchResult;
320    }
321   
322    // /**
323    // * Takes a collection of FTSDataColumnI and converts its 'code' values into
324    // a
325    // * tab delimited string.
326    // *
327    // * @param dataColumnFields
328    // * the collection of FTSDataColumnI to process
329    // * @return the generated comma delimited string from the supplied
330    // * FTSDataColumnI collection
331    // */
332    // private String getDataColumnsFieldsAsTabDelimitedString(
333    // Collection<FTSDataColumnI> dataColumnFields)
334    // {
335    // String result = "";
336    // if (dataColumnFields != null && !dataColumnFields.isEmpty())
337    // {
338    // StringBuilder returnedFields = new StringBuilder();
339    // for (FTSDataColumnI field : dataColumnFields)
340    // {
341    // if (field.getName().equalsIgnoreCase("Uniprot Id"))
342    // {
343    // returnedFields.append("\t").append("Entry");
344    // }
345    // else
346    // {
347    // returnedFields.append("\t").append(field.getName());
348    // }
349    // }
350    // returnedFields.deleteCharAt(0);
351    // result = returnedFields.toString();
352    // }
353    // return result;
354    // }
355   
 
356  0 toggle public static FTSData getFTSData(String tabDelimittedDataStr,
357    FTSRestRequest request)
358    {
359  0 String primaryKey = null;
360   
361  0 Object[] summaryRowData;
362   
363  0 Collection<FTSDataColumnI> diplayFields = request.getWantedFields();
364  0 int colCounter = 0;
365  0 summaryRowData = new Object[diplayFields.size()];
366  0 String[] columns = tabDelimittedDataStr.split("\t");
367  0 for (FTSDataColumnI field : diplayFields)
368    {
369  0 try
370    {
371  0 String fieldData = columns[colCounter];
372  0 if (field.isPrimaryKeyColumn())
373    {
374  0 primaryKey = fieldData;
375  0 summaryRowData[colCounter++] = primaryKey;
376    }
377  0 else if (fieldData == null || fieldData.isEmpty())
378    {
379  0 summaryRowData[colCounter++] = null;
380    }
381    else
382    {
383  0 try
384    {
385  0 summaryRowData[colCounter++] = (field.getDataType()
386    .getDataTypeClass() == Integer.class)
387    ? Integer.valueOf(fieldData.replace(",", ""))
388  0 : (field.getDataType()
389    .getDataTypeClass() == Double.class)
390    ? Double.valueOf(fieldData)
391    : fieldData;
392    } catch (Exception e)
393    {
394  0 e.printStackTrace();
395  0 jalview.bin.Console.outPrintln("offending value:" + fieldData);
396    }
397    }
398    } catch (Exception e)
399    {
400    // e.printStackTrace();
401    }
402    }
403   
404  0 final String primaryKey1 = primaryKey;
405   
406  0 final Object[] summaryRowData1 = summaryRowData;
407  0 return new FTSData()
408    {
 
409  0 toggle @Override
410    public Object[] getSummaryData()
411    {
412  0 return summaryRowData1;
413    }
414   
 
415  0 toggle @Override
416    public Object getPrimaryKey()
417    {
418  0 return primaryKey1;
419    }
420   
421    /**
422    * Returns a string representation of this object;
423    */
 
424  0 toggle @Override
425    public String toString()
426    {
427  0 StringBuilder summaryFieldValues = new StringBuilder();
428  0 for (Object summaryField : summaryRowData1)
429    {
430  0 summaryFieldValues.append(
431  0 summaryField == null ? " " : summaryField.toString())
432    .append("\t");
433    }
434  0 return summaryFieldValues.toString();
435    }
436   
437    /**
438    * Returns hash code value for this object
439    */
 
440  0 toggle @Override
441    public int hashCode()
442    {
443  0 return Objects.hash(primaryKey1, this.toString());
444    }
445   
 
446  0 toggle @Override
447    public boolean equals(Object that)
448    {
449  0 return this.toString().equals(that.toString());
450    }
451    };
452    }
453   
 
454  0 toggle public static UniProtFTSRestClient getInstance()
455    {
456  0 if (instance == null)
457    {
458  0 instance = new UniProtFTSRestClient();
459    }
460  0 return instance;
461    }
462   
 
463  0 toggle @Override
464    public String getColumnDataConfigFileName()
465    {
466  0 return "/fts/uniprot_data_columns-2022.txt";
467    }
468   
469    /* 2022-07-20 bsoares
470    * used for the new API "cursor" pagination. See https://www.uniprot.org/help/pagination
471    */
472    private ArrayList<String> cursors;
473   
474    private int cursorPage = 0;
475   
 
476  0 toggle protected int getCursorPage()
477    {
478  0 return cursorPage;
479    }
480   
 
481  0 toggle protected void setCursorPage(int i)
482    {
483  0 cursorPage = i;
484    }
485   
 
486  0 toggle protected void setPrevCursorPage()
487    {
488  0 if (cursorPage > 0)
489  0 cursorPage--;
490    }
491   
 
492  0 toggle protected void setNextCursorPage()
493    {
494  0 cursorPage++;
495    }
496   
 
497  0 toggle protected void clearCursors()
498    {
499  0 cursors = new ArrayList(10);
500    }
501   
 
502  0 toggle protected String getCursor(int i)
503    {
504  0 return cursors.get(i);
505    }
506   
 
507  0 toggle protected String getNextCursor()
508    {
509  0 if (cursors.size() < cursorPage + 2)
510  0 return null;
511  0 return cursors.get(cursorPage + 1);
512    }
513   
 
514  0 toggle protected String getPrevCursor()
515    {
516  0 if (cursorPage == 0)
517  0 return null;
518  0 return cursors.get(cursorPage - 1);
519    }
520   
 
521  0 toggle protected void setCursor(int i, String c)
522    {
523  0 cursors.ensureCapacity(i + 1);
524  0 while (cursors.size() <= i)
525    {
526  0 cursors.add(null);
527    }
528  0 cursors.set(i, c);
529  0 Console.debug(
530    "Set UniprotFRSRestClient cursors[" + i + "] to '" + c + "'");
531    // cursors.add(c);
532    }
533   
 
534  0 toggle public static String getQueryParam(String param, String u)
535    {
536  0 if (param == null || u == null)
537  0 return null;
538  0 try
539    {
540  0 URL url = new URL(u);
541  0 String[] kevs = url.getQuery().split("&");
542  0 for (int j = 0; j < kevs.length; j++)
543    {
544  0 String[] kev = kevs[j].split("=", 2);
545  0 if (param.equals(kev[0]))
546    {
547  0 return kev[1];
548    }
549    }
550    } catch (MalformedURLException e)
551    {
552  0 Console.warn("Could not obtain next page 'cursor' value from 'u");
553    }
554  0 return null;
555    }
556    }