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