package org.molgenis.data.semanticsearch.service.impl; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import org.molgenis.data.DataService; import org.molgenis.data.Entity; import org.molgenis.data.UnknownEntityException; import org.molgenis.data.meta.model.*; import org.molgenis.data.meta.model.Package; import org.molgenis.data.populate.IdGenerator; import org.molgenis.data.semantic.LabeledResource; import org.molgenis.data.semantic.Relation; import org.molgenis.data.semantic.SemanticTag; import org.molgenis.data.semanticsearch.repository.TagRepository; import org.molgenis.data.semanticsearch.semantic.OntologyTag; import org.molgenis.data.semanticsearch.service.OntologyTagService; import org.molgenis.ontology.core.model.Ontology; import org.molgenis.ontology.core.model.OntologyTerm; import org.molgenis.ontology.core.service.OntologyService; import org.molgenis.security.core.runas.RunAsSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.Map.Entry; import java.util.stream.Stream; import static com.google.common.collect.LinkedHashMultimap.create; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.stream.StreamSupport.stream; import static org.molgenis.data.meta.model.AttributeMetadata.ATTRIBUTE_META_DATA; import static org.molgenis.data.meta.model.EntityTypeMetadata.ATTRIBUTES; import static org.molgenis.data.meta.model.EntityTypeMetadata.ENTITY_TYPE_META_DATA; import static org.molgenis.data.meta.model.PackageMetadata.PACKAGE; import static org.molgenis.data.meta.model.TagMetadata.TAG; /** * Service to tag metadata with ontology terms. */ public class OntologyTagServiceImpl implements OntologyTagService { private final DataService dataService; private final TagRepository tagRepository; private final OntologyService ontologyService; private final IdGenerator idGenerator; private final TagMetadata tagMetadata; private static final Logger LOG = LoggerFactory.getLogger(OntologyTagServiceImpl.class); public OntologyTagServiceImpl(DataService dataService, OntologyService ontologyService, TagRepository tagRepository, IdGenerator idGenerator, TagMetadata tagMetadata) { this.dataService = requireNonNull(dataService); this.tagRepository = requireNonNull(tagRepository); this.ontologyService = requireNonNull(ontologyService); this.idGenerator = requireNonNull(idGenerator); this.tagMetadata = requireNonNull(tagMetadata); } @Override public void removeAttributeTag(String entity, String attribute, String relationIRI, String ontologyTermIRI) { Entity attributeEntity = findAttributeEntity(entity, attribute); Iterable<Entity> tags = attributeEntity.getEntities(AttributeMetadata.TAGS); Iterable<Entity> newTags = Iterables.filter(tags, e -> !isSameTag(relationIRI, ontologyTermIRI, e)); attributeEntity.set(AttributeMetadata.TAGS, newTags); dataService.update(ATTRIBUTE_META_DATA, attributeEntity); updateEntityTypeEntityWithNewAttributeEntity(entity, attribute, attributeEntity); } @Override public void removeAttributeTag(EntityType entityType, SemanticTag<Attribute, OntologyTerm, Ontology> removeTag) { Attribute attribute = removeTag.getSubject(); Entity attributeEntity = findAttributeEntity(entityType.getName(), attribute.getName()); List<Entity> tags = new ArrayList<Entity>(); for (Entity tagEntity : attributeEntity.getEntities(AttributeMetadata.TAGS)) { SemanticTag<Attribute, OntologyTerm, Ontology> tag = asTag(attribute, tagEntity); if (!removeTag.equals(tag)) { tags.add(tagEntity); } } attributeEntity.set(AttributeMetadata.TAGS, tags); dataService.update(ATTRIBUTE_META_DATA, attributeEntity); } @Override @RunAsSystem public Multimap<Relation, OntologyTerm> getTagsForAttribute(EntityType entityType, Attribute attribute) { Multimap<Relation, OntologyTerm> tags = create(); Entity entity = findAttributeEntity(entityType.getName(), attribute.getName()); if (entity == null) { LOG.warn("Cannot find attribute {}.{}", entityType.getName(), attribute.getName()); return tags; } for (Entity tagEntity : entity.getEntities(AttributeMetadata.TAGS)) { SemanticTag<Attribute, OntologyTerm, Ontology> tag = asTag(attribute, tagEntity); tags.put(tag.getRelation(), tag.getObject()); } return tags; } @Override public Iterable<SemanticTag<Package, OntologyTerm, Ontology>> getTagsForPackage(Package package_) { Entity packageEntity = dataService.findOneById(PACKAGE, package_.getIdValue()); if (packageEntity == null) { throw new UnknownEntityException("Unknown package [" + package_.getName() + "]"); } List<SemanticTag<Package, OntologyTerm, Ontology>> tags = Lists.newArrayList(); for (Entity tagEntity : packageEntity.getEntities(PackageMetadata.TAGS)) { tags.add(asTag(package_, tagEntity)); } return tags; } @Override public void addAttributeTag(EntityType entityType, SemanticTag<Attribute, OntologyTerm, Ontology> tag) { Entity entity = findAttributeEntity(entityType.getName(), tag.getSubject().getName()); List<Entity> tags = new ArrayList<Entity>(); for (Entity tagEntity : entity.getEntities(AttributeMetadata.TAGS)) { tags.add(tagEntity); } tags.add(getTagEntity(tag)); entity.set(AttributeMetadata.TAGS, tags); dataService.update(ATTRIBUTE_META_DATA, entity); } @Override public OntologyTag addAttributeTag(String entity, String attribute, String relationIRI, List<String> ontologyTermIRIs) { boolean added = false; Entity attributeEntity = findAttributeEntity(entity, attribute); Tag tag = new Tag(tagMetadata); Stream<OntologyTerm> terms = ontologyTermIRIs.stream().map(ontologyService::getOntologyTerm); OntologyTerm combinedOntologyTerm = OntologyTerm.and(terms.toArray(OntologyTerm[]::new)); Relation relation = Relation.forIRI(relationIRI); tag.setId(idGenerator.generateId()); tag.setCodeSystem(null); tag.setRelationIri(relation.getIRI()); tag.setRelationLabel(relation.getLabel()); tag.setLabel(combinedOntologyTerm.getLabel()); tag.setObjectIri(combinedOntologyTerm.getIRI()); dataService.add(TAG, tag); Map<String, Entity> tags = Maps.newHashMap(); for (Entity attrTag : attributeEntity.getEntities(AttributeMetadata.TAGS)) { tags.put(attrTag.get(TagMetadata.OBJECT_IRI).toString(), attrTag); } if (!tags.containsKey(tag.get(TagMetadata.OBJECT_IRI).toString())) { tags.put(tag.get(TagMetadata.OBJECT_IRI).toString(), tag); added = true; } attributeEntity.set(AttributeMetadata.TAGS, tags.values()); dataService.update(ATTRIBUTE_META_DATA, attributeEntity); updateEntityTypeEntityWithNewAttributeEntity(entity, attribute, attributeEntity); return added ? OntologyTag.create(combinedOntologyTerm, relation) : null; } public Entity getTagEntity(SemanticTag<?, OntologyTerm, Ontology> tag) { return tagRepository.getTagEntity(tag.getObject().getIRI(), tag.getObject().getLabel(), tag.getRelation(), tag.getCodeSystem().getIRI()); } @Override public void removeAllTagsFromEntity(String entityName) { EntityType entityTypedata = dataService.getEntityType(entityName); Iterable<Attribute> attributes = entityTypedata.getAtomicAttributes(); for (Attribute attribute : attributes) { Entity attributeEntity = findAttributeEntity(entityName, attribute.getName()); attributeEntity.set(AttributeMetadata.TAGS, emptyList()); dataService.update(ATTRIBUTE_META_DATA, attributeEntity); updateEntityTypeEntityWithNewAttributeEntity(entityName, attribute.getName(), attributeEntity); } } @Override public Map<String, OntologyTag> tagAttributesInEntity(String entity, Map<Attribute, OntologyTerm> tags) { Map<String, OntologyTag> result = new LinkedHashMap<>(); for (Entry<Attribute, OntologyTerm> tag : tags.entrySet()) { OntologyTerm ontologyTerm = tag.getValue(); OntologyTag ontologyTag = addAttributeTag(entity, tag.getKey().getName(), Relation.isAssociatedWith.getIRI(), Collections.singletonList(ontologyTerm.getIRI())); result.put(tag.getKey().getName(), ontologyTag); } return result; } @Override public void addEntityTag(SemanticTag<EntityType, OntologyTerm, Ontology> tag) { throw new UnsupportedOperationException(); } @Override public void removeEntityTag(SemanticTag<EntityType, OntologyTerm, Ontology> tag) { throw new UnsupportedOperationException(); } @Override public Iterable<SemanticTag<EntityType, LabeledResource, LabeledResource>> getTagsForEntity(EntityType entityType) { throw new UnsupportedOperationException(); } /** * The attribute just got updated, but the entity does not know this yet. To reindex this document in elasticsearch, * update it. * * @param entity name of the entity * @param attribute the name of the attribute that got changed * @param attributeEntity the entity of the attribute that got changed */ private void updateEntityTypeEntityWithNewAttributeEntity(String entity, String attribute, Entity attributeEntity) { Entity entityEntity = dataService.findOneById(ENTITY_TYPE_META_DATA, entity); Iterable<Entity> attributes = entityEntity.getEntities(ATTRIBUTES); entityEntity.set(ATTRIBUTES, Iterables.transform(attributes, att -> att.getString(AttributeMetadata.NAME).equals(attribute) ? attributeEntity : att)); dataService.update(ENTITY_TYPE_META_DATA, entityEntity); } private boolean isSameTag(String relationIRI, String ontologyTermIRI, Entity e) { return ontologyTermIRI.equals(e.getString(TagMetadata.OBJECT_IRI)) && relationIRI .equals(e.getString(TagMetadata.RELATION_IRI)); } @RunAsSystem private Entity findAttributeEntity(String entityName, String attributeName) { Entity entityTypeEntity = dataService.findOneById(ENTITY_TYPE_META_DATA, entityName); Optional<Entity> result = stream(entityTypeEntity.getEntities(ATTRIBUTES).spliterator(), false) .filter(att -> attributeName.equals(att.getString(AttributeMetadata.NAME))).findFirst(); if (!result.isPresent() && entityTypeEntity.get(EntityTypeMetadata.EXTENDS) != null) { return findAttributeEntity( entityTypeEntity.getEntity(EntityTypeMetadata.EXTENDS).getString(EntityTypeMetadata.FULL_NAME), attributeName); } return result.isPresent() ? result.get() : null; } private <SubjectType> SemanticTag<SubjectType, OntologyTerm, Ontology> asTag(SubjectType subjectType, Entity tagEntity) { String identifier = tagEntity.getString(TagMetadata.ID); Relation relation = asRelation(tagEntity); Ontology ontology = asOntology(tagEntity); OntologyTerm ontologyTerm = asOntologyTerm(tagEntity); if (relation == null || ontologyTerm == null) { return null; } return new SemanticTag<SubjectType, OntologyTerm, Ontology>(identifier, subjectType, relation, ontologyTerm, ontology); } private static Relation asRelation(Entity tagEntity) { String relationIRI = tagEntity.getString(TagMetadata.RELATION_IRI); if (relationIRI == null) { return null; } return Relation.forIRI(relationIRI); } private OntologyTerm asOntologyTerm(Entity tagEntity) { String objectIRI = tagEntity.getString(TagMetadata.OBJECT_IRI); if (objectIRI == null) { return null; } return ontologyService.getOntologyTerm(objectIRI); } private Ontology asOntology(Entity tagEntity) { String codeSystemIRI = tagEntity.getString(TagMetadata.CODE_SYSTEM); if (codeSystemIRI == null) { return null; } return ontologyService.getOntology(codeSystemIRI); } }