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;
016    
017    import java.io.InputStream;
018    import java.util.ArrayList;
019    import java.util.List;
020    import java.util.Map;
021    import java.util.Properties;
022    import java.util.Random;
023    import java.util.Stack;
024    
025    import javax.servlet.http.Cookie;
026    
027    import nextapp.echo.app.Alignment;
028    import nextapp.echo.app.ApplicationInstance;
029    import nextapp.echo.app.Color;
030    import nextapp.echo.app.Column;
031    import nextapp.echo.app.Component;
032    import nextapp.echo.app.ContentPane;
033    import nextapp.echo.app.Extent;
034    import nextapp.echo.app.Font;
035    import nextapp.echo.app.Insets;
036    import nextapp.echo.app.ResourceImageReference;
037    import nextapp.echo.app.Row;
038    import nextapp.echo.app.SplitPane;
039    import nextapp.echo.app.TaskQueueHandle;
040    import nextapp.echo.app.WindowPane;
041    import nextapp.echo.app.event.ActionEvent;
042    import nextapp.echo.app.event.ActionListener;
043    import nextapp.echo.app.layout.ColumnLayoutData;
044    import nextapp.echo.webcontainer.ContainerContext;
045    import ch.uzh.ifi.attempto.acewiki.core.AceWikiDataExporter;
046    import ch.uzh.ifi.attempto.acewiki.core.AceWikiEngine;
047    import ch.uzh.ifi.attempto.acewiki.core.AceWikiStorage;
048    import ch.uzh.ifi.attempto.acewiki.core.LanguageHandler;
049    import ch.uzh.ifi.attempto.acewiki.core.LexiconTableExporter;
050    import ch.uzh.ifi.attempto.acewiki.core.Ontology;
051    import ch.uzh.ifi.attempto.acewiki.core.OntologyElement;
052    import ch.uzh.ifi.attempto.acewiki.core.OntologyExportManager;
053    import ch.uzh.ifi.attempto.acewiki.core.OntologyExporter;
054    import ch.uzh.ifi.attempto.acewiki.core.OntologyTextElement;
055    import ch.uzh.ifi.attempto.acewiki.core.StatementTableExporter;
056    import ch.uzh.ifi.attempto.acewiki.core.User;
057    import ch.uzh.ifi.attempto.acewiki.core.UserBase;
058    import ch.uzh.ifi.attempto.acewiki.gui.AboutPage;
059    import ch.uzh.ifi.attempto.acewiki.gui.ArticlePage;
060    import ch.uzh.ifi.attempto.acewiki.gui.ExportWindow;
061    import ch.uzh.ifi.attempto.acewiki.gui.FormPane;
062    import ch.uzh.ifi.attempto.acewiki.gui.IconButton;
063    import ch.uzh.ifi.attempto.acewiki.gui.IndexPage;
064    import ch.uzh.ifi.attempto.acewiki.gui.ListItem;
065    import ch.uzh.ifi.attempto.acewiki.gui.LoginWindow;
066    import ch.uzh.ifi.attempto.acewiki.gui.SearchPage;
067    import ch.uzh.ifi.attempto.acewiki.gui.StartPage;
068    import ch.uzh.ifi.attempto.acewiki.gui.Title;
069    import ch.uzh.ifi.attempto.acewiki.gui.UserWindow;
070    import ch.uzh.ifi.attempto.acewiki.gui.WikiPage;
071    import ch.uzh.ifi.attempto.base.Logger;
072    import ch.uzh.ifi.attempto.echocomp.HSpace;
073    import ch.uzh.ifi.attempto.echocomp.Label;
074    import ch.uzh.ifi.attempto.echocomp.MessageWindow;
075    import ch.uzh.ifi.attempto.echocomp.SmallButton;
076    import ch.uzh.ifi.attempto.echocomp.SolidLabel;
077    import ch.uzh.ifi.attempto.echocomp.Style;
078    import ch.uzh.ifi.attempto.echocomp.TextAreaWindow;
079    import ch.uzh.ifi.attempto.echocomp.TextField;
080    import ch.uzh.ifi.attempto.echocomp.VSpace;
081    import ch.uzh.ifi.attempto.preditor.PreditorWindow;
082    import ch.uzh.ifi.attempto.preditor.WordEditorWindow;
083    import echopoint.externalevent.ExternalEvent;
084    import echopoint.externalevent.ExternalEventListener;
085    import echopoint.externalevent.ExternalEventMonitor;
086    
087    /**
088     * This class represents an AceWiki wiki instance (including its graphical user interface). There
089     * is such a wiki object for every wiki user.
090     *
091     * @author Tobias Kuhn
092     */
093    public class Wiki implements ActionListener, ExternalEventListener {
094    
095            private static final long serialVersionUID = 2777443689044226043L;
096    
097            private Map<String, String> parameters;
098    
099            private final Ontology ontology;
100            private final AceWikiEngine engine;
101            private String language;
102            private User user;
103            private OntologyExportManager ontologyExportManager;
104            private static AceWikiStorage storage;
105    
106            private WikiPage currentPage;
107            private Column pageCol;
108            private ContentPane contentPane = new ContentPane();
109            private Row navigationButtons = new Row();
110            private Logger logger;
111            private SplitPane wikiPane;
112            private Row loginBackground;
113    
114            private IconButton backButton = new IconButton("Back", this);
115            private IconButton forwardButton = new IconButton("Forward", this);
116            private IconButton refreshButton = new IconButton("Refresh", this);
117            private IconButton userButton = new IconButton("User", this);
118            private IconButton logoutButton = new IconButton("Logout", this);
119            private IconButton searchButton = new IconButton("Search", this);
120            private TextField searchTextField = new TextField(170, this);
121            private Label userLabel = new SolidLabel("Anonymous", Font.ITALIC);
122    
123            private SmallButton homeButton = new SmallButton("Main Page", this, 12);
124            private SmallButton indexButton = new SmallButton("Index", this, 12);
125            private SmallButton searchButton2 = new SmallButton("Search", this, 12);
126            private SmallButton aboutButton = new SmallButton("About", this, 12);
127            private SmallButton randomButton = new SmallButton("Random Article", this, 12);
128            private SmallButton newButton = new SmallButton("New Word...", this, 12);
129            private SmallButton exportButton = new SmallButton("Export...", this, 12);
130    
131            private StartPage startPage;
132    
133            private Stack<WikiPage> history = new Stack<WikiPage>();
134            private Stack<WikiPage> forward = new Stack<WikiPage>();
135    
136            private TaskQueueHandle taskQueue;
137            private MessageWindow waitWindow;
138            private List<Task> strongTasks = new ArrayList<Task>();
139            private List<Task> weakTasks = new ArrayList<Task>();
140    
141            private ExternalEventMonitor externalEventMonitor;
142    
143            private AceWikiApp application;
144    
145            private static Properties properties;
146    
147            private boolean disposed = false;
148    
149            private boolean locked = false;
150            private ActionListener lockedListener;
151    
152            /**
153             * Creates a new wiki instance.
154             *
155         * @param backend The backend object contains ontology of the wiki.
156             * @param parameters A set of parameters in the form of name/value pairs.
157             * @param sessionID The session id.
158             */
159            Wiki(Backend backend, Map<String, String> parameters, int sessionID) {
160                    this.parameters = parameters;
161    
162            storage = backend.getStorage();
163                    ontology = backend.getOntology();
164    
165                    engine = ontology.getEngine();
166                    language = getParameter("language");
167                    if (language == null || language.equals("")) {
168                            language = engine.getLanguages()[0];
169                    }
170                    logger = new Logger(getParameter("context:logdir") + "/" + ontology.getName(), "anon", sessionID);
171                    application = (AceWikiApp) ApplicationInstance.getActive();
172                    taskQueue = application.createTaskQueue();
173    
174                    ontologyExportManager = new OntologyExportManager(ontology);
175                    for (OntologyExporter o : engine.getExporters()) {
176                            ontologyExportManager.addExporter(o);
177                    }
178                    ontologyExportManager.addExporter(new LexiconTableExporter());
179                    ontologyExportManager.addExporter(new StatementTableExporter(language));
180                    ontologyExportManager.addExporter(new AceWikiDataExporter());
181    
182                    SplitPane splitPane1 = new SplitPane(SplitPane.ORIENTATION_VERTICAL_TOP_BOTTOM);
183                    splitPane1.setSeparatorPosition(new Extent(50));
184                    splitPane1.setSeparatorHeight(new Extent(0));
185    
186                    SplitPane splitPane2 = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_RIGHT_LEFT);
187                    splitPane2.setSeparatorPosition(new Extent(215));
188                    splitPane2.setSeparatorWidth(new Extent(0));
189    
190                    navigationButtons.setInsets(new Insets(5));
191                    navigationButtons.setBackground(Style.shadedBackground);
192    
193                    navigationButtons.add(backButton);
194                    navigationButtons.add(new HSpace(5));
195                    navigationButtons.add(forwardButton);
196                    navigationButtons.add(new HSpace(5));
197                    navigationButtons.add(refreshButton);
198                    navigationButtons.add(new HSpace(30));
199                    Row userRow = new Row();
200                    userRow.add(userButton);
201                    userRow.add(new HSpace(3));
202                    userLabel.setForeground(Color.DARKGRAY);
203                    userRow.add(userLabel);
204                    logoutButton.setVisible(false);
205                    userRow.add(logoutButton);
206                    userRow.setVisible(isLoginEnabled());
207                    navigationButtons.add(userRow);
208    
209                    ContentPane menuBar = new ContentPane();
210                    menuBar.setBackground(Style.shadedBackground);
211                    menuBar.add(navigationButtons);
212    
213                    Row searchRow = new Row();
214                    searchRow.setInsets(new Insets(5));
215                    searchRow.setBackground(Style.shadedBackground);
216                    searchRow.add(searchButton);
217                    searchRow.add(new HSpace(5));
218                    searchRow.add(searchTextField);
219    
220                    ContentPane searchBar = new ContentPane();
221                    searchBar.setBackground(Style.shadedBackground);
222                    searchBar.add(searchRow);
223    
224                    wikiPane = new SplitPane(
225                                    SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT,
226                                    new Extent(145)
227                            );
228                    wikiPane.setSeparatorHeight(new Extent(0));
229    
230                    ContentPane sideBar = new ContentPane();
231                    sideBar.setBackground(Style.shadedBackground);
232                    Column sideCol = new Column();
233                    sideCol.setInsets(new Insets(10, 10));
234                    sideCol.setCellSpacing(new Extent(1));
235    
236                    Label logo = new Label(new ResourceImageReference(
237                                    "ch/uzh/ifi/attempto/acewiki/gui/img/AceWikiLogoSmall.png"
238                            ));
239                    sideCol.add(logo);
240    
241                    sideCol.add(new VSpace(10));
242    
243                    ColumnLayoutData layout = new ColumnLayoutData();
244                    layout.setAlignment(Alignment.ALIGN_CENTER);
245    
246                    String title = getParameter("title");
247                    if (title != null && title.length() > 0) {
248                            Label titleLabel = new Label(title, Font.ITALIC, 14);
249                            sideCol.add(titleLabel);
250                            titleLabel.setLayoutData(layout);
251                            sideCol.add(new VSpace(5));
252                    }
253    
254                    if (isReadOnly()) {
255                            SolidLabel rolabel = new SolidLabel("— READ ONLY —", Font.ITALIC);
256                            rolabel.setFont(new Font(Style.fontTypeface, Font.ITALIC, new Extent(10)));
257                            rolabel.setLayoutData(layout);
258                            sideCol.add(rolabel);
259                            sideCol.add(new VSpace(5));
260                    }
261    
262                    sideCol.add(new VSpace(20));
263    
264                    SolidLabel label1 = new SolidLabel("Navigation:", Font.ITALIC);
265                    label1.setFont(new Font(Style.fontTypeface, Font.ITALIC, new Extent(10)));
266                    sideCol.add(label1);
267                    sideCol.add(new ListItem(homeButton));
268                    sideCol.add(new ListItem(indexButton));
269                    sideCol.add(new ListItem(searchButton2));
270                    sideCol.add(new ListItem(aboutButton));
271                    sideCol.add(new ListItem(randomButton));
272    
273                    sideCol.add(new VSpace(10));
274    
275                    SolidLabel label2 = new SolidLabel("Actions:", Font.ITALIC);
276                    label2.setFont(new Font(Style.fontTypeface, Font.ITALIC, new Extent(10)));
277                    sideCol.add(label2);
278                    if (!isReadOnly() && getEngine().getLexicalTypes().length > 0) {
279                            sideCol.add(new ListItem(newButton));
280                    }
281                    sideCol.add(new ListItem(exportButton));
282    
283                    externalEventMonitor = new ExternalEventMonitor();
284                    externalEventMonitor.addExternalEventListener(this);
285                    sideCol.add(externalEventMonitor);
286    
287                    //sideCol.add(new VSpace(20));
288                    //sideCol.add(new ItalicLabel("Session ID: " + sessionID));
289    
290                    sideBar.add(sideCol);
291    
292                    SplitPane splitPane3 = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT);
293                    splitPane3.setSeparatorWidth(new Extent(1));
294                    splitPane3.setSeparatorColor(Color.BLACK);
295                    splitPane3.setSeparatorPosition(new Extent(0));
296                    splitPane3.add(new Label());
297    
298                    SplitPane splitPane4 = new SplitPane(SplitPane.ORIENTATION_VERTICAL_TOP_BOTTOM);
299                    splitPane4.setSeparatorHeight(new Extent(1));
300                    splitPane4.setSeparatorColor(Color.BLACK);
301                    splitPane4.setSeparatorPosition(new Extent(0));
302                    splitPane4.add(new Label());
303    
304                    splitPane3.add(splitPane4);
305                    pageCol = new Column();
306                    splitPane4.add(pageCol);
307    
308                    splitPane2.add(searchBar);
309                    splitPane2.add(menuBar);
310    
311                    splitPane1.add(splitPane2);
312                    splitPane1.add(splitPane3);
313    
314                    wikiPane.add(sideBar);
315                    wikiPane.add(splitPane1);
316    
317                    contentPane.add(wikiPane);
318    
319                    startPage = new StartPage(this);
320    
321                    // auto login
322                    if (isLoginEnabled()) {
323                            String userName = getCookie("lastusername");
324                            boolean stayLoggedIn = getCookie("stayloggedin").equals("true");
325                            if (getUserBase().containsUser(userName) && stayLoggedIn) {
326                                    String clientToken = getCookie("stayloggedintoken");
327                                    if (clientToken.length() > 0) {
328                                            log("syst", "try auto login...");
329                                            user = getUserBase().autoLogin(userName, clientToken);
330                                            if (user != null) {
331                                                    log("syst", "auto login successful: " + user.getName());
332                                                    setUser(user);
333                                            } else {
334                                                    log("syst", "auto login failed: " + userName);
335                                                    clearCookie("stayloggedintoken");
336                                            }
337                                    }
338                            }
339                    }
340    
341                    String p = null;
342                    try {
343                            p = ((String[]) getContainerContext().getInitialRequestParameterMap()
344                                            .get("showpage"))[0];
345                    } catch (Exception ex) {}
346    
347                    if (p != null && ontology.getElement(p) != null) {
348                            setCurrentPage(ArticlePage.create(ontology.getElement(p), this));
349                    } else {
350                            setCurrentPage(startPage);
351                    }
352    
353                    // This thread checks regularly for pending tasks and executes them. Strong tasks take
354                    // precedence over weak ones.
355                    Thread asyncThread = new Thread() {
356    
357                            public void run() {
358                                    while (true) {
359                                            try {
360                                                    sleep(500);
361                                            } catch (InterruptedException ex) {}
362    
363                                            if (disposed) {
364                                                    break;
365                                            }
366    
367                                            Task task = null;
368                                            if (strongTasks.size() > 0) {
369                                                    task = strongTasks.remove(0);
370                                            } else if (weakTasks.size() > 0) {
371                                                    task = weakTasks.remove(0);
372                                            }
373    
374                                            final Task fTask = task;
375                                            if (fTask != null) {
376                                                    task.run();
377                                                    application.enqueueTask(taskQueue, new Runnable() {
378                                                            public synchronized void run() {
379                                                                    fTask.updateGUI();
380                                                                    if (waitWindow != null) {
381                                                                            removeWindow(waitWindow);
382                                                                            waitWindow = null;
383                                                                    }
384                                                            }
385                                                    });
386                                            }
387                                    }
388                            }
389    
390                    };
391                    asyncThread.setPriority(Thread.MIN_PRIORITY);
392                    asyncThread.start();
393    
394                    update();
395            }
396    
397            /**
398             * Returns the content pane containing the wiki GUI.
399             *
400             * @return The content pane.
401             */
402            public ContentPane getContentPane() {
403                    return contentPane;
404            }
405    
406            /**
407             * Returns the application instance object of this wiki.
408             *
409             * @return The application instance.
410             */
411            public ApplicationInstance getApplication() {
412                    return application;
413            }
414    
415            /**
416             * Returns the value of the given parameter. These parameters are defined in the web.xml file
417             * of the web application.
418             *
419             * @param paramName The parameter name.
420             * @return The value of the parameter.
421             */
422            public String getParameter(String paramName) {
423                    return parameters.get(paramName);
424            }
425    
426            /**
427             * Returns whether the login features are enabled.
428             *
429             * @return true if login is enabled.
430             */
431            public boolean isLoginEnabled() {
432                    return "yes".equals(getParameter("login"));
433            }
434    
435            /**
436             * Returns whether login is required for viewing the wiki data.
437             *
438             * @return true if login is required for viewing.
439             */
440            public boolean isLoginRequiredForViewing() {
441                    if (!isLoginEnabled()) return false;
442                    return "yes".equals(getParameter("login_required"));
443            }
444    
445            /**
446             * Returns whether login is required for editing the wiki data.
447             *
448             * @return true if login is required for editing.
449             */
450            public boolean isLoginRequiredForEditing() {
451                    if (!isLoginEnabled()) return false;
452                    if (isLoginRequiredForViewing()) return true;
453                    return "edit".equals(getParameter("login_required"));
454            }
455    
456            /**
457             * Returns whether the user registration is open to everyone.
458             *
459             * @return true if the user registration is open.
460             */
461            public boolean isUserRegistrationOpen() {
462                    if (!isLoginEnabled()) return false;
463                    return !"no".equals(getParameter("register"));
464            }
465    
466            /**
467             * Returns whether the wiki is in the current situation editable. This depends on the fact
468             * whether a user is logged in and whether login is required for editing the wiki data.
469             *
470             * @return true if the wiki is editable.
471             */
472            public boolean isEditable() {
473                    return (user != null || !isLoginRequiredForEditing());
474            }
475    
476            /**
477             * Returns true if this wiki is set to be read-only.
478             *
479             * @return true if this wiki is read-only.
480             */
481            public boolean isReadOnly() {
482                    return "on".equals(parameters.get("readonly"));
483            }
484    
485            /**
486             * Shows the window.
487             *
488             * @param window The window to be shown.
489             */
490            public void showWindow(WindowPane window) {
491                    cleanWindows();
492                    if (window instanceof WordEditorWindow ||
493                                    window instanceof PreditorWindow ||
494                                    window instanceof TextAreaWindow) {
495                            int c = getContentPane().getComponentCount() - 1;
496                            window.setPositionX(new Extent(50 + (c % 5)*40));
497                            window.setPositionY(new Extent(50 + (c % 5)*20));
498                    }
499                    getContentPane().add(window);
500            }
501    
502            /**
503             * Shows a word editor window.
504             *
505             * @param element The ontology element to be edited.
506             */
507            public void showEditorWindow(OntologyElement element) {
508                    WordEditorWindow editorWindow = new WordEditorWindow("Word Editor");
509                    editorWindow.addTab(new FormPane(element, editorWindow, this));
510                    showWindow(editorWindow);
511            }
512    
513            /**
514             * Shows a word creator window for the given word type and number.
515             *
516             * @param type The word type.
517             * @param wordNumber The word number.
518             * @param actionListener The actionlistener.
519             */
520            public void showCreatorWindow(String type, int wordNumber, ActionListener actionListener) {
521                    WordEditorWindow creatorWindow = new WordEditorWindow("Word Creator");
522                    creatorWindow.addTab(new FormPane(type, wordNumber, creatorWindow, this, actionListener));
523                    showWindow(creatorWindow);
524            }
525    
526            /**
527             * Removes the window.
528             *
529             * @param window The window to be removed.
530             */
531            public void removeWindow(WindowPane window) {
532                    window.setVisible(false);
533                    window.dispose();
534                    cleanWindows();
535            }
536    
537            private void cleanWindows() {
538                    for (Component c : getContentPane().getComponents()) {
539                            if (!c.isVisible()) {
540                                    getContentPane().remove(c);
541                            }
542                    }
543            }
544    
545            /**
546             * Shows the login window.
547             */
548            public void showLoginWindow() {
549                    if (isLoginRequiredForViewing()) {
550                            getContentPane().removeAll();
551                            loginBackground = new Row();
552                            loginBackground.setInsets(new Insets(10, 10));
553                            loginBackground.setCellSpacing(new Extent(30));
554                            Label loginBgLogo = new Label(getImage("AceWikiLogoSmall.png"));
555                            loginBackground.add(loginBgLogo);
556                            loginBackground.add(new Title(getParameter("title"), true));
557                            getContentPane().add(loginBackground);
558                            getContentPane().setBackground(new Color(230, 230, 230));
559                    }
560                    showWindow(new LoginWindow(this));
561            }
562    
563            /**
564             * Switches to the given page.
565             *
566             * @param page The page to switch to.
567             */
568            public void showPage(WikiPage page) {
569                    if (!currentPage.equals(page)) {
570                            history.push(currentPage);
571                            if (history.size() > 20) history.remove(0);
572                            forward.clear();
573                    }
574                    setCurrentPage(page);
575                    log("navi", "goto: " + page);
576                    update();
577            }
578    
579            /**
580             * Switches to the page of the given ontology element.
581             *
582             * @param e The ontology element the page of which should be shown.
583             */
584            public void showPage(OntologyElement e) {
585                    showPage(ArticlePage.create(e, this));
586            }
587    
588            /**
589             * Go to the previous page in the history.
590             */
591            public void back() {
592                    if (history.isEmpty()) return;
593                    forward.push(currentPage);
594                    if (forward.size() > 20) forward.remove(0);
595                    WikiPage page = history.pop();
596                    setCurrentPage(page);
597                    log("navi", "back: " + page);
598                    update();
599            }
600    
601            /**
602             * Go to the next page in the history.
603             */
604            public void forward() {
605                    if (forward.isEmpty()) return;
606                    history.push(currentPage);
607                    if (history.size() > 20) history.remove(0);
608                    WikiPage page = forward.pop();
609                    setCurrentPage(page);
610                    log("navi", "forw: " + page);
611                    update();
612            }
613    
614            /**
615             * Show the start page.
616             */
617            public void showStartPage() {
618                    showPage(startPage);
619            }
620    
621            /**
622             * Show the index page.
623             */
624            public void showIndexPage() {
625                    showPage(new IndexPage(this));
626            }
627    
628            /**
629             * Show the search page.
630             */
631            public void showSearchPage() {
632                    showPage(new SearchPage(this, ""));
633            }
634    
635            /**
636             * Show the about page.
637             */
638            public void showAboutPage() {
639                    showPage(new AboutPage(this));
640            }
641    
642            /**
643             * Returns the ontology;
644             *
645             * @return The ontology.
646             */
647            public Ontology getOntology() {
648                    return ontology;
649            }
650    
651            /**
652             * Returns the ontology export manager.
653             *
654             * @return The ontology export manager.
655             */
656            public OntologyExportManager getOntologyExportManager() {
657                    return ontologyExportManager;
658            }
659    
660            /**
661             * Returns the user base for this wiki.
662             *
663             * @return The user base.
664             */
665            public UserBase getUserBase() {
666                    return storage.getUserBase(ontology);
667            }
668    
669            /**
670             * Returns all ontology elements. The list is a copy of the internal list.
671             *
672             * @return A list of all ontology elements.
673             */
674            public List<OntologyElement> getOntologyElements() {
675                    return ontology.getOntologyElements();
676            }
677    
678            /**
679             * Updates the GUI.
680             */
681            public void update() {
682                    pageCol.removeAll();
683                    pageCol.add(currentPage);
684    
685                    removeExpiredPages(history);
686                    removeExpiredPages(forward);
687                    backButton.setEnabled(!history.isEmpty());
688                    forwardButton.setEnabled(!forward.isEmpty());
689    
690                    // The commented-out code below checks at every GUI update whether the ontology is consistent or not.
691                    // If not, a red AceWiki logo is shown. Usually, this case should never occur because we check for
692                    // consistency after every new statement.
693                    //if (ontology.isConsistent()) {
694                    //      logo.setIcon(new ResourceImageReference("ch/uzh/ifi/attempto/acewiki/gui/img/AceWikiLogoSmall.png"));
695                    //} else {
696                    //      logo.setIcon(new ResourceImageReference("ch/uzh/ifi/attempto/acewiki/gui/img/AceWikiLogoSmallRed.png"));
697                    //}
698            }
699    
700            private void removeExpiredPages(Stack<WikiPage> stack) {
701                    WikiPage previousPage = null;
702                    for (WikiPage page : new ArrayList<WikiPage>(stack)) {
703                            if (page.isExpired() || page.equals(previousPage)) {
704                                    stack.remove(page);
705                            } else {
706                                    previousPage = page;
707                            }
708                    }
709                    if (stack.size() > 0 && currentPage.equals(stack.peek())) {
710                            stack.pop();
711                    }
712            }
713    
714            private void setCurrentPage(WikiPage currentPage) {
715                    this.currentPage = currentPage;
716                    refresh();
717            }
718    
719            /**
720             * Refreshes the current page.
721             */
722            public void refresh() {
723                    currentPage.update();
724            }
725    
726            public void actionPerformed(ActionEvent e) {
727                    Object src = e.getSource();
728                    String c = e.getActionCommand();
729    
730                    if (locked) {
731                            if (lockedListener != null) {
732                                    lockedListener.actionPerformed(new ActionEvent(this, "locked"));
733                            }
734                            return;
735                    }
736    
737                    if (src == backButton) {
738                            log("page", "pressed: back");
739                            back();
740                    } else if (src == forwardButton) {
741                            log("page", "pressed: forward");
742                            forward();
743                    } else if (src == indexButton) {
744                            log("page", "pressed: index");
745                            showIndexPage();
746                    } else if (src == aboutButton) {
747                            log("page", "pressed: about");
748                            showAboutPage();
749                    } else if (src == homeButton) {
750                            log("page", "pressed: main page");
751                            showStartPage();
752                    } else if (src == randomButton) {
753                            log("page", "pressed: random page");
754                            List<OntologyElement> elements = ontology.getOntologyElements();
755                            if (elements.size() > 0) {
756                                    int r = (new Random()).nextInt(elements.size());
757                                    showPage(elements.get(r));
758                            } else {
759                                    showStartPage();
760                            }
761                    } else if (src == refreshButton) {
762                            log("page", "pressed: refresh");
763                            update();
764                            refresh();
765                    } else if (src == newButton) {
766                            log("page", "pressed: new word");
767                            if (!isEditable()) {
768                                    showLoginWindow();
769                            } else {
770                                    WordEditorWindow w = new WordEditorWindow("Word Creator");
771                                    for (String t : getEngine().getLexicalTypes()) {
772                                            w.addTab(new FormPane(t, w, this));
773                                    }
774                                    showWindow(w);
775                            }
776                    } else if (src == searchButton || src == searchTextField || src == searchButton2) {
777                            log("page", "pressed: search '" + searchTextField.getText() + "'");
778                            String s = searchTextField.getText();
779                            searchTextField.setText("");
780                            OntologyElement el = ontology.getElement(s.replace(' ', '_'));
781                            if (el == null) {
782                                    showPage(new SearchPage(this, s));
783                            } else {
784                                    showPage(el);
785                            }
786                    } else if (src == exportButton) {
787                            showWindow(new ExportWindow(this));
788                    } else if (src == logoutButton) {
789                            showWindow(new MessageWindow(
790                                            "Logout",
791                                            "Do you really want to log out?",
792                                            null,
793                                            this,
794                                            "Yes", "No"
795                                    ));
796                    } else if (src == userButton) {
797                            if (user == null) {
798                                    showLoginWindow();
799                            } else {
800                                    showWindow(new UserWindow(this));
801                            }
802                    } else if (src instanceof MessageWindow && c.equals("Yes")) {
803                            logout();
804                    } else if (src instanceof OntologyTextElement) {
805                            // for newly generated elements
806                            OntologyTextElement te = (OntologyTextElement) src;
807                            log("edit", "new word: " + te.getOntologyElement().getWord());
808                            showPage(te.getOntologyElement());
809                    }
810            }
811    
812            public void externalEvent(ExternalEvent e) {
813                    OntologyElement oe = ontology.getElement(e.getParameter("page"));
814                    if (oe != null) showPage(oe);
815            }
816    
817            /**
818             * Writes the log entry to the log file.
819             *
820             * @param type The type of the log entry.
821             * @param text The text of the log entry.
822             */
823            public void log(String type, String text) {
824                    logger.log(type, text);
825            }
826    
827            /**
828             * Logs in the given user.
829             *
830             * @param user The user to log in.
831             * @param stayLoggedIn Defines whether the user should stay logged in or not.
832             */
833            public void login(User user, boolean stayLoggedIn) {
834                    log("syst", "login");
835                    user.setUserData("stayloggedin", stayLoggedIn + "");
836                    setCookie("stayloggedin", stayLoggedIn + "");
837                    String stayloggedintoken;
838                    if (stayLoggedIn) {
839                            stayloggedintoken = (new Random()).nextLong() + "";
840                    } else {
841                            stayloggedintoken = "";
842                    }
843                    user.setUserData("stayloggedintoken", stayloggedintoken);
844                    setCookie("stayloggedintoken", stayloggedintoken);
845                    setUser(user);
846            }
847    
848            /**
849             * Logs out the current user.
850             */
851            public void logout() {
852                    log("syst", "logout");
853                    user.setUserData("stayloggedintoken", "");
854                    setCookie("stayloggedintoken", "");
855                    application.logout();
856            }
857    
858            /**
859             * Returns the user of this wiki object.
860             *
861             * @return The user.
862             */
863            public User getUser() {
864                    return user;
865            }
866    
867            /**
868             * Sets the user.
869             *
870             * @param user The user.
871             */
872            public void setUser(User user) {
873                    this.user = user;
874                    logger.setUsername(user.getName());
875                    userLabel.setForeground(Color.BLACK);
876                    userLabel.setText(user.getName());
877                    logoutButton.setVisible(true);
878                    if (loginBackground != null) {
879                            getContentPane().removeAll();
880                            getContentPane().add(wikiPane);
881                            getContentPane().setBackground(Color.WHITE);
882                            loginBackground = null;
883                    }
884                    setCookie("lastusername", user.getName());
885            }
886    
887            /**
888             * Sets a cookie on the client.
889             *
890             * @param name The name of the cookie.
891             * @param value The value of the cookie.
892             */
893            public void setCookie(String name, String value) {
894                    Cookie cookie = new Cookie(name, value);
895                    cookie.setMaxAge(1000000000);
896                    getContainerContext().addCookie(cookie);
897            }
898    
899            /**
900             * Clears the given cookie on the client.
901             *
902             * @param name The name of the cookie.
903             */
904            public void clearCookie(String name) {
905                    getContainerContext().addCookie(new Cookie(name, null));
906            }
907    
908            /**
909             * Returns the value of the cookie on the client, or "" if there is no such cookie.
910             *
911             * @param name The name of the cookie.
912             * @return The value of the cookie.
913             */
914            public String getCookie(String name) {
915                    for (Cookie cookie : getContainerContext().getCookies()) {
916                            if ((name + "").equals(cookie.getName())) {
917                                    String value = cookie.getValue();
918                                    if (value == null) return "";
919                                    return value;
920                            }
921                    }
922                    return "";
923            }
924    
925            private ContainerContext getContainerContext() {
926                    return (ContainerContext) application.getContextProperty(
927                            ContainerContext.CONTEXT_PROPERTY_NAME
928                    );
929            }
930    
931            /**
932             * Returns the AceWiki engine.
933             *
934             * @return The AceWiki engine.
935             */
936            public AceWikiEngine getEngine() {
937                    return engine;
938            }
939    
940            /**
941             * Returns the language of this wiki instance.
942             * 
943             * @return The name of the language.
944             */
945            public String getLanguage() {
946                    return language;
947            }
948            
949            /**
950             * Returns the language handler.
951             *
952             * @return The language handler.
953             */
954            public LanguageHandler getLanguageHandler() {
955                    return engine.getLanguageHandler(language);
956            }
957    
958            /**
959             * Returns the logger object.
960             *
961             * @return The logger object.
962             */
963            public Logger getLogger() {
964                    return logger;
965            }
966    
967            /**
968             * Runs the task without showing a wait window while it is executed.
969             *
970             * @param task The task.
971             */
972            public void enqueueTask(Runnable task) {
973                    application.enqueueTask(taskQueue, task);
974            }
975    
976            /**
977             * Runs the task in an asynchronous way and shows a wait window while it is executed. The task
978             * is treated as a strong task that takes precedence over weak tasks.
979             *
980             * @param title The title of the wait window.
981             * @param message The message of the wait window.
982             * @param task The task.
983             */
984            public void enqueueStrongAsyncTask(String title, String message, Task task) {
985                    waitWindow = new MessageWindow(
986                            title,
987                            new ResourceImageReference("ch/uzh/ifi/attempto/acewiki/gui/img/wait.gif"),
988                            message,
989                            null,
990                            null
991                    );
992                    waitWindow.setClosable(false);
993                    showWindow(waitWindow);
994    
995                    strongTasks.add(task);
996            }
997    
998            /**
999             * Runs the task in an asynchronous way without showing a wait window. The task is treated as a
1000             * weak task that can be overtaken by strong tasks.
1001             *
1002             * @param task The task.
1003             */
1004            public void enqueueWeakAsyncTask(Task task) {
1005                    weakTasks.add(task);
1006            }
1007    
1008            /**
1009             * Returns information about AceWiki, like the version number and the release date. This
1010             * information is read from the file "acewiki.properties".
1011             *
1012             * @param key The key string.
1013             * @return The value for the given key.
1014             */
1015            public static String getInfo(String key) {
1016                    if (properties == null) {
1017                            String f = "ch/uzh/ifi/attempto/acewiki/acewiki.properties";
1018                            InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(f);
1019                            properties = new Properties();
1020                            try {
1021                                    properties.load(in);
1022                            } catch (Exception ex) {
1023                                    ex.printStackTrace();
1024                            }
1025                    }
1026    
1027                    return properties.getProperty(key);
1028            }
1029    
1030            /**
1031             * Cleans up when the object is no longer used.
1032             */
1033            public void dispose() {
1034                    disposed = true;
1035                    externalEventMonitor.removeExternalEventListener(this);
1036                    externalEventMonitor.dispose();
1037            }
1038    
1039            /**
1040             * This methods locks the general buttons of the wiki interface. When one of these buttons
1041             * is pressed, the locked-listener is called.
1042             *
1043             * @param lockedListener The listener to be called when one of the buttons is pressed.
1044             */
1045            public void lock(ActionListener lockedListener) {
1046                    if (locked) return;
1047                    locked = true;
1048                    this.lockedListener = lockedListener;
1049            }
1050    
1051            /**
1052             * Unlocks the wiki interface, if it has been locked before.
1053             */
1054            public void unlock() {
1055                    locked = false;
1056            }
1057    
1058            /**
1059             * Returns an image reference for a file in the AceWiki image directory with the given file
1060             * name.
1061             *
1062             * @param fileName The name of the image file.
1063             * @return The image reference.
1064             */
1065            public static ResourceImageReference getImage(String fileName) {
1066                    return Style.getImage("ch/uzh/ifi/attempto/acewiki/gui/img/" + fileName);
1067            }
1068    
1069    }