org.etudes.jforum.view.install.InstallAction.java Source code

Java tutorial

Introduction

Here is the source code for org.etudes.jforum.view.install.InstallAction.java

Source

/**********************************************************************************
 * $URL: https://source.sakaiproject.org/contrib/etudes/sakai-jforum/tags/2.9.11/jforum-tool/src/java/org/etudes/jforum/view/install/InstallAction.java $ 
 * $Id: InstallAction.java 83559 2013-04-30 19:03:29Z murthy@etudes.org $ 
 *********************************************************************************** 
 * 
 * Copyright (c) 2008 Etudes, Inc. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 * 
 * Portions completed before July 1, 2004 Copyright (c) 2003, 2004 Rafael Steil, All rights reserved, licensed under the BSD license. 
 * http://www.opensource.org/licenses/bsd-license.php 
 * 
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met: 
 * 
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following disclaimer. 
 * 2) Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution. 
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE 
 ***********************************************************************************/
package org.etudes.jforum.view.install;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.channels.FileChannel;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.etudes.jforum.ActionServletRequest;
import org.etudes.jforum.Command;
import org.etudes.jforum.ConfigLoader;
import org.etudes.jforum.DBConnection;
import org.etudes.jforum.DataSourceConnection;
import org.etudes.jforum.InstallServlet;
import org.etudes.jforum.SessionFacade;
import org.etudes.jforum.entities.UserSession;
import org.etudes.jforum.util.FileMonitor;
import org.etudes.jforum.util.I18n;
import org.etudes.jforum.util.MD5;
import org.etudes.jforum.util.preferences.ConfigKeys;
import org.etudes.jforum.util.preferences.SystemGlobals;
import org.etudes.jforum.util.preferences.SystemGlobalsListener;
import org.sakaiproject.db.cover.SqlService;

import freemarker.template.SimpleHash;
import freemarker.template.Template;

/**
 * @author Rafael Steil
 */
public class InstallAction extends Command {
    private static Log logger = LogFactory.getLog(InstallAction.class);

    public void welcome() throws Exception {
        this.checkLanguage();

        this.context.put("language", this.getFromSession("language"));
        this.context.put("database", this.getFromSession("database"));
        this.context.put("dbhost", this.getFromSession("dbHost"));
        this.context.put("dbuser", this.getFromSession("dbUser"));
        this.context.put("dbname", this.getFromSession("dbName"));
        this.context.put("dbpasswd", this.getFromSession("dbPassword"));
        this.context.put("dbencoding", this.getFromSession("dbEncoding"));
        this.context.put("use_pool", this.getFromSession("usePool"));
        this.context.put("forumLink", this.getFromSession("forumLink"));
        this.context.put("siteLink", this.getFromSession("siteLink"));
        this.context.put("dbdatasource", this.getFromSession("dbdatasource"));

        this.context.put("moduleAction", "install.htm");
    }

    private void checkLanguage() throws IOException {
        String lang = this.request.getParameter("l");
        if (lang == null || !I18n.languageExists(lang)) {
            return;
        }

        I18n.load(lang);

        UserSession us = new UserSession();
        us.setLang(lang);

        SessionFacade.add(us);
        this.addToSessionAndContext("language", lang);
    }

    private String getFromSession(String key) {
        return (String) this.request.getSession().getAttribute(key);
    }

    private void error() {
        this.context.put("moduleAction", "install_error.htm");
    }

