a b/src/main/java/org/sbaresearch/owl/OwlApiFacade.java
1
/**
2
 * Copyright (c) 2013/2014 Verein zur Foerderung der IT-Sicherheit in Oesterreich (SBA).
3
 * The work has been developed in the TIMBUS Project and the above-mentioned are Members of the TIMBUS Consortium.
4
 * TIMBUS is supported by the European Union under the 7th Framework Programme for research and technological
5
 * development and demonstration activities (FP7/2007-2013) under grant agreement no. 269940.
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
8
 * the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
9
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including without
11
 * limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTIBITLY, or FITNESS FOR A PARTICULAR
12
 * PURPOSE. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise,
13
 * unless required by applicable law or agreed to in writing, shall any Contributor be liable for damages, including
14
 * any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this
15
 * License or out of the use or inability to use the Work.
16
 * See the License for the specific language governing permissions and limitation under the License.
17
 */
18
package org.sbaresearch.owl;
19
20
import ch.lambdaj.function.convert.Converter;
21
import com.hp.hpl.jena.ontology.OntDocumentManager;
22
import com.hp.hpl.jena.ontology.OntModel;
23
import com.hp.hpl.jena.ontology.OntModelSpec;
24
import com.hp.hpl.jena.rdf.model.Model;
25
import com.hp.hpl.jena.rdf.model.ModelFactory;
26
import net.sf.oval.guard.Guarded;
27
import net.sf.oval.guard.Pre;
28
import org.semanticweb.owlapi.apibinding.OWLManager;
29
import org.semanticweb.owlapi.io.FileDocumentTarget;
30
import org.semanticweb.owlapi.io.IRIDocumentSource;
31
import org.semanticweb.owlapi.io.RDFXMLOntologyFormat;
32
import org.semanticweb.owlapi.model.*;
33
import org.slf4j.Logger;
34
import org.slf4j.LoggerFactory;
35
import uk.ac.manchester.cs.owl.owlapi.OWLClassImpl;
36
37
import java.io.*;
38
import java.util.HashSet;
39
import java.util.List;
40
import java.util.Map;
41
import java.util.Set;
42
43
import static ch.lambdaj.Lambda.convert;
44
import static ch.lambdaj.Lambda.filter;
45
import static org.hamcrest.CoreMatchers.notNullValue;
46
47
/**
48
 * This class wraps commonly used functions of the API to provide a simpler interface.
49
 *
50
 * @see <a href="http://owlapi.sourceforge.net/">The OWL API</a>
51
 */
