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