// This file is part of AceWiki. // Copyright 2008-2013, AceWiki developers. // // AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU // Lesser General Public License as published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // // AceWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without // even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with AceWiki. If // not, see http://www.gnu.org/licenses/. package ch.uzh.ifi.attempto.acewiki.core; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import ch.uzh.ifi.attempto.base.LoggerContext; /** * This class represents an AceWiki ontology which consists of ontology element definitions and * of ontological statements. Each ontology element has its own article that consists of * ontological statements. * * @author Tobias Kuhn */ public class Ontology { private AceWikiEngine engine; private CachingReasoner reasoner; private StatementFactory statementFactory; private AceWikiStorage storage; private LoggerContext loggerContext; private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass()); private Map<Long, OntologyElement> idIndex = new TreeMap<Long, OntologyElement>(); private final String name; private final String baseURI; private long idCount = 0; private long stateID = 0; private Map<String, String> parameters; /** * Creates a new empty ontology with the given name and parameters. * * @param name The name of the ontology. * @param parameters The parameters. */ Ontology(String name, Map<String, String> parameters, AceWikiStorage storage) { this.name = name.toString(); // null value throws an exception this.parameters = parameters; this.storage = storage; loggerContext = new LoggerContext(name, "onto", "0"); engine = AbstractAceWikiEngine.createLanguageEngine(this); if (engine.getReasoner() == null) { reasoner = new CachingReasoner(new DummyReasoner()); } else { reasoner = new CachingReasoner(engine.getReasoner()); } reasoner.init(this); statementFactory = new StatementFactory(this); String b = getParameter("baseuri"); if (b == null || b.equals("")) { baseURI = "http://attempto.ifi.uzh.ch/acewiki/default/"; } else { if (b.endsWith("/")) { baseURI = b; } else { baseURI = b + "/"; } } } /** * Returns the AceWiki engine. * * @return The AceWiki engine. */ public AceWikiEngine getEngine() { return engine; } /** * Returns the reasoner object in the form of a caching reasoner. * * @return The reasoner. */ public CachingReasoner getReasoner() { return reasoner; } /** * Returns the statement factory. * * @return The statement factory. */ public StatementFactory getStatementFactory() { return statementFactory; } /** * Returns the storage object. * * @return The storage object. */ public AceWikiStorage getStorage() { return storage; } /** * Registers the given ontology element. * * @param element The ontology element to register. */ public synchronized void register(OntologyElement element) { if (contains(element)) { log("error: element already registered"); throw new RuntimeException("Registration failed: Element is already registered."); } element.initOntology(this); log("register: " + element); stateID++; if (element.getId() == -1) { element.initId(nextId()); } idIndex.put(element.getId(), element); if (element.getId() > idCount) idCount = element.getId(); engine.getWordIndex().elementAdded(element); getReasoner().loadElement(element); getReasoner().flushElements(); getStorage().save(element); } /** * Removes the given ontology element from the ontology. * * @param element The ontology element to be removed. */ public synchronized void remove(OntologyElement element) { if (!contains(element)) { log("error: unknown element"); return; } log("remove: " + element.getWord()); stateID++; engine.getWordIndex().elementRemoved(element); idIndex.remove(element.getId()); for (Sentence s : element.getArticle().getSentences()) { retractSentence(s); } storage.save(element); getReasoner().unloadElement(element); getReasoner().flushElements(); } /** * Changes the word forms of the given ontology element. * * @param element The ontology element to be changed. * @param serializedWords The serialized word forms. */ public synchronized void change(OntologyElement element, String serializedWords) { if (contains(element)) { engine.getWordIndex().elementBeforeChange(element); getReasoner().unloadElement(element); element.setWords(serializedWords); engine.getWordIndex().elementAfterChange(element); getReasoner().loadElement(element); refresh(element); } else { element.setWords(serializedWords); } } /** * Returns all the sentences that use the given ontology element. * * @param element The ontology element. * @return A list of all sentence that contain the ontology element. */ public synchronized List<Sentence> getReferences(OntologyElement element) { List<Sentence> list = new ArrayList<Sentence>(); for (OntologyElement el : idIndex.values()) { for (Sentence s : el.getArticle().getSentences()) { if (s.contains(element)) { list.add(s); } } } return list; } /** * Returns the ontology element with the given name, or null if there is no such element. * * @param name The name of the ontology element. * @return The ontology element. */ public synchronized OntologyElement getElement(String name) { return engine.getWordIndex().getElement(name); } /** * Returns the ontology element with the given id, or null if there is no such element. * * @param id The id of the ontology element. * @return The ontology element. */ public synchronized OntologyElement get(long id) { return idIndex.get(id); } /** * Returns all ontology elements. The list is a copy of the internal list. * * @return A list of all ontology elements. */ public synchronized List<OntologyElement> getOntologyElements() { return new ArrayList<OntologyElement>(idIndex.values()); } /** * Returns all instances of class <code>type</code> in the current list of ontology elements. * The returned iterable has elements whose class is <code>type</code> or a subclass of <code>type</code>. * The returned iterable's iterator does not support <code>remove()</code>. * * @param type the type of ontology elements desired * @return an unmodifiable iterable containing all the ontology elements that are of the requested type */ public synchronized <T> Iterable<T> getOntologyElements(Class<T> type) { return Iterables.filter(idIndex.values(), type); } /** * Returns true if the given ontology element is contained by the ontology (identity check). * * @param ontologyElement The ontology element. * @return true if the ontology element is contained by the ontology. */ public synchronized boolean contains(OntologyElement ontologyElement) { return idIndex.containsValue(ontologyElement); } /** * Returns the name of the ontology. * * @return The name of the ontology. */ public String getName() { return name; } /** * Returns the URI of the ontology (baseURI + name). * * @return The URI of the ontology. */ public String getURI() { return baseURI + name; } /** * Refreshes the given ontology element. All sentences that use the ontology element are * updated. * * @param element The ontology element to be refreshed. */ synchronized void refresh(OntologyElement element) { for (Sentence s : getReferences(element)) { if (s.isIntegrated()) { retractSentence(s); s.update(); commitSentence(s); } else { s.update(); } } storage.save(element); } /** * Commits the sentence. This means that it is added to the reasoner. * * @param sentence The sentence to be commited. * @throws InconsistencyException if the sentence is inconsistent with the existing sentences. */ protected synchronized void commitSentence(Sentence sentence) throws InconsistencyException { if (sentence == null || sentence.isIntegrated()) return; if (!sentence.isReasonable()) { sentence.setIntegrated(true); return; } log("commit sentence"); try { getReasoner().loadSentence(sentence); } catch (InconsistencyException ex) { log("not consistent!"); getReasoner().unloadSentence(sentence); throw ex; } catch (Throwable t) { log("error encountered!"); t.printStackTrace(); System.gc(); getReasoner().unloadSentence(sentence); return; } if (!getReasoner().isConsistent()) { log("not consistent!"); getReasoner().unloadSentence(sentence); throw new InconsistencyException(); } log("consistent!"); sentence.setIntegrated(true); stateID++; } /** * This method tries to reassert a sentence that is not yet integrated. * * @param sentence The sentence to be reasserted. * @throws InconsistencyException if the sentence is inconsistent with the existing sentences. */ public synchronized void reassert(Sentence sentence) throws InconsistencyException { commitSentence(sentence); getStorage().save(sentence.getArticle().getOntologyElement()); } /** * Retracts the sentence. This means that the sentence is removed from the reasoner. * * @param sentence The sentence to be retracted. */ protected synchronized void retractSentence(Sentence sentence) { if ( sentence == null || !sentence.isIntegrated() || !sentence.isReasonable() ) return; log("retract sentence"); stateID++; getReasoner().unloadSentence(sentence); sentence.setIntegrated(false); } /** * This method retracts an integrated sentence so that it is still part of the wiki * article but does not participate in reasoning anymore. * * @param sentence The sentence to be retracted. */ public synchronized void retract(Sentence sentence) { retractSentence(sentence); getStorage().save(sentence.getArticle().getOntologyElement()); } /** * Writes a log entry. * * @param text Log text. */ public void log(String text) { loggerContext.propagateWithinThread(); org.slf4j.MDC.put("type", "onto"); log.info(text); } private long nextId() { return ++idCount; } /** * Returns the state id of the ontology. This id increases each time the ontology changes (more * precisely: each time the part of the ontology that participates in reasoning changes). This * id is used to find out whether cached information is still valid or has to be recalculated. * * @return The state id of the ontology. */ public long getStateID() { return stateID; } /** * Returns a parameter value. * * @param name The parameter name. * @return The parameter value. */ public String getParameter(String name) { return parameters.get(name); } /** * Returns that parameter value as an integer, if it is * parseable as an integer, otherwise returns 0. */ public int getParameterAsInt(String name) { String valueAsStr = getParameter(name); if (valueAsStr != null) { try { return Integer.parseInt(valueAsStr); } catch (NumberFormatException e) {}; } return 0; } /** * Returns the web.xml parameter interpreted as a set of {@code String}s. * Elements must be comma-separated and whitespace is ignored. * * @param paramName The parameter name. * @return The value of the parameter. */ public Set<String> getParameterAsSetOfString(String paramName) { String valueAsStr = getParameter(paramName); if (valueAsStr == null) { return ImmutableSet.of(); } return ImmutableSet.copyOf(Splitter.on(',').omitEmptyStrings().trimResults().split(valueAsStr)); } /** * Returns the language that should be used to express the wiki sentences in the log. * In multilingual wikis this language should express the edited sentences * as clearly and readably as possible, i.e. it should be based on English * and avoid ambiguity. In ACE-based wikis, ACE is a good candidate. */ public String getLoggingLanguage() { String loggingLanguage = getParameter("logging_language"); if (loggingLanguage == null) { return getEngine().getLanguages()[0]; } return loggingLanguage; } }