JAL-1705 include stop codons in derived CDS; support ensemblgenomes
[jalview.git] / src / jalview / ext / ensembl / EnsemblRestClient.java
1 package jalview.ext.ensembl;
2
3 import jalview.io.FileParse;
4
5 import java.io.BufferedReader;
6 import java.io.DataOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.InputStreamReader;
10 import java.net.HttpURLConnection;
11 import java.net.MalformedURLException;
12 import java.net.URL;
13 import java.util.List;
14
15 import javax.ws.rs.HttpMethod;
16
17 import com.stevesoft.pat.Regex;
18
19 /**
20  * Base class for Ensembl REST service clients
21  * 
22  * @author gmcarstairs
23  */
24 abstract class EnsemblRestClient extends EnsemblSequenceFetcher
25 {
26   private final static String ENSEMBL_REST = "http://rest.ensembl.org";
27
28   protected final static String ENSEMBL_GENOMES_REST = "http://rest.ensemblgenomes.org";
29
30   // @see https://github.com/Ensembl/ensembl-rest/wiki/Output-formats
31   private static final String PING_URL = "http://rest.ensembl.org/info/ping.json";
32
33   private final static long RETEST_INTERVAL = 10000L; // 10 seconds
34
35   private static final Regex TRANSCRIPT_REGEX = new Regex(
36             "(ENS)([A-Z]{3}|)T[0-9]{11}$");
37
38   private static final Regex GENE_REGEX = new Regex(
39             "(ENS)([A-Z]{3}|)G[0-9]{11}$");
40
41   private String domain = ENSEMBL_REST;
42
43   private static boolean ensemblRestAvailable = false;
44
45   private static long lastCheck = -1;
46
47   protected volatile boolean inProgress = false;
48
49   /**
50    * Default constructor to use rest.ensembl.org
51    */
52   public EnsemblRestClient()
53   {
54     this(ENSEMBL_REST);
55   }
56
57   /**
58    * Constructor given the target domain to fetch data from
59    * 
60    * @param d
61    */
62   public EnsemblRestClient(String d)
63   {
64     domain = d;
65   }
66
67   String getDomain()
68   {
69     return domain;
70   }
71
72   void setDomain(String d)
73   {
74     domain = d;
75   }
76
77   public boolean isTranscriptIdentifier(String query)
78   {
79     return query == null ? false : TRANSCRIPT_REGEX.search(query);
80   }
81
82   public boolean isGeneIdentifier(String query)
83   {
84     return query == null ? false : GENE_REGEX.search(query);
85   }
86
87   @Override
88   public boolean queryInProgress()
89   {
90     return inProgress;
91   }
92
93   @Override
94   public StringBuffer getRawRecords()
95   {
96     return null;
97   }
98
99   /**
100    * Returns the URL for the client http request
101    * 
102    * @param ids
103    * @return
104    * @throws MalformedURLException
105    */
106   protected abstract URL getUrl(List<String> ids)
107           throws MalformedURLException;
108
109   /**
110    * Returns true if client uses GET method, false if it uses POST
111    * 
112    * @return
113    */
114   protected abstract boolean useGetRequest();
115
116   /**
117    * Return the desired value for the Content-Type request header
118    * 
119    * @param multipleIds
120    * 
121    * @return
122    * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers
123    */
124   protected abstract String getRequestMimeType(boolean multipleIds);
125
126   /**
127    * Return the desired value for the Accept request header
128    * 
129    * @return
130    * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers
131    */
132   protected abstract String getResponseMimeType();
133
134   /**
135    * Tries to connect to Ensembl's REST 'ping' endpoint, and returns true if
136    * successful, else false
137    * 
138    * @return
139    */
140   private boolean checkEnsembl()
141   {
142     try
143     {
144       // note this format works for both ensembl and ensemblgenomes
145       // info/ping.json works for ensembl only (March 2016)
146       URL ping = new URL(getDomain()
147               + "/info/ping?content-type=application/json");
148       HttpURLConnection conn = (HttpURLConnection) ping.openConnection();
149       int rc = conn.getResponseCode();
150       conn.disconnect();
151       if (rc >= 200 && rc < 300)
152       {
153         return true;
154       }
155     } catch (Throwable t)
156     {
157       System.err.println("Error connecting to " + PING_URL + ": "
158               + t.getMessage());
159     }
160     return false;
161   }
162
163   /**
164    * returns a reader to a Fasta response from the Ensembl sequence endpoint
165    * 
166    * @param ids
167    * @return
168    * @throws IOException
169    */
170   protected FileParse getSequenceReader(List<String> ids)
171           throws IOException
172   {
173     URL url = getUrl(ids);
174   
175     BufferedReader reader = getHttpResponse(url, ids);
176     FileParse fp = new FileParse(reader, url.toString(), "HTTP_POST");
177     return fp;
178   }
179
180   /**
181    * Writes the HTTP request and gets the response as a reader.
182    * 
183    * @param url
184    * @param ids
185    *          written as Json POST body if more than one
186    * @return
187    * @throws IOException
188    *           if response code was not 200, or other I/O error
189    */
190   protected BufferedReader getHttpResponse(URL url, List<String> ids)
191           throws IOException
192   {
193     // long now = System.currentTimeMillis();
194     HttpURLConnection connection = (HttpURLConnection) url.openConnection();
195   
196     /*
197      * POST method allows multiple queries in one request; it is supported for
198      * sequence queries, but not for overlap
199      */
200     boolean multipleIds = ids.size() > 1;// useGetRequest();
201     connection.setRequestMethod(multipleIds ? HttpMethod.POST
202             : HttpMethod.GET);
203     connection.setRequestProperty("Content-Type",
204             getRequestMimeType(multipleIds));
205     connection.setRequestProperty("Accept", getResponseMimeType());
206
207     connection.setUseCaches(false);
208     connection.setDoInput(true);
209     connection.setDoOutput(multipleIds);
210
211     if (multipleIds)
212     {
213       writePostBody(connection, ids);
214     }
215   
216     InputStream response = connection.getInputStream();
217     int responseCode = connection.getResponseCode();
218   
219     if (responseCode != 200)
220     {
221       /*
222        * note: a GET request for an invalid id returns an error code e.g. 415
223        * but POST request returns 200 and an empty Fasta response 
224        */
225       throw new IOException(
226               "Response code was not 200. Detected response was "
227                       + responseCode);
228     }
229     // System.out.println(getClass().getName() + " took "
230     // + (System.currentTimeMillis() - now) + "ms to fetch");
231   
232     BufferedReader reader = null;
233     reader = new BufferedReader(new InputStreamReader(response, "UTF-8"));
234     return reader;
235   }
236
237   /**
238    * Rechecks if Ensembl is responding, unless the last check was successful and
239    * the retest interval has not yet elapsed. Returns true if Ensembl is up,
240    * else false.
241    * 
242    * @return
243    */
244   protected boolean isEnsemblAvailable()
245   {
246     long now = System.currentTimeMillis();
247     boolean retest = now - lastCheck > RETEST_INTERVAL;
248     if (ensemblRestAvailable && !retest)
249     {
250       return true;
251     }
252     ensemblRestAvailable = checkEnsembl();
253     lastCheck = now;
254     return ensemblRestAvailable;
255   }
256
257   /**
258    * Constructs, writes and flushes the POST body of the request, containing the
259    * query ids in JSON format
260    * 
261    * @param connection
262    * @param ids
263    * @throws IOException
264    */
265   protected void writePostBody(HttpURLConnection connection,
266           List<String> ids) throws IOException
267   {
268     boolean first;
269     StringBuilder postBody = new StringBuilder(64);
270     postBody.append("{\"ids\":[");
271     first = true;
272     for (String id : ids)
273     {
274       if (!first)
275       {
276         postBody.append(",");
277       }
278       first = false;
279       postBody.append("\"");
280       postBody.append(id.trim());
281       postBody.append("\"");
282     }
283     postBody.append("]}");
284     byte[] thepostbody = postBody.toString().getBytes();
285     connection.setRequestProperty("Content-Length",
286             Integer.toString(thepostbody.length));
287     DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
288     wr.write(thepostbody);
289     wr.flush();
290     wr.close();
291   }
292
293 }