215eb7a720ac5f5e8b4ed104cd43fd1be13eb058
[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 /**
18  * Base class for Ensembl REST service clients
19  * 
20  * @author gmcarstairs
21  */
22 abstract class EnsemblRestClient extends EnsemblSequenceFetcher
23 {
24   protected final static String ENSEMBL_REST = "http://rest.ensembl.org";
25
26   protected static final String SEQUENCE_ID_URL = ENSEMBL_REST
27           + "/sequence/id";
28
29   // @see https://github.com/Ensembl/ensembl-rest/wiki/Output-formats
30   private static final String PING_URL = "http://rest.ensembl.org/info/ping.json";
31
32   private final static long RETEST_INTERVAL = 10000L; // 10 seconds
33
34   private static boolean ensemblRestAvailable = false;
35
36   private static long lastCheck = -1;
37
38   protected volatile boolean inProgress = false;
39
40   @Override
41   public boolean queryInProgress()
42   {
43     return inProgress;
44   }
45
46   @Override
47   public StringBuffer getRawRecords()
48   {
49     return null;
50   }
51
52   /**
53    * Returns the URL for the client http request
54    * 
55    * @param ids
56    * @return
57    * @throws MalformedURLException
58    */
59   protected abstract URL getUrl(List<String> ids)
60           throws MalformedURLException;
61
62   /**
63    * Returns true if client uses GET method, false if it uses POST
64    * 
65    * @return
66    */
67   protected abstract boolean useGetRequest();
68
69   /**
70    * Return the desired value for the Content-Type request header
71    * 
72    * @param multipleIds
73    * 
74    * @return
75    * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers
76    */
77   protected abstract String getRequestMimeType(boolean multipleIds);
78
79   /**
80    * Return the desired value for the Accept request header
81    * 
82    * @return
83    * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers
84    */
85   protected abstract String getResponseMimeType();
86
87   /**
88    * Tries to connect to Ensembl's REST 'ping' endpoint, and returns true if
89    * successful, else false
90    * 
91    * @return
92    */
93   private boolean checkEnsembl()
94   {
95     try
96     {
97       URL ping = new URL(PING_URL);
98       HttpURLConnection conn = (HttpURLConnection) ping.openConnection();
99       int rc = conn.getResponseCode();
100       conn.disconnect();
101       if (rc >= 200 && rc < 300)
102       {
103         return true;
104       }
105     } catch (Throwable t)
106     {
107       System.err.println("Error connecting to " + PING_URL + ": "
108               + t.getMessage());
109     }
110     return false;
111   }
112
113   /**
114    * returns a reader to a Fasta response from the Ensembl sequence endpoint
115    * 
116    * @param ids
117    * @return
118    * @throws IOException
119    */
120   protected FileParse getSequenceReader(List<String> ids)
121           throws IOException
122   {
123     URL url = getUrl(ids);
124   
125     BufferedReader reader = getHttpResponse(url, ids);
126     FileParse fp = new FileParse(reader, url.toString(), "HTTP_POST");
127     return fp;
128   }
129
130   /**
131    * Writes the HTTP request and gets the response as a reader.
132    * 
133    * @param url
134    * @param ids
135    *          written as Json POST body if more than one
136    * @return
137    * @throws IOException
138    *           if response code was not 200, or other I/O error
139    */
140   protected BufferedReader getHttpResponse(URL url, List<String> ids)
141           throws IOException
142   {
143     // long now = System.currentTimeMillis();
144     HttpURLConnection connection = (HttpURLConnection) url.openConnection();
145   
146     /*
147      * POST method allows multiple queries in one request; it is supported for
148      * sequence queries, but not for overlap
149      */
150     boolean multipleIds = ids.size() > 1;// useGetRequest();
151     connection.setRequestMethod(multipleIds ? HttpMethod.POST
152             : HttpMethod.GET);
153     connection.setRequestProperty("Content-Type",
154             getRequestMimeType(multipleIds));
155     connection.setRequestProperty("Accept", getResponseMimeType());
156
157     connection.setUseCaches(false);
158     connection.setDoInput(true);
159     connection.setDoOutput(multipleIds);
160
161     if (multipleIds)
162     {
163       writePostBody(connection, ids);
164     }
165   
166     InputStream response = connection.getInputStream();
167     int responseCode = connection.getResponseCode();
168   
169     if (responseCode != 200)
170     {
171       /*
172        * note: a GET request for an invalid id returns an error code e.g. 415
173        * but POST request returns 200 and an empty Fasta response 
174        */
175       throw new IOException(
176               "Response code was not 200. Detected response was "
177                       + responseCode);
178     }
179     // System.out.println(getClass().getName() + " took "
180     // + (System.currentTimeMillis() - now) + "ms to fetch");
181   
182     BufferedReader reader = null;
183     reader = new BufferedReader(new InputStreamReader(response, "UTF-8"));
184     return reader;
185   }
186
187   /**
188    * Rechecks if Ensembl is responding, unless the last check was successful and
189    * the retest interval has not yet elapsed. Returns true if Ensembl is up,
190    * else false.
191    * 
192    * @return
193    */
194   protected boolean isEnsemblAvailable()
195   {
196     long now = System.currentTimeMillis();
197     boolean retest = now - lastCheck > RETEST_INTERVAL;
198     if (ensemblRestAvailable && !retest)
199     {
200       return true;
201     }
202     ensemblRestAvailable = checkEnsembl();
203     lastCheck = now;
204     return ensemblRestAvailable;
205   }
206
207   /**
208    * Constructs, writes and flushes the POST body of the request, containing the
209    * query ids in JSON format
210    * 
211    * @param connection
212    * @param ids
213    * @throws IOException
214    */
215   protected void writePostBody(HttpURLConnection connection,
216           List<String> ids) throws IOException
217   {
218     boolean first;
219     StringBuilder postBody = new StringBuilder(64);
220     postBody.append("{\"ids\":[");
221     first = true;
222     for (String id : ids)
223     {
224       if (!first)
225       {
226         postBody.append(",");
227       }
228       first = false;
229       postBody.append("\"");
230       postBody.append(id.trim());
231       postBody.append("\"");
232     }
233     postBody.append("]}");
234     byte[] thepostbody = postBody.toString().getBytes();
235     connection.setRequestProperty("Content-Length",
236             Integer.toString(thepostbody.length));
237     DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
238     wr.write(thepostbody);
239     wr.flush();
240     wr.close();
241   }
242
243 }