esg.node.util.migrate.UserMigrationTool.java Source code

Java tutorial

Introduction

Here is the source code for esg.node.util.migrate.UserMigrationTool.java

Source

/***************************************************************************
*                                                                          *
*  Organization: Earth System Grid Federation                              *
*                                                                          *
****************************************************************************
*                                                                          *
*   Copyright (c) 2009, Lawrence Livermore National Security, LLC.         *
*   Produced at the Lawrence Livermore National Laboratory                 *
*   LLNL-CODE-420962                                                       *
*                                                                          *
*   All rights reserved. This file is part of the:                         *
*   Earth System Grid Federation (ESGF)                                    *
*   Data Node Software Stack, Version 1.0                                  *
*                                                                          *
*   For details, see http://esgf.org/esg-node-site/                        *
*   Please also read this link                                             *
*    http://esgf.org/LICENSE                                               *
*                                                                          *
*   * Redistribution and use in source and binary forms, with or           *
*   without modification, are permitted provided that the following        *
*   conditions are met:                                                    *
*                                                                          *
*   * Redistributions of source code must retain the above copyright       *
*   notice, this list of conditions and the disclaimer below.              *
*                                                                          *
*   * Redistributions in binary form must reproduce the above copyright    *
*   notice, this list of conditions and the disclaimer (as noted below)    *
*   in the documentation and/or other materials provided with the          *
*   distribution.                                                          *
*                                                                          *
*   Neither the name of the LLNS/LLNL 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 LAWRENCE    *
*   LIVERMORE NATIONAL SECURITY, LLC, THE U.S. DEPARTMENT OF ENERGY 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 esg.node.util.migrate;

import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import java.util.regex.Matcher;

import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool.impl.GenericObjectPool;

import esg.common.util.ESGFProperties;
import esg.node.security.ESGFDataAccessException;
import esg.node.security.GroupRoleDAO;
import esg.node.security.UserInfo;
import esg.node.security.UserInfoCredentialedDAO;
import esg.node.security.UserInfoDAO;

/**
   Description:
   A tool for migrating user accounts from the "gateway" to the "idp" node
    
*/
public final class UserMigrationTool {

    private static Log log = LogFactory.getLog(UserMigrationTool.class);

    //For source database access...
    private PoolingDataSource sourceDataSource = null;
    private GenericObjectPool connectionPool = null;
    private QueryRunner queryRunner = null;

    //For target (local) database access...
    private UserInfo userInfo = null;
    private UserInfoCredentialedDAO userDAO = null;
    private GroupRoleDAO groupRoleDAO = null;

    //hostname of source machine
    private String source = null;

    //hostname of "this" target machine. (remember this migration is a *pull* operation)
    private String myHost = null;

    //-------------------------------------------------------
    //Remote "Gateway" queries
    //-------------------------------------------------------
    private static final String sourceUserInfoQuery = "select firstname, lastname, email, username, password, dn, organization, city, state, country, openid from security.user";
    private static final String sourceGroupInfoQuery = "select g.name as name, g.description as description, g.visible as visible, g.automatic_approval as automatic_approval from security.group as g";
    private static final String sourceRoleInfoQuery = "select name, description from security.role";
    private static final String sourcePermissionInfoQuery = "select u.openid as oid, g.name as gname, r.name as rname from security.user as u, security.group as g, security.role as r, security.membership as m, security.status as st where m.user_id=u.id and m.group_id=g.id and m.role_id=r.id and m.status_id=st.id and st.name='valid' and u.openid like 'http%'";
    //-------------------------------------------------------

    private boolean verbatimMigration = false;

    public UserMigrationTool() {
    }