    public void doInstall() throws Exception {
        Connection conn = null;

        if (!this.checkForWritableDir()) {
            return;
        }

        this.removeUserConfig();

        if (!"passed".equals(this.getFromSession("configureDatabase"))) {
            logger.info("Going to configure the database...");
            conn = this.configureDatabase();
            if (conn == null) {
                this.context.put("message", I18n.getMessage("Install.databaseError"));
                this.error();
                return;
            }
        }

        logger.info("Database configuration ok");

        // Database Configuration is ok
        this.addToSessionAndContext("configureDatabase", "passed");

        // Sakai never uses this kind of connection -- JMH
        //      DBConnection simpleConnection = new SimpleConnection();
        //      if (conn == null) {
        //         conn = simpleConnection.getConnection();
        //      }

        // Sakai uses a datasource
        DBConnection dsConnection = new DataSourceConnection();

        if (!"passed".equals(this.getFromSession("createTables")) && !this.createTables(conn)) {
            this.context.put("message", I18n.getMessage("Install.createTablesError"));
            dsConnection.releaseConnection(conn);
            this.error();
            return;
        }

        // Create tables is ok
        this.addToSessionAndContext("createTables", "passed");
        logger.info("Table creation is ok");

        if (!"passed".equals(this.getFromSession("importTablesData")) && !this.importTablesData(conn)) {
            this.context.put("message", I18n.getMessage("Install.importTablesDataError"));
            dsConnection.releaseConnection(conn);
            this.error();
            return;
        }

        // Dump is ok
        this.addToSessionAndContext("importTablesData", "passed");

        if (!this.updateAdminPassword(conn)) {
            this.context.put("message", I18n.getMessage("Install.updateAdminError"));
            dsConnection.releaseConnection(conn);
            this.error();
            return;
        }

        dsConnection.releaseConnection(conn);

        InstallServlet.setRedirect(this.request.getContextPath() + "/install/install"
                + SystemGlobals.getValue(ConfigKeys.SERVLET_EXTENSION) + "?module=install&action=finished");
    }

    private void removeUserConfig() {
        /*File f = new File(SystemGlobals.getValue(ConfigKeys.INSTALLATION_CONFIG));
        if (f.exists() && f.canWrite()) {
           try {
        f.delete();
           }
           catch (Exception e) {
        logger.info(e.toString());
           }
        }*/
    }

    public void finished() throws Exception {
        this.context.put("clickHere", I18n.getMessage("Install.clickHere"));
        this.context.put("forumLink", this.getFromSession("forumLink"));
        this.context.put("moduleAction", "install_finished.htm");

        String lang = this.getFromSession("language");
        if (lang == null) {
            lang = "en_US";
        }

        this.context.put("lang", lang);

        this.doFinalSteps();
        this.configureSystemGlobals();

        SystemGlobals.loadQueries(SystemGlobals.getValue(ConfigKeys.SQL_QUERIES_GENERIC));
        SystemGlobals.loadQueries(SystemGlobals.getValue(ConfigKeys.SQL_QUERIES_DRIVER));

        SessionFacade.remove(this.request.getSession().getId());
    }

    private void doFinalSteps() {
        try {
            // Modules Mapping
            String modulesMapping = SystemGlobals.getValue(ConfigKeys.CONFIG_DIR) + "/modulesMapping.properties";
            if (new File(modulesMapping).canWrite()) {
                Properties p = new Properties();
                p.load(new FileInputStream(modulesMapping));

                if (p.containsKey("install")) {
                    p.remove("install");

                    p.store(new FileOutputStream(modulesMapping), "Modified by JForum Installer");

                    this.addToSessionAndContext("mappingFixed", "true");
                    ConfigLoader.loadModulesMapping(SystemGlobals.getValue(ConfigKeys.CONFIG_DIR));
                }
            }
        } catch (Exception e) {
            logger.warn("Error while working on modulesMapping.properties: " + e);
        }

        try {
            // Index renaming
            String index = SystemGlobals.getApplicationPath() + "/index.htm";
            File indexFile = new File(index);
            if (indexFile.canWrite()) {
                String newIndex = SystemGlobals.getApplicationPath() + "/new_rename.htm";
                File newIndexFile = new File(newIndex);
                if (newIndexFile.exists()) {
                    indexFile.delete();
                    newIndexFile.renameTo(indexFile);

                    this.addToSessionAndContext("indexFixed", "true");
                }
            }
        } catch (Exception e) {
            logger.warn("Error while renaming index.htm: " + e);
        }
    }

