uk.co.flax.biosolr.elasticsearch.mapper.ontology.owl.OntologyHelper.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.flax.biosolr.elasticsearch.mapper.ontology.owl.OntologyHelper.java

Source

/**
 * Copyright (c) 2015 Lemur Consulting Ltd.
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.flax.biosolr.elasticsearch.mapper.ontology.owl;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
import org.semanticweb.owlapi.model.OWLAnnotationProperty;
import org.semanticweb.owlapi.model.OWLAnnotationValue;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
import org.semanticweb.owlapi.reasoner.Node;
import org.semanticweb.owlapi.reasoner.NodeSet;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import uk.co.flax.biosolr.elasticsearch.mapper.ontology.OntologySettings;

/**
 * Helper class for loading an ontology and making its properties easily
 * accessible.
 *
 * @author mlp
 */
public class OntologyHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(OntologyHelper.class);

    private final OntologySettings config;
    private final URI ontologyUri;

    private final OWLOntology ontology;
    private final OWLReasoner reasoner;
    // private final ShortFormProvider shortFormProvider;
    private final IRI owlNothingIRI;

    private final Map<IRI, OWLClass> owlClassMap = new HashMap<>();

    private Map<IRI, Collection<String>> labels = new HashMap<>();
    private Map<IRI, Collection<String>> synonyms = new HashMap<>();
    private Map<IRI, Collection<String>> definitions = new HashMap<>();

    private long lastCallTime;

    /**
     * Construct a new ontology helper instance with a string representing the
     * ontology URI.
     * 
     * @param ontologyUriString the URI.
     * @param config the ontology configuration, containing the property URIs
     * for labels, synonyms, etc.
     * @throws OWLOntologyCreationException if the ontology cannot be read for
     * some reason - internal inconsistencies, etc.
     * @throws URISyntaxException if the URI cannot be parsed.
     */
    public OntologyHelper(OntologySettings config) throws OWLOntologyCreationException, URISyntaxException {
        this(new URI(config.getOntologyUri()), config);
    }

    /**
     * Construct a new ontology helper instance.
     * 
     * @param ontologyUri the URI giving the location of the ontology.
     * @param config the ontology configuration, containing the property URIs
     * for labels, synonyms, etc.
     * @throws OWLOntologyCreationException if the ontology cannot be read for
     * some reason - internal inconsistencies, etc.
     * @throws URISyntaxException if the URI cannot be parsed.
     */
    public OntologyHelper(URI ontologyUri, OntologySettings config)
            throws OWLOntologyCreationException, URISyntaxException {
        this.config = config;

        if (!ontologyUri.isAbsolute()) {
            // Try to read as a file from the resource path
            LOGGER.debug("Ontology URI {} is not absolute - loading from classpath", ontologyUri);
            URL url = this.getClass().getClassLoader().getResource(ontologyUri.toString());
            if (url != null) {
                ontologyUri = url.toURI();
            }
        }
        this.ontologyUri = ontologyUri;
        LOGGER.info("Loading ontology from " + ontologyUri + "...");

        OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
        IRI iri = IRI.create(ontologyUri);
        this.ontology = manager.loadOntologyFromOntologyDocument(iri);
        // Use a buffering reasoner - not interested in ongoing changes
        this.reasoner = new StructuralReasonerFactory().createReasoner(ontology);
        // this.shortFormProvider = new SimpleShortFormProvider();
        this.owlNothingIRI = manager.getOWLDataFactory().getOWLNothing().getIRI();

        // Initialise the class map
        initialiseClassMap();
    }

    private void initialiseClassMap() {
        for (OWLClass clazz : ontology.getClassesInSignature()) {
            owlClassMap.put(clazz.getIRI(), clazz);
        }
    }

    /**
     * Explicitly dispose of the helper class, closing down any resources in
     * use.
     */
    public void dispose() {
        if (reasoner != null) {
            LOGGER.info("Disposing of ontology reasoner for {}", ontologyUri);
            reasoner.dispose();
        }
    }

    /**
     * Get the OWL class for an IRI.
     * 
     * @param iri the IRI of the required class.
     * @return the class from the ontology, or <code>null</code> if no such
     * class can be found, or the IRI string is null.
     */
    public OWLClass getOwlClass(String iri) {
        OWLClass ret = null;

        if (StringUtils.isNotBlank(iri)) {
            ret = owlClassMap.get(IRI.create(iri));
        }

        return ret;
    }

    /**
     * Find the labels for a single OWL class.
     * 
     * @param owlClass the class whose labels are required.
     * @return a collection of labels for the class. Never <code>null</code>.
     */
    public Collection<String> findLabels(OWLClass owlClass) {
        return findLabels(owlClass.getIRI());
    }

    /**
     * Find all of the labels for a collection of OWL class IRIs.
     * 
     * @param iris the IRIs whose labels should be looked up.
     * @return a collection of labels. Never <code>null</code>.
     */
    public Collection<String> findLabelsForIRIs(Collection<String> iris) {
        Set<String> labels = new HashSet<>();
        iris.stream().map(iri -> findLabels(IRI.create(iri))).forEach(labels::addAll);
        return labels;
    }

    /**
     * Find all of the synonyms for an OWL class.
     * 
     * @param owlClass
     * @return the synonyms. Never <code>null</code>.
     */
    public Collection<String> findSynonyms(OWLClass owlClass) {
        return findSynonyms(owlClass.getIRI());
    }

    /**
     * Find all of the definitions for an OWL class.
     * 
     * @param owlClass
     * @return the definitions. Never <code>null</code>.
     */
    public Collection<String> findDefinitions(OWLClass owlClass) {
        return findDefinitions(owlClass.getIRI());
    }

    private Collection<String> findLabels(IRI iri) {
        if (!labels.containsKey(iri)) {
            Collection<String> classNames = findPropertyValueStrings(config.getLabelPropertyUris(), iri);
            labels.put(iri, classNames);
        }
        return labels.get(iri);
    }

    private Collection<String> findSynonyms(IRI iri) {
        if (!synonyms.containsKey(iri)) {
            Collection<String> classNames = findPropertyValueStrings(config.getSynonymPropertyUris(), iri);
            synonyms.put(iri, classNames);
        }
        return synonyms.get(iri);
    }

    private Collection<String> findDefinitions(IRI iri) {
        if (!definitions.containsKey(iri)) {
            Collection<String> classNames = findPropertyValueStrings(config.getDefinitionPropertyUris(), iri);
            definitions.put(iri, classNames);
        }
        return definitions.get(iri);
    }

    private Collection<String> findPropertyValueStrings(List<String> propertyUris, IRI iri) {
        Collection<String> classNames = new HashSet<>();

        OWLDataFactory odf = ontology.getOWLOntologyManager().getOWLDataFactory();

        // For every property URI, find the annotations for this entry
        propertyUris.parallelStream().map(uri -> odf.getOWLAnnotationProperty(IRI.create(uri)))
                .map(prop -> findAnnotationNames(iri, prop)).forEach(classNames::addAll);

        return classNames;
    }

    private Collection<String> findAnnotationNames(IRI iri, OWLAnnotationProperty annotationType) {
        Collection<String> classNames = new HashSet<String>();

        // get all literal annotations
        for (OWLAnnotationAssertionAxiom axiom : ontology.getAnnotationAssertionAxioms(iri)) {
            if (axiom.getAnnotation().getProperty().equals(annotationType)) {
                OWLAnnotationValue value = axiom.getAnnotation().getValue();
                Optional<String> name = getOWLAnnotationValueAsString(value);
                if (name.isPresent()) {
                    classNames.add(name.get());
                }
            }
        }

        return classNames;
    }

    /**
     * Get the direct child URIs for an OWL class.
     * 
     * @param owlClass
     * @return the child URIs, as strings. Never <code>null</code>.
     */
    public Collection<String> getChildUris(OWLClass owlClass) {
        return getSubclassUris(owlClass, true);
    }

    /**
     * Get all descendant URIs for an OWL class, including direct children.
     * 
     * @param owlClass
     * @return the descendant URIs, as strings. Never <code>null</code>.
     */
    public Collection<String> getDescendantUris(OWLClass owlClass) {
        return getSubclassUris(owlClass, false);
    }

    /**
     * Get direct parent URIs for an OWL class.
     * 
     * @param owlClass
     * @return the parent URIs, as strings. Never <code>null</code>.
     */
    public Collection<String> getParentUris(OWLClass owlClass) {
        return getSuperclassUris(owlClass, true);
    }

    /**
     * Get all ancestor URIs for an OWL class, including direct parents.
     * 
     * @param owlClass
     * @return the ancestor URIs, as strings. Never <code>null</code>.
     */
    public Collection<String> getAncestorUris(OWLClass owlClass) {
        return getSuperclassUris(owlClass, false);
    }

    private Collection<String> getSubclassUris(OWLClass owlClass, boolean direct) {
        return getUrisFromNodeSet(reasoner.getSubClasses(owlClass, direct));
    }

    private Collection<String> getSuperclassUris(OWLClass owlClass, boolean direct) {
        return getUrisFromNodeSet(reasoner.getSuperClasses(owlClass, direct));
    }

    private Collection<String> getUrisFromNodeSet(NodeSet<OWLClass> nodeSet) {
        Set<String> uris = new HashSet<>();

        for (Node<OWLClass> node : nodeSet) {
            for (OWLClass expr : node.getEntities()) {
                if (isClassSatisfiable(expr)) {
                    uris.add(expr.getIRI().toURI().toString());
                }
            }
        }

        return uris;
    }

    private boolean isClassSatisfiable(OWLClass owlClass) {
        return !owlClass.isAnonymous() && !owlClass.getIRI().equals(owlNothingIRI);
    }

    /**
     * Retrieve a map of related classes for a particular class.
     * 
     * @param owlClass
     * @return a map of relation type to a list of IRIs for nodes with that
     * relationship.
     */
    public Map<String, List<String>> getRestrictions(OWLClass owlClass) {
        RestrictionVisitor visitor = new RestrictionVisitor(Collections.singleton(ontology));
        for (OWLSubClassOfAxiom ax : ontology.getSubClassAxiomsForSubClass(owlClass)) {
            OWLClassExpression superCls = ax.getSuperClass();
            // Ask our superclass to accept a visit from the RestrictionVisitor
            // - if it is an existential restriction then our restriction visitor
            // will answer it - if not our visitor will ignore it
            superCls.accept(visitor);
        }

        Map<String, List<String>> restrictions = new HashMap<>();
        for (OWLObjectSomeValuesFrom val : visitor.getSomeValues()) {
            OWLClassExpression exp = val.getFiller();

            // Get the shortname of the property expression
            String shortForm = null;
            Set<OWLObjectProperty> signatureProps = val.getProperty().getObjectPropertiesInSignature();
            for (OWLObjectProperty sigProp : signatureProps) {
                Collection<String> labels = findLabels(sigProp.getIRI());
                if (labels.size() > 0) {
                    shortForm = new ArrayList<String>(labels).get(0);
                }
            }

            if (shortForm != null && !exp.isAnonymous()) {
                IRI iri = exp.asOWLClass().getIRI();

                if (!restrictions.containsKey(shortForm)) {
                    restrictions.put(shortForm, new ArrayList<String>());
                }
                restrictions.get(shortForm).add(iri.toString());
            }
        }

        return restrictions;
    }

    private Optional<String> getOWLAnnotationValueAsString(OWLAnnotationValue value) {
        if (value instanceof IRI) {
            Optional<String> shortForm = getShortForm((IRI) value);
            if (shortForm.isPresent()) {
                return Optional.of(shortForm.get());
            }
        } else if (value instanceof OWLLiteral) {
            return Optional.of(((OWLLiteral) value).getLiteral());
        }
        return Optional.empty();
    }

    private Optional<String> getShortForm(IRI entityIRI) {
        LOGGER.trace("Attempting to extract fragment name of URI '" + entityIRI + "'");
        String termURI = entityIRI.toString();
        URI entUri = entityIRI.toURI();

        // we want the "final part" of the URI...
        if (!StringUtils.isEmpty(entUri.getFragment())) {
            // a uri with a non-null fragment, so use this...
            LOGGER.trace("Extracting fragment name using URI fragment (" + entUri.getFragment() + ")");
            return Optional.of(entUri.getFragment());
        } else if (entityIRI.toURI().getPath() != null) {
            // no fragment, but there is a path so try and extract the final
            // part...
            if (entityIRI.toURI().getPath().contains("/")) {
                LOGGER.trace("Extracting fragment name using final part of the path of the URI");
                return Optional.of(
                        entityIRI.toURI().getPath().substring(entityIRI.toURI().getPath().lastIndexOf('/') + 1));
            } else {
                // no final path part, so just return whole path
                LOGGER.trace("Extracting fragment name using the path of the URI");
                return Optional.of(entityIRI.toURI().getPath());
            }
        } else {
            // no fragment, path is null, we've run out of rules so don't
            // shorten
            LOGGER.trace("No rules to shorten this URI could be found (" + termURI + ")");
            return Optional.empty();
        }
    }

    /**
     * Update the record of the last time this class was used. This
     * method should be called every time the helper class is accessed.
     */
    public void updateLastCallTime() {
        this.lastCallTime = System.currentTimeMillis();
    }

    /**
     * @return the last time the helper was called.
     */
    public long getLastCallTime() {
        return lastCallTime;
    }

    public Set<String> getRestrictionProperties() {
        Set<String> properties = new HashSet<>();

        Set<OWLObjectProperty> objectProperties = ontology.getObjectPropertiesInSignature();
        for (OWLObjectProperty oop : objectProperties) {
            List<String> labels = new ArrayList<>(findLabels(oop.getIRI()));
            if (!labels.isEmpty()) {
                properties.add(labels.get(0));
            } else {
                Optional<String> shortForm = getShortForm(oop.getIRI());
                if (shortForm.isPresent()) {
                    properties.add(shortForm.get());
                }
            }
        }

        return properties;
    }

}