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