From f209db54e72f7996e8ac7bd51788ab30c5d5da28 Mon Sep 17 00:00:00 2001
From: David Hoksza <david.hoksza@uni.lu>
Date: Thu, 20 Jul 2017 17:54:02 +0200
Subject: [PATCH] Model extended to include pdb annotation (species class
 extended and added 1:N to uniprot and 1:N to structure). ORM mapping. Unit
 tests working. Tested on web server with SNCA.

---
 .../services/annotators/PdbAnnotator.java     |  97 +++--
 .../annotators/PdbBestMappingEntry.java       |  25 +-
 .../services/annotators/PdbAnnotatorTest.java |  29 +-
 .../mapviewer/model/map/species/Species.java  |  27 ++
 .../model/map/species/field/Structure.java    | 344 ++++++++++++++++++
 .../map/species/field/UniprotRecord.java      | 181 +++++++++
 .../map/species/field/AllFieldTests.java      |   2 +
 .../map/species/field/StructureTest.java      |  68 ++++
 .../map/species/field/UniprotRecordTest.java  |  61 ++++
 ...ix_db_20171307.sql => fix_db_20170713.sql} |   2 +-
 persist/src/db/11.0.1/fix_db_20170720.sql     |  62 ++++
 .../resources/applicationContext-persist.xml  |   2 +
 12 files changed, 852 insertions(+), 48 deletions(-)
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/Structure.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/UniprotRecord.java
 create mode 100644 model/src/test/java/lcsb/mapviewer/model/map/species/field/StructureTest.java
 create mode 100644 model/src/test/java/lcsb/mapviewer/model/map/species/field/UniprotRecordTest.java
 rename persist/src/db/11.0.1/{fix_db_20171307.sql => fix_db_20170713.sql} (99%)
 create mode 100644 persist/src/db/11.0.1/fix_db_20170720.sql

diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotator.java
index 3995f04896..fc3b02a9bf 100644
--- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotator.java
+++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotator.java
@@ -24,7 +24,10 @@ import lcsb.mapviewer.model.map.MiriamType;
 //import lcsb.mapviewer.model.map.species.Gene;
 import lcsb.mapviewer.model.map.species.Protein;
 import lcsb.mapviewer.model.map.species.Rna;
+import lcsb.mapviewer.model.map.species.Species;
 import lcsb.mapviewer.modelutils.map.ElementUtils;
