From a6d20bcb6d748252eaa8b213b8947ac9b05eb1ac Mon Sep 17 00:00:00 2001
From: David Hoksza <david.hoksza@uni.lu>
Date: Tue, 14 Nov 2017 15:24:59 +0100
Subject: [PATCH] Basic TAIR annotation implemented, including unit tests.

---
 .../services/annotators/TairAnnotator.java    | 206 +++++++++++++++
 .../applicationContext-annotation.xml         |   1 +
 .../annotators/AllAnnotatorTests.java         |   1 +
 .../annotators/TairAnnotatorTest.java         | 245 ++++++++++++++++++
 persist/src/db/11.1.1/fix_db_20171114.sql     |   2 +
 5 files changed, 455 insertions(+)
 create mode 100644 annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotator.java
 create mode 100644 annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotatorTest.java
 create mode 100644 persist/src/db/11.1.1/fix_db_20171114.sql

diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotator.java
new file mode 100644
index 0000000000..06bc49b4c8
--- /dev/null
+++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotator.java
@@ -0,0 +1,206 @@
+package lcsb.mapviewer.annotation.services.annotators;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+import org.kohsuke.rngom.ast.builder.Annotations;
+
+import lcsb.mapviewer.annotation.cache.GeneralCacheInterface;
+import lcsb.mapviewer.annotation.cache.SourceNotAvailable;
+import lcsb.mapviewer.annotation.cache.WebPageDownloader;
+import lcsb.mapviewer.annotation.services.ExternalServiceStatus;
+import lcsb.mapviewer.annotation.services.ExternalServiceStatusType;
+import lcsb.mapviewer.annotation.services.IExternalService;
+import lcsb.mapviewer.annotation.services.WrongResponseCodeIOException;
+import lcsb.mapviewer.common.exception.InvalidArgumentException;
+import lcsb.mapviewer.model.map.BioEntity;
+import lcsb.mapviewer.model.map.MiriamData;
+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;
+
+/**
+ * This is a class that implements a backend to TAIR.
+ * 
+ * @author David Hoksza
+ * 
+ */
+public class TairAnnotator extends ElementAnnotator implements IExternalService {
+
+	/**
+	 * Default class logger.
+	 */
+	private static Logger	logger					= Logger.getLogger(TairAnnotator.class);
+
+	/**
+	 * Pattern used for finding UniProt symbol from TAIR info page .
+	 */
+	private Pattern				tairToUniprot		= Pattern.compile("UniProtKB=([^-\"]*)");
+
+	/**
+	 * Default constructor.
+	 */
+	public TairAnnotator() {
+		super(TairAnnotator.class, new Class[] { Protein.class, Gene.class, Rna.class }, false); //TODO - check with Simone
+	}
+
+	@Override
+	public ExternalServiceStatus getServiceStatus() {
+		ExternalServiceStatus status = new ExternalServiceStatus(getCommonName(), getUrl());
+
+		GeneralCacheInterface cacheCopy = getCache();
+		this.setCache(null);
+
+		try {
+			MiriamData md = tairToUniprot(new MiriamData(MiriamType.TAIR_LOCUS, "AT1G01030"));
+
+			status.setStatus(ExternalServiceStatusType.OK);
+			if (md == null || !md.getResource().equalsIgnoreCase("Q9MAN1")) {
+				status.setStatus(ExternalServiceStatusType.CHANGED);
+			}
+		} catch (Exception e) {
+			logger.error(status.getName() + " is down", e);
+			status.setStatus(ExternalServiceStatusType.DOWN);
+		}
+		this.setCache(cacheCopy);
+		return status;
+	}
+
+	@Override
+	public void annotateElement(BioEntity object) throws AnnotatorException {
+		if (isAnnotatable(object)) {
+			boolean uniprotFound = false;
+			MiriamData mdTair = null;
+			for (MiriamData md : object.getMiriamData()) {
+				if (md.getDataType().equals(MiriamType.TAIR_LOCUS)) {
+					mdTair = md;
+				}
+				if (md.getDataType().equals(MiriamType.UNIPROT)) {
+					uniprotFound = true;
+				}
+			}
+
+			if (mdTair == null || uniprotFound) {
+				return;
+			}
+			
+			MiriamData mdUniprot = tairToUniprot(mdTair);			
+			if (mdUniprot != null) {
+				object.addMiriamData(mdUniprot);
+			}
+		}		
+	}
+
+	/**
+	 * Returns url to TAIR page about TAIR entry.
+	 * 
+	 * @param tairId
+	 *          tair identifier
+	 * @return url to TAIR page about the TAIR entry
+	 */
+	private String getTairUrl(String tairId) {
+		return "http://arabidopsis.org/servlets/TairObject?type=locus&name=" + tairId;
+	}
+
+	/**
+	 * Parse TAIR webpage to find information about
+	 * {@link MiriamType#UNIPROT} and returns them.
+	 * 
+	 * @param pageContent
+	 *          tair info page
+	 * @return uniprot identifier found on the page
+	 */
+	private Collection<MiriamData> parseUniprot(String pageContent) {
+		Collection<MiriamData> result = new HashSet<MiriamData>();
+		Matcher m = tairToUniprot.matcher(pageContent);
+		if (m.find()) {
+			result.add(new MiriamData(MiriamType.UNIPROT, m.group(1)));
+		}
+		return result;
+	}
+
+	@Override
+	public Object refreshCacheQuery(Object query) throws SourceNotAvailable {
+		String name;
+		String result = null;
+		if (query instanceof String) {
+			name = (String) query;
+			if (name.startsWith("http")) {
+				try {
+					result = getWebPageContent(name);
+				} catch (IOException e) {
+					throw new SourceNotAvailable(e);
+				}
+			} else {
+				throw new InvalidArgumentException("Don't know what to do with query: " + query);
+			}
+		} else {
+			throw new InvalidArgumentException("Don't know what to do with class: " + query.getClass());
+		}
+		return result;
+	}
+
+	/**
+	 * Transform tair identifier into uniprot identifier.
+	 * 
+	 * @param tair
+	 *          {@link MiriamData} with tair identifier
+	 * @return {@link MiriamData} with uniprot identifier
+	 * @throws UniprotSearchException
+	 *           thrown when there is a problem with accessing external database
+	 */
+	public MiriamData tairToUniprot(MiriamData tair) throws AnnotatorException {
+		if (tair == null) {
+			return null;
+		}
+
+		if (!MiriamType.TAIR_LOCUS.equals(tair.getDataType())) {
+			throw new InvalidArgumentException(MiriamType.TAIR_LOCUS + " expected.");
+		}
+
+		String accessUrl = getTairUrl(tair.getResource());
+		try {
+			String pageContent = getWebPageContent(accessUrl);
+			Collection<MiriamData> collection = parseUniprot(pageContent);
+			if (collection.size() > 0) {
+				return collection.iterator().next();
+			} else {
+				logger.warn("Cannot find tair data for id: " + tair.getResource());
+				return null;
+			}
+		} catch (WrongResponseCodeIOException exception) {
+			logger.warn("Cannot find tair data for id: " + tair.getResource());
+			return null;
+		} catch (IOException exception) {
+			throw new AnnotatorException(exception);
+		}
+
+	}
+
+	@Override
+	public String getCommonName() {
+		return MiriamType.TAIR_LOCUS.getCommonName();
+	}
+
+	@Override
+	public String getUrl() {
+		return MiriamType.TAIR_LOCUS.getDbHomepage();
+	}
+
+	@Override
+	protected WebPageDownloader getWebPageDownloader() {
+		return super.getWebPageDownloader();
+	}
+
+	@Override
+	protected void setWebPageDownloader(WebPageDownloader webPageDownloader) {
+		super.setWebPageDownloader(webPageDownloader);
+	}
+
+}
diff --git a/annotation/src/main/resources/applicationContext-annotation.xml b/annotation/src/main/resources/applicationContext-annotation.xml
index a7fb40eb35..d93c9675f4 100644
--- a/annotation/src/main/resources/applicationContext-annotation.xml
+++ b/annotation/src/main/resources/applicationContext-annotation.xml
@@ -21,6 +21,7 @@
 	<bean id="HgncAnnotator" class="lcsb.mapviewer.annotation.services.annotators.HgncAnnotator"/>
 	<bean id="ReconAnnotator" class="lcsb.mapviewer.annotation.services.annotators.ReconAnnotator"/>
 	<bean id="PdbAnnotator" class="lcsb.mapviewer.annotation.services.annotators.PdbAnnotator"/>
