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    }