package org.molgenis.data.semanticsearch.service.impl;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.mockito.Mockito;
import org.molgenis.data.DataService;
import org.molgenis.data.Entity;
import org.molgenis.data.QueryRule;
import org.molgenis.data.meta.MetaDataService;
import org.molgenis.data.meta.model.*;
import org.molgenis.data.semanticsearch.explain.bean.ExplainedAttribute;
import org.molgenis.data.semanticsearch.explain.bean.ExplainedQueryString;
import org.molgenis.data.semanticsearch.explain.service.ElasticSearchExplainService;
import org.molgenis.data.semanticsearch.semantic.Hit;
import org.molgenis.data.semanticsearch.service.OntologyTagService;
import org.molgenis.data.semanticsearch.string.Stemmer;
import org.molgenis.data.support.QueryImpl;
import org.molgenis.ontology.core.model.OntologyTerm;
import org.molgenis.ontology.core.service.OntologyService;
import org.molgenis.test.data.AbstractMolgenisSpringTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.molgenis.data.meta.model.AttributeMetadata.ATTRIBUTE_META_DATA;
import static org.testng.Assert.*;
@ContextConfiguration(classes = SemanticSearchServiceImplTest.Config.class)
public class SemanticSearchServiceImplTest extends AbstractMolgenisSpringTest
{
@Autowired
private EntityTypeFactory entityTypeFactory;
@Autowired
private AttributeFactory attrMetaDataFactory;
@Autowired
private OntologyService ontologyService;
@Autowired
private SemanticSearchServiceHelper semanticSearchServiceHelper;
@Autowired
private DataService dataService;
@Autowired
private SemanticSearchServiceImpl semanticSearchService;
private List<String> ontologies;
private OntologyTerm standingHeight;
private OntologyTerm bodyWeight;
private OntologyTerm hypertension;
private OntologyTerm maternalHypertension;
private List<OntologyTerm> ontologyTerms;
private Attribute attribute;
@BeforeMethod
public void beforeTest()
{
ontologies = asList("1", "2");
standingHeight = OntologyTerm
.create("http://onto/height", "Standing height", asList("Standing height", "length"));
bodyWeight = OntologyTerm.create("http://onto/bmi", "Body weight", asList("Body weight", "Mass in kilograms"));
hypertension = OntologyTerm.create("http://onto/hyp", "Hypertension");
maternalHypertension = OntologyTerm.create("http://onto/mhyp", "Maternal hypertension");
ontologyTerms = asList(standingHeight, bodyWeight, hypertension, maternalHypertension);
attribute = attrMetaDataFactory.create().setName("attr1");
}
@BeforeMethod
public void init()
{
when(semanticSearchServiceHelper.getOtLabelAndSynonyms(standingHeight))
.thenReturn(Sets.newHashSet("Standing height", "Standing height", "length"));
when(semanticSearchServiceHelper.getOtLabelAndSynonyms(bodyWeight))
.thenReturn(Sets.newHashSet("Body weight", "Body weight", "Mass in kilograms"));
when(semanticSearchServiceHelper.getOtLabelAndSynonyms(hypertension))
.thenReturn(Sets.newHashSet("Hypertension"));
when(semanticSearchServiceHelper.getOtLabelAndSynonyms(maternalHypertension))
.thenReturn(Sets.newHashSet("Maternal hypertension"));
}
@Test
public void testSearchHypertension() throws InterruptedException, ExecutionException
{
Mockito.reset(ontologyService);
attribute.setDescription("History of Hypertension");
when(ontologyService.findOntologyTerms(ontologies, ImmutableSet.<String>of("history", "hypertens"), 100))
.thenReturn(ontologyTerms);
Hit<OntologyTerm> result = semanticSearchService.findTags(attribute, ontologies);
assertEquals(result, null);
}
@Test
public void testDistanceFrom()
{
Stemmer stemmer = new Stemmer();
Assert.assertEquals(semanticSearchService
.distanceFrom("Hypertension", ImmutableSet.<String>of("history", "hypertens"), stemmer), .6923, 0.0001,
"String distance should be equal");
Assert.assertEquals(semanticSearchService
.distanceFrom("Maternal Hypertension", ImmutableSet.<String>of("history", "hypertens"), stemmer), .5454,
0.0001, "String distance should be equal");
;
}
@Test
public void testSearchDescription() throws InterruptedException, ExecutionException
{
Mockito.reset(ontologyService);
attribute.setDescription("Standing height in meters.");
when(ontologyService
.findOntologyTerms(ontologies, ImmutableSet.<String>of("standing", "height", "meters"), 100))
.thenReturn(ontologyTerms);
Hit<OntologyTerm> result = semanticSearchService.findTags(attribute, ontologies);
assertEquals(result, Hit.<OntologyTerm>create(standingHeight, 0.81250f));
}
@Test
public void testSearchLabel() throws InterruptedException, ExecutionException
{
Mockito.reset(ontologyService);
attribute.setDescription("Standing height (m.)");
when(ontologyService.findOntologyTerms(ontologies, ImmutableSet.<String>of("standing", "height", "m"), 100))
.thenReturn(ontologyTerms);
Hit<OntologyTerm> result = semanticSearchService.findTags(attribute, ontologies);
assertEquals(result, Hit.<OntologyTerm>create(standingHeight, 0.92857f));
}
@Test
public void testIsSingleMatchHighQuality()
{
List<ExplainedQueryString> explanations1 = asList(
ExplainedQueryString.create("height", "height", "standing height", 50.0));
assertFalse(semanticSearchService
.isSingleMatchHighQuality(Sets.newHashSet("height"), Sets.newHashSet("height"), explanations1));
List<ExplainedQueryString> explanations2 = asList(
ExplainedQueryString.create("body length", "body length", "height", 100));
assertTrue(semanticSearchService.isSingleMatchHighQuality(Sets.newHashSet("height in meter"),
Sets.newHashSet("height in meter", "height"), explanations2));
List<ExplainedQueryString> explanations3 = asList(
ExplainedQueryString.create("fasting", "fasting", "fasting", 100),
ExplainedQueryString.create("glucose", "blood glucose", "blood glucose", 50));
assertFalse(semanticSearchService.isSingleMatchHighQuality(Sets.newHashSet("fasting glucose"),
Sets.newHashSet("fasting glucose", "fasting", "blood glucose"), explanations3));
List<ExplainedQueryString> explanations4 = asList(
ExplainedQueryString.create("number of", "number of", "number", 100));
assertFalse(semanticSearchService.isSingleMatchHighQuality(Sets.newHashSet("number of cigarette smoked"),
Sets.newHashSet("number of cigarette smoked", "number of"), explanations4));
}
@Test
public void testIsGoodMatch()
{
Map<String, Double> matchedTags = new HashMap<String, Double>();
matchedTags.put("height", 100.0);
matchedTags.put("weight", 50.0);
assertFalse(semanticSearchService.isGoodMatch(matchedTags, "blood"));
assertFalse(semanticSearchService.isGoodMatch(matchedTags, "weight"));
assertTrue(semanticSearchService.isGoodMatch(matchedTags, "height"));
Map<String, Double> matchedTags2 = new HashMap<String, Double>();
matchedTags2.put("fasting", 100.0);
matchedTags2.put("glucose", 100.0);
assertTrue(semanticSearchService.isGoodMatch(matchedTags2, "fasting glucose"));
}
@Test
public void testFindAttributes()
{
EntityType sourceEntityType = entityTypeFactory.create().setSimpleName("sourceEntityType");
// Mock the id's of the attribute entities that should be searched
List<String> attributeIdentifiers = asList("1", "2");
when(semanticSearchServiceHelper.getAttributeIdentifiers(sourceEntityType)).thenReturn(attributeIdentifiers);
// Mock the createDisMaxQueryRule method
List<QueryRule> rules = new ArrayList<QueryRule>();
QueryRule targetQueryRuleLabel = new QueryRule(AttributeMetadata.LABEL, QueryRule.Operator.FUZZY_MATCH,
"height");
rules.add(targetQueryRuleLabel);
QueryRule targetQueryRuleOntologyTermTag = new QueryRule(AttributeMetadata.LABEL,
QueryRule.Operator.FUZZY_MATCH, "standing height");
rules.add(targetQueryRuleOntologyTermTag);
QueryRule targetQueryRuleOntologyTermTagSyn = new QueryRule(AttributeMetadata.LABEL,
QueryRule.Operator.FUZZY_MATCH, "length");
rules.add(targetQueryRuleOntologyTermTagSyn);
QueryRule disMaxQueryRule = new QueryRule(rules);
disMaxQueryRule.setOperator(QueryRule.Operator.DIS_MAX);
when(semanticSearchServiceHelper
.createDisMaxQueryRuleForAttribute(Sets.newHashSet("targetAttribute"), Collections.emptyList()))
.thenReturn(disMaxQueryRule);
Entity entity1 = mock(Entity.class);
when(entity1.getString(AttributeMetadata.NAME)).thenReturn("height_0");
when(entity1.getString(AttributeMetadata.LABEL)).thenReturn("height");
when(entity1.getString(AttributeMetadata.DESCRIPTION)).thenReturn("this is a height measurement in m!");
List<QueryRule> disMaxQueryRules = Lists
.newArrayList(new QueryRule(AttributeMetadata.ID, QueryRule.Operator.IN, attributeIdentifiers),
new QueryRule(QueryRule.Operator.AND), disMaxQueryRule);
Attribute attributeHeight = attrMetaDataFactory.create().setName("height_0");
Attribute attributeWeight = attrMetaDataFactory.create().setName("weight_0");
sourceEntityType.addAttribute(attributeHeight);
sourceEntityType.addAttribute(attributeWeight);
// Case 1
when(dataService.findAll(ATTRIBUTE_META_DATA, new QueryImpl<>(disMaxQueryRules)))
.thenReturn(Stream.of(entity1));
Map<Attribute, ExplainedAttribute> termsActual1 = semanticSearchService
.findAttributes(sourceEntityType, Sets.newHashSet("targetAttribute"), Collections.emptyList());
Map<Attribute, ExplainedAttribute> termsExpected1 = ImmutableMap
.of(attributeHeight, ExplainedAttribute.create(attributeHeight));
assertEquals(termsActual1.toString(), termsExpected1.toString());
// Case 2
when(dataService.findAll(ATTRIBUTE_META_DATA, new QueryImpl<>(disMaxQueryRules))).thenReturn(Stream.empty());
Map<Attribute, ExplainedAttribute> termsActual2 = semanticSearchService
.findAttributes(sourceEntityType, Sets.newHashSet("targetAttribute"), Collections.emptyList());
Map<Attribute, ExplainedAttribute> termsExpected2 = ImmutableMap.of();
assertEquals(termsActual2, termsExpected2);
Mockito.reset(ontologyService);
attribute.setDescription("Standing height (Ångstrøm)");
when(ontologyService.findOntologyTerms(ontologies, ImmutableSet.of("standing", "height", "ångstrøm"), 100))
.thenReturn(ontologyTerms);
Hit<OntologyTerm> result = semanticSearchService.findTags(attribute, ontologies);
assertEquals(result, Hit.<OntologyTerm>create(standingHeight, 0.76471f));
}
@Test
public void testSearchUnicode() throws InterruptedException, ExecutionException
{
Mockito.reset(ontologyService);
attribute.setDescription("/əˈnædrəməs/");
when(ontologyService.findOntologyTerms(ontologies, ImmutableSet.of("əˈnædrəməs"), 100))
.thenReturn(ontologyTerms);
Hit<OntologyTerm> result = semanticSearchService.findTags(attribute, ontologies);
assertEquals(result, null);
}
@Test
public void testSearchMultipleTags() throws InterruptedException, ExecutionException
{
Mockito.reset(ontologyService);
attribute.setDescription("Body mass index");
when(ontologyService.findOntologyTerms(ontologies, ImmutableSet.of("body", "mass", "index"), 100))
.thenReturn(ontologyTerms);
Hit<OntologyTerm> result = semanticSearchService.findTags(attribute, ontologies);
assertEquals(result, null);
}
@Configuration
public static class Config
{
@Bean
MetaDataService metaDataService()
{
return mock(MetaDataService.class);
}
@Bean
OntologyService ontologyService()
{
return mock(OntologyService.class);
}
@Bean
SemanticSearchServiceImpl semanticSearchService()
{
return new SemanticSearchServiceImpl(dataService(), ontologyService(), metaDataService(),
semanticSearchServiceHelper(), elasticSearchExplainService());
}
@Bean
OntologyTagService ontologyTagService()
{
return mock(OntologyTagService.class);
}
@Bean
DataService dataService()
{
return mock(DataService.class);
}
@Bean
ElasticSearchExplainService elasticSearchExplainService()
{
return mock(ElasticSearchExplainService.class);
}
@Bean
SemanticSearchServiceHelper semanticSearchServiceHelper()
{
return mock(SemanticSearchServiceHelper.class);
}
}
}