// 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.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * This class implements persistent storage features for AceWiki data on the basis of a simple file * and folder based system. * * @author Tobias Kuhn */ public class FileBasedStorage implements AceWikiStorage { private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass()); private final HashMap<String, Ontology> ontologies = new HashMap<String, Ontology>(); private final Map<String, UserBase> userBases = new HashMap<String, UserBase>(); private String dir; private final List<Ontology> incompleteOntologies = new ArrayList<Ontology>(); /** * Creates a new storage object. * * @param dir The path at which ontologies should be stored. */ public FileBasedStorage(String dir) { this.dir = dir.replaceFirst("/*$", ""); File d = new File(dir); if (!d.exists()) d.mkdir(); } /** * Returns the ontology with the given name (or creates an empty ontology if the ontology * cannot be found). A parameter map is used for ontology parameters. When the ontology with * the respective name has already been loaded, this ontology is returned and the parameters * are ignored. The following parameters are supported: * "baseuri": The base URI that is used to identify the ontology elements. The complete URI of * the ontology is baseURI + name. The default is an empty string. * "global_restrictions_policy": A string representing the policy how to enforce the global * restrictions on axioms in OWL 2. At the moment, the options "no_chains" and "unchecked" * are available. * "reasoner": Defines the reasoner or reasoner interface to be used. Currently supported are * the HermiT reasoner ("HermiT", default), the Pellet reasoner ("Pellet"), the OWLlink * interface ("OWLlink"), or none ("none"). * "owl_profile": Sets an OWL profile that defines which statements are used for reasoning. * Possible values are "OWL2Full" (default), "OWL2EL", "OWL2QL", and "OWL2RL". Note that * the global restrictions of the EL profile are not checked. * * @param name The name of the ontology. * @param parameters The parameters. * @return The loaded ontology. */ public Ontology getOntology(String name, Map<String, String> parameters) { if (ontologies.get(name) != null) { return ontologies.get(name); } return loadOntology(name, parameters); } private synchronized Ontology loadOntology(String name, Map<String, String> parameters) { if (ontologies.get(name) != null) { return ontologies.get(name); } Ontology ontology = new Ontology(name, parameters, this); incompleteOntologies.add(ontology); ontologies.put(name, ontology); ontology.log("loading ontology"); log.info("Loading: '{}'", name); File dataDir = new File(dir + "/" + name); File dataFile = new File(dir + "/" + name + ".acewikidata"); if (dataDir.exists()) { for (File file : dataDir.listFiles()) { try { long id = new Long(file.getName()); FileInputStream in = new FileInputStream(file); byte[] bytes = new byte[in.available()]; in.read(bytes); in.close(); String s = new String(bytes, "UTF-8"); loadOntologyElement(s, id, ontology); } catch (NumberFormatException ex) { ontology.log("ignoring file: " + file.getName()); } catch (IOException ex) { ontology.log("cannot read file: " + file.getName()); } } } else if (dataFile.exists()) { try { BufferedReader in = new BufferedReader(new FileReader(dataFile)); String s = ""; String line = in.readLine(); long id = -1; while (line != null) { if (line.matches("\\s*")) { // empty line if (s.length() > 0) { loadOntologyElement(s, id, ontology); s = ""; id = -1; } } else if (line.matches("[0-9]+") && s.length() == 0) { // line with id id = new Long(line); } else if (line.startsWith("%")) { // comment } else { s += line + "\n"; } line = in.readLine(); } in.close(); } catch (IOException ex) { ontology.log("cannot read file: " + dataFile.getName()); } } else { ontology.log("no data found; blank ontology is created"); } incompleteOntologies.remove(ontology); ontology.log("loading statements"); List<OntologyElement> elements = ontology.getOntologyElements(); for (OntologyElement oe : elements) { ontology.getReasoner().loadElement(oe); for (Sentence s : oe.getArticle().getSentences()) { if (s.isReasonable() && s.isIntegrated()) { ontology.getReasoner().loadSentence(s); } } save(oe); } if (ontology.get(0) == null) { OntologyElement mainPage = GeneralTopic.makeMain("acewiki_page_main"); mainPage.initId(0); ontology.register(mainPage); } ontology.getReasoner().load(); return ontology; } /** * Loads an ontology element from its serialized form. * * @param serializedElement The serialized ontology element. * @param id The id of the ontology element. * @param ontology The ontology at which the ontology element should be registered. */ private static void loadOntologyElement(String serializedElement, long id, Ontology ontology) { final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FileBasedStorage.class); List<String> lines = new ArrayList<String>(Arrays.asList(serializedElement.split("\n"))); if (lines.size() == 0 || !lines.get(0).startsWith("type:")) { log.warn("Cannot read ontology element (missing 'type')"); return; } String type = lines.remove(0).substring("type:".length()); OntologyElement oe = ontology.getEngine().createOntologyElement(type); // Dummy ontology element for the main page article: if (type.equals(GeneralTopic.MAINPAGE_TYPE)) { id = 0; oe = GeneralTopic.makeMain("acewiki_page_main"); } if (oe != null) { if (lines.size() == 0 || !lines.get(0).startsWith("words:")) { log.warn("Missing 'words' for ontology element: {}", oe); } else { String serializedWords = lines.remove(0).substring("words:".length()); ontology.change(oe, serializedWords); } } if (oe != null) { oe.initOntology(ontology); oe.initArticle(loadArticle(lines, oe)); oe.initId(id); ontology.register(oe); } else { log.warn("Failed to load ontology element with id {}", id); } return; } private static Article loadArticle(List<String> lines, OntologyElement element) { final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FileBasedStorage.class); Article a = new Article(element); List<Statement> statements = new ArrayList<Statement>(); while (!lines.isEmpty()) { String l = lines.remove(0); Statement statement = null; // If loading of a statement causes an exception then we ignore this statement // (but print it out along with the exception message) // and continue loading the other statements. // TODO The statement will be removed from the underlying storage, which // is maybe a bad thing? Then again, the storage represents only the current // state of the wiki and one should have a history/backup anyway. // TODO A better option might be to turn such sentences into comments, so that users could fix them. try { statement = loadStatement(l, a); } catch (Exception e) { log.warn("Bad statement: ", e); } if (statement == null) { log.warn("Cannot read statement: {}", l); } else { statements.add(statement); } } a.initStatements(statements); return a; } /** * Loads a statement from a serialized form. * * @param serializedStatement The serialized statement as a string. * @param article The article of the statement. * @return The new statement object. */ private static Statement loadStatement(String serializedStatement, Article article) { if (serializedStatement.length() < 2) return null; String s = serializedStatement.substring(2); Ontology ontology = article.getOntology(); StatementFactory statementFactory = ontology.getStatementFactory(); if (serializedStatement.startsWith("| ")) { Sentence sentence = statementFactory.createSentence(s, article); sentence.setIntegrated(true); return sentence; } else if (serializedStatement.startsWith("# ")) { Sentence sentence = statementFactory.createSentence(s, article); sentence.setIntegrated(false); return sentence; } else if (serializedStatement.startsWith("c ")) { String t = s.replaceAll("~n", "\n").replaceAll("~t", "~"); return statementFactory.createComment(t, article); } return null; } public synchronized void save(OntologyElement oe) { Ontology o = oe.getOntology(); String name = o.getName(); // Ontology elements of incomplete ontologies are not saved at this point: if (incompleteOntologies.contains(o)) return; if (!(new File(dir)).exists()) (new File(dir)).mkdir(); if (!(new File(dir + "/" + name)).exists()) (new File(dir + "/" + name)).mkdir(); if (!o.contains(oe)) { (new File(dir + "/" + name + "/" + oe.getId())).delete(); return; } try { FileOutputStream out = new FileOutputStream(dir + "/" + name + "/" + oe.getId()); out.write(serialize(oe).getBytes("UTF-8")); out.close(); } catch (IOException ex) { ex.printStackTrace(); } } /** * Serializes the given ontology element as a string. * * @param element The ontology element. * @return The serialized representation of the ontology element. */ public static String serialize(OntologyElement element) { String s = "type:" + element.getInternalType() + "\n"; String w = element.serializeWords(); if (w.length() > 0) { s += "words:" + w + "\n"; } for (Statement st : element.getArticle().getStatements()) { if (st instanceof Comment) { s += "c "; } else { if (((Sentence) st).isIntegrated()) { s += "| "; } else { s += "# "; } } s += st.serialize() + "\n"; } return s; } /** * Serializes the given list of ontology elements according to the AceWiki data format. * * @param elements The list of ontology elements. * @return The serialized representation of the ontology elements. */ public static String serialize(List<OntologyElement> elements) { String s = ""; for (OntologyElement oe : elements) { s += oe.getId() + "\n" + serialize(oe) + "\n"; } return s; } public UserBase getUserBase(Ontology ontology) { UserBase userBase = userBases.get(ontology.getName()); if (userBase == null) { userBase = new UserBase(ontology, this); userBases.put(ontology.getName(), userBase); File userDir = new File(dir + "/" + ontology.getName() + ".users"); if (userDir.exists()) { for (File file : userDir.listFiles()) { User user = loadUser(userBase, file); userBase.addUser(user); } } else { userDir.mkdir(); } } return userBase; } private static User loadUser(UserBase userBase, File file) { final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FileBasedStorage.class); try { long id = new Long(file.getName()); FileInputStream in = new FileInputStream(file); byte[] bytes = new byte[in.available()]; in.read(bytes); in.close(); String s = new String(bytes, "UTF-8"); String[] lines = s.split("\n"); if (lines.length < 2 || !lines[0].startsWith("name:") || !lines[1].startsWith("pw:")) { log.warn("Invalid user file: {}", id); return null; } String name = lines[0].substring("name:".length()); String pw = lines[1].substring("pw:".length()); if (pw.startsWith("\"") && pw.endsWith("\"")) { pw = User.getPasswordHash(pw.substring(1, pw.length()-1)); } Map<String, String> userdata = new HashMap<String, String>(); for (int i = 2 ; i < lines.length ; i++) { String l = lines[i]; int p = l.indexOf(":"); if (p > -1) { String n = l.substring(0, p); String v = l.substring(p+1); userdata.put(n, v); } } return new User(id, name, pw, userdata, userBase); } catch (NumberFormatException ex) { log.warn("ignoring user file: {}", file.getName()); } catch (IOException ex) { log.warn("cannot read user file: {}", file.getName()); } return null; } public void save(User user) { try { String n = user.getUserBase().getOntology().getName(); File d = new File(dir + "/" + n + ".users"); if (!d.exists()) d.mkdir(); FileOutputStream out = new FileOutputStream( new File(dir + "/" + n + ".users" + "/" + user.getId()) ); out.write(serialize(user).getBytes("UTF-8")); out.close(); } catch (IOException ex) { ex.printStackTrace(); } } private static String serialize(User user) { String ud = ""; List<String> keys = user.getUserDataKeys(); Collections.sort(keys); for (String k : keys) { String v = user.getUserData(k); if (v != null && v.length() > 0) { ud += k + ":" + v + "\n"; } } return "name:" + user.getName() + "\n" + "pw:" + user.getHashedPassword() + "\n\n" + ud; } }