Package org.fao.geonet

Source Code of org.fao.geonet.DatabaseMigration$Version

package org.fao.geonet;

import com.google.common.util.concurrent.Callables;
import com.vividsolutions.jts.util.Assert;
import jeeves.server.sources.http.ServletPathFinder;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.Pair;
import org.fao.geonet.lib.DatabaseType;
import org.fao.geonet.lib.Lib;
import org.fao.geonet.utils.Log;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.servlet.ServletContext;
import javax.sql.DataSource;

/**
* Postprocessor that runs after the jdbcDataSource bean has been initialized and migrates the
* database as soon as it is.
* <p/>
* User: Jesse
* Date: 9/2/13
* Time: 8:01 PM
*/
public class DatabaseMigration implements BeanPostProcessor {
    private static final int VERSION_NUMBER_ID_BEFORE_2_11 = 15;
    private static final int SUBVERSION_NUMBER_ID_BEFORE_2_11 = 16;
    private static final String JAVA_MIGRATION_PREFIX = "java:";

    @Autowired
    private SystemInfo systemInfo;
    @Autowired
    private ApplicationContext _applicationContext;

    private Callable<LinkedHashMap<String, List<String>>> _migration;

    private Logger _logger = Log.createLogger(Geonet.GEONETWORK);
    private boolean foundErrors;

    @Override
    public final Object postProcessBeforeInitialization(final Object bean, final String beanName) {
        return bean;  // Do nothing
    }

    @Override
    public final Object postProcessAfterInitialization(final Object bean, final String beanName) {
        if (bean instanceof DataSource) {
            try {
                String version;
                String subVersion;
                ServletContext servletContext;
                String path;


                try {
                    servletContext = _applicationContext.getBean(ServletContext.class);
                } catch (NoSuchBeanDefinitionException e) {
                    if (_applicationContext instanceof WebApplicationContext) {
                        WebApplicationContext context = (WebApplicationContext) _applicationContext;
                        servletContext = context.getServletContext();
                    } else {
                        _logger.warning("No servletContext found.  Database migration aborted.");
                        return bean;
                    }
                }

                version = this.systemInfo.getVersion();
                subVersion = this.systemInfo.getSubVersion();
                ServletPathFinder pathFinder = new ServletPathFinder(servletContext);

                path = pathFinder.getAppPath();
                migrateDatabase(servletContext, path, (DataSource) bean, version, subVersion);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
            return bean;
        }
        return bean;
    }

    public final void setMigration(final LinkedHashMap<String, List<String>> migration) {
        this._migration = Callables.returning(migration);
    }

    public final void setMigrationLoader(final Callable<LinkedHashMap<String, List<String>>> migration) {
        this._migration = migration;
    }

    private void migrateDatabase(ServletContext servletContext, String path, final DataSource dataSource, final String webappVersion,
                                 final String subVersion) throws Exception {
        _logger.info("  - Migration ...");

        Connection conn = null;
        Statement statement = null;
        try {
            conn = dataSource.getConnection();
            statement = conn.createStatement();
            this.foundErrors = doMigration(webappVersion, subVersion, servletContext, path, conn, statement);

        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }
        }
    }

    boolean doMigration(String webappVersion, String subVersion, ServletContext servletContext, String path, Connection conn,
                        Statement statement) throws Exception {
        // Get db version and subversion
        Pair<String, String> dbVersionInfo = getDatabaseVersion(statement);
        String dbVersion = dbVersionInfo.one();
        String dbSubVersion = dbVersionInfo.two();

        boolean anyMigrationError = false;

        // Migrate db if needed
        _logger.info("      Webapp   version:" + webappVersion + " subversion:" + subVersion);
        _logger.info("      Database version:" + dbVersion + " subversion:" + dbSubVersion);
        if (dbVersion == null || webappVersion == null) {
            _logger.warning("      Database does not contain any version information. Check that the database is a GeoNetwork "
                            + "database with data.  Migration step aborted.");
            return true;
        }

        Version from = new Version(), to = new Version();

        try {
            from = parseVersionNumber(dbVersion);
            to = parseVersionNumber(webappVersion);
        } catch (Exception e) {
            _logger.warning("      Error parsing version numbers: " + e.getMessage());
            e.printStackTrace();
        }

        switch (to.compareTo(from)) {
            case -1:
                _logger.info("      Running on a newer database version.");
                break;
            case 0:
                _logger.info("      Webapp version = Database version, no migration task to apply.");
                break;
            case 1:
                boolean anyMigrationAction = false;

                // Migrating from 2.0 to 2.5 could be done 2.0 -> 2.3 -> 2.4 -> 2.5
                String dbType = DatabaseType.lookup(conn).toString();
                _logger.debug("      Migrating from " + from + " to " + to + " (dbtype:" + dbType + ")...");

                _logger.info("      Loading SQL migration step configuration from <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n ...");
                for (Map.Entry<String, List<String>> migrationEntry : _migration.call().entrySet()) {
                    Version versionNumber = parseVersionNumber(migrationEntry.getKey());
                    if (versionNumber.compareTo(from) > 0 && versionNumber.compareTo(to) < 1) {
                        _logger.info("       - running tasks for " + versionNumber + "...");
                        for (String file : migrationEntry.getValue()) {
                            if (file.startsWith(JAVA_MIGRATION_PREFIX)) {
                                anyMigrationAction = true;
                                anyMigrationError |= runJavaMigration(conn, file);
                            } else {
                                int lastSep = file.lastIndexOf('/');
                                Assert.isTrue(lastSep > -1, file + " has the wrong format");
                                String filePath = path + file.substring(0, lastSep);

                                String filePrefix = file.substring(lastSep);
                                anyMigrationAction = true;
                                _logger.info("         - SQL migration file:" + filePath + " prefix:" + filePrefix + " ...");
                                try {
                                    Lib.db.insertData(servletContext, statement, path, filePath, filePrefix);
                                } catch (Exception e) {
                                    _logger.info("          Errors occurs during SQL migration file: " + e.getMessage());
                                    e.printStackTrace();
                                    anyMigrationError = true;
                                }
                            }
                        }
                    }
                }
                if (anyMigrationAction && !anyMigrationError) {
                    _logger.info("      Successfull migration.\n"
                                 + "      Catalogue administrator still need to update the catalogue\n"
                                 + "      logo and data directory in order to complete the migration process.\n"
                                 + "      Lucene index rebuild is also recommended after migration."
                    );
                }

                if (!anyMigrationAction) {
                    _logger.warning("      No migration task found between webapp and database version.\n"
                                    + "      The system may be unstable or may failed to start if you try to run \n"
                                    + "      the current GeoNetwork " + webappVersion + " with an older database (ie. " + dbVersion
                                    + "\n"
                                    + "      ). Try to run the migration task manually on the current database\n"
                                    + "      before starting the application or start with a new empty database.\n"
                                    + "      Sample SQL scripts for migration could be found in WEB-INF/sql/migrate folder.\n"
                    );

                }

                if (anyMigrationError) {
                    _logger.warning("      Error occurs during migration. Check the log file for more details.");
                }
                // TODO : Maybe some migration stuff has to be done in Java ?
                break;
        }

        return anyMigrationError;
    }

