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    }