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