JAL-3193 removal of rest.ensemblgenomes.org
[jalview.git] / src / jalview / ext / ensembl / EnsemblMap.java
1 package jalview.ext.ensembl;
2
3 import jalview.datamodel.AlignmentI;
4 import jalview.datamodel.DBRefSource;
5 import jalview.datamodel.GeneLociI;
6 import jalview.util.MapList;
7
8 import java.io.BufferedReader;
9 import java.io.IOException;
10 import java.net.MalformedURLException;
11 import java.net.URL;
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.Iterator;
15 import java.util.List;
16
17 import org.json.simple.JSONArray;
18 import org.json.simple.JSONObject;
19 import org.json.simple.parser.JSONParser;
20 import org.json.simple.parser.ParseException;
21
22 public class EnsemblMap extends EnsemblRestClient
23 {
24   private static final String MAPPED = "mapped";
25
26   private static final String MAPPINGS = "mappings";
27
28   private static final String CDS = "cds";
29
30   private static final String CDNA = "cdna";
31
32   /**
33    * Default constructor (to use rest.ensembl.org)
34    */
35   public EnsemblMap()
36   {
37     super();
38   }
39
40   @Override
41   public String getDbName()
42   {
43     return DBRefSource.ENSEMBL;
44   }
45
46   @Override
47   public AlignmentI getSequenceRecords(String queries) throws Exception
48   {
49     return null; // not used
50   }
51
52   /**
53    * Constructs a URL of the format <code>
54    * http://rest.ensembl.org/map/human/GRCh38/17:45051610..45109016:1/GRCh37?content-type=application/json
55    * </code>
56    * 
57    * @param species
58    * @param chromosome
59    * @param fromRef
60    * @param toRef
61    * @param startPos
62    * @param endPos
63    * @return
64    * @throws MalformedURLException
65    */
66   protected URL getAssemblyMapUrl(String species, String chromosome, String fromRef,
67           String toRef, int startPos, int endPos)
68           throws MalformedURLException
69   {
70     /*
71      * start-end might be reverse strand - present forwards to the service
72      */
73     boolean forward = startPos <= endPos;
74     int start = forward ? startPos : endPos;
75     int end = forward ? endPos : startPos;
76     String strand = forward ? "1" : "-1";
77     String url = String.format(
78             "%s/map/%s/%s/%s:%d..%d:%s/%s?content-type=application/json",
79             getDomain(), species, fromRef, chromosome, start, end, strand,
80             toRef);
81     return new URL(url);
82   }
83
84   @Override
85   protected boolean useGetRequest()
86   {
87     return true;
88   }
89
90   @Override
91   protected URL getUrl(List<String> ids) throws MalformedURLException
92   {
93     return null; // not used
94   }
95
96   /**
97    * Calls the REST /map service to get the chromosomal coordinates (start/end)
98    * in 'toRef' that corresponding to the (start/end) queryRange in 'fromRef'
99    * 
100    * @param species
101    * @param chromosome
102    * @param fromRef
103    * @param toRef
104    * @param queryRange
105    * @return
106    * @see http://rest.ensembl.org/documentation/info/assembly_map
107    */
108   public int[] getAssemblyMapping(String species, String chromosome,
109           String fromRef, String toRef, int[] queryRange)
110   {
111     URL url = null;
112     BufferedReader br = null;
113
114     try
115     {
116       url = getAssemblyMapUrl(species, chromosome, fromRef, toRef, queryRange[0],
117               queryRange[1]);
118       br = getHttpResponse(url, null);
119       return (parseAssemblyMappingResponse(br));
120     } catch (Throwable t)
121     {
122       System.out.println("Error calling " + url + ": " + t.getMessage());
123       return null;
124     } finally
125     {
126       if (br != null)
127       {
128         try
129         {
130           br.close();
131         } catch (IOException e)
132         {
133           // ignore
134         }
135       }
136     }
137   }
138
139   /**
140    * Parses the JSON response from the /map/&lt;species&gt;/ REST service. The
141    * format is (with some fields omitted)
142    * 
143    * <pre>
144    *  {"mappings": 
145    *    [{
146    *       "original": {"end":45109016,"start":45051610},
147    *       "mapped"  : {"end":43186384,"start":43128978} 
148    *  }] }
149    * </pre>
150    * 
151    * @param br
152    * @return
153    */
154   protected int[] parseAssemblyMappingResponse(BufferedReader br)
155   {
156     int[] result = null;
157     JSONParser jp = new JSONParser();
158
159     try
160     {
161       JSONObject parsed = (JSONObject) jp.parse(br);
162       JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
163
164       Iterator rvals = mappings.iterator();
165       while (rvals.hasNext())
166       {
167         // todo check for "mapped"
168         JSONObject val = (JSONObject) rvals.next();
169         JSONObject mapped = (JSONObject) val.get(MAPPED);
170         int start = Integer.parseInt(mapped.get("start").toString());
171         int end = Integer.parseInt(mapped.get("end").toString());
172         String strand = mapped.get("strand").toString();
173         if ("1".equals(strand))
174         {
175           result = new int[] { start, end };
176         }
177         else
178         {
179           result = new int[] { end, start };
180         }
181       }
182     } catch (IOException | ParseException | NumberFormatException e)
183     {
184       // ignore
185     }
186     return result;
187   }
188
189   /**
190    * Calls the REST /map/cds/id service, and returns a DBRefEntry holding the
191    * returned chromosomal coordinates, or returns null if the call fails
192    * 
193    * @param division
194    *          e.g. Ensembl, EnsemblMetazoa
195    * @param accession
196    *          e.g. ENST00000592782, Y55B1AR.1.1
197    * @param start
198    * @param end
199    * @return
200    */
201   public GeneLociI getCdsMapping(String division, String accession,
202           int start, int end)
203   {
204     return getIdMapping(division, accession, start, end, CDS);
205   }
206
207   /**
208    * Calls the REST /map/cdna/id service, and returns a DBRefEntry holding the
209    * returned chromosomal coordinates, or returns null if the call fails
210    * 
211    * @param division
212    *          e.g. Ensembl, EnsemblMetazoa
213    * @param accession
214    *          e.g. ENST00000592782, Y55B1AR.1.1
215    * @param start
216    * @param end
217    * @return
218    */
219   public GeneLociI getCdnaMapping(String division, String accession,
220           int start, int end)
221   {
222     return getIdMapping(division, accession, start, end, CDNA);
223   }
224
225   GeneLociI getIdMapping(String division, String accession, int start,
226           int end, String cdsOrCdna)
227   {
228     URL url = null;
229     BufferedReader br = null;
230
231     try
232     {
233       String domain = new EnsemblInfo().getDomain(division);
234       if (domain != null)
235       {
236         url = getIdMapUrl(domain, accession, start, end, cdsOrCdna);
237         br = getHttpResponse(url, null);
238         if (br != null)
239         {
240           return (parseIdMappingResponse(br, accession));
241         }
242       }
243       return null;
244     } catch (Throwable t)
245     {
246       System.out.println("Error calling " + url + ": " + t.getMessage());
247       return null;
248     } finally
249     {
250       if (br != null)
251       {
252         try
253         {
254           br.close();
255         } catch (IOException e)
256         {
257           // ignore
258         }
259       }
260     }
261   }
262
263   /**
264    * Constructs a URL to the /map/cds/<id> or /map/cdna/<id> REST service
265    * 
266    * @param domain
267    * @param accession
268    * @param start
269    * @param end
270    * @param cdsOrCdna
271    * @return
272    * @throws MalformedURLException
273    */
274   URL getIdMapUrl(String domain, String accession, int start, int end,
275           String cdsOrCdna) throws MalformedURLException
276   {
277     String url = String
278             .format("%s/map/%s/%s/%d..%d?include_original_region=1&content-type=application/json",
279                     domain, cdsOrCdna, accession, start, end);
280     return new URL(url);
281   }
282
283   /**
284    * Parses the JSON response from the /map/cds/ or /map/cdna REST service. The
285    * format is
286    * 
287    * <pre>
288    * {"mappings":
289    *   [
290    *    {"assembly_name":"TAIR10","end":2501311,"seq_region_name":"1","gap":0,
291    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2501114},
292    *    {"assembly_name":"TAIR10","end":2500815,"seq_region_name":"1","gap":0,
293    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2500714}
294    *   ]
295    * }
296    * </pre>
297    * 
298    * @param br
299    * @param accession
300    * @return
301    */
302   GeneLociI parseIdMappingResponse(BufferedReader br, String accession)
303   {
304     JSONParser jp = new JSONParser();
305
306     try
307     {
308       JSONObject parsed = (JSONObject) jp.parse(br);
309       JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
310
311       Iterator rvals = mappings.iterator();
312       String assembly = null;
313       String chromosome = null;
314       int fromEnd = 0;
315       List<int[]> regions = new ArrayList<>();
316
317       while (rvals.hasNext())
318       {
319         JSONObject val = (JSONObject) rvals.next();
320         JSONObject original = (JSONObject) val.get("original");
321         fromEnd = Integer.parseInt(original.get("end").toString());
322
323         JSONObject mapped = (JSONObject) val.get(MAPPED);
324         int start = Integer.parseInt(mapped.get("start").toString());
325         int end = Integer.parseInt(mapped.get("end").toString());
326         String ass = mapped.get("assembly_name").toString();
327         if (assembly != null && !assembly.equals(ass))
328         {
329           System.err
330                   .println("EnsemblMap found multiple assemblies - can't resolve");
331           return null;
332         }
333         assembly = ass;
334         String chr = mapped.get("seq_region_name").toString();
335         if (chromosome != null && !chromosome.equals(chr))
336         {
337           System.err
338                   .println("EnsemblMap found multiple chromosomes - can't resolve");
339           return null;
340         }
341         chromosome = chr;
342         String strand = mapped.get("strand").toString();
343         if ("-1".equals(strand))
344         {
345           regions.add(new int[] { end, start });
346         }
347         else
348         {
349           regions.add(new int[] { start, end });
350         }
351       }
352
353       /*
354        * processed all mapped regions on chromosome, assemble the result,
355        * having first fetched the species id for the accession
356        */
357       final String species = new EnsemblLookup().getSpecies(accession);
358       final String as = assembly;
359       final String chr = chromosome;
360       List<int[]> fromRange = Collections.singletonList(new int[] { 1,
361           fromEnd });
362       final MapList map = new MapList(fromRange, regions, 1, 1);
363       return new GeneLociI()
364       {
365
366         @Override
367         public String getSpeciesId()
368         {
369           return species == null ? "" : species;
370         }
371
372         @Override
373         public String getAssemblyId()
374         {
375           return as;
376         }
377
378         @Override
379         public String getChromosomeId()
380         {
381           return chr;
382         }
383
384         @Override
385         public MapList getMap()
386         {
387           return map;
388         }
389       };
390     } catch (IOException | ParseException | NumberFormatException e)
391     {
392       // ignore
393     }
394
395     return null;
396   }
397
398 }