+import lcsb.mapviewer.model.map.species.field.Structure;
+import lcsb.mapviewer.model.map.species.field.UniprotRecord;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
@@ -82,10 +85,10 @@ public class PdbAnnotator extends ElementAnnotator implements IExternalService {
 		this.setCache(null);
 
 		try {
-			Collection<MiriamData> pdbMiriamData = uniProtToPdb(new MiriamData(MiriamType.UNIPROT, "P29373"));			
+			Collection<Structure> structures = uniProtToPdb(new MiriamData(MiriamType.UNIPROT, "P29373"));			
 
-			if (pdbMiriamData.size() > 0){
-				if (pdbMiriamData.iterator().next().getResource() != null) { //TODO - is this id?
+			if (structures.size() > 0){
+				if (structures.iterator().next().getPdbId() != null) { //TODO - is this id?
 					status.setStatus(ExternalServiceStatusType.OK);
 				} else {
 					status.setStatus(ExternalServiceStatusType.CHANGED);					
@@ -102,48 +105,72 @@ public class PdbAnnotator extends ElementAnnotator implements IExternalService {
 		return status;
 	}
 	
-	public MiriamData getUnitProt(BioEntity bioEntity) {
+	public Set<MiriamData> getUnitProts(BioEntity bioEntity) {
+		HashSet<MiriamData> mds = new HashSet<MiriamData>(); 
 		for (MiriamData md : bioEntity.getMiriamData()) {
 			if (md.getDataType().equals(MiriamType.UNIPROT)) {
-				return md;
+				mds.add(md);
 			}
 		}
-		return null;		
+		return mds.size() > 0 ? mds : null;
 	}
 
 	@Override
 	public void annotateElement(BioEntity bioEntity) throws AnnotatorException {
 		if (isAnnotatable(bioEntity)) {
-			MiriamData md = getUnitProt(bioEntity);
-			if (md == null) {				
+			Set<MiriamData> mds = getUnitProts(bioEntity);
+			if (mds == null) {				
 				uniprotAnnotator.annotateElement(bioEntity);
-				md = getUnitProt(bioEntity);
+				mds = getUnitProts(bioEntity);
 			}
 			
 			/*If no UniProt ID was found, try to search HGNC which then searches UNIPROT 
 			 * based on the gene name.
 			 */
-			if (md == null) {				
+			if (mds == null) {				
 				hgncAnnotator.annotateElement(bioEntity);
-				md = getUnitProt(bioEntity);
+				mds = getUnitProts(bioEntity);
 			}
 
-			if (md == null) {
+			if (mds == null) {
 				return;
 			}
 			
-			try {				
-				Set<MiriamData> annotations = (Set<MiriamData>)uniProtToPdb(md);				
-				if (annotations.size() == 0) {
-					logger.warn(elementUtils.getElementTag(bioEntity) + " No PDB mapping for UniProt ID: " + md.getResource());
-				} else {
-					bioEntity.addMiriamData(annotations);					
-				}
-			} catch (WrongResponseCodeIOException exception) {
-				logger.warn("Response error when trying to find PDB mapping for UniProt ID: " + md.getResource());
-			} catch (IOException exception) {
-				throw new AnnotatorException(exception);
+			for (MiriamData md : mds) {
+				try {				
+					Set<Structure> structures = (Set<Structure>)uniProtToPdb(md);				
+					if (structures.size() == 0) {
+						logger.warn(elementUtils.getElementTag(bioEntity) + " No PDB mapping for UniProt ID: " + md.getResource());
+					} else {
+						//add the annotations to the set of annotation irrespective on
+						//which uniprot record the structures belong to (since annotations do 
+						//not include concept of hierarchy or complex data types)
+						Set<MiriamData> annotations = new HashSet<MiriamData>();
+						for (Structure s : structures) {
+							annotations.add(new MiriamData(MiriamType.PDB, s.getPdbId()));							
+						}						
+						bioEntity.addMiriamData(annotations);
+						
+						//insert the full information directly into species, .i.e.
+						//create new uniprot record which includes the mapped structures 
+						//and add it to the species (bioentity)						
+						UniprotRecord ur = new UniprotRecord();
+						ur.setUniprotId(md.getResource());
+						ur.setSpecies((Species)bioEntity);
+						for (Structure s : structures) {
+							s.setUniprot(ur);
+						}
+						ur.setStructures(structures);
+						((Species)bioEntity).getUniprots().add(ur);
+					}
+				} catch (WrongResponseCodeIOException exception) {
+					logger.warn("Response error when trying to find PDB mapping for UniProt ID: " + md.getResource());
+				} catch (IOException exception) {
+					throw new AnnotatorException(exception);
+				}				
+				
 			}
+			
 		}
 	}
 
@@ -166,8 +193,8 @@ public class PdbAnnotator extends ElementAnnotator implements IExternalService {
 	 *          JSON file with the UniProt to PDB mapping
 	 * @return set of PDB identifiers found on the webpage
 	 */
-	private Collection<MiriamData> processMappingData(String pageContentJson) {
-		Collection<MiriamData> result = new HashSet<MiriamData>();
+	private Collection<Structure> processMappingData(String pageContentJson) {
+		Collection<Structure> result = new HashSet<Structure>();
 		Gson g = new Gson();
 		java.lang.reflect.Type t = new TypeToken<Map<String, List<PdbBestMappingEntry>>>(){}.getType();
 		Map<String, List<PdbBestMappingEntry>> m = g.fromJson(pageContentJson, t);
@@ -175,7 +202,8 @@ public class PdbAnnotator extends ElementAnnotator implements IExternalService {
 			for (String key : m.keySet()){
 			    for(PdbBestMappingEntry e : m.get(key)) {
 			    	if (e != null){
-			    		result.add(new MiriamData(MiriamType.PDB, e.pdb_id));			    		
+			    		result.add(e.convertToStructure());
+			    		//result.add(new MiriamData(MiriamType.PDB, e.pdb_id));			    		
 			    	}			    	
 			    }
 			}
@@ -231,7 +259,7 @@ public class PdbAnnotator extends ElementAnnotator implements IExternalService {
 	 * @return JSON String with mapping.
 	 *           thrown when there is a problem with accessing external database
 	 */
-	public Collection<MiriamData> uniProtToPdb(MiriamData uniprot) throws IOException /*throws UniprotSearchException*/ {
+	public Collection<Structure> uniProtToPdb(MiriamData uniprot) throws IOException {
 		if (uniprot == null) {
 			return null;
 		}
@@ -241,18 +269,9 @@ public class PdbAnnotator extends ElementAnnotator implements IExternalService {
 		}
 
 		String accessUrl = getPdbMappingUrl(uniprot.getResource());
-//		try {
-			String json = getWebPageContent(accessUrl);
-			if (!isJson(json)) {
-				return null;
-			} else {
-				return processMappingData(json); 
-			}
-			
-//		} catch (IOException e) {
-//			throw new UniprotSearchException("Problem with accessing mapping data: ", e); //TODO
-//		}
-
+		String json = getWebPageContent(accessUrl);
+		
+		return isJson(json) ? processMappingData(json) : null;
 	}
 
 	@Override
diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbBestMappingEntry.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbBestMappingEntry.java
index 00f63e8ddd..c3cf1c9b2c 100644
--- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbBestMappingEntry.java
+++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/PdbBestMappingEntry.java
@@ -1,21 +1,40 @@
 package lcsb.mapviewer.annotation.services.annotators;
 
+import lcsb.mapviewer.model.map.species.field.Structure;
+
 /**
  * Structure of the PDB entries returned by the PDBe REST API "Best Structures"
  * 
  * @author David Hoksza
  *
  */
-public class PdbBestMappingEntry {
-	public String end;
+public class PdbBestMappingEntry {	
 	public String chain_id;
 	public String experimental_method;
 	public String pdb_id;
 	public int start;
+	public int end;
 	public int unp_end;
 	public double coverage;
 	public int unp_start;
 	public double resolution;
 	public int tax_id;
-
+	
+	public Structure convertToStructure() {
+		
+		Structure s = new Structure();
+		
+		s.setPdbId(this.pdb_id);
+		s.setChainId(this.chain_id);
+		s.setCoverage(this.coverage);
+		s.setResolution(this.resolution);
+		s.setStructStart(this.start);
+		s.setStructEnd(this.end);
+		s.setUnpStart(this.unp_start);
+		s.setUnpEnd(this.unp_end);
+		s.setExperimentalMethod(this.experimental_method);
+		s.setTaxId(this.tax_id);
+		
+		return s;
+	}
 }
diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotatorTest.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotatorTest.java
index c417885b45..a5af588359 100644
--- a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotatorTest.java
+++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/PdbAnnotatorTest.java
@@ -1,10 +1,13 @@
 package lcsb.mapviewer.annotation.services.annotators;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.when;
 
+import java.util.Set;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -18,6 +21,8 @@ import lcsb.mapviewer.model.map.MiriamData;
 import lcsb.mapviewer.model.map.MiriamType;
 import lcsb.mapviewer.model.map.species.GenericProtein;
 import lcsb.mapviewer.model.map.species.Species;
+import lcsb.mapviewer.model.map.species.field.Structure;
+import lcsb.mapviewer.model.map.species.field.UniprotRecord;
 
 public class PdbAnnotatorTest extends AnnotationTestFunctions {
 
@@ -38,8 +43,9 @@ public class PdbAnnotatorTest extends AnnotationTestFunctions {
 	@Test
 	public void testAnnotate1() throws Exception {
 		try {
+			String uniprotId = "P29373"; 
 			Species protein = new GenericProtein("id");
-			protein.setName("P29373");
+			protein.setName(uniprotId);
 			/* One needs to have the UniProt ID first.
 			 * This tests simulates situation when the Uniprot annotator
 			 * is called first.
@@ -60,7 +66,20 @@ public class PdbAnnotatorTest extends AnnotationTestFunctions {
 				}
 			}
 			assertTrue("No PDB annotation extracted from pdb annotator", pdb);
-
+			
+			Set<UniprotRecord> urs = protein.getUniprots();
+			assertTrue(urs.size() > 0);
+			UniprotRecord ur = null;
+			for (UniprotRecord ur1 : urs) {
+				if (ur1.getUniprotId() == uniprotId) {
+					ur = ur1;
+				}
+			}
+			assertNotNull(ur);
+			Set<Structure> ss = ur.getStructures(); 
+			assertNotNull(ss);
+			assertTrue(ss.size() > 0);
+			assertTrue(ss.iterator().next().getUniprot() == ur);
 		} catch (Exception e) {
 			e.printStackTrace();
 			throw e;
@@ -197,7 +216,7 @@ public class PdbAnnotatorTest extends AnnotationTestFunctions {
 		WebPageDownloader downloader = pdbAnnotator.getWebPageDownloader();
 		try {
 			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
-			when(mockDownloader.getFromNetwork(anyString())).thenReturn("");
+			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenReturn("");
 			pdbAnnotator.setWebPageDownloader(mockDownloader);
 			assertEquals(ExternalServiceStatusType.DOWN, pdbAnnotator.getServiceStatus().getStatus());
 		} catch (Exception e) {
@@ -213,7 +232,7 @@ public class PdbAnnotatorTest extends AnnotationTestFunctions {
 		WebPageDownloader downloader = pdbAnnotator.getWebPageDownloader();
 		try {
 			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
-			when(mockDownloader.getFromNetwork(anyString())).thenReturn("{\"P29373\": [{\"xxx\": 140}]}");
+			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenReturn("{\"P29373\": [{\"xxx\": 140}]}");
 			pdbAnnotator.setWebPageDownloader(mockDownloader);
 			assertEquals(ExternalServiceStatusType.CHANGED, pdbAnnotator.getServiceStatus().getStatus());
 		} catch (Exception e) {
@@ -234,7 +253,7 @@ public class PdbAnnotatorTest extends AnnotationTestFunctions {
 			int cntAnnotations1 = protein.getMiriamData().size();
 			
 			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
-			when(mockDownloader.getFromNetwork(anyString())).thenReturn("\"P29373\": [{\"xxx\": 140}]}");
+			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenReturn("\"P29373\": [{\"xxx\": 140}]}");
 			pdbAnnotator.setWebPageDownloader(mockDownloader);
 			
 			pdbAnnotator.annotateElement(protein);
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/Species.java b/model/src/main/java/lcsb/mapviewer/model/map/species/Species.java
index 62875191ca..a873e01f7d 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/species/Species.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/Species.java
@@ -18,6 +18,7 @@ import org.hibernate.annotations.CascadeType;
 
 import lcsb.mapviewer.model.map.reaction.ReactionNode;
 import lcsb.mapviewer.model.map.species.field.PositionToCompartment;
+import lcsb.mapviewer.model.map.species.field.UniprotRecord;
 
 /**
  * Structure used for representing information about single element.
@@ -121,6 +122,13 @@ public abstract class Species extends Element {
 	 * Is species hypothetical.
 	 */
 	private Boolean								hypothetical					= null;
+	
+	/**
+	 * List of uniprot records which are associated with this species.
+	 */
+	@Cascade({ CascadeType.ALL })
+	@OneToMany(fetch = FetchType.EAGER, mappedBy = "species", orphanRemoval = true)
+	private Set<UniprotRecord>						uniprots					= new HashSet<>();
 
 	/**
 	 * Constructor that set element identifier.
@@ -168,6 +176,8 @@ public abstract class Species extends Element {
 		homodimer = original.getHomodimer();
 		positionToCompartment = original.getPositionToCompartment();
 		hypothetical = original.getHypothetical();
+		
+		uniprots = original.getUniprots();
 
 		// don't copy reaction nodes
 	}
@@ -404,6 +414,23 @@ public abstract class Species extends Element {
 		return hypothetical;
 	}
 
+	/**
+	 * @param uniprots
+	 *          set of uniprot records for this species
+	 * @see #uniprots
+	 */
+	public void setUniprots(Set<UniprotRecord> uniprots) {
+		this.uniprots = uniprots;
+	}
+	
+	/**
+	 * @return the uniprot
+	 * @see #uniprots
+	 */
+	public Set<UniprotRecord> getUniprots() {
+		return uniprots;
+	}
+
 	/**
 	 * @param hypothetical
 	 *          the hypothetical to set
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/Structure.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/Structure.java
new file mode 100644
index 0000000000..5dbe0b9053
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/Structure.java
@@ -0,0 +1,344 @@
+package lcsb.mapviewer.model.map.species.field;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import lcsb.mapviewer.common.exception.NotImplementedException;
+
+/**
+ * This class stores structure information as obtained from the SIFTS API 
+ * (https://www.ebi.ac.uk/pdbe/api/doc/sifts.html best_structures), which provides
+ * the following fields
+ * 	pdb_id: the PDB ID which maps to the UniProt ID
+ * 	chain_id: the specific chain of the PDB which maps to the UniProt ID
+ * 	coverage: the percent coverage of the entire UniProt sequence
+ * 	resolution: the resolution of the structure
+ * 	start: the structure residue number which maps to the start of the mapped sequence
+ * 	end: the structure residue number which maps to the end of the mapped sequence
+ * 	unp_start: the sequence residue number which maps to the structure start
+ * 	unp_end: the sequence residue number which maps to the structure end
+ * 	experimental_method: type of experiment used to determine structure
+ * 	tax_id: taxonomic ID of the protein's original organism 
+ * 
+ * @author David Hoksza
+ * 
+ */
+@Entity
+@Table(name = "structure_table")
+//@org.hibernate.annotations.GenericGenerator(name = "test-increment-strategy", strategy = "increment")
+public class Structure implements Serializable {
+	
+	/**
+	 * 
+	 */
+	private static final long		serialVersionUID			= 1L;
+	
+
+	/**
+	 * Unique identifier in the database.
+	 */
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name = "iddb", unique = true, nullable = false)
+	private int							id;
+	
+	/**
+	 * Uniprot record to which this structure belongs to.
+	 */
+	@ManyToOne(fetch = FetchType.EAGER)
+	@JoinColumn(name = "uniprot_id", nullable = false)
+	private UniprotRecord				uniprot;
+	
+	/**
+	 * the PDB ID which maps to the UniProt ID
+	 */
+	@Column(name = "pdb_id")
+	private String						pdbId									= null;
+	
+	/**
+	 * the specific chain of the PDB which maps to the UniProt ID
+	 */
+	@Column(name = "chain_id")
+	private String						chainId									= null;
+	
+	/**
+	 * the percent coverage of the entire UniProt sequence
+	 */
+	@Column(name = "coverage")
+	private Double						coverage								= null;
+	
+	/**
+	 * the resolution of the structure
+	 */
+	@Column(name = "resolution")
+	private Double						resolution								= null;
+	
+	/**
+	 * the structure residue number which maps to the start of the mapped sequence
+	 */
+	@Column(name = "struct_start")
+	private Integer						structStart								= null;
+	
+	/**
+	 * the structure residue number which maps to the end of the mapped sequence 
+	 */
+	@Column(name = "struct_end")
+	private Integer						structEnd								= null;
+	
+	/**
+	 * the sequence residue number which maps to the structure start
+	 */
+	@Column(name = "unp_start")
+	private Integer						unpStart								= null;
+	
+	/**
+	 * the sequence residue number which maps to the structure end
+	 */
+	@Column(name = "unp_end")
+	private Integer						unpEnd									= null;
+	
+	/**
+	 * type of experiment used to determine structure
+	 */
+	@Column(name = "experimental_method")
+	private String						experimentalMethod						= null;
+	
+	/**
+	 * taxonomic ID of the protein's original organism
+	 */
+	@Column(name = "tax_id")
+	private Integer						taxId									= null;
+	
+	/**
+	 * Default constructor.
+	 */
+	public Structure() {
+	}
+
+	/**
+	 * Constructor that initialize object with the data taken from the parameter.
+	 * 
+	 * @param s
+	 *          original object from which data is taken
+	 */
+	public Structure(Structure s) {
+		this.id = s.id;
+		this.uniprot = s.uniprot;
+		this.pdbId = s.pdbId;
+		this.chainId = s.chainId;
+		this.coverage = s.coverage;
+		this.resolution = s.resolution;
+		this.structStart = s.structStart;
+		this.structEnd = s.structEnd;
+		this.unpStart = s.unpStart;
+		this.unpEnd = s.unpEnd;
+		this.experimentalMethod = s.experimentalMethod;
+		this.taxId = s.taxId;		
+	}	
+
+	/**
+	 * Creates copy of the object.
+	 * 
+	 * @return copy of the object.
+	 */
+	public Structure copy() {
+		if (this.getClass() ==  Structure.class) {
+			return new Structure(this);
+		} else {
+			throw new NotImplementedException("Method copy() should be overriden in class " + this.getClass());
+		}
+	}
+	
+	/**
+	 * @return the idModificationResidue
+	 * @see #id
+	 */
+	public int getId() {
+		return id;
+	}
+
+	/**
+	 * @param id
+	 *          the id to set
+	 * @see #id
+	 */
+	public void setId(int id) {
+		this.id = id;
+	}
+
+	/**
+	 * @return the uniprot
+	 */
+	public UniprotRecord getUniprot() {
+		return uniprot;
+	}
+
+	/**
+	 * @param uniprot the uniprot to set
+	 */
+	public void setUniprot(UniprotRecord uniprot) {
+		this.uniprot = uniprot;
+	}
+
+	/**
+	 * @return the pdbId
+	 */
+	public String getPdbId() {
+		return pdbId;
+	}
+
+	/**
+	 * @param pdbId the pdbId to set
+	 */
+	public void setPdbId(String pdbId) {
+		this.pdbId = pdbId;
+	}
+
+	/**
+	 * @return the chainId
+	 */
+	public String getChainId() {
+		return chainId;
+	}
+
+	/**
+	 * @param chainId the chainId to set
+	 */
+	public void setChainId(String chainId) {
+		this.chainId = chainId;
+	}
+
+	/**
+	 * @return the coverage
+	 */
+	public Double getCoverage() {
+		return coverage;
+	}
+
+	/**
+	 * @param coverage the coverage to set
+	 */
+	public void setCoverage(Double coverage) {
+		this.coverage = coverage;
+	}
+
+	/**
+	 * @return the resolution
+	 */
+	public Double getResolution() {
+		return resolution;
+	}
+
+	/**
+	 * @param resolution the resolution to set
+	 */
+	public void setResolution(Double resolution) {
+		this.resolution = resolution;
+	}
+
+	/**
+	 * @return the structStart
+	 */
+	public Integer getStructStart() {
+		return structStart;
+	}
+
+	/**
+	 * @param structStart the structStart to set
+	 */
+	public void setStructStart(Integer structStart) {
+		this.structStart = structStart;
+	}
+
+	/**
+	 * @return the structEnd
+	 */
+	public Integer getStructEnd() {
+		return structEnd;
+	}
+
+	/**
+	 * @param structEnd the structEnd to set
+	 */
+	public void setStructEnd(Integer structEnd) {
+		this.structEnd = structEnd;
+	}
+
+	/**
+	 * @return the unpStart
+	 */
+	public Integer getUnpStart() {
+		return unpStart;
+	}
+
+	/**
+	 * @param unpStart the unpStart to set
+	 */
+	public void setUnpStart(Integer unpStart) {
+		this.unpStart = unpStart;
+	}
+
+	/**
+	 * @return the unpEnd
+	 */
+	public Integer getUnpEnd() {
+		return unpEnd;
+	}
+
+	/**
+	 * @param unpEnd the unpEnd to set
+	 */
+	public void setUnpEnd(Integer unpEnd) {
+		this.unpEnd = unpEnd;
+	}
+
+	/**
+	 * @return the experimentalMethod
+	 */
+	public String getExperimentalMethod() {
+		return experimentalMethod;
+	}
+
+	/**
+	 * @param experimentalMethod the experimentalMethod to set
+	 */
+	public void setExperimentalMethod(String experimentalMethod) {
+		this.experimentalMethod = experimentalMethod;
+	}
+
+	/**
+	 * @return the taxId
+	 */
+	public Integer getTaxId() {
+		return taxId;
+	}
+
+	/**
+	 * @param taxId the taxId to set
+	 */
+	public void setTaxId(Integer taxId) {
+		this.taxId = taxId;
+	}
+
+	/* (non-Javadoc)
+	 * @see java.lang.Object#toString()
+	 */
+	@Override
+	public String toString() {
+		return "Structure [pdbId=" + pdbId + ", chainId=" + chainId + ", coverage=" + coverage + ", resolution="
+				+ resolution + ", structStart=" + structStart + ", structEnd=" + structEnd + ", unpStart=" + unpStart
+				+ ", unpEnd=" + unpEnd + ", experimentalMethod=" + experimentalMethod + ", taxId=" + taxId + "]";
+	}
+	
+	
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/UniprotRecord.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/UniprotRecord.java
new file mode 100644
index 0000000000..92346cb4f2
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/UniprotRecord.java
@@ -0,0 +1,181 @@
+package lcsb.mapviewer.model.map.species.field;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.hibernate.annotations.Cascade;
+import org.hibernate.annotations.CascadeType;
+
+import lcsb.mapviewer.common.exception.NotImplementedException;
+import lcsb.mapviewer.model.map.species.Species;
+
+/**
+ * This class stores basically only uniprot Id which is used as mapping between
+ * species and uniprot record.
+ * 
+ * @author David Hoksza
+ * 
+ */
+
+@Entity
+@Table(name = "uniprot_table")
+//@org.hibernate.annotations.GenericGenerator(name = "test-increment-strategy", strategy = "increment")
+public class UniprotRecord implements Serializable {
+
+	/**
+	 * 
+	 */
+	private static final long		serialVersionUID			= 1L;
+	
+
+	/**
+	 * Unique identifier in the database.
+	 */
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name = "iddb", unique = true, nullable = false)
+	private int							id;
+
+	
+	/**
+	 * ID of the uniprot record
+	 */
+	@Column(name = "uniprot_id", nullable = false)
+	private String						uniprotId									= "";
+
+	/**
+	 * Species to which this uniprot record belongs to.
+	 */
+	@ManyToOne(fetch = FetchType.EAGER)
+	@JoinColumn(name = "element_id", nullable = false)
+	private Species						species;
+	
+	/**
+	 * List of uniprot records which are associated with this species.
+	 */
+	@Cascade({ CascadeType.ALL })
+	@OneToMany(fetch = FetchType.EAGER, mappedBy = "uniprot", orphanRemoval = true)
+	private Set<Structure>						structures					= new HashSet<>();
+	
+	/**
+	 * Default constructor.
+	 */
+	public UniprotRecord() {
+	}
+
+	/**
+	 * Constructor that initialize object with the data taken from the parameter.
+	 * 
+	 * @param ur
+	 *          original object from which data is taken
+	 */
+	public UniprotRecord(UniprotRecord ur) {		
+		this.id = ur.id;
+		this.uniprotId = ur.uniprotId;
+		this.species = ur.species;
+	}
+
+	/**
+	 * Creates copy of the object.
+	 * 
+	 * @return copy of the object.
+	 */
+	public UniprotRecord copy() {
+		if (this.getClass() == UniprotRecord.class) {
+			return new UniprotRecord(this);
+		} else {
+			throw new NotImplementedException("Method copy() should be overriden in class " + this.getClass());
+		}
+	}
+	
+	
+	
+	/* (non-Javadoc)
+	 * @see java.lang.Object#toString()
+	 */
+	@Override
+	public String toString() {
+		return "UniprotRecord [id=" + id + ", uniprotId=" + uniprotId + ", species=" + species + ", structures="
+				+ structures + "]";
+	}
+
+	/**
+	 * @return the idModificationResidue
+	 * @see #id
+	 */
+	public int getId() {
+		return id;
+	}
+
+	/**
+	 * @param id
+	 *          the id to set
+	 * @see #id
+	 */
+	public void setId(int id) {
+		this.id = id;
+	}
+	
+	/**
+	 * @return the uniprot id
+	 * @see #uniprotId
+	 */
+	public String getUniprotId() {
+		return uniprotId;
+	}
+
+	/**
+	 * @param uniprot id
+	 *          the id to set
+	 * @see #idModificationResidue
+	 */
+	public void setUniprotId(String uniprotId) {
+		this.uniprotId = uniprotId;
+	}
+	
+	/**
+	 * @param species
+	 *          species to which this uniprot record belongs
+	 * @see #species
+	 */
+	public void setSpecies(Species species) {
+		this.species = species;
+	}
+	
+	/**
+	 * @return the species
+	 * @see #species
+	 */
+	public Species getSpecies() {
+		return species;
+	}
+	
+	/**
+	 * @param structures
+	 *          set of structures mapped to this uniprot record
+	 * @see #structures
+	 */
+	public void setStructures(Set<Structure> structures) {
+		this.structures = structures;
+	}
+	
+	/**
+	 * @return the structures
+	 * @see #structures
+	 */
+	public Set<Structure> getStructures() {
+		return structures;
+	}
+}
diff --git a/model/src/test/java/lcsb/mapviewer/model/map/species/field/AllFieldTests.java b/model/src/test/java/lcsb/mapviewer/model/map/species/field/AllFieldTests.java
index 3cc809ba3b..512ff1dab9 100644
--- a/model/src/test/java/lcsb/mapviewer/model/map/species/field/AllFieldTests.java
+++ b/model/src/test/java/lcsb/mapviewer/model/map/species/field/AllFieldTests.java
@@ -11,6 +11,8 @@ import org.junit.runners.Suite.SuiteClasses;
 		ModificationResidueTest.class, //
 		PositionToCompartmentTest.class, //
 		RnaRegionTest.class,//
+		StructureTest.class,//
+		UniprotRecordTest.class,//
 })
 public class AllFieldTests {
 
diff --git a/model/src/test/java/lcsb/mapviewer/model/map/species/field/StructureTest.java b/model/src/test/java/lcsb/mapviewer/model/map/species/field/StructureTest.java
new file mode 100644
index 0000000000..ddcce64170
--- /dev/null
+++ b/model/src/test/java/lcsb/mapviewer/model/map/species/field/StructureTest.java
@@ -0,0 +1,68 @@
+package lcsb.mapviewer.model.map.species.field;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.util.HashSet;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import lcsb.mapviewer.common.exception.NotImplementedException;
+import lcsb.mapviewer.model.map.species.GenericProtein;
+import lcsb.mapviewer.model.map.species.Species;
+
+public class StructureTest {
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testGetters() {
+		Structure s = new Structure();
+		UniprotRecord ur = new UniprotRecord();
+		ur.setId(100);
+		ur.setUniprotId("P29373");
+
+		int id = 101;
+		s.setUniprot(ur);
+		s.setId(id);
+		
+		ur.setStructures(new HashSet<Structure>() {private static final long serialVersionUID = 1L; {add(s);} });
+
+		assertEquals(ur, s.getUniprot());
+		assertEquals(id, s.getId());
+		assertEquals(s, ur.getStructures().iterator().next());
+	}
+
+	@Test
+	public void testCopy() {
+		Structure s = new Structure().copy();
+		assertNotNull(s);
+	}
+
+	@Test
+	public void testInvalidCopy() {
+		Structure s = Mockito.spy(Structure.class);
+		try {
+			s.copy();
+			fail("Exception expected");
+		} catch (NotImplementedException e) {
+
+		}
+	}
+
+}
diff --git a/model/src/test/java/lcsb/mapviewer/model/map/species/field/UniprotRecordTest.java b/model/src/test/java/lcsb/mapviewer/model/map/species/field/UniprotRecordTest.java
new file mode 100644
index 0000000000..38460ffb23
--- /dev/null
+++ b/model/src/test/java/lcsb/mapviewer/model/map/species/field/UniprotRecordTest.java
@@ -0,0 +1,61 @@
+package lcsb.mapviewer.model.map.species.field;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import lcsb.mapviewer.common.exception.NotImplementedException;
+import lcsb.mapviewer.model.map.species.GenericProtein;
+import lcsb.mapviewer.model.map.species.Species;
+
+public class UniprotRecordTest {
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testGetters() {
+		UniprotRecord ur = new UniprotRecord();
+		Species species = new GenericProtein("id");
+		int id = 94;
+
+		ur.setSpecies(species);
+		ur.setId(id);
+
+		assertEquals(species, ur.getSpecies());
+		assertEquals(id, ur.getId());
+	}
+
+	@Test
+	public void testCopy() {
+		UniprotRecord ur = new UniprotRecord().copy();
+		assertNotNull(ur);
+	}
+
+	@Test
+	public void testInvalidCopy() {
+		UniprotRecord ur = Mockito.spy(UniprotRecord.class);
+		try {
+			ur.copy();
+			fail("Exception expected");
+		} catch (NotImplementedException e) {
+
+		}
+	}
+
+}
diff --git a/persist/src/db/11.0.1/fix_db_20171307.sql b/persist/src/db/11.0.1/fix_db_20170713.sql
similarity index 99%
rename from persist/src/db/11.0.1/fix_db_20171307.sql
rename to persist/src/db/11.0.1/fix_db_20170713.sql
index 94523a2c4d..c0f3e49c8e 100644
--- a/persist/src/db/11.0.1/fix_db_20171307.sql
+++ b/persist/src/db/11.0.1/fix_db_20170713.sql
@@ -1,2 +1,2 @@
-DELETE FROM cache_type WHERE classname = 'lcsb.mapviewer.annotation.services.annotators.PdbAnnotator'
+DELETE FROM cache_type WHERE classname = 'lcsb.mapviewer.annotation.services.annotators.PdbAnnotator'
 INSERT INTO cache_type(validity, classname) VALUES (365, 'lcsb.mapviewer.annotation.services.annotators.PdbAnnotator')
\ No newline at end of file
diff --git a/persist/src/db/11.0.1/fix_db_20170720.sql b/persist/src/db/11.0.1/fix_db_20170720.sql
new file mode 100644
index 0000000000..71cf63626c
--- /dev/null
+++ b/persist/src/db/11.0.1/fix_db_20170720.sql
@@ -0,0 +1,62 @@
+CREATE SEQUENCE uniprot_table_iddb_seq
+  INCREMENT 1
+  MINVALUE 1
+  MAXVALUE 9223372036854775807
+  START 1
+  CACHE 1;
+
+-- 1:N mapping between element and its uniprot mappings, each of which is then used to map (PDB) structures to it
+CREATE TABLE uniprot_table
+(
+  iddb integer NOT NULL DEFAULT nextval('uniprot_table_iddb_seq'::regclass),
+  element_id integer NOT NULL,  
+  uniprot_id character varying(255) NOT NULL,
+  CONSTRAINT uniprot_pkey PRIMARY KEY (iddb),
+  CONSTRAINT uniprot_element_fk FOREIGN KEY (element_id)
+      REFERENCES public.element_table (iddb) MATCH SIMPLE
+      ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+
+CREATE SEQUENCE structure_table_iddb_seq
+  INCREMENT 1
+  MINVALUE 1
+  MAXVALUE 9223372036854775807
+  START 1
+  CACHE 1;
+
+/*
+based on https://www.ebi.ac.uk/pdbe/api/doc/sifts.html best_structures API
+
+		pdb_id: the PDB ID which maps to the UniProt ID
+                chain_id: the specific chain of the PDB which maps to the UniProt ID
+                coverage: the percent coverage of the entire UniProt sequence
+                resolution: the resolution of the structure
+                start: the structure residue number which maps to the start of the mapped sequence
+                end: the structure residue number which maps to the end of the mapped sequence
+                unp_start: the sequence residue number which maps to the structure start
+                unp_end: the sequence residue number which maps to the structure end
+                experimental_method: type of experiment used to determine structure
+                tax_id: taxonomic ID of the protein's original organism
+*/
+CREATE TABLE structure_table
+(
+  iddb integer NOT NULL DEFAULT nextval('structure_table_iddb_seq'::regclass),
+  uniprot_id integer NOT NULL,
+  pdb_id character varying(255) NOT NULL, --should be character(4), but to be on the safe side...
+  chain_id character varying(1)NOT NULL,
+  struct_start integer,
+  struct_end integer,
+  unp_start integer,
+  unp_end integer,  
+  experimental_method character varying(255),
+  coverage double precision,
+  resolution double precision,
+  tax_id integer,
+  CONSTRAINT structure_pkey PRIMARY KEY (iddb),
+  CONSTRAINT structure_uniprot_fk FOREIGN KEY (uniprot_id)
+      REFERENCES public.uniprot_table (iddb) MATCH SIMPLE
+      ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+
diff --git a/persist/src/main/resources/applicationContext-persist.xml b/persist/src/main/resources/applicationContext-persist.xml
index 896e204ca1..896a8cafaf 100644
--- a/persist/src/main/resources/applicationContext-persist.xml
+++ b/persist/src/main/resources/applicationContext-persist.xml
@@ -185,6 +185,8 @@
 				<value>lcsb.mapviewer.model.map.species.field.AntisenseRnaRegion</value>
 				<value>lcsb.mapviewer.model.map.species.field.ModificationResidue</value>
 				<value>lcsb.mapviewer.model.map.species.field.RnaRegion</value>
+				<value>lcsb.mapviewer.model.map.species.field.UniprotRecord</value>
+				<value>lcsb.mapviewer.model.map.species.field.Structure</value>
 				
 				<value>lcsb.mapviewer.model.map.layout.graphics.Layer</value>
 				<value>lcsb.mapviewer.model.map.layout.graphics.LayerText</value>
-- 
GitLab