001 // This file is part of AceWiki.
002 // Copyright 2008-2012, AceWiki developers.
003 //
004 // AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU
005 // Lesser General Public License as published by the Free Software Foundation, either version 3 of
006 // the License, or (at your option) any later version.
007 //
008 // AceWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009 // even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
010 // Lesser General Public License for more details.
011 //
012 // You should have received a copy of the GNU Lesser General Public License along with AceWiki. If
013 // not, see http://www.gnu.org/licenses/.
014
015 package ch.uzh.ifi.attempto.acewiki.owl;
016
017 import java.net.MalformedURLException;
018 import java.net.URL;
019 import java.util.ArrayList;
020 import java.util.Collections;
021 import java.util.HashMap;
022 import java.util.HashSet;
023 import java.util.LinkedHashMap;
024 import java.util.List;
025 import java.util.Map;
026 import java.util.Set;
027
028 import org.semanticweb.HermiT.Reasoner;
029 import org.semanticweb.owlapi.apibinding.OWLManager;
030 import org.semanticweb.owlapi.model.IRI;
031 import org.semanticweb.owlapi.model.OWLAxiom;
032 import org.semanticweb.owlapi.model.OWLClass;
033 import org.semanticweb.owlapi.model.OWLClassExpression;
034 import org.semanticweb.owlapi.model.OWLDataFactory;
035 import org.semanticweb.owlapi.model.OWLDeclarationAxiom;
036 import org.semanticweb.owlapi.model.OWLDifferentIndividualsAxiom;
037 import org.semanticweb.owlapi.model.OWLLogicalEntity;
038 import org.semanticweb.owlapi.model.OWLNamedIndividual;
039 import org.semanticweb.owlapi.model.OWLOntology;
040 import org.semanticweb.owlapi.model.OWLOntologyCreationException;
041 import org.semanticweb.owlapi.model.OWLOntologyManager;
042 import org.semanticweb.owlapi.owllink.OWLlinkHTTPXMLReasonerFactory;
043 import org.semanticweb.owlapi.owllink.OWLlinkReasonerConfiguration;
044 import org.semanticweb.owlapi.owllink.builtin.response.OWLlinkErrorResponseException;
045 import org.semanticweb.owlapi.profiles.OWL2ELProfile;
046 import org.semanticweb.owlapi.profiles.OWL2QLProfile;
047 import org.semanticweb.owlapi.profiles.OWL2RLProfile;
048 import org.semanticweb.owlapi.profiles.OWLProfile;
049 import org.semanticweb.owlapi.reasoner.OWLReasoner;
050 import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
051 import org.semanticweb.owlapi.util.Version;
052
053 import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;
054 import ch.uzh.ifi.attempto.acewiki.core.AceWikiReasoner;
055 import ch.uzh.ifi.attempto.acewiki.core.AnswerElement;
056 import ch.uzh.ifi.attempto.acewiki.core.Concept;
057 import ch.uzh.ifi.attempto.acewiki.core.InconsistencyException;
058 import ch.uzh.ifi.attempto.acewiki.core.Individual;
059 import ch.uzh.ifi.attempto.acewiki.core.LanguageUtils;
060 import ch.uzh.ifi.attempto.acewiki.core.Ontology;
061 import ch.uzh.ifi.attempto.acewiki.core.OntologyElement;
062 import ch.uzh.ifi.attempto.acewiki.core.Question;
063 import ch.uzh.ifi.attempto.acewiki.core.Sentence;
064
065 /**
066 * This is a reasoner implementation that connects to an OWL reasoner. At the moment, it can
067 * directly connect to HermiT and Pellet. Additionally, reasoners like FaCT++ can be accessed via
068 * the OWLlink interface.
069 *
070 * @author Tobias Kuhn
071 */
072 public class AceWikiOWLReasoner implements AceWikiReasoner {
073
074 private static OWLDataFactory dataFactory = new OWLDataFactoryImpl();
075 private static OWLlinkHTTPXMLReasonerFactory owllinkReasonerFactory;
076
077 private static Object owllinkReasonerSyncToken = new Object();
078
079 private Ontology ontology;
080
081 private OWLOntologyManager manager;
082 private OWLOntology owlOntology;
083 private Map<OWLAxiom, Integer> axiomsMap = new HashMap<OWLAxiom, Integer>();
084 private OWLReasoner owlReasoner;
085 private String reasonerType = "none";
086 private Object reasonerSyncToken = new Object();
087 private OWLDifferentIndividualsAxiom diffIndsAxiom;
088 private boolean diffIndsAxiomOutdated = true;
089 private OWLProfile owlProfile;
090 private String globalRestrPolicy;
091 private Map<String, String> infoMap = new LinkedHashMap<String, String>();
092
093 /**
094 * Creates a new reasoner object.
095 */
096 public AceWikiOWLReasoner() {
097 manager = OWLManager.createOWLOntologyManager();
098 try {
099 owlOntology = manager.createOntology();
100 } catch (OWLOntologyCreationException ex) {
101 ex.printStackTrace();
102 }
103 }
104
105 public void init(Ontology ontology) {
106 this.ontology = ontology;
107
108 String p = (getParameter("owl_profile") + "").toLowerCase();
109 if (p.equals("owl2el")) {
110 owlProfile = new OWL2ELProfile();
111 } else if (p.equals("owl2ql")) {
112 owlProfile = new OWL2QLProfile();
113 } else if (p.equals("owl2rl")) {
114 owlProfile = new OWL2RLProfile();
115 } else {
116 owlProfile = null;
117 }
118
119 String grp = (getParameter("global_restrictions_policy") + "").toLowerCase();
120 if (grp.equals("unchecked")) {
121 globalRestrPolicy = "unchecked";
122 } else {
123 globalRestrPolicy = "no_chains";
124 }
125
126 infoMap.put("global restrictions policy", globalRestrPolicy);
127 infoMap.put("OWL profile", getOWLProfileName());
128 }
129
130 private List<OntologyElement> getOntologyElements() {
131 return ontology.getOntologyElements();
132 }
133
134 private OntologyElement getOntologyElement(String name) {
135 return ontology.getElement(name);
136 }
137
138 private String getParameter(String name) {
139 return ontology.getParameter(name);
140 }
141
142 public Map<String, String> getInfo() {
143 return infoMap;
144 }
145
146 /**
147 * Returns a string representing the policy how to enforce the global restrictions on the
148 * axioms in OWL 2.
149 *
150 * @return The global restrictions policy.
151 */
152 public String getGlobalRestrictionsPolicy() {
153 return globalRestrPolicy;
154 }
155
156 private IRI getIRI() {
157 return IRI.create(ontology.getURI());
158 }
159
160 /**
161 * Returns the OWL profile that defines which statements are used for reasoning, or null if the
162 * full language of OWL is used.
163 *
164 * @return The used OWL profile.
165 */
166 public OWLProfile getOWLProfile() {
167 return owlProfile;
168 }
169
170 /**
171 * Returns the name of the current OWL profile.
172 *
173 * @return The OWL profile name.
174 */
175 public String getOWLProfileName() {
176 if (owlProfile == null) return "OWL 2 Full";
177 return owlProfile.getName();
178 }
179
180 /**
181 * Returns a new OWL ontology object representing the full ontology or the consistent part of
182 * it.
183 *
184 * @param consistent true if only the consistent part should be exported.
185 * @return An OWL ontology object of the full ontology.
186 */
187 public OWLOntology exportOWLOntology(boolean consistent) {
188 Set<OWLAxiom> axioms = new HashSet<OWLAxiom>();
189 for (OntologyElement el : getOntologyElements()) {
190 OWLDeclarationAxiom owlDecl = null;
191 if (el instanceof OWLOntoElement) {
192 owlDecl = ((OWLOntoElement) el).getOWLDeclaration();
193 }
194 if (owlDecl != null) {
195 axioms.add(owlDecl);
196 }
197 for (Sentence s : el.getArticle().getSentences()) {
198 OWLSentence os = (OWLSentence) s;
199 if (os instanceof Question || !os.isOWL()) continue;
200 if (consistent && (!os.isReasonable() || !os.isIntegrated())) continue;
201 axioms.addAll(os.getOWLAxioms());
202 }
203 }
204 axioms.add(diffIndsAxiom);
205
206 OWLOntology o = null;
207 try {
208 o = manager.createOntology(axioms, getIRI());
209 manager.removeOntology(o);
210 } catch (Exception ex) {
211 ex.printStackTrace();
212 }
213
214 return o;
215 }
216
217 private OntologyElement get(OWLLogicalEntity owlEntity) {
218 if (owlEntity == null) return null;
219 if (owlEntity.isTopEntity() || owlEntity.isBottomEntity()) return null;
220 String iri = owlEntity.getIRI().toString();
221 if (!iri.startsWith(ontology.getURI())) return null;
222 String name = iri.substring(iri.indexOf("#") + 1);
223 return getOntologyElement(name);
224 }
225
226 /**
227 * Returns the OWL ontology manager.
228 *
229 * @return The OWL ontology manager.
230 */
231 public OWLOntologyManager getOWLOntologyManager() {
232 return manager;
233 }
234
235 public String getReasonerName() {
236 if (owlReasoner == null) return null;
237 return owlReasoner.getReasonerName();
238 }
239
240 public String getReasonerVersion() {
241 if (owlReasoner == null) return null;
242 Version v = owlReasoner.getReasonerVersion();
243 if (v == null) return null;
244 return v.getMajor() + "." + v.getMinor() + "." + v.getPatch() + "." + v.getBuild();
245 }
246
247 public String getReasonerType() {
248 return reasonerType;
249 }
250
251 public void load() {
252 log("loading reasoner");
253 String type = getParameter("reasoner");
254 if (type == null) type = "";
255 type = type.toLowerCase();
256 reasonerType = type;
257
258 String s = getParameter("reasoner_url");
259 if (s == null || s.length() == 0) s = "http://localhost:8080";
260 URL url = null;
261 try {
262 url = new URL(s);
263 } catch (MalformedURLException ex) { ex.printStackTrace(); }
264
265 if (owlReasoner != null) owlReasoner.dispose();
266
267 if (type.equals("none")) {
268 log("no reasoner");
269 reasonerType = "none";
270 owlReasoner = null;
271 } else if (type.equals("hermit")) {
272 log("loading HermiT");
273 reasonerType = "HermiT";
274 owlReasoner = new Reasoner(owlOntology);
275 } else if (type.equals("pellet")) {
276 log("loading Pellet");
277 reasonerType = "Pellet";
278 // The Pellet libraries are not part of the AceWiki package (because of license
279 // reasons). For that reason, the pellet reasoner has to be loaded dynamically.
280 OWLReasonerFactory reasonerFactory = null;
281 try {
282 ClassLoader classLoader = Ontology.class.getClassLoader();
283 String className = "com.clarkparsia.pellet.owlapiv3.PelletReasonerFactory";
284 reasonerFactory = (OWLReasonerFactory) classLoader.loadClass(className).newInstance();
285 } catch (Exception ex) {
286 ex.printStackTrace();
287 }
288 owlReasoner = reasonerFactory.createNonBufferingReasoner(owlOntology);
289 } else if (type.equals("owllink")) {
290 log("loading OWLlink");
291 reasonerType = "OWLlink";
292 if (owllinkReasonerFactory == null) {
293 owllinkReasonerFactory = new OWLlinkHTTPXMLReasonerFactory();
294 }
295 OWLlinkReasonerConfiguration config = new OWLlinkReasonerConfiguration(url);
296 owlReasoner = owllinkReasonerFactory.createReasoner(owlOntology, config);
297 // reasoner calls over OWLlink have to be synchronized:
298 reasonerSyncToken = owllinkReasonerSyncToken;
299 //} else if (type.equals("dig")) {
300 //try {
301 // reasoner = new DIGReasoner(OWLManager.createOWLOntologyManager());
302 // ((DIGReasoner) reasoner).getReasoner().setReasonerURL(url);
303 //} catch (Exception ex) { ex.printStackTrace(); }
304 } else if (type.equals("")) {
305 log("no reasoner type specified: loading HermiT as default");
306 reasonerType = "HermiT";
307 owlReasoner = new Reasoner(owlOntology);
308 } else {
309 log("ERROR: Unknown reasoner type: " + type);
310 reasonerType = "none";
311 owlReasoner = null;
312 }
313 updateDifferentIndividualsAxiom();
314 flush();
315
316 log("reasoner loaded");
317 }
318
319 /**
320 * Updates the axiom that states that all named individuals are different. Thus, unique
321 * name assumption is applied.
322 */
323 private synchronized void updateDifferentIndividualsAxiom() {
324 if (!diffIndsAxiomOutdated) return;
325
326 if (diffIndsAxiom != null) {
327 unloadAxiom(diffIndsAxiom);
328 }
329
330 Set<OWLNamedIndividual> inds = new HashSet<OWLNamedIndividual>();
331 for (OntologyElement oe : getOntologyElements()) {
332 if (oe instanceof OWLIndividual) {
333 inds.add(((OWLIndividual) oe).getOWLRepresentation());
334 }
335 }
336 diffIndsAxiom = dataFactory.getOWLDifferentIndividualsAxiom(inds);
337
338 loadAxiom(diffIndsAxiom);
339
340 diffIndsAxiomOutdated = false;
341 }
342
343 public void flushElements() {
344 flush();
345 }
346
347 private void flush() {
348 if (owlReasoner != null) {
349 synchronized (reasonerSyncToken) {
350 owlReasoner.flush();
351 }
352 }
353 }
354
355 public void loadElement(OntologyElement element) {
356 OWLDeclarationAxiom owlDecl = null;
357 if (element instanceof OWLOntoElement) {
358 owlDecl = ((OWLOntoElement) element).getOWLDeclaration();
359 }
360 if (owlDecl != null) {
361 manager.addAxiom(owlOntology, owlDecl);
362 flush();
363 }
364 if (element instanceof OWLIndividual) {
365 diffIndsAxiomOutdated = true;
366 }
367 }
368
369 public void unloadElement(OntologyElement element) {
370 OWLDeclarationAxiom owlDecl = null;
371 if (element instanceof OWLOntoElement) {
372 owlDecl = ((OWLOntoElement) element).getOWLDeclaration();
373 }
374 if (owlDecl != null) {
375 manager.removeAxiom(owlOntology, owlDecl);
376 flush();
377 }
378 if (element instanceof OWLIndividual) {
379 diffIndsAxiomOutdated = true;
380 }
381 }
382
383 public synchronized List<Concept> getConcepts(Individual ind) {
384 List<Concept> concepts = new ArrayList<Concept>();
385 OWLIndividual owlInd = (OWLIndividual) ind;
386 for (OWLClass oc : getConcepts(owlInd.getOWLRepresentation())) {
387 if (oc.isOWLThing() || oc.isOWLNothing()) continue;
388 String conceptURI = oc.getIRI().toString();
389 if (conceptURI.startsWith("http://attempto.ifi.uzh.ch/ace#")) continue;
390 String conceptName = conceptURI.substring(conceptURI.indexOf("#") + 1);
391 concepts.add((Concept) getOntologyElement(conceptName));
392 }
393 return concepts;
394 }
395
396 private synchronized Set<OWLClass> getConcepts(OWLNamedIndividual owlInd) {
397 if (owlReasoner == null) {
398 return Collections.emptySet();
399 } else {
400 synchronized (reasonerSyncToken) {
401 return owlReasoner.getTypes(owlInd, false).getFlattened();
402 }
403 }
404 }
405
406 public synchronized List<Individual> getIndividuals(Concept concept) {
407 OWLConcept ac = (OWLConcept) concept;
408 List<Individual> inds = new ArrayList<Individual>();
409 for (OWLNamedIndividual oi : getIndividuals(ac.getOWLRepresentation())) {
410 OntologyElement oe = get(oi);
411 if (oe instanceof Individual) {
412 inds.add((Individual) oe);
413 }
414 }
415 return inds;
416 }
417
418 private synchronized Set<OWLNamedIndividual> getIndividuals(OWLClassExpression owlClass) {
419 if (owlReasoner == null) {
420 return Collections.emptySet();
421 } else {
422 synchronized (reasonerSyncToken) {
423 return owlReasoner.getInstances(owlClass, false).getFlattened();
424 }
425 }
426 }
427
428 public synchronized List<Concept> getSuperConcepts(Concept concept) {
429 OWLConcept ac = (OWLConcept) concept;
430 List<Concept> concepts = new ArrayList<Concept>();
431 for (OWLClass oc : getSuperConcepts(ac.getOWLRepresentation())) {
432 OntologyElement oe = get(oc);
433 if (oe instanceof Concept) {
434 concepts.add((Concept) oe);
435 }
436 }
437 return concepts;
438 }
439
440 private synchronized Set<OWLClass> getSuperConcepts(OWLClass owlClass) {
441 if (owlReasoner == null) {
442 return Collections.emptySet();
443 } else {
444 synchronized (reasonerSyncToken) {
445 return owlReasoner.getSuperClasses(owlClass, false).getFlattened();
446 }
447 }
448 }
449
450 public synchronized List<Concept> getSubConcepts(Concept concept) {
451 OWLConcept ac = (OWLConcept) concept;
452 List<Concept> concepts = new ArrayList<Concept>();
453 for (OWLClass oc : getSubConcepts(ac.getOWLRepresentation())) {
454 OntologyElement oe = get(oc);
455 if (oe instanceof Concept) {
456 concepts.add((Concept) oe);
457 }
458 }
459 return concepts;
460 }
461
462 private synchronized Set<OWLClass> getSubConcepts(OWLClass owlClass) {
463 if (owlReasoner == null) {
464 return Collections.emptySet();
465 } else {
466 synchronized (reasonerSyncToken) {
467 return owlReasoner.getSubClasses(owlClass, false).getFlattened();
468 }
469 }
470 }
471
472 public synchronized List<AnswerElement> getAnswer(Question q) {
473 if (owlReasoner == null) return null;
474
475 OWLQuestion question = (OWLQuestion) q;
476
477 OWLNamedIndividual quInd = question.getQuestionOWLIndividual();
478 OWLClassExpression quClass = question.getQuestionOWLClass();
479 List<AnswerElement> list = new ArrayList<AnswerElement>();
480
481 if (quInd != null) {
482 for (OWLClass oc : getConcepts(quInd)) {
483 OntologyElement oe = get(oc);
484 if (oe instanceof OWLConcept) {
485 list.add((OWLConcept) oe);
486 }
487 }
488 } else if (quClass != null) {
489 Set<OWLNamedIndividual> owlInds = getIndividuals(quClass);
490 for (OWLNamedIndividual oi : owlInds) {
491 OntologyElement oe = get(oi);
492 if (oe instanceof OWLIndividual) {
493 list.add((OWLIndividual) oe);
494 }
495 }
496 }
497
498 LanguageUtils.sortOntologyElements(list);
499 return list;
500 }
501
502 public synchronized boolean isConsistent() {
503 if (owlReasoner == null) return true;
504 boolean c = true;
505 try {
506 synchronized (reasonerSyncToken) {
507 // The method isConsistent is poorly supported by the implementations.
508 //c = reasoner.isConsistent();
509 c = owlReasoner.isSatisfiable(dataFactory.getOWLThing());
510 }
511 } catch (Exception ex) {
512 c = false;
513 }
514 return c;
515 }
516
517 public synchronized boolean isSatisfiable(Concept concept) {
518 if (owlReasoner == null) return true;
519 if (!(concept instanceof OWLConcept)) return false;
520 if (owlOntology.containsClassInSignature(((OWLConcept) concept).getIRI())) {
521 synchronized (reasonerSyncToken) {
522 OWLConcept ac = (OWLConcept) concept;
523 return owlReasoner.isSatisfiable(ac.getOWLRepresentation());
524 }
525 } else {
526 return true;
527 }
528 }
529
530 public void loadSentence(Sentence s) {
531 OWLSentence sentence = (OWLSentence) s;
532 try {
533 for (OWLAxiom ax : sentence.getOWLAxioms()) {
534 loadAxiom(ax);
535 }
536 flush();
537 } catch (OWLlinkErrorResponseException ex) {
538 // FaCT++ throws an exception here when inconsistency is encountered
539 // TODO Is this always the case?
540 if ("FaCT++.Kernel: inconsistent ontology".equals(ex.getMessage())) {
541 throw new InconsistencyException();
542 } else {
543 // We get here when the global restrictions are violated with FaCT++ and OWLlink
544 throw ex;
545 }
546 } catch (IllegalArgumentException ex) {
547 // We get here when the global restrictions are violated with HermiT
548 throw ex;
549 }
550 }
551
552 public void unloadSentence(Sentence s) {
553 OWLSentence sentence = (OWLSentence) s;
554 for (OWLAxiom ax : sentence.getOWLAxioms()) {
555 unloadAxiom(ax);
556 }
557 flush();
558 }
559
560 private void loadAxiom(OWLAxiom ax) {
561 Integer count = axiomsMap.get(ax);
562 if (count == null) count = 0;
563 if (count == 0) {
564 manager.addAxiom(owlOntology, ax);
565 }
566 axiomsMap.put(ax, count+1);
567 }
568
569 private void unloadAxiom(OWLAxiom ax) {
570 Integer count = axiomsMap.get(ax);
571 if (count == 1) {
572 manager.removeAxiom(owlOntology, ax);
573 }
574 axiomsMap.put(ax, count-1);
575 }
576
577 private void log(String text) {
578 ontology.log(text);
579 }
580
581 }