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