JAL-4036 use the legacy endpoint (and report the FTS query when debug/trace enabled)
[jalview.git] / src / jalview / fts / service / uniprot / UniProtFTSRestClient.java
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.util.ArrayList;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Objects;
28
29 import javax.ws.rs.core.MediaType;
30
31 import com.sun.jersey.api.client.Client;
32 import com.sun.jersey.api.client.ClientResponse;
33 import com.sun.jersey.api.client.WebResource;
34 import com.sun.jersey.api.client.config.DefaultClientConfig;
35
36 import jalview.bin.Cache;
37 import jalview.bin.Console;
38 import jalview.fts.api.FTSData;
39 import jalview.fts.api.FTSDataColumnI;
40 import jalview.fts.api.FTSRestClientI;
41 import jalview.fts.core.FTSRestClient;
42 import jalview.fts.core.FTSRestRequest;
43 import jalview.fts.core.FTSRestResponse;
44 import jalview.util.MessageManager;
45 import jalview.util.Platform;
46
47 public class UniProtFTSRestClient extends FTSRestClient
48 {
49   private static final String DEFAULT_UNIPROT_DOMAIN = "https://legacy.uniprot.org";
50
51   static
52   {
53     Platform.addJ2SDirectDatabaseCall(DEFAULT_UNIPROT_DOMAIN);
54   }
55
56   private static FTSRestClientI instance = null;
57
58   public final String uniprotSearchEndpoint;
59
60   public UniProtFTSRestClient()
61   {
62     super();
63     uniprotSearchEndpoint = Cache.getDefault("UNIPROT_DOMAIN",
64             DEFAULT_UNIPROT_DOMAIN) + "/uniprot/";
65   }
66
67   @SuppressWarnings("unchecked")
68   @Override
69   public FTSRestResponse executeRequest(FTSRestRequest uniprotRestRequest)
70           throws Exception
71   {
72     try
73     {
74       String wantedFields = getDataColumnsFieldsAsCommaDelimitedString(
75               uniprotRestRequest.getWantedFields());
76       int responseSize = (uniprotRestRequest.getResponseSize() == 0)
77               ? getDefaultResponsePageSize()
78               : uniprotRestRequest.getResponseSize();
79
80       int offSet = uniprotRestRequest.getOffSet();
81       String query;
82       if (isAdvancedQuery(uniprotRestRequest.getSearchTerm()))
83       {
84         query = uniprotRestRequest.getSearchTerm();
85       }
86       else
87       {
88         query = uniprotRestRequest.getFieldToSearchBy()
89                 .equalsIgnoreCase("Search All")
90                         ? uniprotRestRequest.getSearchTerm()
91                                 + " or mnemonic:"
92                                 + uniprotRestRequest.getSearchTerm()
93                         : uniprotRestRequest.getFieldToSearchBy() + ":"
94                                 + uniprotRestRequest.getSearchTerm();
95       }
96
97       // BH 2018 the trick here is to coerce the classes in Javascript to be
98       // different from the ones in Java yet still allow this to be correct for
99       // Java
100       Client client;
101       Class<ClientResponse> clientResponseClass;
102       if (Platform.isJS())
103       {
104         // JavaScript only -- coerce types to Java types for Java
105         client = (Client) (Object) new jalview.javascript.web.Client();
106         clientResponseClass = (Class<ClientResponse>) (Object) jalview.javascript.web.ClientResponse.class;
107       }
108       else
109       /**
110        * Java only
111        * 
112        * @j2sIgnore
113        */
114       {
115         // Java only
116         client = Client.create(new DefaultClientConfig());
117         clientResponseClass = ClientResponse.class;
118       }
119
120       WebResource webResource = null;
121       webResource = client.resource(uniprotSearchEndpoint)
122               .queryParam("format", "tab")
123               .queryParam("columns", wantedFields)
124               .queryParam("limit", String.valueOf(responseSize))
125               .queryParam("offset", String.valueOf(offSet))
126               .queryParam("sort", "score").queryParam("query", query);
127       if (Console.isDebugEnabled())
128       {
129         Console.debug("Uniprot FTS Request: " + webResource.toString());
130       }
131       // Execute the REST request
132       ClientResponse clientResponse = webResource
133               .accept(MediaType.TEXT_PLAIN).get(clientResponseClass);
134       String uniProtTabDelimittedResponseString = clientResponse
135               .getEntity(String.class);
136       // Make redundant objects eligible for garbage collection to conserve
137       // memory
138       // System.out.println(">>>>> response : "
139       // + uniProtTabDelimittedResponseString);
140       if (clientResponse.getStatus() != 200)
141       {
142         String errorMessage = getMessageByHTTPStatusCode(
143                 clientResponse.getStatus(), "Uniprot");
144         throw new Exception(errorMessage);
145
146       }
147       int xTotalResults = Platform.isJS() ? 1
148               : Integer.valueOf(clientResponse.getHeaders()
149                       .get("X-Total-Results").get(0));
150       clientResponse = null;
151       client = null;
152       return parseUniprotResponse(uniProtTabDelimittedResponseString,
153               uniprotRestRequest, xTotalResults);
154     } catch (Exception e)
155     {
156       String exceptionMsg = e.getMessage();
157       if (exceptionMsg.contains("SocketException"))
158       {
159         // No internet connection
160         throw new Exception(MessageManager.getString(
161                 "exception.unable_to_detect_internet_connection"));
162       }
163       else if (exceptionMsg.contains("UnknownHostException"))
164       {
165         // The server 'http://www.uniprot.org' is unreachable
166         throw new Exception(MessageManager.formatMessage(
167                 "exception.fts_server_unreachable", "Uniprot"));
168       }
169       else
170       {
171         throw e;
172       }
173     }
174   }
175
176   public boolean isAdvancedQuery(String query)
177   {
178     if (query.contains(" AND ") || query.contains(" OR ")
179             || query.contains(" NOT ") || query.contains(" ! ")
180             || query.contains(" || ") || query.contains(" && ")
181             || query.contains(":") || query.contains("-"))
182     {
183       return true;
184     }
185     return false;
186   }
187
188   public FTSRestResponse parseUniprotResponse(
189           String uniProtTabDelimittedResponseString,
190           FTSRestRequest uniprotRestRequest, int xTotalResults)
191   {
192     FTSRestResponse searchResult = new FTSRestResponse();
193     List<FTSData> result = null;
194     if (uniProtTabDelimittedResponseString == null
195             || uniProtTabDelimittedResponseString.trim().isEmpty())
196     {
197       searchResult.setNumberOfItemsFound(0);
198       return searchResult;
199     }
200     String[] foundDataRow = uniProtTabDelimittedResponseString.split("\n");
201     if (foundDataRow != null && foundDataRow.length > 0)
202     {
203       result = new ArrayList<>();
204       boolean firstRow = true;
205       for (String dataRow : foundDataRow)
206       {
207         // The first data row is usually the header data. This should be
208         // filtered out from the rest of the data See: JAL-2485
209         if (firstRow)
210         {
211           firstRow = false;
212           continue;
213         }
214         // System.out.println(dataRow);
215         result.add(getFTSData(dataRow, uniprotRestRequest));
216       }
217       searchResult.setNumberOfItemsFound(xTotalResults);
218       searchResult.setSearchSummary(result);
219     }
220     return searchResult;
221   }
222
223   // /**
224   // * Takes a collection of FTSDataColumnI and converts its 'code' values into
225   // a
226   // * tab delimited string.
227   // *
228   // * @param dataColumnFields
229   // * the collection of FTSDataColumnI to process
230   // * @return the generated comma delimited string from the supplied
231   // * FTSDataColumnI collection
232   // */
233   // private String getDataColumnsFieldsAsTabDelimitedString(
234   // Collection<FTSDataColumnI> dataColumnFields)
235   // {
236   // String result = "";
237   // if (dataColumnFields != null && !dataColumnFields.isEmpty())
238   // {
239   // StringBuilder returnedFields = new StringBuilder();
240   // for (FTSDataColumnI field : dataColumnFields)
241   // {
242   // if (field.getName().equalsIgnoreCase("Uniprot Id"))
243   // {
244   // returnedFields.append("\t").append("Entry");
245   // }
246   // else
247   // {
248   // returnedFields.append("\t").append(field.getName());
249   // }
250   // }
251   // returnedFields.deleteCharAt(0);
252   // result = returnedFields.toString();
253   // }
254   // return result;
255   // }
256
257   public static FTSData getFTSData(String tabDelimittedDataStr,
258           FTSRestRequest request)
259   {
260     String primaryKey = null;
261
262     Object[] summaryRowData;
263
264     Collection<FTSDataColumnI> diplayFields = request.getWantedFields();
265     int colCounter = 0;
266     summaryRowData = new Object[diplayFields.size()];
267     String[] columns = tabDelimittedDataStr.split("\t");
268     for (FTSDataColumnI field : diplayFields)
269     {
270       try
271       {
272         String fieldData = columns[colCounter];
273         if (field.isPrimaryKeyColumn())
274         {
275           primaryKey = fieldData;
276           summaryRowData[colCounter++] = primaryKey;
277         }
278         else if (fieldData == null || fieldData.isEmpty())
279         {
280           summaryRowData[colCounter++] = null;
281         }
282         else
283         {
284           try
285           {
286             summaryRowData[colCounter++] = (field.getDataType()
287                     .getDataTypeClass() == Integer.class)
288                             ? Integer.valueOf(fieldData.replace(",", ""))
289                             : (field.getDataType()
290                                     .getDataTypeClass() == Double.class)
291                                             ? Double.valueOf(fieldData)
292                                             : fieldData;
293           } catch (Exception e)
294           {
295             e.printStackTrace();
296             System.out.println("offending value:" + fieldData);
297           }
298         }
299       } catch (Exception e)
300       {
301         // e.printStackTrace();
302       }
303     }
304
305     final String primaryKey1 = primaryKey;
306
307     final Object[] summaryRowData1 = summaryRowData;
308     return new FTSData()
309     {
310       @Override
311       public Object[] getSummaryData()
312       {
313         return summaryRowData1;
314       }
315
316       @Override
317       public Object getPrimaryKey()
318       {
319         return primaryKey1;
320       }
321
322       /**
323        * Returns a string representation of this object;
324        */
325       @Override
326       public String toString()
327       {
328         StringBuilder summaryFieldValues = new StringBuilder();
329         for (Object summaryField : summaryRowData1)
330         {
331           summaryFieldValues.append(
332                   summaryField == null ? " " : summaryField.toString())
333                   .append("\t");
334         }
335         return summaryFieldValues.toString();
336       }
337
338       /**
339        * Returns hash code value for this object
340        */
341       @Override
342       public int hashCode()
343       {
344         return Objects.hash(primaryKey1, this.toString());
345       }
346
347       @Override
348       public boolean equals(Object that)
349       {
350         return this.toString().equals(that.toString());
351       }
352     };
353   }
354
355   public static FTSRestClientI getInstance()
356   {
357     if (instance == null)
358     {
359       instance = new UniProtFTSRestClient();
360     }
361     return instance;
362   }
363
364   @Override
365   public String getColumnDataConfigFileName()
366   {
367     return "/fts/uniprot_data_columns.txt";
368   }
369
370 }