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