// Path: src/main/java/fabzo/kraken/EnvironmentContext.java
// public class EnvironmentContext {
//     public static final String PORT_REF_TO = "%s.ports.%s.to";
//     public static final String PORT_REF_FROM = "%s.ports.%s.from";
//     public static final String IP_REF = "%s.ip";
// 
//     private final String salt;
//     private Option<String> publicFacingIP = Option.none();
//     private Map<String, String> environmentVariables = HashMap.empty();
//     private List<Runnable> shutdownHooks = List.empty();
// 
//     public EnvironmentContext(final String salt) {
//         this.salt = salt;
//     }
// 
//     public void putEnv(final String name, final String value) {
//         this.environmentVariables = environmentVariables.put(name, value);
//     }
// 
//     public void addPort(final String componentName, final String portName, final int fromPort, final int toPort) {
//         putEnv(String.format(PORT_REF_FROM, componentName, portName), String.valueOf(fromPort));
//         putEnv(String.format(PORT_REF_TO, componentName, portName), String.valueOf(toPort));
//     }
// 
//     public void setIP(final String componentName, final String ip) {
//         putEnv(String.format(IP_REF, componentName), String.valueOf(ip));
//     }
// 
//     public String salt() {
//         return salt;
//     }
// 
//     public Option<String> publicFacingIP() {
//         return publicFacingIP;
//     }
// 
//     public String resolve(final String text) {
//         return StrSubstitutor.replace(text, environmentVariables.toJavaMap());
//     }
// 
//     public void setPublicFacingIP(final String ip) {
//         publicFacingIP = Option.of(ip);
//     }
// 
//     public Option<Integer> port(final String name, final String portName) {
//         return environmentVariables
//                 .get(String.format(PORT_REF_FROM, name, portName))
//                 .map(this::toInt);
//     }
// 
//     private Integer toInt(final String port) {
//         return Try.of(() -> Integer.valueOf(port)).getOrNull();
//     }
// 
//     /**
//      * Returns the IP assigned to the service by its lifecycle handler. If no IP has been
//      * assigned this function will fallback to the public facing IP or localhost.
//      *
//      * @param name Service name
//      * @return Service IP, public facing host IP, or localhost
//      */
//     public String ip(final String name) {
//         return environmentVariables
//                 .get(String.format(IP_REF, name))
//                 .getOrElse(() -> publicFacingIP().getOrElse("localhost"));
//     }
// 
//     public void registerShutdownHook(final Runnable shutdownHook) {
//         shutdownHooks = shutdownHooks.append(shutdownHook);
//     }
// 
//     public List<Runnable> shutdownHooks() {
//         return shutdownHooks;
//     }
// }
// 
// Path: src/main/java/fabzo/kraken/components/InfrastructureComponent.java
// public abstract class InfrastructureComponent implements Named {
//     private ComponentState state = ComponentState.WAITING;
//     private List<Wait> waitFunctions = List.empty();
// 
//     public InfrastructureComponent withWait(final Wait waitFunc) {
//         this.waitFunctions = waitFunctions.append(waitFunc);
//         return this;
//     }
// 
//     public List<Wait> waitFuncs() {
//         return waitFunctions;
//     }
// 
//     public void setState(final ComponentState state) {
//         this.state = state;
//     }
// 
//     public ComponentState state() {
//         return state;
//     }
// }


