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.core; 016 017 import java.io.BufferedReader; 018 import java.io.File; 019 import java.io.FileInputStream; 020 import java.io.FileOutputStream; 021 import java.io.FileReader; 022 import java.io.IOException; 023 import java.util.ArrayList; 024 import java.util.Arrays; 025 import java.util.Collections; 026 import java.util.HashMap; 027 import java.util.List; 028 import java.util.Map; 029 030 /** 031 * This class implements persistent storage features for AceWiki data on the basis of a simple file 032 * and folder based system. 033 * 034 * @author Tobias Kuhn 035 */ 036 public class FileBasedStorage implements AceWikiStorage { 037 038 private final HashMap<String, Ontology> ontologies = new HashMap<String, Ontology>(); 039 private final Map<String, UserBase> userBases = new HashMap<String, UserBase>(); 040 private String dir; 041 private final List<Ontology> incompleteOntologies = new ArrayList<Ontology>(); 042 043 /** 044 * Creates a new storage object. 045 * 046 * @param dir The path at which ontologies should be stored. 047 */ 048 public FileBasedStorage(String dir) { 049 this.dir = dir.replaceFirst("/*$", ""); 050 File d = new File(dir); 051 if (!d.exists()) d.mkdir(); 052 } 053 054 /** 055 * Returns the ontology with the given name (or creates an empty ontology if the ontology 056 * cannot be found). A parameter map is used for ontology parameters. When the ontology with 057 * the respective name has already been loaded, this ontology is returned and the parameters 058 * are ignored. The following parameters are supported: 059 * "baseuri": The base URI that is used to identify the ontology elements. The complete URI of 060 * the ontology is baseURI + name. The default is an empty string. 061 * "global_restrictions_policy": A string representing the policy how to enforce the global 062 * restrictions on axioms in OWL 2. At the moment, the options "no_chains" and "unchecked" 063 * are available. 064 * "reasoner": Defines the reasoner or reasoner interface to be used. Currently supported are 065 * the HermiT reasoner ("HermiT", default), the Pellet reasoner ("Pellet"), the OWLlink 066 * interface ("OWLlink"), or none ("none"). 067 * "owl_profile": Sets an OWL profile that defines which statements are used for reasoning. 068 * Possible values are "OWL2Full" (default), "OWL2EL", "OWL2QL", and "OWL2RL". Note that 069 * the global restrictions of the EL profile are not checked. 070 * 071 * @param name The name of the ontology. 072 * @param parameters The parameters. 073 * @return The loaded ontology. 074 */ 075 public Ontology getOntology(String name, Map<String, String> parameters) { 076 if (ontologies.get(name) != null) { 077 return ontologies.get(name); 078 } 079 return loadOntology(name, parameters); 080 } 081 082 private synchronized Ontology loadOntology(String name, Map<String, String> parameters) { 083 if (ontologies.get(name) != null) { 084 return ontologies.get(name); 085 } 086 Ontology ontology = new Ontology(name, parameters, this); 087 incompleteOntologies.add(ontology); 088 ontologies.put(name, ontology); 089 ontology.log("loading ontology"); 090 System.err.println("Loading '" + name + "'"); 091 File dataDir = new File(dir + "/" + name); 092 File dataFile = new File(dir + "/" + name + ".acewikidata"); 093 if (dataDir.exists()) { 094 System.err.print("Entities: "); 095 ConsoleProgressBar pb1 = new ConsoleProgressBar(dataDir.listFiles().length); 096 for (File file : dataDir.listFiles()) { 097 pb1.addOne(); 098 try { 099 long id = new Long(file.getName()); 100 FileInputStream in = new FileInputStream(file); 101 byte[] bytes = new byte[in.available()]; 102 in.read(bytes); 103 in.close(); 104 String s = new String(bytes, "UTF-8"); 105 loadOntologyElement(s, id, ontology); 106 } catch (NumberFormatException ex) { 107 ontology.log("ignoring file: " + file.getName()); 108 } catch (IOException ex) { 109 ontology.log("cannot read file: " + file.getName()); 110 } 111 } 112 pb1.complete(); 113 } else if (dataFile.exists()) { 114 System.err.print("Entities: "); 115 ConsoleProgressBar pb1 = null; 116 try { 117 BufferedReader in = new BufferedReader(new FileReader(dataFile)); 118 pb1 = new ConsoleProgressBar(dataFile.length()); 119 String s = ""; 120 String line = in.readLine(); 121 long id = -1; 122 while (line != null) { 123 pb1.add(line.length() + 1); 124 if (line.matches("\\s*")) { 125 // empty line 126 if (s.length() > 0) { 127 loadOntologyElement(s, id, ontology); 128 s = ""; 129 id = -1; 130 } 131 } else if (line.matches("[0-9]+") && s.length() == 0) { 132 // line with id 133 id = new Long(line); 134 } else if (line.startsWith("%")) { 135 // comment 136 } else { 137 s += line + "\n"; 138 } 139 line = in.readLine(); 140 } 141 in.close(); 142 } catch (IOException ex) { 143 ontology.log("cannot read file: " + dataFile.getName()); 144 } 145 if (pb1 != null) pb1.complete(); 146 } else { 147 ontology.log("no data found; blank ontology is created"); 148 } 149 150 incompleteOntologies.remove(ontology); 151 152 ontology.log("loading statements"); 153 System.err.print("Statements: "); 154 List<OntologyElement> elements = ontology.getOntologyElements(); 155 ConsoleProgressBar pb2 = new ConsoleProgressBar(elements.size()); 156 for (OntologyElement oe : elements) { 157 pb2.addOne(); 158 ontology.getReasoner().loadElement(oe); 159 for (Sentence s : oe.getArticle().getSentences()) { 160 if (s.isReasonable() && s.isIntegrated()) { 161 ontology.getReasoner().loadSentence(s); 162 } 163 } 164 save(oe); 165 } 166 pb2.complete(); 167 168 if (ontology.get(0) == null) { 169 OntologyElement mainPage = new DummyOntologyElement("mainpage", "Main Page"); 170 mainPage.initId(0); 171 ontology.register(mainPage); 172 } 173 174 ontology.getReasoner().load(); 175 176 return ontology; 177 } 178 179 /** 180 * Loads an ontology element from its serialized form. 181 * 182 * @param serializedElement The serialized ontology element. 183 * @param id The id of the ontology element. 184 * @param ontology The ontology at which the ontology element should be registered. 185 */ 186 private static void loadOntologyElement(String serializedElement, long id, Ontology ontology) { 187 List<String> lines = new ArrayList<String>(Arrays.asList(serializedElement.split("\n"))); 188 if (lines.size() == 0 || !lines.get(0).startsWith("type:")) { 189 System.err.println("Cannot read ontology element (missing 'type')"); 190 return; 191 } 192 String type = lines.remove(0).substring("type:".length()); 193 OntologyElement oe = ontology.getEngine().createOntologyElement(type); 194 if (oe != null) { 195 if (!lines.get(0).startsWith("words:")) { 196 System.err.println("Cannot read ontology element (missing 'words')"); 197 return; 198 } 199 String serializedWords = lines.remove(0).substring("words:".length()); 200 ontology.change(oe, serializedWords); 201 } 202 203 // Dummy ontology element for the main page article: 204 if (type.equals("mainpage")) { 205 id = 0; 206 oe = new DummyOntologyElement("mainpage", "Main Page"); 207 } 208 209 if (oe != null) { 210 oe.initOntology(ontology); 211 oe.initArticle(loadArticle(lines, oe)); 212 oe.initId(id); 213 ontology.register(oe); 214 } else { 215 System.err.println("Failed to load ontology element with id " + id); 216 } 217 return; 218 } 219 220 private static Article loadArticle(List<String> lines, OntologyElement element) { 221 Article a = new Article(element); 222 List<Statement> statements = new ArrayList<Statement>(); 223 while (!lines.isEmpty()) { 224 String l = lines.remove(0); 225 Statement statement = loadStatement(l, a); 226 if (statement == null) { 227 System.err.println("Cannot read statement: " + l); 228 } else { 229 statements.add(statement); 230 } 231 } 232 a.initStatements(statements); 233 return a; 234 } 235 236 /** 237 * Loads a statement from a serialized form. 238 * 239 * @param serializedStatement The serialized statement as a string. 240 * @param article The article of the statement. 241 * @return The new statement object. 242 */ 243 private static Statement loadStatement(String serializedStatement, Article article) { 244 if (serializedStatement.length() < 2) return null; 245 String s = serializedStatement.substring(2); 246 247 Ontology ontology = article.getOntology(); 248 StatementFactory statementFactory = ontology.getStatementFactory(); 249 if (serializedStatement.startsWith("| ")) { 250 Sentence sentence = statementFactory.createSentence(s, article); 251 sentence.setIntegrated(true); 252 return sentence; 253 } else if (serializedStatement.startsWith("# ")) { 254 Sentence sentence = statementFactory.createSentence(s, article); 255 sentence.setIntegrated(false); 256 return sentence; 257 } else if (serializedStatement.startsWith("c ")) { 258 String t = s.replaceAll("~n", "\n").replaceAll("~t", "~"); 259 return statementFactory.createComment(t, article); 260 } 261 262 return null; 263 } 264 265 public synchronized void save(OntologyElement oe) { 266 Ontology o = oe.getOntology(); 267 String name = o.getName(); 268 269 // Ontology elements of incomplete ontologies are not saved at this point: 270 if (incompleteOntologies.contains(o)) return; 271 272 if (!(new File(dir)).exists()) (new File(dir)).mkdir(); 273 if (!(new File(dir + "/" + name)).exists()) (new File(dir + "/" + name)).mkdir(); 274 275 if (!o.contains(oe)) { 276 (new File(dir + "/" + name + "/" + oe.getId())).delete(); 277 return; 278 } 279 280 try { 281 FileOutputStream out = new FileOutputStream(dir + "/" + name + "/" + oe.getId()); 282 out.write(serialize(oe).getBytes("UTF-8")); 283 out.close(); 284 } catch (IOException ex) { 285 ex.printStackTrace(); 286 } 287 } 288 289 /** 290 * Serializes the given ontology element as a string. 291 * 292 * @param element The ontology element. 293 * @return The serialized representation of the ontology element. 294 */ 295 public static String serialize(OntologyElement element) { 296 String s = "type:" + element.getInternalType() + "\n"; 297 String w = element.serializeWords(); 298 if (w.length() > 0) { 299 s += "words:" + w + "\n"; 300 } 301 for (Statement st : element.getArticle().getStatements()) { 302 if (st instanceof Comment) { 303 s += "c "; 304 } else { 305 if (((Sentence) st).isIntegrated()) { 306 s += "| "; 307 } else { 308 s += "# "; 309 } 310 } 311 s += st.serialize() + "\n"; 312 } 313 return s; 314 } 315 316 /** 317 * Serializes the given list of ontology elements according to the AceWiki data format. 318 * 319 * @param elements The list of ontology elements. 320 * @return The serialized representation of the ontology elements. 321 */ 322 public static String serialize(List<OntologyElement> elements) { 323 String s = ""; 324 for (OntologyElement oe : elements) { 325 s += oe.getId() + "\n" + serialize(oe) + "\n"; 326 } 327 return s; 328 } 329 330 public UserBase getUserBase(Ontology ontology) { 331 UserBase userBase = userBases.get(ontology.getName()); 332 if (userBase == null) { 333 userBase = new UserBase(ontology, this); 334 userBases.put(ontology.getName(), userBase); 335 File userDir = new File(dir + "/" + ontology.getName() + ".users"); 336 if (userDir.exists()) { 337 for (File file : userDir.listFiles()) { 338 User user = loadUser(userBase, file); 339 userBase.addUser(user); 340 } 341 } else { 342 userDir.mkdir(); 343 } 344 } 345 return userBase; 346 } 347 348 private static User loadUser(UserBase userBase, File file) { 349 try { 350 long id = new Long(file.getName()); 351 FileInputStream in = new FileInputStream(file); 352 byte[] bytes = new byte[in.available()]; 353 in.read(bytes); 354 in.close(); 355 String s = new String(bytes, "UTF-8"); 356 String[] lines = s.split("\n"); 357 if (lines.length < 2 || !lines[0].startsWith("name:") || !lines[1].startsWith("pw:")) { 358 System.err.println("Invalid user file: " + id); 359 return null; 360 } 361 String name = lines[0].substring("name:".length()); 362 String pw = lines[1].substring("pw:".length()); 363 if (pw.startsWith("\"") && pw.endsWith("\"")) { 364 pw = User.getPasswordHash(pw.substring(1, pw.length()-1)); 365 } 366 Map<String, String> userdata = new HashMap<String, String>(); 367 for (int i = 2 ; i < lines.length ; i++) { 368 String l = lines[i]; 369 int p = l.indexOf(":"); 370 if (p > -1) { 371 String n = l.substring(0, p); 372 String v = l.substring(p+1); 373 userdata.put(n, v); 374 } 375 } 376 return new User(id, name, pw, userdata, userBase); 377 } catch (NumberFormatException ex) { 378 System.err.println("ignoring user file: " + file.getName()); 379 } catch (IOException ex) { 380 System.err.println("cannot read user file: " + file.getName()); 381 } 382 return null; 383 } 384 385 public void save(User user) { 386 try { 387 String n = user.getUserBase().getOntology().getName(); 388 File d = new File(dir + "/" + n + ".users"); 389 if (!d.exists()) d.mkdir(); 390 FileOutputStream out = new FileOutputStream( 391 new File(dir + "/" + n + ".users" + "/" + user.getId()) 392 ); 393 out.write(serialize(user).getBytes("UTF-8")); 394 out.close(); 395 } catch (IOException ex) { 396 ex.printStackTrace(); 397 } 398 } 399 400 private static String serialize(User user) { 401 String ud = ""; 402 List<String> keys = user.getUserDataKeys(); 403 Collections.sort(keys); 404 for (String k : keys) { 405 String v = user.getUserData(k); 406 if (v != null && v.length() > 0) { 407 ud += k + ":" + v + "\n"; 408 } 409 } 410 return "name:" + user.getName() + "\n" + "pw:" + user.getHashedPassword() + "\n\n" + ud; 411 } 412 413 }