package liquibase.ext.percona;

/*
 * 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.
 */
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import liquibase.database.DatabaseConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.logging.LogFactory;
import liquibase.logging.Logger;

/**
 * Wraps a {@link DatabaseConnection} to have easy access
 * to its connection properties like host, port, user and password.
 */
public class DatabaseConnectionUtil {
    static String passwordForTests = null;

    private Logger log = LogFactory.getInstance().getLog();

    private DatabaseConnection connection;

    public DatabaseConnectionUtil(DatabaseConnection connection) {
        this.connection = connection;
    }

    public String getHost() {
        Pattern p = Pattern.compile("jdbc:mysql://([^@]+@)?([^:/]+)");
        Matcher m = p.matcher(connection.getURL());
        if (m.find()) {
            return m.group(2);
        }
        return "";
    }

    public String getPort() {
        Pattern p = Pattern.compile("jdbc:mysql://[^:/]+:(\\d+)");
        Matcher m = p.matcher(connection.getURL());
        if (m.find()) {
            return m.group(1);
        }
        return "3306";
    }

    public String getUser() {
        String connectionUserName = connection.getConnectionUserName();
        if (connectionUserName.contains("@")) {
            return connectionUserName.substring(0, connectionUserName.indexOf('@'));
        }
        return connectionUserName;
    }

    private static Class<?> loadClass(String name, ClassLoader loader) {
        try {
            return loader.loadClass(name);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    public String getPassword() {
        if (connection instanceof JdbcConnection) {
            Connection jdbcCon = ((JdbcConnection) connection).getWrappedConnection();
            try {
                Class<?> connectionImplClass = null;

                // MySQL Connector 5.1.38
                connectionImplClass = loadClass("com.mysql.jdbc.ConnectionImpl", jdbcCon.getClass().getClassLoader());

                // MySQL Connector 6.0.4
                if (connectionImplClass == null) {
                    connectionImplClass = loadClass("com.mysql.cj.jdbc.ConnectionImpl", jdbcCon.getClass().getClassLoader());
                }

                // Unknown MySQL Connector version?
                if (connectionImplClass == null) {
                    throw new RuntimeException("Couldn't find class ConnectionImpl");
                }

                // ConnectionImpl stores the properties, and the jdbc connection is a subclass of it...
                Field propsField = connectionImplClass.getDeclaredField("props");
                propsField.setAccessible(true);
                Properties props = (Properties) propsField.get(jdbcCon);
                String password = props.getProperty("password");
                if (password != null && !password.trim().isEmpty()) {
                    return password;
                }
            } catch (Exception e) {
                log.warning("Couldn't determine the password from JdbcConnection", e);
            }
        } else if (passwordForTests != null) {
            log.warning("Using passwordForTests");
            return passwordForTests;
        }
        return null;
    }
}

@@ -3173,32 +3173,229 @@
              %7D%0A%0A
+                if (!connectionImplClass.isInstance(jdbcCon)) %7B%0A                    throw new RuntimeException(%22JdbcConnection is unsupported: %22 + jdbcCon.getClass().getName());%0A                %7D%0A%0A
                 

Check for actual JdbcConnection class before accessing its properties refs #12