package org.molgenis.ontology.importer.repository; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; import com.google.common.collect.TreeTraverser; import org.apache.commons.lang3.StringUtils; import org.molgenis.data.Entity; import org.molgenis.data.populate.IdGenerator; import org.molgenis.data.MolgenisDataException; import org.molgenis.data.Repository; import org.molgenis.data.mem.InMemoryRepository; import org.molgenis.data.meta.model.EntityType; import org.molgenis.data.support.FileRepositoryCollection; import org.molgenis.data.support.GenericImporterExtensions; import org.molgenis.ontology.core.meta.*; import org.molgenis.ontology.utils.OWLClassContainer; import org.molgenis.ontology.utils.OntologyLoader; import org.molgenis.ontology.utils.ZipFileUtil; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLOntologyCreationException; import org.springframework.beans.factory.annotation.Autowired; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static org.molgenis.ontology.core.meta.OntologyMetaData.ONTOLOGY; import static org.molgenis.ontology.core.meta.OntologyTermDynamicAnnotationMetaData.ONTOLOGY_TERM_DYNAMIC_ANNOTATION; import static org.molgenis.ontology.core.meta.OntologyTermMetaData.ONTOLOGY_TERM; import static org.molgenis.ontology.core.meta.OntologyTermNodePathMetaData.ONTOLOGY_TERM_NODE_PATH; import static org.molgenis.ontology.core.meta.OntologyTermSynonymMetaData.ONTOLOGY_TERM_SYNONYM; /** * RepositoryCollection for the import of an owl file. * <p> * Reads the owl file's contents using an {@link OntologyLoader}. Fills {@link InMemoryRepository}s with their contents. */ public class OntologyRepositoryCollection extends FileRepositoryCollection { private static final String PSEUDO_ROOT_CLASS_NODEPATH = "0[0]"; private final static String PSEUDO_ROOT_CLASS_LABEL = "top"; private final File file; private final String fileName; @Autowired private IdGenerator idGenerator; @Autowired private OntologyFactory ontologyFactory; @Autowired private OntologyTermNodePathFactory ontologyTermNodePathFactory; @Autowired private OntologyTermDynamicAnnotationFactory ontologyTermDynamicAnnotationFactory; @Autowired private OntologyTermSynonymFactory ontologyTermSynonymFactory; @Autowired private OntologyTermFactory ontologyTermFactory; // repositories private Repository<Entity> ontologyRepository; private Repository<Entity> nodePathRepository; private Repository<Entity> ontologyTermRepository; private Repository<Entity> annotationRepository; private Repository<Entity> synonymRepository; private Map<String, Repository<Entity>> repositories; private OntologyLoader loader; private Multimap<String, OntologyTermNodePath> nodePathsPerOntologyTerm = ArrayListMultimap.create(); private Ontology ontologyEntity; /** * Creates a new {@link OntologyRepositoryCollection} for an ontology file * * @param file the ontology file */ public OntologyRepositoryCollection(File file) { super(GenericImporterExtensions.getOntology()); this.file = requireNonNull(file); String name = file.getName(); if (name.endsWith(GenericImporterExtensions.OBO_ZIP.toString())) { name = name.substring(0, name.lastIndexOf('.' + GenericImporterExtensions.OBO_ZIP.toString())) .replace('.', '_'); } else if (name.endsWith(GenericImporterExtensions.OWL_ZIP.toString())) { name = name.substring(0, name.lastIndexOf('.' + GenericImporterExtensions.OWL_ZIP.toString())) .replace('.', '_'); } else { throw new IllegalArgumentException(format("Not a obo.zip or owl.zip file [%s]", file.getName())); } this.fileName = name; } @Override public void init() throws IOException { ontologyRepository = new InMemoryRepository(ontologyFactory.getEntityType()); nodePathRepository = new InMemoryRepository(ontologyTermNodePathFactory.getEntityType()); ontologyTermRepository = new InMemoryRepository(ontologyTermFactory.getEntityType()); annotationRepository = new InMemoryRepository(ontologyTermDynamicAnnotationFactory.getEntityType()); synonymRepository = new InMemoryRepository(ontologyTermSynonymFactory.getEntityType()); repositories = ImmutableMap .of(ONTOLOGY_TERM_DYNAMIC_ANNOTATION, annotationRepository, ONTOLOGY_TERM_SYNONYM, synonymRepository, ONTOLOGY_TERM_NODE_PATH, nodePathRepository, ONTOLOGY, ontologyRepository, ONTOLOGY_TERM, ontologyTermRepository); List<File> uploadedFiles = ZipFileUtil.unzip(file); try { loader = new OntologyLoader(fileName, uploadedFiles.get(0)); } catch (OWLOntologyCreationException e) { throw new IOException(e); } createOntology(); createNodePaths(); createOntologyTerms(); } /** * Initializes the {@link #ontologyEntity} and adds it to the {@link #ontologyRepository}. */ private void createOntology() { ontologyEntity = ontologyFactory.create(); ontologyEntity.setId(idGenerator.generateId()); ontologyEntity.setOntologyIri(loader.getOntologyIRI()); ontologyEntity.setOntologyName(loader.getOntologyName()); ontologyRepository.add(ontologyEntity); } /** * Creates {@link OntologyTermNodePathMetaData} {@link Entity}s for an entire ontology tree and writes them to the * {@link #nodePathsPerOntologyTerm} {@link Multimap}. */ private void createNodePaths() { TreeTraverser<OWLClassContainer> traverser = new TreeTraverser<OWLClassContainer>() { @Override public Iterable<OWLClassContainer> children(OWLClassContainer container) { int count = 0; List<OWLClassContainer> containers = new ArrayList<>(); for (OWLClass childClass : loader.getChildClass(container.getOwlClass())) { containers.add(new OWLClassContainer(childClass, constructNodePath(container.getNodePath(), count), false)); count++; } return containers; } }; OWLClass pseudoRootClass = loader.createClass(PSEUDO_ROOT_CLASS_LABEL, loader.getRootClasses()); Iterator<OWLClassContainer> iterator = traverser .preOrderTraversal(new OWLClassContainer(pseudoRootClass, PSEUDO_ROOT_CLASS_NODEPATH, true)).iterator(); while (iterator.hasNext()) { OWLClassContainer container = iterator.next(); OWLClass ontologyTerm = container.getOwlClass(); String ontologyTermNodePath = container.getNodePath(); String ontologyTermIRI = ontologyTerm.getIRI().toString(); OntologyTermNodePath nodePathEntity = createNodePathEntity(container, ontologyTermNodePath); nodePathsPerOntologyTerm.put(ontologyTermIRI, nodePathEntity); } } /** * Creates {@link OntologyTermMetaData} {@link Entity}s for all {@link OWLClass}ses in the {@link #loader} and adds * them to the {@link #ontologyTermRepository}. */ private void createOntologyTerms() { loader.getAllclasses().forEach(this::createOntologyTerm); } /** * Creates an {@link OntologyTermMetaData} {@link Entity} and adds it in the {@link #ontologyTermRepository} * * @param ontologyTermClass the OWLClass to create an entity for * @return the created ontology term {@link Entity} */ private Entity createOntologyTerm(OWLClass ontologyTermClass) { String ontologyTermIRI = ontologyTermClass.getIRI().toString(); String ontologyTermName = loader.getLabel(ontologyTermClass); OntologyTerm ontologyTerm = ontologyTermFactory.create(); ontologyTerm.setId(idGenerator.generateId()); ontologyTerm.setOntologyTermIri(ontologyTermIRI); ontologyTerm.setOntologyTermName(ontologyTermName); ontologyTerm.setOntologyTermSynonyms(createSynonyms(ontologyTermClass)); ontologyTerm.setOntologyTermDynamicAnnotations(createDynamicAnnotations(ontologyTermClass)); ontologyTerm.setOntologyTermNodePaths(nodePathsPerOntologyTerm.get(ontologyTermIRI)); ontologyTerm.setOntology(ontologyEntity); ontologyTermRepository.add(ontologyTerm); return ontologyTerm; } /** * Creates {@link OntologyTermSynonymMetaData} {@link Entity}s for an ontology term * * @param ontologyTerm {@link OWLClass} for the ontology term * @return {@link List} of created synonym {@link Entity}s */ private List<OntologyTermSynonym> createSynonyms(OWLClass ontologyTerm) { return loader.getSynonyms(ontologyTerm).stream().map(this::createSynonym).collect(Collectors.toList()); } /** * Creates an {@link OntologyTermSynonymMetaData} {@link Entity} and adds it to the {@link #synonymRepository}. * * @param synonym String of the synonym to create an {@link Entity} for * @return the created {@link Entity} */ private OntologyTermSynonym createSynonym(String synonym) { OntologyTermSynonym entity = ontologyTermSynonymFactory.create(); entity.setId(idGenerator.generateId()); entity.setOntologyTermSynonym(synonym); synonymRepository.add(entity); return entity; } /** * Creates {@link OntologyTermDynamicAnnotationMetaData} {@link Entity}s for the databaseIds of an ontology term. * * @param term the term to create annotation entities for * @return List of created {@link Entity}s. */ private List<OntologyTermDynamicAnnotation> createDynamicAnnotations(OWLClass term) { return loader.getDatabaseIds(term).stream().map(this::createDynamicAnnotation).collect(Collectors.toList()); } /** * Creates an {@link OntologyTermDynamicAnnotationMetaData} {@link Entity} for a key:value label. * * @param label the key:value label * @return the {@link Entity} */ private OntologyTermDynamicAnnotation createDynamicAnnotation(String label) { OntologyTermDynamicAnnotation entity = ontologyTermDynamicAnnotationFactory.create(); entity.setId(idGenerator.generateId()); String fragments[] = label.split(":"); entity.setName(fragments[0]); entity.setValue(fragments[1]); entity.setLabel(label); annotationRepository.add(entity); return entity; } /** * Constructs the node path string for a child node * * @param parentNodePath node path string of the node's parent * @param currentPosition position of the node in the parent's child list * @return node path string */ private String constructNodePath(String parentNodePath, int currentPosition) { StringBuilder nodePathStringBuilder = new StringBuilder(); if (!StringUtils.isEmpty(parentNodePath)) nodePathStringBuilder.append(parentNodePath).append('.'); nodePathStringBuilder.append(currentPosition).append('[') .append(nodePathStringBuilder.toString().split("\\.").length - 1).append(']'); return nodePathStringBuilder.toString(); } /** * Creates a {@link OntologyTermNodePathMetaData} {@link Entity} and stores it in the {@link #nodePathRepository}. * * @param container {@link OWLClassContainer} for the path to the ontology term * @param ontologyTermNodePathText the node path * @return the created {@link Entity} */ private OntologyTermNodePath createNodePathEntity(OWLClassContainer container, String ontologyTermNodePathText) { OntologyTermNodePath ontologyTermNodePath = ontologyTermNodePathFactory.create(); ontologyTermNodePath.setId(idGenerator.generateId()); ontologyTermNodePath.setNodePath(ontologyTermNodePathText); ontologyTermNodePath.setRoot(container.isRoot()); nodePathRepository.add(ontologyTermNodePath); return ontologyTermNodePath; } @Override public Iterable<String> getEntityNames() { return repositories.keySet(); } @Override public String getName() { throw new UnsupportedOperationException(); } @Override public Iterator<Repository<Entity>> iterator() { throw new UnsupportedOperationException(); } @Override public Repository<Entity> getRepository(String name) { if (!repositories.containsKey(name)) { throw new MolgenisDataException(format("Unknown entity name [%s]", name)); } return repositories.get(name); } @Override public boolean hasRepository(String name) { if (null == name) return false; Iterator<String> entityNames = getEntityNames().iterator(); while (entityNames.hasNext()) { if (entityNames.next().equals(name)) return true; } return false; } @Override public boolean hasRepository(EntityType entityType) { return hasRepository(entityType.getName()); } }