52
@Guarded
53
public class OwlApiFacade {
54
55
    private static final Logger LOG = LoggerFactory.getLogger(OwlApiFacade.class);
56
57
    private OWLOntologyManager manager;
58
    private OWLOntology ontology;
59
    private OWLDataFactory dataFactory;
60
61
    /**
62
     * Wraps an existing ontology.
63
     *
64
     * @param ontology An existing ontology.
65
     */
66
    public OwlApiFacade(OWLOntology ontology) {
67
        this.ontology = ontology;
68
        this.manager = ontology.getOWLOntologyManager();
69
        this.dataFactory = manager.getOWLDataFactory();
70
    }
71
72
    /**
73
     * Use OWLManager.createOWLOntologyManager() and OWLManager.getOWLDataFactory()) per default.
74
     *
75
     * @param manager     @see org.semanticweb.owlapi.model.OWLOntologyManager
76
     * @param dataFactory @see org.semanticweb.owlapi.model.OWLDataFactory
77
     */
78
    public OwlApiFacade(OWLOntologyManager manager, OWLDataFactory dataFactory) {
79
        this.manager = manager;
80
        this.dataFactory = dataFactory;
81
    }
82
83
    @SuppressWarnings("unused")
84
    public OWLOntology getOntology() {
85
        return ontology;
86
    }
87
88
    @SuppressWarnings("unused")
89
    public OWLDataFactory getDataFactory() {
90
        return dataFactory;
91
    }
92
93
    @SuppressWarnings("unused")
94
    public OWLOntologyManager getManager() {
95
        return manager;
96
    }
97
98
    @Pre(expr = "_this.manager!=null", lang = "js")
99
    public void setIRIMapping(final Map<String, String> iriMapping) {
100
        manager.addIRIMapper(new OWLOntologyIRIMapper() {
101
            @Override
102
            public IRI getDocumentIRI(IRI iri) {
103
                if (!iriMapping.containsKey(iri.toString())) return iri;
104
                return IRI.create(iriMapping.get(iri.toString()));
105
            }
106
        });
107
    }
108
109
    /**
110
     * Load ontology from an URI. Does not throw exceptions if resolving any import fails.
111
     *
112
     * @param owlUri The ontology to load. This can be either an URL ("http://*") or local path ("file:*").
113
     */
114
    @Pre(expr = "_this.manager!=null", lang = "js")
115
    public void load(String owlUri) throws OWLOntologyCreationException {
116
        ontology = manager.loadOntologyFromOntologyDocument(
117
                new IRIDocumentSource(IRI.create(owlUri)),
118
                new OWLOntologyLoaderConfiguration().setMissingImportHandlingStrategy(MissingImportHandlingStrategy.SILENT));
119
        if (ontology.getOntologyID().getOntologyIRI() == null) {
120
            LOG.info("Ontology IRI not found, using url...");
121
            // manager.setOntologyDocumentIRI(ontology, IRI.create(owlUri)); // FIXME: does not seem to work @#%!$%
122
            // FIXME: megahack to force using a valid ontology IRI...
123
            manager = OWLManager.createOWLOntologyManager();
124
            ontology = manager.createOntology(IRI.create(owlUri), new HashSet<OWLOntology>() {{
125
                add(ontology);
126
            }});
127
        }
128
    }
129
130
    /**
131
     * Load ontology from an URI, resolves missing imports by looking up the alternative locations mapping.
132
     * This is useful for unpublished ontologies.
133
     *
134
     * @param owlUri     The ontology to load. This can be either an URL ("http://*") or local path ("file:*").
135
     * @param iriMapping The mapping of import to actual ontology locations.
136
     * @throws OWLOntologyCreationException
137
     * @see OwlApiFacade::load
138
     */
139
    @Pre(expr = "_this.manager!=null", lang = "js")
140
    public void load(String owlUri, final Map<String, String> iriMapping) throws OWLOntologyCreationException {
141
        setIRIMapping(iriMapping);
142
        load(owlUri);
143
    }
144
145
    /**
146
     * Loads an ontology from a stream. Does not resolve missing imports but throws an exception.
147
     *
148
     * @param owlContent The ontology to load.
149
     * @throws OWLOntologyCreationException
150
     */
151
    @Pre(expr = "_this.manager!=null", lang = "js")
152
    public void load(InputStream owlContent) throws OWLOntologyCreationException {
153
        ontology = manager.loadOntologyFromOntologyDocument(owlContent);
154
    }
155
156
    /**
157
     * Creates an empty ontology. This is useful for testing purposes.
158
     *
159
     * @param iri The IRI of the new ontology.
160
     * @throws OWLOntologyCreationException
161
     */
162
    @Pre(expr = "_this.manager!=null", lang = "js")
163
    public void create(String iri) throws OWLOntologyCreationException {
164
        ontology = manager.createOntology(IRI.create(iri));
165
    }
166
167
    /**
168
     * No white spaces are allowed as individual name!
169
     */
170
    @Pre(expr = "_this.ontology!=null && _this.manager!=null && _this.dataFactory!=null", lang = "js")
171
    public OWLNamedIndividual addIndividual(String name) {
172
        OWLNamedIndividual indiv = dataFactory.getOWLNamedIndividual(createIri(name));
173
        OWLDeclarationAxiom indivDeclaration = dataFactory.getOWLDeclarationAxiom(indiv);
174
        manager.applyChange(new AddAxiom(ontology, indivDeclaration));
175
        return indiv;
176
    }
177
178
    /**
179
     * @see OwlApiFacade::addIndividual, owlApiFacade::makeInstanceOf
180
     */
181
    @Pre(expr = "_this.ontology!=null && _this.manager!=null && _this.dataFactory!=null", lang = "js")
182
    public OWLNamedIndividual addIndividual(String indivName, String className) {
183
        OWLNamedIndividual owlNamedIndividual = addIndividual(indivName);
184
        makeInstanceOf(owlNamedIndividual, className);
185
        return owlNamedIndividual;
186
    }
187
188
    /**
189
     * Checks if an individual exists. If provided with the fragment only searches in all imports also.
190
     *
191
     * @param indivName The individual to search for, given by fragment or namespace and fragment.
192
     * @return True if found (possibly in an imported ontology), false else.
193
     */
194
    @Pre(expr = "_this.ontology!=null", lang = "js")
195
    public boolean containsIndividual(String indivName) {
196
        if (ontology.containsIndividualInSignature(createIri(indivName), true)) return true;
197
        if (createIri(indivName).toString().equals(indivName)) return false;
198
        for (OWLOntology importedOntology : ontology.getImports()) {
199
            String qualifiedIndivName = importedOntology.getOntologyID().getOntologyIRI() + "#" + indivName;
200
            if (ontology.containsIndividualInSignature(IRI.create(qualifiedIndivName), true)) return true;
201
        }
202
        for (OWLImportsDeclaration importDeclaration : ontology.getImportsDeclarations()) {
203
            String qualifiedIndivName = importDeclaration.getIRI().toString() + "#" + indivName;
204
            if (ontology.containsIndividualInSignature(IRI.create(qualifiedIndivName), true)) return true;
205
        }
206
        return false;
207
    }
208
209
    /**
210
     * Checks if an individual exists using the label annotation. Also looks in all imports.
211
     *
212
     * @param label The label to search for.
213
     * @return True if an individual with the specified label does exist, false else.
214
     */
215
    @Pre(expr = "_this.ontology!=null && label!=null", lang = "js")
216
    public boolean containsIndividualByLabel(String label) {
217
        return containsIndividualByLabel(ontology, label);
218
    }
219
220
    private boolean containsIndividualByLabel(OWLOntology ontology, String label) {
221
        for (OWLNamedIndividual individual : ontology.getIndividualsInSignature(true)) {
222
            for (OWLAnnotation annotation : individual.getAnnotations(ontology)) {
223
                if (!annotation.getProperty().equals(ontology.getOWLOntologyManager().getOWLDataFactory().getRDFSLabel())) {
224
                    continue;
225
                }
226
                if (((OWLLiteral) annotation.getValue()).getLiteral().equals(label)) {
227
                    return true;
228
                }
229
            }
230
        }
231
        for (OWLOntology model : ontology.getImports()) {
232
            if (containsIndividualByLabel(model, label)) return true;
233
        }
234
        return false;
235
    }
236
237
    @Pre(expr = "_this.ontology!=null && _this.dataFactory!=null", lang = "js")
238
    public OWLNamedIndividual getIndividual(String indivName) throws OwlElementNotFoundException {
239
        IRI indivIri = createIri(indivName);
240
        if (!ontology.containsIndividualInSignature(indivIri, true)) throw new OwlElementNotFoundException();
241
        return dataFactory.getOWLNamedIndividual(indivIri);
242
    }
243
244
    /**
245
     * This method expects an unqualified name and tries to find it using the base IRI of the ontology but optionally also
246
     * using the base IRIs of all imported ontologies.
247
     * This is useful to get an individual without knowing the exact ontology it has been defined.
248
     *
249
     * @param indivName                      The unqualified individual name.
250
     * @param searchInImportedOntologiesAlso Specifies whether the search should be extended to all imported ontologies.
251
     * @throws OwlElementNotFoundException If no matching individual can be found.
252
     */
253
    public OWLNamedIndividual getIndividualByUnqualifiedName(String indivName, boolean searchInImportedOntologiesAlso) throws OwlElementNotFoundException {
254
        if (containsIndividual(createIri(indivName).toString())) return getIndividual(indivName);
255
        if (!searchInImportedOntologiesAlso)
256
            throw new OwlElementNotFoundException(String.format("Individual with name %s not found, consider setting searchInImportedOntologiesAlso to true.", indivName));
257
        for (OWLOntology importedOntology : ontology.getImports()) {
258
            String qualifiedIndivName = importedOntology.getOntologyID().getOntologyIRI() + "#" + indivName;
259
            if (containsIndividual(qualifiedIndivName)) return getIndividual(qualifiedIndivName);
260
        }
261
        for (OWLImportsDeclaration importDeclaration : ontology.getImportsDeclarations()) {
262
            String qualifiedIndivName = importDeclaration.getIRI().toString() + "#" + indivName;
263
            if (containsIndividual(qualifiedIndivName)) return getIndividual(qualifiedIndivName);
264
        }
265
        throw new OwlElementNotFoundException();
266
    }
267
268
    /**
269
     * Retrieves an individual by matching label. This method also considers imported ontologies.
270
     *
271
     * @param label The label to search for (RDFS label).
272
     * @return The individual if it could be found.
273
     * @throws OwlElementNotFoundException If the individual could not be found.
274
     */
275
    @Pre(expr = "_this.ontology!=null && label!=null", lang = "js")
276
    public OWLNamedIndividual getIndividualByLabel(String label) throws OwlElementNotFoundException {
277
        if (!containsIndividualByLabel(label)) {
278
            throw new OwlElementNotFoundException(String.format("Individual with label %s not found, consider setting searchInImportedOntologiesAlso to true.", label));
279
        }
280
        OWLNamedIndividual individual = getIndividualByLabel(ontology, label);
281
        if (individual == null) {
282
            throw new OwlElementNotFoundException(String.format("Individual with label %s not found, consider setting searchInImportedOntologiesAlso to true.", label));
283
        }
284
        return individual;
285
    }
286
287
    private OWLNamedIndividual getIndividualByLabel(OWLOntology ontology, String label) {
288
        for (OWLNamedIndividual individual : ontology.getIndividualsInSignature(true)) {
289
            for (OWLAnnotation annotation : individual.getAnnotations(ontology)) {
290
                if (!annotation.getProperty().equals(ontology.getOWLOntologyManager().getOWLDataFactory().getRDFSLabel())) {
291
                    continue;
292
                }
293
                if (((OWLLiteral) annotation.getValue()).getLiteral().equals(label)) {
294
                    return individual;
295
                }
296
            }
297
        }
298
        for (OWLOntology model : ontology.getImports()) {
299
            OWLNamedIndividual result = getIndividualByLabel(model, label);
300
            if (result != null) return result;
301
        }
302
        return null;
303
    }
304
305
    /**
306
     * Note: This method can be used for individuals only.
307
     */
308
    @Pre(expr = "_this.ontology!=null && name!=null && name!=''", lang = "js")
309
    public IRI createIri(String name) {
310
        if (name.contains("#")) {
311
            return IRI.create(name); // OwlAPI does not recognize numeric-only fragments, so just split manually
312
        }
313
        return IRI.create(ontology.getOntologyID().getOntologyIRI() + "#", name);
314
    }
315
316
    /**
317
     * IRI.create does not seem to split numeric fragments correctly, this method tries to.
318
     */
319
    public static String getFragment(IRI iri) {
320
        // does not work for i.e. "http://timbus.teco.edu/ontologies/Scenarios/MusicClassification.owl#7cb4eeb2", split incorrectly by IRI.create
321
        // if (iri.getFragment() != null) return iri.getFragment();
322
        int prefixEndIndex = iri.toString().lastIndexOf('#') + 1;
323
        return iri.toString().substring(prefixEndIndex);
324
    }
325
326
    /**
327
     * IRI.create does not seem to split numeric fragments correctly, this method tries to.
328
     */
329
    public static String getNamespace(IRI iri) {
330
        int prefixEndIndex = iri.toString().lastIndexOf('#') + 1;
331
        return iri.toString().substring(0, prefixEndIndex - 1) + "#";
332
    }
333
334
    @Pre(expr = "_this.ontology!=null && _this.manager!=null && _this.dataFactory!=null", lang = "js")
335
    public void makeInstanceOf(OWLNamedIndividual individual, String baseClass) {
336
        OWLClass classExpression = dataFactory.getOWLClass(createIri(baseClass));
337
        OWLClassAssertionAxiom classAssertion = dataFactory.getOWLClassAssertionAxiom(classExpression, individual);
338
        manager.applyChange(new AddAxiom(ontology, classAssertion));
339
    }
340
341
    @Pre(expr = "_this.ontology!=null && _this.manager!=null && _this.dataFactory!=null", lang = "js")
342
    public void addObjectProperty(OWLNamedIndividual individual1, String propertyName, OWLNamedIndividual individual2) {
343
        OWLObjectProperty objectProperty = dataFactory.getOWLObjectProperty(createIri(propertyName));
344
        OWLObjectPropertyAssertionAxiom objectPropertyAssertionAxiom = dataFactory.getOWLObjectPropertyAssertionAxiom(objectProperty, individual1, individual2);
345
        manager.applyChange(new AddAxiom(ontology, objectPropertyAssertionAxiom));
346
    }
347
348
    @Pre(expr = "_this.ontology!=null && _this.manager!=null && _this.dataFactory!=null", lang = "js")
349
    public void addDataProperty(OWLNamedIndividual individual, String propertyName, OWLLiteral owlLiteral) {
350
        OWLDataProperty dataProperty = dataFactory.getOWLDataProperty(createIri(propertyName));
351
        OWLDataPropertyAssertionAxiom dataPropertyAssertion = dataFactory
352
                .getOWLDataPropertyAssertionAxiom(dataProperty, individual, owlLiteral);
353
        manager.applyChange(new AddAxiom(ontology, dataPropertyAssertion));
354
    }
355
356
    @Pre(expr = "_this.dataFactory!=null", lang = "js")
357
    public OWLLiteral getOWLLiteral(String data, OWLDatatype datatype) {
358
        return dataFactory.getOWLLiteral(data, datatype);
359
    }
360
361
    @Pre(expr = "_this.ontology!=null && _this.manager!=null", lang = "js")
362
    public void save(String filename) throws OWLOntologyStorageException {
363
        manager.saveOntology(ontology, new RDFXMLOntologyFormat(), new FileDocumentTarget(new File(filename)));
364
    }
365
366
    @Pre(expr = "_this.ontology!=null &&  _this.manager!=null && _this.dataFactory!=null", lang = "js")
367
    public void addOntology(String owlUri) {
368
        OWLImportsDeclaration importsDeclaration = dataFactory.getOWLImportsDeclaration(IRI.create(owlUri));
369
        manager.applyChange(new AddImport(ontology, importsDeclaration));
370
    }
371
372
    @Pre(expr = "_this.ontology!=null &&  _this.manager!=null", lang = "js")
373
    public Model getJenaModel(final boolean ignoreMissingImports) throws OWLOntologyStorageException, OWLOntologyCreationException, IOException {
374
        // TODO: cache and make more performant
375
        OntModel ontologyModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM);
376
        ontologyModel.getDocumentManager().setProcessImports(true);
377
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
378
        manager.saveOntology(ontology, new RDFXMLOntologyFormat(), outputStream);
379
        ontologyModel.setDynamicImports(true);
380
        ontologyModel.getDocumentManager().setReadFailureHandler(new OntDocumentManager.ReadFailureHandler() {
381
            @Override
382
            public void handleFailedRead(String url, Model model, Exception e) {
383
                LOG.info("Loading imported model: " + url);
384
                OwlApiFacade api = new OwlApiFacade(OWLManager.createOWLOntologyManager(), OWLManager.getOWLDataFactory());
385
                try {
386
                    api.load(url);
387
                    model.add(api.getJenaModel(false));
388
                } catch (OWLOntologyCreationException | IOException | OWLOntologyStorageException f) {
389
                    if (ignoreMissingImports) return;
390
                    throw new ModelConversionException(f);
391
                }
392
            }
393
        });
394
        ontologyModel.read(new ByteArrayInputStream(outputStream.toByteArray()), ontology.getOntologyID().getOntologyIRI().toString(), "RDF/XML");
395
        ontologyModel.loadImports();
396
        return ontologyModel;
397
    }
398
399
    @Pre(expr = "_this.ontology!=null && owlNamedIndividual!=null", lang = "js")
400
    public List<IRI> getClassesOfIndividual(OWLNamedIndividual owlNamedIndividual) {
401
        Set<OWLClassExpression> types = owlNamedIndividual.getTypes(ontology.getOWLOntologyManager().getOntologies());
402
        return filter(notNullValue(), convert(types, new Converter<OWLClassExpression, IRI>() {
403
            @Override
404
            public IRI convert(OWLClassExpression owlClassExpression) {
405
                if (!(owlClassExpression instanceof OWLClassImpl)) return null;
406
                return ((OWLClassImpl) owlClassExpression).getIRI();
407
            }
408
        }));
409
    }
410
411
    @Pre(expr = "_this.ontology!=null && individual!=null && classIRI!=null", lang = "js")
412
    public boolean isIndividualOfClass(OWLNamedIndividual individual, IRI classIRI) {
413
        return getClassesOfIndividual(individual).contains(classIRI);
414
    }
415
416
}