JAL-2920 use HGVS notation for protein variant feature
[jalview.git] / src / jalview / ws / dbsources / Uniprot.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.ws.dbsources;
22
23 import jalview.bin.Cache;
24 import jalview.datamodel.Alignment;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.DBRefEntry;
27 import jalview.datamodel.DBRefSource;
28 import jalview.datamodel.PDBEntry;
29 import jalview.datamodel.Sequence;
30 import jalview.datamodel.SequenceFeature;
31 import jalview.datamodel.SequenceI;
32 import jalview.datamodel.xdb.uniprot.UniprotEntry;
33 import jalview.datamodel.xdb.uniprot.UniprotFeature;
34 import jalview.datamodel.xdb.uniprot.UniprotFile;
35 import jalview.schemes.ResidueProperties;
36 import jalview.util.StringUtils;
37 import jalview.ws.seqfetcher.DbSourceProxyImpl;
38
39 import java.io.InputStream;
40 import java.io.InputStreamReader;
41 import java.io.Reader;
42 import java.net.URL;
43 import java.net.URLConnection;
44 import java.util.ArrayList;
45 import java.util.Vector;
46
47 import org.exolab.castor.mapping.Mapping;
48 import org.exolab.castor.xml.Unmarshaller;
49
50 import com.stevesoft.pat.Regex;
51
52 /**
53  * @author JimP
54  * 
55  */
56 public class Uniprot extends DbSourceProxyImpl
57 {
58   private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org";
59
60   private static final String BAR_DELIMITER = "|";
61
62   /*
63    * Castor mapping loaded from uniprot_mapping.xml
64    */
65   private static Mapping map;
66
67   /**
68    * Constructor
69    */
70   public Uniprot()
71   {
72     super();
73   }
74
75   private String getDomain()
76   {
77     return Cache.getDefault("UNIPROT_DOMAIN", DEFAULT_UNIPROT_DOMAIN);
78   }
79
80   /*
81    * (non-Javadoc)
82    * 
83    * @see jalview.ws.DbSourceProxy#getAccessionSeparator()
84    */
85   @Override
86   public String getAccessionSeparator()
87   {
88     return null;
89   }
90
91   /*
92    * (non-Javadoc)
93    * 
94    * @see jalview.ws.DbSourceProxy#getAccessionValidator()
95    */
96   @Override
97   public Regex getAccessionValidator()
98   {
99     return new Regex("([A-Z]+[0-9]+[A-Z0-9]+|[A-Z0-9]+_[A-Z0-9]+)");
100   }
101
102   /*
103    * (non-Javadoc)
104    * 
105    * @see jalview.ws.DbSourceProxy#getDbSource()
106    */
107   @Override
108   public String getDbSource()
109   {
110     return DBRefSource.UNIPROT;
111   }
112
113   /*
114    * (non-Javadoc)
115    * 
116    * @see jalview.ws.DbSourceProxy#getDbVersion()
117    */
118   @Override
119   public String getDbVersion()
120   {
121     return "0"; // we really don't know what version we're on.
122   }
123
124   /**
125    * Reads a file containing the reply to the EBI Fetch Uniprot data query,
126    * unmarshals it to a UniprotFile object, and returns the list of UniprotEntry
127    * data models (mapped from &lt;entry&gt; elements)
128    * 
129    * @param fileReader
130    * @return
131    */
132   public Vector<UniprotEntry> getUniprotEntries(Reader fileReader)
133   {
134     UniprotFile uni = new UniprotFile();
135     try
136     {
137       if (map == null)
138       {
139         // 1. Load the mapping information from the file
140         map = new Mapping(uni.getClass().getClassLoader());
141         URL url = getClass().getResource("/uniprot_mapping.xml");
142         map.loadMapping(url);
143       }
144
145       // 2. Unmarshal the data
146       Unmarshaller unmar = new Unmarshaller(uni);
147       unmar.setIgnoreExtraElements(true);
148       unmar.setMapping(map);
149       if (fileReader != null)
150       {
151         uni = (UniprotFile) unmar.unmarshal(fileReader);
152       }
153     } catch (Exception e)
154     {
155       System.out.println("Error getUniprotEntries() " + e);
156     }
157
158     return uni.getUniprotEntries();
159   }
160
161   /*
162    * (non-Javadoc)
163    * 
164    * @see jalview.ws.DbSourceProxy#getSequenceRecords(java.lang.String[])
165    */
166   @Override
167   public AlignmentI getSequenceRecords(String queries) throws Exception
168   {
169     startQuery();
170     try
171     {
172       queries = queries.toUpperCase().replaceAll(
173               "(UNIPROT\\|?|UNIPROT_|UNIREF\\d+_|UNIREF\\d+\\|?)", "");
174       AlignmentI al = null;
175
176       String downloadstring = getDomain() + "/uniprot/" + queries
177               + ".xml";
178       URL url = null;
179       URLConnection urlconn = null;
180
181       url = new URL(downloadstring);
182       urlconn = url.openConnection();
183       InputStream istr = urlconn.getInputStream();
184       Vector<UniprotEntry> entries = getUniprotEntries(
185               new InputStreamReader(istr, "UTF-8"));
186
187       if (entries != null)
188       {
189         ArrayList<SequenceI> seqs = new ArrayList<>();
190         for (UniprotEntry entry : entries)
191         {
192           seqs.add(uniprotEntryToSequenceI(entry));
193         }
194         al = new Alignment(seqs.toArray(new SequenceI[0]));
195
196       }
197       stopQuery();
198       return al;
199     } catch (Exception e)
200     {
201       throw (e);
202     } finally
203     {
204       stopQuery();
205     }
206   }
207
208   /**
209    * 
210    * @param entry
211    *          UniprotEntry
212    * @return SequenceI instance created from the UniprotEntry instance
213    */
214   public SequenceI uniprotEntryToSequenceI(UniprotEntry entry)
215   {
216     String id = getUniprotEntryId(entry);
217     SequenceI sequence = new Sequence(id,
218             entry.getUniprotSequence().getContent());
219     sequence.setDescription(getUniprotEntryDescription(entry));
220
221     final String dbVersion = getDbVersion();
222     ArrayList<DBRefEntry> dbRefs = new ArrayList<>();
223     for (String accessionId : entry.getAccession())
224     {
225       DBRefEntry dbRef = new DBRefEntry(DBRefSource.UNIPROT, dbVersion,
226               accessionId);
227
228       // mark dbRef as a primary reference for this sequence
229       dbRefs.add(dbRef);
230     }
231
232     Vector<PDBEntry> onlyPdbEntries = new Vector<>();
233     for (PDBEntry pdb : entry.getDbReference())
234     {
235       DBRefEntry dbr = new DBRefEntry();
236       dbr.setSource(pdb.getType());
237       dbr.setAccessionId(pdb.getId());
238       dbr.setVersion(DBRefSource.UNIPROT + ":" + dbVersion);
239       dbRefs.add(dbr);
240       if ("PDB".equals(pdb.getType()))
241       {
242         onlyPdbEntries.addElement(pdb);
243       }
244       if ("EMBL".equals(pdb.getType()))
245       {
246         // look for a CDS reference and add it, too.
247         String cdsId = (String) pdb.getProperty("protein sequence ID");
248         if (cdsId != null && cdsId.trim().length() > 0)
249         {
250           // remove version
251           String[] vrs = cdsId.split("\\.");
252           dbr = new DBRefEntry(DBRefSource.EMBLCDS, vrs.length > 1 ? vrs[1]
253                   : DBRefSource.UNIPROT + ":" + dbVersion, vrs[0]);
254           dbRefs.add(dbr);
255         }
256       }
257       if ("Ensembl".equals(pdb.getType()))
258       {
259         /*UniprotXML
260          * <dbReference type="Ensembl" id="ENST00000321556">
261         * <molecule id="Q9BXM7-1"/>
262         * <property type="protein sequence ID" value="ENSP00000364204"/>
263         * <property type="gene ID" value="ENSG00000158828"/>
264         * </dbReference> 
265          */
266         String cdsId = (String) pdb.getProperty("protein sequence ID");
267         if (cdsId != null && cdsId.trim().length() > 0)
268         {
269           dbr = new DBRefEntry(DBRefSource.ENSEMBL,
270                   DBRefSource.UNIPROT + ":" + dbVersion, cdsId.trim());
271           dbRefs.add(dbr);
272
273         }
274       }
275     }
276
277     sequence.setPDBId(onlyPdbEntries);
278     if (entry.getFeature() != null)
279     {
280       for (UniprotFeature uf : entry.getFeature())
281       {
282         SequenceFeature copy = new SequenceFeature(uf.getType(),
283                 getDescription(uf), uf.getBegin(), uf.getEnd(), "Uniprot");
284         copy.setStatus(uf.getStatus());
285         sequence.addSequenceFeature(copy);
286       }
287     }
288     for (DBRefEntry dbr : dbRefs)
289     {
290       sequence.addDBRef(dbr);
291     }
292     return sequence;
293   }
294
295   /**
296    * Constructs a feature description from the description and (optionally)
297    * original and variant fields of the Uniprot XML feature
298    * 
299    * @param uf
300    * @return
301    */
302   protected static String getDescription(UniprotFeature uf)
303   {
304     String orig = uf.getOriginal();
305     String var = uf.getVariation();
306     StringBuilder sb = new StringBuilder();
307
308     /*
309      * append variant in standard format if present
310      * e.g. p.Arg59Lys
311      */
312     if (orig != null && !orig.isEmpty() && var != null && !var.isEmpty())
313     {
314       sb.append("p.");
315       String orig3 = ResidueProperties.aa2Triplet.get(orig);
316       sb.append(orig3 == null ? orig : StringUtils.toSentenceCase(orig3));
317       sb.append(Integer.toString(uf.getPosition()));
318       String var3 = ResidueProperties.aa2Triplet.get(var);
319       sb.append(var3 == null ? var : StringUtils.toSentenceCase(var3));
320       sb.append(" ");
321     }
322
323     String description = uf.getDescription();
324     if (description != null)
325     {
326       sb.append(description);
327     }
328
329     return sb.toString();
330   }
331
332   /**
333    * 
334    * @param entry
335    *          UniportEntry
336    * @return protein name(s) delimited by a white space character
337    */
338   public static String getUniprotEntryDescription(UniprotEntry entry)
339   {
340     StringBuilder desc = new StringBuilder(32);
341     if (entry.getProtein() != null && entry.getProtein().getName() != null)
342     {
343       boolean first = true;
344       for (String nm : entry.getProtein().getName())
345       {
346         if (!first)
347         {
348           desc.append(" ");
349         }
350         first = false;
351         desc.append(nm);
352       }
353     }
354     return desc.toString();
355   }
356
357   /**
358    *
359    * @param entry
360    *          UniprotEntry
361    * @return The accession id(s) and name(s) delimited by '|'.
362    */
363   public static String getUniprotEntryId(UniprotEntry entry)
364   {
365     StringBuilder name = new StringBuilder(32);
366     for (String n : entry.getName())
367     {
368       if (name.length() > 0)
369       {
370         name.append(BAR_DELIMITER);
371       }
372       name.append(n);
373     }
374     return name.toString();
375   }
376
377   /*
378    * (non-Javadoc)
379    * 
380    * @see jalview.ws.DbSourceProxy#isValidReference(java.lang.String)
381    */
382   @Override
383   public boolean isValidReference(String accession)
384   {
385     // TODO: make the following a standard validator
386     return (accession == null || accession.length() < 2) ? false
387             : getAccessionValidator().search(accession);
388   }
389
390   /**
391    * return LDHA_CHICK uniprot entry
392    */
393   @Override
394   public String getTestQuery()
395   {
396     return "P00340";
397   }
398
399   @Override
400   public String getDbName()
401   {
402     return "Uniprot"; // getDbSource();
403   }
404
405   @Override
406   public int getTier()
407   {
408     return 0;
409   }
410 }