Clover icon

Coverage Report

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

File UniProtFTSRestClient.java

 

Coverage histogram

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

Code metrics

62
139
24
1
556
379
73
0.53
5.79
24
3.04

Classes

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