    private void configureSystemGlobals() throws Exception {
        SystemGlobals.setValue(ConfigKeys.USER_HASH_SEQUENCE,
                MD5.crypt(this.getFromSession("dbPassword") + System.currentTimeMillis()));

        SystemGlobals.setValue(ConfigKeys.FORUM_LINK, this.getFromSession("forumLink"));
        SystemGlobals.setValue(ConfigKeys.HOMEPAGE_LINK, this.getFromSession("siteLink"));
        // SystemGlobals.setValue(ConfigKeys.I18N_DEFAULT, this.getFromSession("language"));
        SystemGlobals.setValue(ConfigKeys.INSTALLED, "true");
        SystemGlobals.saveInstallation();

        this.restartSystemGlobals();
    }

    private boolean importTablesData(Connection conn) throws Exception {
        boolean status = true;
        boolean autoCommit = conn.getAutoCommit();
        conn.setAutoCommit(false);

        String dbType = this.getFromSession("database");

        if (dbType.startsWith("mysql")) {
            dbType = "mysql";
        }

        List statements = this
                .readFromDat(SystemGlobals.getApplicationResourceDir() + "/setup-files/" + dbType + "_dump.dat");
        for (Iterator iter = statements.iterator(); iter.hasNext();) {
            String query = (String) iter.next();
            if (query == null || "".equals(query.trim())) {
                continue;
            }

            query = query.trim();

            Statement s = conn.createStatement();

            try {
                if (query.startsWith("UPDATE") || query.startsWith("INSERT") || query.startsWith("SET")) {
                    s.executeUpdate(query);
                } else if (query.startsWith("SELECT")) {
                    s.executeQuery(query);
                } else {
                    throw new Exception("Invalid query: " + query);
                }
            } catch (SQLException ex) {
                status = false;
                conn.rollback();
                logger.error("Error importing data for " + query + ": " + ex);
                this.context.put("exceptionMessage", ex.getMessage() + "\n" + query);
                break;
            } finally {
                s.close();
            }
        }

        conn.setAutoCommit(autoCommit);
        return status;
    }

    private boolean createTables(Connection conn) throws Exception {
        logger.info("Going to create tables...");
        String dbType = this.getFromSession("database");

        if ("postgresql".equals(dbType)) {
            this.dropPostgresqlTables(conn);
        } else if (dbType.startsWith("mysql")) {
            dbType = "mysql";
        }

        boolean status = true;
        List statements = this
                .readFromDat(SystemGlobals.getApplicationPath() + "/install/" + dbType + "_structure.dat");
        for (Iterator iter = statements.iterator(); iter.hasNext();) {
            String query = (String) iter.next();
            if (query == null || "".equals(query.trim())) {
                continue;
            }

            Statement s = conn.createStatement();

            try {
                s.executeUpdate(query);
            } catch (SQLException ex) {
                status = false;

                logger.error("Error executing query: " + query + ": " + ex);
                this.context.put("exceptionMessage", ex.getMessage() + "\n" + query);

                break;
            } finally {
                s.close();
            }
        }

        return status;
    }

    private boolean checkForWritableDir() {
        boolean canWriteToWebInf = this.canWriteToWebInf();
        boolean canWriteToIndex = this.canWriteToIndex();

        if (!canWriteToWebInf || !canWriteToIndex) {
            this.context.put("message", I18n.getMessage("Install.noWritePermission"));
            this.context.put("tryAgain", true);
            this.error();
            return false;
        }

        return true;
    }

    private boolean canWriteToWebInf() {
        return new File(SystemGlobals.getValue(ConfigKeys.CONFIG_DIR) + "/modulesMapping.properties").canWrite();
    }

    private boolean canWriteToIndex() {
        return new File(SystemGlobals.getApplicationPath() + "/index.htm").canWrite();
    }

    private void copyFile(String from, String to) throws Exception {
        FileChannel source = new FileInputStream(new File(from)).getChannel();
        FileChannel dest = new FileOutputStream(new File(to)).getChannel();

        source.transferTo(0, source.size(), dest);
        source.close();
        dest.close();
    }

