JAL-3959 add key retrieval urls that support CORS needed by JalviewJS
[jalview.git] / src / jalview / ws / ebi / EBIFetchClient.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 package jalview.ws.ebi;
22
23 import java.util.Locale;
24
25 import jalview.datamodel.DBRefSource;
26 import jalview.util.MessageManager;
27 import jalview.util.Platform;
28
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.net.HttpURLConnection;
35 import java.net.URL;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.StringTokenizer;
39
40 /**
41  * DOCUMENT ME!
42  * 
43  * @author $author$
44  * @version $Revision$
45  */
46 public class EBIFetchClient
47 {
48
49   /**
50    * Creates a new EBIFetchClient object.
51    */
52   public EBIFetchClient()
53   {
54   }
55
56   /**
57    * DOCUMENT ME!
58    * 
59    * @return DOCUMENT ME!
60    */
61   public String[] getSupportedDBs()
62   {
63     // TODO - implement rest call for dbfetch getSupportedDBs
64     throw new Error(MessageManager.getString("error.not_yet_implemented"));
65   }
66
67   /**
68    * DOCUMENT ME!
69    * 
70    * @return DOCUMENT ME!
71    */
72   public String[] getSupportedFormats()
73   {
74     // TODO - implement rest call for dbfetch getSupportedFormats
75     throw new Error(MessageManager.getString("error.not_yet_implemented"));
76   }
77
78   /**
79    * DOCUMENT ME!
80    * 
81    * @return DOCUMENT ME!
82    */
83   public String[] getSupportedStyles()
84   {
85     // TODO - implement rest call for dbfetch getSupportedStyles
86     throw new Error(MessageManager.getString("error.not_yet_implemented"));
87   }
88
89   /**
90    * Send an HTTP fetch request to EBI and save the reply in a temporary file.
91    * 
92    * @param ids
93    *          the query formatted as db:query1;query2;query3
94    * @param format
95    *          the format wanted
96    * @param ext
97    *          for the temporary file to hold response (without separator)
98    * @return the file holding the response
99    * @throws OutOfMemoryError
100    */
101
102   public File fetchDataAsFile(String ids, String format, String ext)
103           throws OutOfMemoryError
104   {
105     File outFile = null;
106     try
107     {
108       outFile = File.createTempFile("jalview", "." + ext);
109       outFile.deleteOnExit();
110       fetchData(ids, format, outFile);
111       if (outFile.length() == 0)
112       {
113         outFile.delete();
114         return null;
115       }
116     } catch (Exception ex)
117     {
118     }
119     return outFile;
120   }
121
122   /**
123    * Fetches queries and either saves the response to a file or returns as
124    * string data
125    * 
126    * @param ids
127    * @param format
128    * @param outFile
129    * @return
130    * @throws OutOfMemoryError
131    */
132   String[] fetchData(String ids, String format, File outFile)
133           throws OutOfMemoryError
134   {
135     StringBuilder querystring = new StringBuilder(ids.length());
136     String database = parseIds(ids, querystring);
137     if (database == null)
138     {
139       System.err.println("Invalid Query string : '" + ids + "'");
140       System.err.println("Should be of form 'dbname:q1;q2;q3;q4'");
141       return null;
142     }
143
144     // note: outFile is currently always specified, so return value is null
145     String[] rslt = fetchBatch(querystring.toString(), database, format,
146             outFile);
147
148     return (rslt != null && rslt.length > 0 ? rslt : null);
149   }
150
151   /**
152    * Parses ids formatted as dbname:q1;q2;q3, returns the dbname and adds
153    * queries as comma-separated items to the querystring. dbname must be
154    * specified for at least one queryId. Returns null if a mixture of different
155    * dbnames is found (ignoring case).
156    * 
157    * @param ids
158    * @param queryString
159    * @return
160    */
161   static String parseIds(String ids, StringBuilder queryString)
162   {
163     String database = null;
164     StringTokenizer queries = new StringTokenizer(ids, ";");
165     boolean appending = queryString.length() > 0;
166     while (queries.hasMoreTokens())
167     {
168       String query = queries.nextToken();
169       int p = query.indexOf(':');
170       if (p > -1)
171       {
172         String db = query.substring(0, p);
173         if (database != null && !db.equalsIgnoreCase(database))
174         {
175           /*
176            * different databases mixed in together - invalid
177            */
178           return null;
179         }
180         database = db;
181         query = query.substring(p + 1);
182       }
183       queryString.append(appending ? "," : "");
184       queryString.append(query);
185       appending = true;
186     }
187     return database;
188   }
189
190   /**
191    * Fetches queries and either saves the response to a file or (if no file
192    * specified) returns as string data
193    * 
194    * @param ids
195    * @param database
196    * @param format
197    * @param outFile
198    * @return array of lines from EBI only if outFile is null (which it will not
199    *         be)
200    * @throws OutOfMemoryError
201    */
202   String[] fetchBatch(String ids, String database, String format,
203           File outFile) throws OutOfMemoryError
204   {
205     String url = buildUrl(ids, database, format);
206     InputStream is = null;
207     BufferedReader br = null;
208     try
209     {
210       URL rcall = new URL(url);
211       HttpURLConnection conn = (HttpURLConnection) rcall.openConnection();
212       int responseCode = conn.getResponseCode();
213       if (responseCode == 200)
214       {
215         is = conn.getInputStream();
216         if (outFile != null)
217         {
218           Platform.streamToFile(is, outFile);
219           return null;
220         }
221         br = new BufferedReader(new InputStreamReader(is));
222         String rtn;
223         List<String> arl = new ArrayList<>();
224         while ((rtn = br.readLine()) != null)
225         {
226           arl.add(rtn);
227         }
228         return (String[]) arl.toArray();
229       }
230       System.err.println(
231               "Warning: response code " + responseCode + " for " + url);
232     } catch (OutOfMemoryError er)
233     {
234       System.out.println("OUT OF MEMORY DOWNLOADING QUERY FROM " + database
235               + ":\n" + ids);
236       throw er;
237     } catch (Exception ex)
238     {
239       if (!ex.getMessage().startsWith(
240               "uk.ac.ebi.jdbfetch.exceptions.DbfNoEntryFoundException"))
241       {
242         System.err.println("Unexpected exception when retrieving from "
243                 + database + "\nQuery was : '" + ids + "'");
244         ex.printStackTrace(System.err);
245       }
246     } finally
247     {
248       if (is != null)
249       {
250         try
251         {
252           is.close();
253         } catch (IOException e)
254         {
255         }
256       }
257       if (br != null)
258       {
259         try
260         {
261           br.close();
262         } catch (IOException e)
263         {
264         }
265       }
266     }
267     return null;
268   }
269   static {
270     Platform.addJ2SDirectDatabaseCall("https://www.ebi.ac.uk/");
271   }
272   /**
273    * Constructs the URL to fetch from
274    * 
275    * @param ids
276    * @param database
277    * @param format
278    * @return
279    */
280   static String buildUrl(String ids, String database, String format)
281   {
282     String url;
283     if (database.equalsIgnoreCase(DBRefSource.EMBL)
284             || database.equalsIgnoreCase(DBRefSource.EMBLCDS))
285     {
286       url = "https://www.ebi.ac.uk/ena/browser/api/embl/"
287               + ids.toLowerCase(Locale.ROOT) + "?download=true&gzip=true";
288     }
289     else
290     {
291       url = "https://www.ebi.ac.uk/Tools/dbfetch/dbfetch/"
292               + database.toLowerCase(Locale.ROOT) + "/" + ids.toLowerCase(Locale.ROOT)
293               + (format != null ? "/" + format : "");
294     }
295     return url;
296   }
297 }