package org.molgenis.ontology.core.repository;
import com.google.common.collect.FluentIterable;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.common.collect.Iterables;
import org.elasticsearch.common.collect.Lists;
import org.molgenis.data.*;
import org.molgenis.data.QueryRule.Operator;
import org.molgenis.data.support.QueryImpl;
import org.molgenis.ontology.core.meta.OntologyMetaData;
import org.molgenis.ontology.core.meta.OntologyTermMetaData;
import org.molgenis.ontology.core.meta.OntologyTermNodePathMetaData;
import org.molgenis.ontology.core.meta.OntologyTermSynonymMetaData;
import org.molgenis.ontology.core.model.Ontology;
import org.molgenis.ontology.core.model.OntologyTerm;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
import static org.molgenis.ontology.core.meta.OntologyTermMetaData.*;
/**
* Maps {@link OntologyTermMetaData} {@link Entity} <-> {@link OntologyTerm}
*/
public class OntologyTermRepository
{
private final DataService dataService;
@Autowired
public OntologyTermRepository(DataService dataService)
{
this.dataService = requireNonNull(dataService);
}
/**
* FIXME write docs
*
* @param term
* @param pageSize
* @return
*/
public List<OntologyTerm> findOntologyTerms(String term, int pageSize)
{
Iterable<Entity> ontologyTermEntities;
// #1 find exact match
Query<Entity> termNameQuery = new QueryImpl<Entity>().eq(OntologyTermMetaData.ONTOLOGY_TERM_NAME, term)
.pageSize(pageSize);
ontologyTermEntities = new Iterable<Entity>()
{
@Override
public Iterator<Entity> iterator()
{
return dataService.findAll(ONTOLOGY_TERM, termNameQuery).iterator();
}
};
if (!ontologyTermEntities.iterator().hasNext())
{
Query<Entity> termsQuery = new QueryImpl<>().search(term).pageSize(pageSize);
ontologyTermEntities = new Iterable<Entity>()
{
@Override
public Iterator<Entity> iterator()
{
return dataService.findAll(ONTOLOGY_TERM, termsQuery).iterator();
}
};
}
return Lists.newArrayList(Iterables.transform(ontologyTermEntities, OntologyTermRepository::toOntologyTerm));
}
/**
* Finds exact {@link OntologyTerm}s within {@link Ontology}s.
*
* @param ontologyIds IDs of the {@link Ontology}s to search in
* @param terms {@link List} of search terms. the {@link OntologyTerm} must match at least one of these terms
* @param pageSize max number of results
* @return {@link List} of {@link OntologyTerm}s
*/
public List<OntologyTerm> findExcatOntologyTerms(List<String> ontologyIds, Set<String> terms, int pageSize)
{
List<OntologyTerm> findOntologyTerms = findOntologyTerms(ontologyIds, terms, pageSize);
return findOntologyTerms.stream().filter(ontologyTerm -> isOntologyTermExactMatch(terms, ontologyTerm))
.collect(Collectors.toList());
}
private boolean isOntologyTermExactMatch(Set<String> terms, OntologyTerm ontologyTerm)
{
Set<String> lowerCaseSearchTerms = terms.stream().map(StringUtils::lowerCase).collect(Collectors.toSet());
for (String synonym : ontologyTerm.getSynonyms())
{
if (lowerCaseSearchTerms.contains(synonym.toLowerCase()))
{
return true;
}
}
if (lowerCaseSearchTerms.contains(ontologyTerm.getLabel().toLowerCase()))
{
return true;
}
return false;
}
/**
* Finds {@link OntologyTerm}s within {@link Ontology}s.
*
* @param ontologyIds IDs of the {@link Ontology}s to search in
* @param terms {@link List} of search terms. the {@link OntologyTerm} must match at least one of these terms
* @param pageSize max number of results
* @return {@link List} of {@link OntologyTerm}s
*/
public List<OntologyTerm> findOntologyTerms(List<String> ontologyIds, Set<String> terms, int pageSize)
{
List<QueryRule> rules = new ArrayList<QueryRule>();
for (String term : terms)
{
if (rules.size() > 0)
{
rules.add(new QueryRule(Operator.OR));
}
rules.add(new QueryRule(OntologyTermMetaData.ONTOLOGY_TERM_SYNONYM, Operator.FUZZY_MATCH, term));
}
rules = Arrays.asList(new QueryRule(ONTOLOGY, Operator.IN, ontologyIds), new QueryRule(Operator.AND),
new QueryRule(rules));
final List<QueryRule> finalRules = rules;
Iterable<Entity> termEntities = new Iterable<Entity>()
{
@Override
public Iterator<Entity> iterator()
{
return dataService.findAll(ONTOLOGY_TERM, new QueryImpl<Entity>(finalRules).pageSize(pageSize))
.iterator();
}
};
return Lists.newArrayList(Iterables.transform(termEntities, OntologyTermRepository::toOntologyTerm));
}
public List<OntologyTerm> getAllOntologyTerms(String ontologyId)
{
Entity ontologyEntity = dataService.findOne(OntologyMetaData.ONTOLOGY,
new QueryImpl<Entity>().eq(OntologyMetaData.ONTOLOGY_IRI, ontologyId));
if (ontologyEntity != null)
{
Iterable<Entity> ontologyTermEntities = new Iterable<Entity>()
{
@Override
public Iterator<Entity> iterator()
{
return dataService.findAll(OntologyTermMetaData.ONTOLOGY_TERM,
new QueryImpl<Entity>().eq(OntologyTermMetaData.ONTOLOGY, ontologyEntity)
.pageSize(Integer.MAX_VALUE)).iterator();
}
};
return Lists
.newArrayList(Iterables.transform(ontologyTermEntities, OntologyTermRepository::toOntologyTerm));
}
return Collections.emptyList();
}
/**
* Retrieves an {@link OntologyTerm} for one or more IRIs
*
* @param iris Array of {@link OntologyTerm} IRIs
* @return combined {@link OntologyTerm} for the iris.
*/
public OntologyTerm getOntologyTerm(String[] iris)
{
List<OntologyTerm> ontologyTerms = Lists.newArrayList();
for (String iri : iris)
{
OntologyTerm ontologyTerm = toOntologyTerm(
dataService.findOne(ONTOLOGY_TERM, QueryImpl.EQ(ONTOLOGY_TERM_IRI, iri)));
if (ontologyTerm == null)
{
return null;
}
ontologyTerms.add(ontologyTerm);
}
return OntologyTerm.and(ontologyTerms.toArray(new OntologyTerm[0]));
}
/**
* Calculate the distance between any two ontology terms in the ontology tree structure by calculating the
* difference in nodePaths.
*
* @param ontologyTerm1
* @param ontologyTerm2
* @return the distance between two ontology terms
*/
public int getOntologyTermDistance(OntologyTerm ontologyTerm1, OntologyTerm ontologyTerm2)
{
String nodePath1 = getOntologyTermNodePath(ontologyTerm1);
String nodePath2 = getOntologyTermNodePath(ontologyTerm2);
if (StringUtils.isEmpty(nodePath1))
{
throw new MolgenisDataAccessException("The nodePath cannot be null : " + ontologyTerm1.toString());
}
if (StringUtils.isEmpty(nodePath2))
{
throw new MolgenisDataAccessException("The nodePath cannot be null : " + ontologyTerm2.toString());
}
return calculateNodePathDistance(nodePath1, nodePath2);
}
private String getOntologyTermNodePath(OntologyTerm ontologyTerm)
{
Entity ontologyTermEntity = dataService
.findOne(ONTOLOGY_TERM, new QueryImpl<Entity>().eq(ONTOLOGY_TERM_IRI, ontologyTerm.getIRI()));
Iterable<Entity> ontologyTermNodePathEntities = ontologyTermEntity
.getEntities(OntologyTermMetaData.ONTOLOGY_TERM_NODE_PATH);
for (Entity ontologyTermNodePathEntity : ontologyTermNodePathEntities)
{
return ontologyTermNodePathEntity.getString(OntologyTermNodePathMetaData.NODE_PATH);
}
return null;
}
/**
* Calculate the distance between nodePaths, e.g. 0[0].1[1].2[2], 0[0].2[1].2[2]. The distance is the non-overlap
* part of the strings
*
* @param nodePath1
* @param nodePath2
* @return distance
*/
public int calculateNodePathDistance(String nodePath1, String nodePath2)
{
String[] nodePathFragment1 = nodePath1.split("\\.");
String[] nodePathFragment2 = nodePath2.split("\\.");
int overlapBlock = 0;
while (overlapBlock < nodePathFragment1.length && overlapBlock < nodePathFragment2.length
&& nodePathFragment1[overlapBlock].equals(nodePathFragment2[overlapBlock]))
{
overlapBlock++;
}
return nodePathFragment1.length + nodePathFragment2.length - overlapBlock * 2;
}
/**
* Retrieve all descendant ontology terms
*
* @param ontologyTerm
* @return a list of {@link OntologyTerm}
*/
public List<OntologyTerm> getChildren(OntologyTerm ontologyTerm)
{
Iterable<Entity> ontologyTermEntities = new Iterable<Entity>()
{
@Override
public Iterator<Entity> iterator()
{
return dataService.findAll(ONTOLOGY_TERM, QueryImpl.EQ(ONTOLOGY_TERM_IRI, ontologyTerm.getIRI()))
.iterator();
}
};
List<OntologyTerm> children = new ArrayList<OntologyTerm>();
for (Entity ontologyTermEntity : ontologyTermEntities)
{
Entity ontologyEntity = ontologyTermEntity.getEntity(OntologyTermMetaData.ONTOLOGY);
ontologyTermEntity.getEntities(OntologyTermMetaData.ONTOLOGY_TERM_NODE_PATH).forEach(
ontologyTermNodePathEntity -> children
.addAll(getChildOntologyTermsByNodePath(ontologyEntity, ontologyTermNodePathEntity)));
}
return children;
}
public List<OntologyTerm> getChildOntologyTermsByNodePath(Entity ontologyEntity, Entity nodePathEntity)
{
String nodePath = nodePathEntity.getString(OntologyTermNodePathMetaData.NODE_PATH);
Iterable<Entity> relatedOntologyTermEntities = new Iterable<Entity>()
{
@Override
public Iterator<Entity> iterator()
{
return dataService.findAll(OntologyTermMetaData.ONTOLOGY_TERM, new QueryImpl<Entity>(
new QueryRule(OntologyTermMetaData.ONTOLOGY_TERM_NODE_PATH, Operator.FUZZY_MATCH,
"\"" + nodePath + "\"")).and().eq(OntologyTermMetaData.ONTOLOGY, ontologyEntity))
.iterator();
}
};
Iterable<Entity> childOntologyTermEntities = FluentIterable.from(relatedOntologyTermEntities)
.filter(entity -> qualifiedNodePath(nodePath, entity)).toList();
return Lists
.newArrayList(Iterables.transform(childOntologyTermEntities, OntologyTermRepository::toOntologyTerm));
}
private boolean qualifiedNodePath(String nodePath, Entity entity)
{
Iterable<Entity> nodePathEntities = entity.getEntities(OntologyTermMetaData.ONTOLOGY_TERM_NODE_PATH);
return Lists.newArrayList(nodePathEntities).stream().anyMatch(nodePathEntity ->
{
String childNodePath = nodePathEntity.getString(OntologyTermNodePathMetaData.NODE_PATH);
return !StringUtils.equals(nodePath, childNodePath) && childNodePath.startsWith(nodePath);
});
}
private static OntologyTerm toOntologyTerm(Entity entity)
{
if (entity == null)
{
return null;
}
// Collect synonyms if there are any
List<String> synonyms = new ArrayList<String>();
Iterable<Entity> ontologyTermSynonymEntities = entity.getEntities(OntologyTermMetaData.ONTOLOGY_TERM_SYNONYM);
if (ontologyTermSynonymEntities != null)
{
ontologyTermSynonymEntities.forEach(synonymEntity -> synonyms
.add(synonymEntity.getString(OntologyTermSynonymMetaData.ONTOLOGY_TERM_SYNONYM_ATTR)));
}
if (!synonyms.contains(entity.getString(ONTOLOGY_TERM_NAME)))
{
synonyms.add(entity.getString(ONTOLOGY_TERM_NAME));
}
return OntologyTerm
.create(entity.getString(ONTOLOGY_TERM_IRI), entity.getString(ONTOLOGY_TERM_NAME), null, synonyms);
}
}