    private Connection configureDatabase() throws Exception {
        // Respect the DB vendor defined in sakai -- JMH
        String database = SqlService.getVendor();

        if (database.startsWith("mysql")) {
            if ("mysql41".equals(database)) {
                String path = SystemGlobals.getValue(ConfigKeys.CONFIG_DIR) + "/database/mysql";

                this.copyFile(path + "/mysql_41.sql", path + "/mysql_41-bkp.sql");

                new File(path + "/mysql.sql").delete();
                new File(path + "/mysql_41.sql").renameTo(new File(path + "/mysql.sql"));

                this.copyFile(path + "/mysql_41-bkp.sql", path + "/mysql_41.sql");
            }

            database = "mysql";
        }

        // These values are never read -- JMH
        //      SystemGlobals.setValue(ConfigKeys.DATABASE_CONNECTION_IMPLEMENTATION, implementation);
        //      SystemGlobals.setValue(ConfigKeys.DATABASE_DRIVER_NAME, database);

        SystemGlobals.saveInstallation();
        this.restartSystemGlobals();

        int fileChangesDelay = SystemGlobals.getIntValue(ConfigKeys.FILECHANGES_DELAY);
        if (fileChangesDelay > 0) {
            /*FileMonitor.getInstance().addFileChangeListener(new SystemGlobalsListener(),
                  SystemGlobals.getValue(ConfigKeys.INSTALLATION_CONFIG), fileChangesDelay);*/
        }

        Connection conn = null;

        try {
            // Always use the datasource provided by sakai -- JMH
            DBConnection s = new DataSourceConnection();
            s.init();

            conn = s.getConnection();
        } catch (Exception e) {
            logger.warn("Error while trying to get a connection: " + e);
            this.context.put("exceptionMessage", e.getMessage());
            return null;
        }

        return conn;
    }

    private void restartSystemGlobals() throws Exception {
        String appPath = SystemGlobals.getApplicationPath();
        SystemGlobals.initGlobals(appPath, appPath + "/WEB-INF/config/SystemGlobals.properties");

        // There's no need for hot-swap DB config, since it isn't supported in Sakai -- JMH
        //SystemGlobals.loadAdditionalDefaults(SystemGlobals.getValue(ConfigKeys.DATABASE_DRIVER_CONFIG));

        /*if (new File(SystemGlobals.getValue(ConfigKeys.INSTALLATION_CONFIG)).exists()) {
        SystemGlobals.loadAdditionalDefaults(SystemGlobals.getValue(ConfigKeys.INSTALLATION_CONFIG));
        }*/
    }

    private boolean updateAdminPassword(Connection conn) throws Exception {
        logger.info("Going to update the administrator's password");

        boolean status = false;

        try {
            PreparedStatement p = conn
                    .prepareStatement("UPDATE jforum_users SET user_password = ? WHERE username = 'Admin'");
            p.setString(1, MD5.crypt(this.getFromSession("adminPassword")));
            p.executeUpdate();
            p.close();

            status = true;
        } catch (Exception e) {
            logger.warn("Error while trying to update the administrator's password: " + e);
            this.context.put("exceptionMessage", e.getMessage());
        }

        return status;
    }

    public void checkInformation() throws Exception {
        String language = this.request.getParameter("language");
        String database = this.request.getParameter("database");
        String dbHost = this.request.getParameter("dbhost");
        String dbUser = this.request.getParameter("dbuser");
        String dbName = this.request.getParameter("dbname");
        String dbPassword = this.request.getParameter("dbpasswd");
        String dbEncoding = this.request.getParameter("dbencoding");
        String dbEncodingOther = this.request.getParameter("dbencoding_other");
        String usePool = this.request.getParameter("use_pool");
        String forumLink = this.request.getParameter("forum_link");
        String adminPassword = this.request.getParameter("admin_pass1");

        dbHost = this.notNullDefault(dbHost, "localhost");
        dbEncodingOther = this.notNullDefault(dbEncodingOther, "utf-8");
        dbEncoding = this.notNullDefault(dbEncoding, dbEncodingOther);
        forumLink = this.notNullDefault(forumLink, "http://localhost");
        dbName = this.notNullDefault(dbName, "jforum");

        if ("hsqldb".equals(database)) {
            dbUser = this.notNullDefault(dbUser, "sa");
        }

        this.addToSessionAndContext("language", language);
        this.addToSessionAndContext("database", database);
        this.addToSessionAndContext("dbHost", dbHost);
        this.addToSessionAndContext("dbUser", dbUser);
        this.addToSessionAndContext("dbName", dbName);
        this.addToSessionAndContext("dbPassword", dbPassword);
        this.addToSessionAndContext("dbEncoding", dbEncoding);
        this.addToSessionAndContext("usePool", usePool);
        this.addToSessionAndContext("forumLink", forumLink);
        this.addToSessionAndContext("siteLink", this.request.getParameter("site_link"));
        this.addToSessionAndContext("adminPassword", adminPassword);
        this.addToSessionAndContext("dbdatasource", this.request.getParameter("dbdatasource"));
        this.addToSessionAndContext("db_connection_type", this.request.getParameter("db_connection_type"));

        this.addToSessionAndContext("configureDatabase", null);
        this.addToSessionAndContext("createTables", null);
        this.addToSessionAndContext("importTablesData", null);

        this.context.put("canWriteToWebInf", this.canWriteToWebInf());
        this.context.put("canWriteToIndex", this.canWriteToIndex());

        this.context.put("moduleAction", "install_check_info.htm");
    }

