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