    //ToDo: should throw exception here
    public UserMigrationTool init(Properties props) {
        log.trace("props = " + props);
        if (setupTargetResources())
            setupSourceResources(props);
        try {
            java.util.Properties esgfProperties = new java.util.Properties();
            esgfProperties.load(new java.io.BufferedReader(
                    new java.io.FileReader(System.getenv().get("ESGF_HOME") + "/config/esgf.properties")));
            myHost = esgfProperties.getProperty("esgf.host");
            myHost.trim(); //pretty much calling this to force an NPE if property is not found... so I can stop here (die early).
            verbatimMigration = Boolean
                    .valueOf(System.getProperty("verbatim_migration", System.getenv().get("verbatim_migration")));
        } catch (Exception e) {
            log.error(e);
        }
        return this;
    }

    //-------------------------------------------------------
    //Remote "Gateway" resouce setup...
    //-------------------------------------------------------
    public UserMigrationTool setupSourceResources(Properties props) {

        log.info("Setting up source data source ");
        if (props == null) {
            log.error("Property object is [" + props + "]: Cannot setup up data source");
            return this;
        }
        String user = props.getProperty("db.user", "dbsuper");
        String password = props.getProperty("db.password");

        //Ex: jdbc:postgresql://pcmdi3.llnl.gov:5432/esgcet
        String database = props.getProperty("db.database", "gateway-esg");
        String host = props.getProperty("db.host", "localhost");
        String port = props.getProperty("db.port", "5432"); //or perhaps 8080
        String protocol = props.getProperty("db.protocol", "jdbc:postgresql:");

        this.source = host;
        return this.setupSourceResources(protocol, host, port, database, user, password);

    }

