// 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;
}
}