    private List readFromDat(String filename) throws Exception {
        List l = new ArrayList();

        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream in = new ObjectInputStream(fis);
        l = (ArrayList) in.readObject();
        in.close();

        return l;
    }

    private void dropPostgresqlTables(Connection conn) throws Exception {
        String[] tables = { "jforum_banlist", "jforum_banlist_seq", "jforum_categories",
                "jforum_categories_order_seq", "jforum_categories_seq", "jforum_config", "jforum_config_seq",
                "jforum_forums", "jforum_forums_seq", "jforum_groups", "jforum_groups_seq", "jforum_posts",
                "jforum_posts_seq", "jforum_posts_text", "jforum_privmsgs", "jforum_privmsgs_seq",
                "jforum_privmsgs_text", "jforum_ranks", "jforum_ranks_seq", "jforum_role_values", "jforum_roles",
                "jforum_roles_seq", "jforum_search_results", "jforum_search_topics", "jforum_search_wordmatch",
                "jforum_search_words", "jforum_search_words_seq", "jforum_sessions", "jforum_smilies",
                "jforum_smilies_seq", "jforum_themes", "jforum_themes_seq", "jforum_topics", "jforum_topics_seq",
                "jforum_topics_watch", "jforum_user_groups", "jforum_users", "jforum_users_seq", "jforum_vote_desc",
                "jforum_vote_desc_seq", "jforum_vote_results", "jforum_vote_voters", "jforum_words",
                "jforum_words_seq", "jforum_karma_seq", "jforum_karma", "jforum_bookmarks_seq", "jforum_bookmarks",
                "jforum_quota_limit", "jforum_quota_limit_seq", "jforum_extension_groups_seq",
                "jforum_extension_groups", "jforum_extensions_seq", "jforum_extensions", "jforum_attach_seq",
                "jforum_attach", "jforum_attach_desc_seq", "jforum_attach_desc", "jforum_attach_quota_seq",
                "jforum_attach_quota" };

        for (int i = 0; i < tables.length; i++) {
            Statement s = conn.createStatement();
            String query = tables[i].endsWith("_seq") ? "DROP SEQUENCE " : "DROP TABLE ";
            query += tables[i];

            try {
                s.executeUpdate(query);
            } catch (SQLException e) {
                logger.info("IGNORE: " + e.getMessage());
            }

            s.close();
        }
    }

    private void addToSessionAndContext(String key, String value) {
        this.request.getSession().setAttribute(key, value);
        this.context.put(key, value);
    }

    private String notNullDefault(String value, String useDefault) {
        if (value == null || value.trim().equals("")) {
            return useDefault;
        }

        return value;
    }

    /** 
     * @see org.etudes.jforum.Command#list()
     */
    public void list() throws Exception {
        this.welcome();
    }

    /** 
     * @see org.etudes.jforum.Command#process()
     */
    public Template process(ActionServletRequest request, HttpServletResponse response, SimpleHash context)
            throws Exception {
        this.setTemplateName("default/empty.htm");
        return super.process(request, response, context);
    }
}