Merge branch 'develop' into features/JAL-1793VCF
[jalview.git] / src / jalview / ext / ensembl / EnsemblLookup.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.ext.ensembl;
22
23 import jalview.bin.Cache;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.GeneLociI;
26 import jalview.util.MapList;
27
28 import java.io.BufferedReader;
29 import java.io.IOException;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.function.Function;
36
37 import org.json.simple.JSONObject;
38 import org.json.simple.parser.JSONParser;
39 import org.json.simple.parser.ParseException;
40
41 /**
42  * A client for the Ensembl /lookup REST endpoint, used to find the gene
43  * identifier given a gene, transcript or protein identifier, or to extract the
44  * species or chromosomal coordinates from the same service response
45  * 
46  * @author gmcarstairs
47  */
48 public class EnsemblLookup extends EnsemblRestClient
49 {
50   private static final String SPECIES = "species";
51
52   /**
53    * Default constructor (to use rest.ensembl.org)
54    */
55   public EnsemblLookup()
56   {
57     super();
58   }
59
60   /**
61    * Constructor given the target domain to fetch data from
62    * 
63    * @param
64    */
65   public EnsemblLookup(String d)
66   {
67     super(d);
68   }
69
70   @Override
71   public String getDbName()
72   {
73     return "ENSEMBL";
74   }
75
76   @Override
77   public AlignmentI getSequenceRecords(String queries) throws Exception
78   {
79     return null;
80   }
81
82   @Override
83   protected URL getUrl(List<String> ids) throws MalformedURLException
84   {
85     String identifier = ids.get(0);
86     return getUrl(identifier, null);
87   }
88
89   /**
90    * Gets the url for lookup of the given identifier, optionally with objectType
91    * also specified in the request
92    * 
93    * @param identifier
94    * @param objectType
95    * @return
96    */
97   protected URL getUrl(String identifier, String objectType)
98   {
99     String url = getDomain() + "/lookup/id/" + identifier
100             + CONTENT_TYPE_JSON;
101     if (objectType != null)
102     {
103       url += "&" + OBJECT_TYPE + "=" + objectType;
104     }
105
106     try
107     {
108       return new URL(url);
109     } catch (MalformedURLException e)
110     {
111       return null;
112     }
113   }
114
115   @Override
116   protected boolean useGetRequest()
117   {
118     return true;
119   }
120
121   @Override
122   protected String getRequestMimeType(boolean multipleIds)
123   {
124     return "application/json";
125   }
126
127   @Override
128   protected String getResponseMimeType()
129   {
130     return "application/json";
131   }
132
133   /**
134    * Returns the gene id related to the given identifier (which may be for a
135    * gene, transcript or protein)
136    * 
137    * @param identifier
138    * @return
139    */
140   public String getGeneId(String identifier)
141   {
142     return (String) getResult(identifier, null, br -> parseGeneId(br));
143   }
144
145   /**
146    * Calls the Ensembl lookup REST endpoint and retrieves the 'species' for the
147    * given identifier, or null if not found
148    * 
149    * @param identifier
150    * @return
151    */
152   public String getSpecies(String identifier)
153   {
154     return (String) getResult(identifier, null,
155             br -> getAttribute(br, SPECIES));
156   }
157
158   /**
159    * Calls the /lookup/id rest service and delegates parsing of the JSON
160    * response to the supplied parser
161    * 
162    * @param identifier
163    * @param objectType
164    *          (optional)
165    * @param parser
166    * @return
167    */
168   protected Object getResult(String identifier, String objectType,
169           Function<BufferedReader, Object> parser)
170   {
171     List<String> ids = Arrays.asList(new String[] { identifier });
172
173     BufferedReader br = null;
174     try
175     {
176       URL url = getUrl(identifier, objectType);
177       if (url != null)
178       {
179         br = getHttpResponse(url, ids);
180       }
181       return br == null ? null : parser.apply(br);
182     } catch (IOException e)
183     {
184       // ignore
185       return null;
186     } finally
187     {
188       if (br != null)
189       {
190         try
191         {
192           br.close();
193         } catch (IOException e)
194         {
195           // ignore
196         }
197       }
198     }
199   }
200
201   /**
202    * Answers the value of 'attribute' from the JSON response, or null if not
203    * found
204    * 
205    * @param br
206    * @param attribute
207    * @return
208    */
209   protected String getAttribute(BufferedReader br, String attribute)
210   {
211     String value = null;
212     JSONParser jp = new JSONParser();
213     try
214     {
215       JSONObject val = (JSONObject) jp.parse(br);
216       value = val.get(attribute).toString();
217     } catch (ParseException | NullPointerException | IOException e)
218     {
219       // ignore
220     }
221     return value;
222   }
223
224   /**
225    * Parses the JSON response and returns the gene identifier, or null if not
226    * found. If the returned object_type is Gene, returns the id, if Transcript
227    * returns the Parent. If it is Translation (peptide identifier), then the
228    * Parent is the transcript identifier, so we redo the search with this value,
229    * specifying that object_type should be Transcript.
230    * 
231    * @param br
232    * @return
233    */
234   protected String parseGeneId(BufferedReader br)
235   {
236     String geneId = null;
237     JSONParser jp = new JSONParser();
238     try
239     {
240       JSONObject val = (JSONObject) jp.parse(br);
241       String type = val.get(OBJECT_TYPE).toString();
242       if (OBJECT_TYPE_GENE.equalsIgnoreCase(type))
243       {
244         // got the gene - just returns its id
245         geneId = val.get(JSON_ID).toString();
246       }
247       else if (OBJECT_TYPE_TRANSCRIPT.equalsIgnoreCase(type))
248       {
249         // got the transcript - return its (Gene) Parent
250         geneId = val.get(PARENT).toString();
251       }
252       else if (OBJECT_TYPE_TRANSLATION.equalsIgnoreCase(type))
253       {
254         // got the protein - get its Parent, restricted to type Transcript
255         String transcriptId = val.get(PARENT).toString();
256         geneId = (String) getResult(transcriptId, OBJECT_TYPE_TRANSCRIPT,
257                 reader -> parseGeneId(reader));
258       }
259     } catch (ParseException | IOException e)
260     {
261       // ignore
262     }
263     return geneId;
264   }
265
266   /**
267    * Calls the /lookup/id rest service for the given id, and if successful,
268    * parses and returns the gene's chromosomal coordinates
269    * 
270    * @param geneId
271    * @return
272    */
273   public GeneLociI getGeneLoci(String geneId)
274   {
275     return (GeneLociI) getResult(geneId, OBJECT_TYPE_GENE,
276             br -> parseGeneLoci(br));
277   }
278
279   /**
280    * Parses the /lookup/id response for species, asssembly_name,
281    * seq_region_name, start, end and returns an object that wraps them, or null
282    * if unsuccessful
283    * 
284    * @param br
285    * @return
286    */
287   GeneLociI parseGeneLoci(BufferedReader br)
288   {
289     JSONParser jp = new JSONParser();
290     try
291     {
292       JSONObject val = (JSONObject) jp.parse(br);
293       final String species = val.get("species").toString();
294       final String assembly = val.get("assembly_name").toString();
295       final String chromosome = val.get("seq_region_name").toString();
296       String strand = val.get("strand").toString();
297       int start = Integer.parseInt(val.get("start").toString());
298       int end = Integer.parseInt(val.get("end").toString());
299       int fromEnd = end - start + 1;
300       boolean reverseStrand = "-1".equals(strand);
301       int toStart = reverseStrand ? end : start;
302       int toEnd = reverseStrand ? start : end;
303       List<int[]> fromRange = Collections.singletonList(new int[] { 1,
304           fromEnd });
305       List<int[]> toRange = Collections.singletonList(new int[] { toStart,
306           toEnd });
307       final MapList map = new MapList(fromRange, toRange, 1, 1);
308       return new GeneLociI()
309       {
310
311         @Override
312         public String getSpeciesId()
313         {
314           return species == null ? "" : species;
315         }
316
317         @Override
318         public String getAssemblyId()
319         {
320           return assembly;
321         }
322
323         @Override
324         public String getChromosomeId()
325         {
326           return chromosome;
327         }
328
329         @Override
330         public MapList getMap()
331         {
332           return map;
333         }
334       };
335     } catch (ParseException | NullPointerException | IOException
336             | NumberFormatException | ClassCastException e)
337     {
338       Cache.log.error("Error looking up gene loci: " + e.getMessage());
339       e.printStackTrace();
340     }
341     return null;
342   }
343
344 }