    public UserMigrationTool setupSourceResources(String protocol, String host, String port, String database,
            String user, String password) {

        System.out.println("Setting up source resources...");

        String connectURI = protocol + "//" + host + ":" + port + "/" + database; //zoiks
        log.debug("Source Connection URI  = " + connectURI);
        log.debug("Source Connection User = " + user);
        log.debug("Source Connection Password = " + (null == password ? password : "********"));
        this.connectionPool = new GenericObjectPool(null);
        ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectURI, user, password);
        PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,
                connectionPool, null, null, false, true);
        this.sourceDataSource = new PoolingDataSource(connectionPool);
        this.queryRunner = new QueryRunner(sourceDataSource);
        return this;
    }

    //-------------------------------------------------------
    //Target (local) resource setup...
    //-------------------------------------------------------
    private boolean setupTargetResources() {
        return this.setupTargetResources(null, null);
    }

    private boolean setupTargetResources(UserInfoCredentialedDAO userDAO_, GroupRoleDAO groupRoleDAO_) {

        System.out.println("Setting up target (local) resources...");

        try {
            ESGFProperties env = new ESGFProperties();
            if (userDAO_ == null) {
                log.trace("need to instantiate user data object...");
                this.userDAO = new UserInfoCredentialedDAO("rootAdmin", env.getAdminPassword(), env);
            } else {
                this.userDAO = userDAO;
            }
            log.trace("userDAO = " + (userDAO == null ? "[NULL]" : "[OK]"));

            if (groupRoleDAO_ == null) {
                log.trace("need to instantiate group/role data object...");
                this.groupRoleDAO = new GroupRoleDAO(env);
            } else {
                log.trace("re-using previously instantiated group/role data object");
                this.groupRoleDAO = groupRoleDAO_;
            }
            log.trace("group/role = " + (groupRoleDAO == null ? "[NULL]" : "[OK]"));

        } catch (java.io.IOException e) {
            e.printStackTrace();
        }

        return ((null != this.userDAO) && (null != this.groupRoleDAO));
    }

    public void shutdownSourceResources() {
        log.info("Shutting Down Source Database Resource...");
        try {
            connectionPool.close();
        } catch (Exception ex) {
            log.error("Problem with closing connection Pool!", ex);
        }
        sourceDataSource = null;
    }

    //-------------------------------------------------------
    //Pump the data from source --to-> target
    //-------------------------------------------------------

    public int migrate() {
        migrateRoles();
        migrateGroups();
        migrateUsers();
        migratePermissions();
        return 0;
    }

    public int migrateRoles() {
        int ret = 0;
        ResultSetHandler<Integer> rolesResultSetHandler = new ResultSetHandler<Integer>() {
            public Integer handle(ResultSet rs) throws SQLException {
                int i = 0;
                String roleName = null;
                while (rs.next()) {
                    roleName = transformRoleName(rs.getString(1));
                    //                                              [name]         [description]
                    if (UserMigrationTool.this.groupRoleDAO.addRole(roleName, rs.getString(2))) {
                        i++;
                        System.out.println("Migrated role #" + i + ": " + roleName);
                    }
                }
                return i;
            }
        };

        try {
            ret = queryRunner.query(sourceRoleInfoQuery, rolesResultSetHandler);
            log.info("Migrated [" + ret + "] role records");
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return ret;
    }

    public int migrateGroups() {
        int ret = 0;
        ResultSetHandler<Integer> groupsResultSetHandler = new ResultSetHandler<Integer>() {
            public Integer handle(ResultSet rs) throws SQLException {
                int i = 0;
                String groupName = transformGroupName(rs.getString(1));
                while (rs.next()) {
                    //                                               [name]         [description]    [visible]         [automatic_approval]
                    if (UserMigrationTool.this.groupRoleDAO.addGroup(groupName, rs.getString(2), rs.getBoolean(3),
                            rs.getBoolean(4))) {
                        i++;
                        System.out.println("Migrated group #" + i + ": " + groupName);
                    }
                }
                return i;
            }
        };

        try {
            ret = queryRunner.query(sourceGroupInfoQuery, groupsResultSetHandler);
            log.info("Migrated [" + ret + "] group records");
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return ret;
    }

    //Simple helper function to transform strings.  In this case we
    //use it to turn User to user, which is more amenable to the P2P
    //group naming convention. Clearly, this can become as ellaborate
    //as we may wish...
    //User -> user
    //default -> user
    private String transformGroupName(String in) {
        String out = null;
        if (in.equals("User")) {
            out = in.toLowerCase();
        } else if (in.equals("default")) {
            out = "user";
        } else {
            out = in;
        }
        return out;
    }

    private String transformRoleName(String in) {
        String out = null;
        if (in.equals("User")) {
            out = in.toLowerCase();
        } else if (in.equals("default")) {
            out = "user";
        } else {
            out = in;
        }
        return out;
    }

    public int migrateUsers() {
        int ret = 0;
        ResultSetHandler<Integer> usersResultSetHandler = new ResultSetHandler<Integer>() {
            public Integer handle(ResultSet rs) throws SQLException {
                int i = 0;
                int migrateCount = 0;
                int transCount = 0;
                int errorCount = 0;
                String currentUsername = null;
                String openid = null;
                String target = null;
                while (rs.next()) {
                    try {
                        currentUsername = rs.getString("username");
                        if (currentUsername != null && currentUsername.equals("rootAdmin")) {
                            System.out.println("NOTE: Will not overwrite local rootAdmin information");
                            continue;
                        }
                        openid = rs.getString("openid");
                        log.trace("Inspecting openid: " + openid);
                        UserInfo userInfo = UserMigrationTool.this.userDAO.getUserById(openid);
                        userInfo.setFirstName(rs.getString("firstname")).
                        //setMiddleName(rs.getString("middlename")).
                        setLastName(rs.getString("lastname")).setEmail(rs.getString("email"))
                                .setDn(rs.getString("dn")).setOrganization(rs.getString("organization")).
                        //setOrgType(rs.getString("organization_type")).
                        setCity(rs.getString("city")).setState(rs.getString("state"))
                                .setCountry(rs.getString("country"));
                        //NOTE: verification token not applicable
                        //Status code msut be set separately... (below) field #13
                        //Password literal must be set separately... (see setPassword - with true boolean, below) field #14

                        if (currentUsername != null)
                            userInfo.setUserName(currentUsername);

                        if (userInfo.getOpenid().matches("http.*" + UserMigrationTool.this.source + ".*")) {
                            if (verbatimMigration) {
                                UserMigrationTool.this.userDAO.addUser(userInfo);
                                UserMigrationTool.this.userDAO.setPassword(userInfo.getOpenid(),
                                        rs.getString("password"), true); //password (literal)
                                System.out.println("**Migrated User #" + i + ": " + userInfo.getUserName() + " ("
                                        + openid + ") --> " + userInfo.getOpenid());
                            }
                            userInfo.setOpenid(null); //This will cause the DAO to generate a local Openid
                            UserMigrationTool.this.userDAO.addUser(userInfo);
                            //UserMigrationTool.this.userDAO.setStatusCode(userInfo.getOpenid(),rs.getInt(13)); //statusCode
                            UserMigrationTool.this.userDAO.setPassword(userInfo.getOpenid(),
                                    rs.getString("password"), true); //password (literal)
                            System.out.println("Migrated User #" + i + ": " + userInfo.getUserName() + " (" + openid
                                    + ") --> " + userInfo.getOpenid());
                            migrateCount++;
                        } else {
                            UserMigrationTool.this.userDAO.addUser(userInfo);
                            System.out.println("Transferred User #" + i + ": " + userInfo.getUserName() + " --> "
                                    + userInfo.getOpenid());
                            transCount++;
                        }
                        i++;
                    } catch (Throwable t) {
                        log.error("Sorry, could NOT migrate/transfer user: " + currentUsername);
                        errorCount++;
                    }
                }
                log.info("Inspected " + i + " User Records: Migrated " + migrateCount + ", Transferred "
                        + transCount + ", with " + errorCount + " failed");
                return i;
            }
        };

        try {
            ret = queryRunner.query(sourceUserInfoQuery, usersResultSetHandler);
            log.info("Migrated [" + ret + "] user records");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return ret;

    }

    private String transformOpenid(String sourceOpenid) {

        String openid = sourceOpenid;
        String username = null;

        if (sourceOpenid.matches("http.*" + this.source + ".*")) {
            //Discern if they user put in a an openid url or just a username, 
            //set values accordingly...
            Matcher openidMatcher = UserInfoDAO.openidUrlPattern.matcher(openid);
            String openidHost = null;
            String openidPort = null;
            String openidPath = null;
            if (openidMatcher.find()) {
                openidHost = openidMatcher.group(1);
                openidPort = openidMatcher.group(2);
                openidPath = openidMatcher.group(3);
                username = openidMatcher.group(4);

                log.trace("submitted openid = " + openid);
                log.trace("openidHost = " + openidHost);
                log.trace("openidPort = " + openidPort);
                log.trace("openidPath = " + openidPath);
                log.trace("username   = " + username);

                //reconstruct and transform the url scrubbing out port if necessary...
                openid = "https://" + myHost + "/esgf-idp/openid/" + username;
                log.trace("transformed oid: " + sourceOpenid + " -> " + openid);
                return openid;
            } else {
                log.warn("Could not transform openid: " + sourceOpenid);
            }
        }
        return openid;
    }

    public int migratePermissions() {
        int ret = 0;
        ResultSetHandler<Integer> permissionsResultSetHandler = new ResultSetHandler<Integer>() {
            public Integer handle(ResultSet rs) throws SQLException {
                int i = 0;
                String oid = null;
                String gname = null;
                String rname = null;
                int errorCount = 0;
                int migrateCount = 0;
                while (rs.next()) {
                    try {
                        if (verbatimMigration) {
                            oid = rs.getString(1);
                            gname = transformGroupName(rs.getString(2));
                            rname = transformRoleName(rs.getString(3));
                            if (UserMigrationTool.this.userDAO.addPermissionByOpenid(oid, gname, rname)) {
                                migrateCount++;
                                System.out.println("**Migrated Permission #" + i + ": oid[" + oid + "] [" + gname
                                        + "] [" + rname + "]");
                            }
                        }
                        oid = transformOpenid(rs.getString(1));
                        gname = transformGroupName(rs.getString(2));
                        rname = transformRoleName(rs.getString(3));
                        if (UserMigrationTool.this.userDAO.addPermissionByOpenid(oid, gname, rname)) {
                            migrateCount++;
                            System.out.println("Migrated Permission #" + i + ": oid[" + oid + "] [" + gname + "] ["
                                    + rname + "]");
                        }
                    } catch (ESGFDataAccessException e) {
                        log.error("Sorry, could NOT create permission tuple: oid[" + oid + "] g[" + gname + "] r["
                                + rname + "] ");
                        errorCount++;
                    }
                    i++;
                }
                log.info("Inspected " + i + " Permission Records: Migrated " + migrateCount + ", with " + errorCount
                        + " failed");
                return i;
            }
        };

        try {
            ret = queryRunner.query(sourcePermissionInfoQuery, permissionsResultSetHandler);
            log.info("Migrated [" + ret + "] permission records");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return ret;
    }

    //-------------------------------------------------------
    //Main
    //-------------------------------------------------------
    public static void main(String[] args) {
        try {
            //Enter the connection URI information
            //setup source connection
            Properties props = new Properties();
            if (args.length >= 4) {
                for (int i = 0; i < (args.length - 1); i++) {
                    System.out.println();
                    if ("-U".equals(args[i])) {
                        i++;
                        System.out.print("user = ");
                        if (args[i].startsWith("-")) {
                            --i;
                            continue;
                        }
                        props.setProperty("db.user", args[i]);
                        System.out.print(args[i]);
                        continue;
                    }
                    if ("-h".equals(args[i])) {
                        i++;
                        System.out.print("host = ");
                        if (args[i].startsWith("-")) {
                            --i;
                            continue;
                        }
                        props.setProperty("db.host", args[i]);
                        System.out.print(args[i]);
                        continue;
                    }
                    if ("-p".equals(args[i])) {
                        i++;
                        System.out.print("port = ");
                        if (args[i].startsWith("-")) {
                            --i;
                            continue;
                        }
                        props.setProperty("db.port", args[i]);
                        System.out.print(args[i]);
                        continue;
                    }
                    if ("-d".equals(args[i])) {
                        i++;
                        System.out.print("database = ");
                        if (args[i].startsWith("-")) {
                            --i;
                            continue;
                        }
                        props.setProperty("db.database", args[i]);
                        System.out.print(args[i]);
                        continue;
                    }
                }
                System.out.println();
            } else {
                System.out.println("\nUsage:");
                System.out.println(
                        "  java -jar esgf-security-user-migration-x.x.x.jar -U <username> -h <host> -p <port> -d <database>");
                System.out.println("  (hit return and then enter your password)\n");
                System.exit(1);
            }

            char password[] = null;
            try {
                password = PasswordField.getPassword(System.in, "Enter source database password: ");
            } catch (IOException ioe) {
                System.err.println("Ooops sumthin' ain't right with the input... :-(");
                System.exit(1);
                ioe.printStackTrace();
            }
            if (password == null) {
                System.out.println("No password entered");
                System.exit(1);
            }

            props.setProperty("db.password", String.valueOf(password));

            System.out.println();

            (new UserMigrationTool()).init(props).migrate();

        } catch (Throwable t) {
            System.out.println(t.getMessage());
            System.out.println(
                    "\n Sorry, please check your database connection information again, was not able to migrate users :-(\n");
            System.exit(1);
        }

        System.out.println("\ndone :-)\n");
        System.out.println(" Thank you for migrating to the ESGF P2P Node");
        System.out.println(" http://esgf.org\n");
    }

}