+	<bean id="TairAnnotator" class="lcsb.mapviewer.annotation.services.annotators.TairAnnotator"/>
 	<bean id="UniprotAnnotator" class="lcsb.mapviewer.annotation.services.annotators.UniprotAnnotator"/>
 	
 	<bean id="ChEMBLParser" class="lcsb.mapviewer.annotation.services.ChEMBLParser"/>
diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java
index 218bd397ee..f49dd7668d 100644
--- a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java
+++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java
@@ -15,6 +15,7 @@ import org.junit.runners.Suite.SuiteClasses;
 		HgncAnnotatorTest.class, //
 		PdbAnnotatorTest.class, //
 		ReconAnnotatorTest.class, //
+		TairAnnotatorTest.class, //
 		UniprotAnnotatorTest.class, //
 })
 public class AllAnnotatorTests {
diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotatorTest.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotatorTest.java
new file mode 100644
index 0000000000..1f9621829e
--- /dev/null
+++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/TairAnnotatorTest.java
@@ -0,0 +1,245 @@
+package lcsb.mapviewer.annotation.services.annotators;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+import org.apache.http.client.HttpResponseException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javassist.NotFoundException;
+import lcsb.mapviewer.annotation.AnnotationTestFunctions;
+import lcsb.mapviewer.annotation.cache.GeneralCacheInterface;
+import lcsb.mapviewer.annotation.cache.GeneralCacheWithExclusion;
+import lcsb.mapviewer.annotation.cache.WebPageDownloader;
+import lcsb.mapviewer.annotation.services.ExternalServiceStatusType;
+import lcsb.mapviewer.common.exception.InvalidArgumentException;
+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;
+
+public class TairAnnotatorTest extends AnnotationTestFunctions {
+	
+	@Autowired
+	TairAnnotator tairAnnotator;
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testAnnotate1() throws Exception {
+		try {
+
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			protein.addMiriamData(new MiriamData(MiriamType.TAIR_LOCUS, "AT1G01030"));
+			
+			tairAnnotator.annotateElement(protein);
+
+			MiriamData mdUniprot = null;
+
+			for (MiriamData md : protein.getMiriamData()) {
+				if (md.getDataType().equals(MiriamType.UNIPROT)) {
+					mdUniprot = md;
+				}
+			}
+			
+			assertTrue("No UNIPROT annotation extracted from TAIR annotator", mdUniprot != null);
+			assertTrue("Invalid UNIPROT annotation extracted from TAIR annotator", mdUniprot.getResource().equalsIgnoreCase("Q9MAN1") );
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+	
+	
+	@Test
+	public void testAnnotateInvalidTair() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			protein.addMiriamData(new MiriamData(MiriamType.TAIR_LOCUS, "bla"));
+			tairAnnotator.annotateElement(protein);
+
+			assertEquals(1, protein.getMiriamData().size());
+
+			assertEquals(1, getWarnings().size());
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+
+	}
+
+	@Test
+	public void testInvalidTairToUniprot() throws Exception {
+		try {
+			assertNull(tairAnnotator.tairToUniprot(null));
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testInvalidTairToUniprot2() throws Exception {
+		try {
+			tairAnnotator.tairToUniprot(new MiriamData(MiriamType.WIKIPEDIA, "bla"));
+			fail("Exception expected");
+		} catch (InvalidArgumentException e) {
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+
+	@Test
+	public void testAnnotateInvalid() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			tairAnnotator.annotateElement(protein);
+
+			assertEquals(0, protein.getMiriamData().size());
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+
+	}
+
+	@Test
+	public void testTairToUniprot() throws Exception {
+		try {
+			assertEquals(new MiriamData(MiriamType.UNIPROT, "Q9MAN1"), 
+					tairAnnotator.tairToUniprot(new MiriamData(MiriamType.TAIR_LOCUS, "AT1G01030")));
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testRefreshInvalidCacheQuery() throws Exception {
+		try {
+			tairAnnotator.refreshCacheQuery("invalid_query");
+			fail("Exception expected");
+		} catch (InvalidArgumentException e) {
+			assertTrue(e.getMessage().contains("Don't know what to do"));
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testRefreshInvalidCacheQuery2() throws Exception {
+		try {
+			tairAnnotator.refreshCacheQuery(new Object());
+			fail("Exception expected");
+		} catch (InvalidArgumentException e) {
+			assertTrue(e.getMessage().contains("Don't know what to do"));
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testRefreshCacheQuery() throws Exception {
+		try {
+			Object res = tairAnnotator.refreshCacheQuery("http://google.cz/");
+			assertNotNull(res);
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testStatus() throws Exception {
+		try {
+			assertEquals(ExternalServiceStatusType.OK, tairAnnotator.getServiceStatus().getStatus());
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testSimulateDownStatus() throws Exception {
+		WebPageDownloader downloader = tairAnnotator.getWebPageDownloader();
+		try {
+			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
+			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenThrow(new IOException());
+			tairAnnotator.setWebPageDownloader(mockDownloader);
+			assertEquals(ExternalServiceStatusType.DOWN, tairAnnotator.getServiceStatus().getStatus());
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		} finally {
+			tairAnnotator.setWebPageDownloader(downloader);
+		}
+	}
+
+
+//	@Test
+//	public void testAnnotateWithUniprotServerError() throws Exception {
+//		WebPageDownloader downloader = uniprotAnnotator.getWebPageDownloader();
+//		GeneralCacheInterface cache = uniprotAnnotator.getCache();
+//		uniprotAnnotator.setCache(new GeneralCacheWithExclusion(cache, 1));
+//		try {
+//			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
+//			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenThrow(new IOException());
+//			uniprotAnnotator.setWebPageDownloader(mockDownloader);
+//			Species protein = new GenericProtein("id");
+//			protein.addMiriamData(new MiriamData(MiriamType.UNIPROT, "P01308"));
+//			uniprotAnnotator.annotateElement(protein);
+//			fail("Exception expected");
+//		} catch (AnnotatorException e) {
+//		} catch (Exception e) {
+//			e.printStackTrace();
+//			throw e;
+//		} finally {
+//			uniprotAnnotator.setCache(cache);
+//			uniprotAnnotator.setWebPageDownloader(downloader);
+//		}
+//	}
+
+	@Test
+	public void testSimulateChangedStatus() throws Exception {
+		WebPageDownloader downloader = tairAnnotator.getWebPageDownloader();
+		try {
+			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
+			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenReturn("GN   Name=ACSS2; Synonyms=ACAS2;");
+			tairAnnotator.setWebPageDownloader(mockDownloader);
+			assertEquals(ExternalServiceStatusType.CHANGED, tairAnnotator.getServiceStatus().getStatus());
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		} finally {
+			tairAnnotator.setWebPageDownloader(downloader);
+		}
+	}
+
+}
diff --git a/persist/src/db/11.1.1/fix_db_20171114.sql b/persist/src/db/11.1.1/fix_db_20171114.sql
new file mode 100644
index 0000000000..4eae7d939e
--- /dev/null
+++ b/persist/src/db/11.1.1/fix_db_20171114.sql
@@ -0,0 +1,2 @@
+DELETE FROM cache_type WHERE classname = 'lcsb.mapviewer.annotation.services.annotators.TairAnnotator';
+INSERT INTO cache_type(validity, classname) VALUES (365, 'lcsb.mapviewer.annotation.services.annotators.TairAnnotator');
\ No newline at end of file
-- 
GitLab