    private boolean runJavaMigration(Connection conn, String file) {
        try {
            String className = file.substring(JAVA_MIGRATION_PREFIX.length());
            _logger.info("         - Java migration class:" + className);

            DatabaseMigrationTask task = (DatabaseMigrationTask) Class.forName(className).newInstance();
            task.update(conn);
            return false;
        } catch (Throwable e) {
            _logger.info("          Errors occurs during Java migration file: " + e.getMessage());
            e.printStackTrace();
            return true;
        }
    }

    /**
     * Return database version and subversion number.
     *
     * @return
     */
    private Pair<String, String> getDatabaseVersion(Statement statement) throws SQLException {
        String version = null;
        String subversion = null;

        try {
            version = newLookup(statement, Geonet.Settings.VERSION);
            subversion = newLookup(statement, Geonet.Settings.SUBVERSION);

            if (version == null) {
                // Before 2.11, settings was a tree. Check using keys
                version = oldLookup(statement, VERSION_NUMBER_ID_BEFORE_2_11);
                subversion = oldLookup(statement, SUBVERSION_NUMBER_ID_BEFORE_2_11);
            }
        } catch (SQLException e) {
            _logger.info("     Error getting database version: " + e.getMessage() +
                         ". Probably due to an old version. Trying with new Settings structure.");
        }

        return Pair.read(version, subversion);
    }

    private String newLookup(Statement statement, String key) throws SQLException {
        ResultSet results = null;
        try {
            final String newGetVersion = "SELECT value FROM Settings WHERE name = '" + key + "'";
            results = statement.executeQuery(newGetVersion);
            if (results.next()) {
                return results.getString(1);
            }
            return null;
        } finally {
            if (results != null) {
                results.close();
            }
        }
    }

    private String oldLookup(Statement statement, int key) throws SQLException {
        ResultSet results = null;
        try {
            final String newGetVersion = "SELECT value FROM Settings WHERE id = " + key;
            results = statement.executeQuery(newGetVersion);
            if (results.next()) {
                return results.getString(1);
            }
            return null;
        } finally {
            if (results != null) {
                results.close();
            }
        }
    }

    /**
     * Parses a version number removing extra "-*" element and returning an integer. "2.7.0-SNAPSHOT" is returned as 270.
     *
     * @param number The version number to parse
     * @return The version number as an integer
     * @throws Exception
     */
    private Version parseVersionNumber(String number) throws Exception {
        // Remove extra "-SNAPSHOT" info which may be in version number
        int dashIdx = number.indexOf("-");
        if (dashIdx != -1) {
            number = number.substring(0, number.indexOf("-"));
        }
        switch (numDots(number)) {
            case 0 :
                number += ".0.0";
                break;
            case 1 :number += ".0";
                break;
        }

        final String[] parts = number.split("\\.");
        String major = parts[0];
        String minor = parts.length > 1 ? parts[1] : "0";
        String micro = parts.length > 2 ? parts[2] : "0";
        return new Version(major, minor, micro);
    }

    private int numDots(String number) {
        int num = 0;

        for (int i = 0; i < number.length(); i++) {
            if (number.charAt(i) == '.') {
                num++;
            }
        }

        return num;
    }

    public LinkedHashMap<String, List<String>> getMigrationConfig() throws Exception {
        return _migration.call();
    }

    public boolean isFoundErrors() {
        return foundErrors;
    }

    public static class Version implements Comparable<Version> {
        private final int major, minor, micro;

        public Version(String major, String minor, String micro) {
            this.major = Integer.parseInt(major);
            this.minor = Integer.parseInt(minor);
            this.micro = Integer.parseInt(micro);
        }

        public Version() {
            this("0","0","0");
        }

        @Override
        public int compareTo(Version o) {
            if (major != o.major) {
                return major - o.major;
            }
            if (minor != o.minor) {
                return minor - o.minor;
            }
            if (micro != o.micro) {
                return micro - o.micro;
            }
            return 0;
        }

        @Override
        public String toString() {
            return major + "." + minor + "." + micro;
        }
    }
}
TOP

Related Classes of org.fao.geonet.DatabaseMigration$Version

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.