import com.google.common.base.MoreObjects;
import fabzo.kraken.EnvironmentContext;
import fabzo.kraken.components.InfrastructureComponent;
import io.vavr.control.Option;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import java.sql.DriverManager;
import java.time.Duration;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static com.jayway.awaitility.Awaitility.await;
        }

        return String.format(DEFAULT_JDBC_URL, driver, ip, port.get(), database, username, password);
    }

    @Override
    public boolean execute(final EnvironmentContext ctx, final InfrastructureComponent component) {
        val url = ctx.resolve(connectionUrl.getOrElse(() -> createConnectionUrl(component, ctx)));

        log.info("Waiting for database to become available for up to {}", atMost);
        log.info("Connection URL is {}", url);

        val properties = new Properties();
        properties.put("connectTimeout", "5000");

        val counter = new AtomicInteger();
        await().atMost(atMost.toMillis(), TimeUnit.MILLISECONDS).until(() -> {
            log.debug("Connection attempt ({}) to {}", counter.incrementAndGet(), database);
            try (val connection = DriverManager.getConnection(url, properties);
                 val statement = connection.prepareStatement(TEST_SQL)) {

                statement.execute();
                return true;
            } catch(final Exception ignored) {
                log.debug(" -> Attempt ({}) failed due to {}[{}]",
                        counter.get(),
                        ignored.getClass().getSimpleName(),
                        ignored.getMessage());
                return false;
            }
// Path: src/main/java/fabzo/kraken/EnvironmentContext.java
// public class EnvironmentContext {
//     public static final String PORT_REF_TO = "%s.ports.%s.to";
//     public static final String PORT_REF_FROM = "%s.ports.%s.from";
//     public static final String IP_REF = "%s.ip";
// 
//     private final String salt;
//     private Option<String> publicFacingIP = Option.none();
//     private Map<String, String> environmentVariables = HashMap.empty();
//     private List<Runnable> shutdownHooks = List.empty();
// 
//     public EnvironmentContext(final String salt) {
//         this.salt = salt;
//     }
// 
//     public void putEnv(final String name, final String value) {
//         this.environmentVariables = environmentVariables.put(name, value);
//     }
// 
//     public void addPort(final String componentName, final String portName, final int fromPort, final int toPort) {
//         putEnv(String.format(PORT_REF_FROM, componentName, portName), String.valueOf(fromPort));
//         putEnv(String.format(PORT_REF_TO, componentName, portName), String.valueOf(toPort));
//     }
// 
//     public void setIP(final String componentName, final String ip) {
//         putEnv(String.format(IP_REF, componentName), String.valueOf(ip));
//     }
// 
//     public String salt() {
//         return salt;
//     }
// 
//     public Option<String> publicFacingIP() {
//         return publicFacingIP;
//     }
// 
//     public String resolve(final String text) {
//         return StrSubstitutor.replace(text, environmentVariables.toJavaMap());
//     }
// 
//     public void setPublicFacingIP(final String ip) {
//         publicFacingIP = Option.of(ip);
//     }
// 
//     public Option<Integer> port(final String name, final String portName) {
//         return environmentVariables
//                 .get(String.format(PORT_REF_FROM, name, portName))
//                 .map(this::toInt);
//     }
// 
//     private Integer toInt(final String port) {
//         return Try.of(() -> Integer.valueOf(port)).getOrNull();
//     }
// 
//     /**
//      * Returns the IP assigned to the service by its lifecycle handler. If no IP has been
//      * assigned this function will fallback to the public facing IP or localhost.
//      *
//      * @param name Service name
//      * @return Service IP, public facing host IP, or localhost
//      */
//     public String ip(final String name) {
//         return environmentVariables
//                 .get(String.format(IP_REF, name))
//                 .getOrElse(() -> publicFacingIP().getOrElse("localhost"));
//     }
// 
//     public void registerShutdownHook(final Runnable shutdownHook) {
//         shutdownHooks = shutdownHooks.append(shutdownHook);
//     }
// 
//     public List<Runnable> shutdownHooks() {
//         return shutdownHooks;
//     }
// }
// 
// Path: src/main/java/fabzo/kraken/components/InfrastructureComponent.java
// public abstract class InfrastructureComponent implements Named {
//     private ComponentState state = ComponentState.WAITING;
//     private List<Wait> waitFunctions = List.empty();
// 
//     public InfrastructureComponent withWait(final Wait waitFunc) {
//         this.waitFunctions = waitFunctions.append(waitFunc);
//         return this;
//     }
// 
//     public List<Wait> waitFuncs() {
//         return waitFunctions;
//     }
// 
//     public void setState(final ComponentState state) {
//         this.state = state;
//     }
// 
//     public ComponentState state() {
//         return state;
//     }
// }


// Path: src/main/java/fabzo/kraken/wait/DatabaseWait.java
import com.google.common.base.MoreObjects;
import fabzo.kraken.EnvironmentContext;
import fabzo.kraken.components.InfrastructureComponent;
import io.vavr.control.Option;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import java.sql.DriverManager;
import java.time.Duration;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static com.jayway.awaitility.Awaitility.await;

        }

        return String.format(DEFAULT_JDBC_URL, driver, ip, port.get(), database, username, password);
    }

    @Override
    public boolean execute(final EnvironmentContext ctx, final InfrastructureComponent component) {
        val url = ctx.resolve(connectionUrl.getOrElse(() -> createConnectionUrl(component, ctx)));

        log.info("Waiting for database to become available for up to {}", atMost);
        log.info("Connection URL is {}", url);

        val properties = new Properties();
        properties.put("connectTimeout", "5000");

        val counter = new AtomicInteger();
        await().atMost(atMost.toMillis(), TimeUnit.MILLISECONDS).until(() -> {
            log.debug("Connection attempt ({}) to {}", counter.incrementAndGet(), database);
            try (val connection = DriverManager.getConnection(url, properties);
                 val statement = connection.prepareStatement(TEST_SQL)) {

                statement.execute();
                return true;
            } catch(final Exception ignored) {
                log.debug(" -> Attempt ({}) failed due to {}[{}]",
                        counter.get(),
                        ignored.getClass().getSimpleName(),
                        ignored.getMessage());
                return false;
            }
        });