JAL-1705 use GET for single queries; set gene name as description on
[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     HttpURLConnection connection = (HttpURLConnection) url.openConnection();
126   
127     /*
128      * POST method allows multiple queries in one request; it is supported for
129      * sequence queries, but not for overlap
130      */
131     boolean multipleIds = ids.size() > 1;// useGetRequest();
132     connection.setRequestMethod(multipleIds ? HttpMethod.POST
133             : HttpMethod.GET);
134     connection.setRequestProperty("Content-Type",
135             getRequestMimeType(multipleIds));
136     connection.setRequestProperty("Accept", getResponseMimeType());
137
138     connection.setUseCaches(false);
139     connection.setDoInput(true);
140     connection.setDoOutput(multipleIds);
141
142     if (multipleIds)
143     {
144       writePostBody(connection, ids);
145     }
146   
147     InputStream response = connection.getInputStream();
148     int responseCode = connection.getResponseCode();
149   
150     if (responseCode != 200)
151     {
152       /*
153        * note: a GET request for an invalid id returns an error code e.g. 415
154        * but POST request returns 200 and an empty Fasta response 
155        */
156       throw new RuntimeException(
157               "Response code was not 200. Detected response was "
158                       + responseCode);
159     }
160   
161     BufferedReader reader = null;
162     reader = new BufferedReader(new InputStreamReader(response, "UTF-8"));
163     FileParse fp = new FileParse(reader, url.toString(), "HTTP_POST");
164     return fp;
165   }
166
167   /**
168    * Rechecks if Ensembl is responding, unless the last check was successful and
169    * the retest interval has not yet elapsed. Returns true if Ensembl is up,
170    * else false.
171    * 
172    * @return
173    */
174   protected boolean isEnsemblAvailable()
175   {
176     long now = System.currentTimeMillis();
177     boolean retest = now - lastCheck > RETEST_INTERVAL;
178     if (ensemblRestAvailable && !retest)
179     {
180       return true;
181     }
182     ensemblRestAvailable = checkEnsembl();
183     lastCheck = now;
184     return ensemblRestAvailable;
185   }
186
187   /**
188    * Constructs, writes and flushes the POST body of the request, containing the
189    * query ids in JSON format
190    * 
191    * @param connection
192    * @param ids
193    * @throws IOException
194    */
195   protected void writePostBody(HttpURLConnection connection,
196           List<String> ids) throws IOException
197   {
198     boolean first;
199     StringBuilder postBody = new StringBuilder(64);
200     postBody.append("{\"ids\":[");
201     first = true;
202     for (String id : ids)
203     {
204       if (!first)
205       {
206         postBody.append(",");
207       }
208       first = false;
209       postBody.append("\"");
210       postBody.append(id.trim());
211       postBody.append("\"");
212     }
213     postBody.append("]}");
214     byte[] thepostbody = postBody.toString().getBytes();
215     connection.setRequestProperty("Content-Length",
216             Integer.toString(thepostbody.length));
217     DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
218     wr.write(thepostbody);
219     wr.flush();
220     wr.close();
221   }
222
223 }