org.fabrician.enabler.DockerContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.fabrician.enabler.DockerContainer.java

Source

/*
 * Copyright (c) 2014 TIBCO Software Inc. All Rights Reserved.
 *
 * Use is subject to the terms of the TIBCO license terms accompanying the download of this code.
 * In most instances, the license terms are contained in a file named license.txt.
 */
package org.fabrician.enabler;

import java.io.File;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.math.NumberUtils;
import org.fabrician.enabler.predicates.ContainerPredicates;
import org.fabrician.enabler.util.BuildCmdOptions;
import org.fabrician.enabler.util.DockerActivationInfo;
import org.fabrician.enabler.util.DockerActivationInfo.Entry;
import org.fabrician.enabler.util.DockerfileBuildLock;
import org.fabrician.enabler.util.ExecCmdProcessInjector;
import org.fabrician.enabler.util.RunCmdAuxiliaryOptions;
import org.fabrician.enabler.util.SpecialDirective;

import com.datasynapse.fabric.admin.info.NotificationEngineInfo;
import com.datasynapse.fabric.common.ActivationInfo;
import com.datasynapse.fabric.common.ArchiveActivationInfo;
import com.datasynapse.fabric.common.RuntimeContextVariable;
import com.datasynapse.fabric.common.message.ArchiveLocator;
import com.datasynapse.fabric.container.AbstractContainer;
import com.datasynapse.fabric.container.ArchiveDetail;
import com.datasynapse.fabric.container.ArchiveManagement;
import com.datasynapse.fabric.container.ArchiveProvider;
import com.datasynapse.fabric.container.ExecContainer;
import com.datasynapse.fabric.container.Feature;
import com.datasynapse.fabric.container.ProcessWrapper;
import com.datasynapse.fabric.domain.featureinfo.HttpFeatureInfo;
import com.datasynapse.fabric.util.ContainerUtils;
import com.datasynapse.fabric.util.DynamicVarsUtils;
import com.datasynapse.gridserver.engine.EngineProperties;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import static org.fabrician.enabler.util.SpecialDirective.PORT_EXPOSE;
import static org.fabrician.enabler.util.SpecialDirective.PORT_MAP;
import static org.fabrician.enabler.util.SpecialDirective.VOL_MAP;
import static org.fabrician.enabler.util.SpecialDirective.ENV_VAR;
import static org.fabrician.enabler.util.SpecialDirective.ENV_FILE;
import static org.fabrician.enabler.util.SpecialDirective.SEC_OPT;

/**
 * A Silver Fabric Docker container proxy.
 * <p>
 * This proxy manages the Docker container lifecycle spawned and integrates the docker container to the Silver Fabric broker infrastructure.
 * </p>
 * 
 */
public class DockerContainer extends ExecContainer implements ArchiveManagement, ArchiveProvider {
    private static final long serialVersionUID = 5194159591618328739L;
    private static final String BIND_ON_ALL_LOCAL_ADDRESSES_VAR = "BIND_ON_ALL_LOCAL_ADDRESSES";
    private static final String DOCKER_CONTAINER_BIND_ADDRESS_VAR = "DOCKER_CONTAINER_BIND_ADDRESS";
    private static final String DOCKER_CONTAINER_NAME_VAR = "DOCKER_CONTAINER_NAME";
    private static final String DOCKER_CONTAINER_TAG_VAR = "DOCKER_CONTAINER_TAG";
    private static final String USE_SUDO_VAR = "USE_SUDO";
    private static final String REUSE_CONTAINER_VAR = "REUSE_CONTAINER";

    private static final String EXEC_CMD_FILE_VAR = "EXEC_CMD_FILE";
    private static final String EXEC_CMD_DELAY_VAR = "EXEC_CMD_DELAY";
    private static final String EXEC_CMD_ENABLED_VAR = "EXEC_CMD_ENABLED";

    private static final String DOCKER_PORTS_EXPOSED_VAR = "DOCKER_PORTS_EXPOSED";
    private static final String DOCKER_PORT_MAPPINGS_VAR = "DOCKER_PORT_MAPPINGS";
    private static final String DOCKER_VOL_MAPPINGS_VAR = "DOCKER_VOL_MAPPINGS";
    private static final String DOCKER_ENVS_VAR = "DOCKER_ENVS";
    private static final String DOCKER_AUXILIARY_OPTIONS_VAR = "DOCKER_AUXILIARY_OPTIONS";
    private static final String DOCKER_SECURITY_OPTIONS_VAR = "DOCKER_SECURITY_OPTS";

    // build
    private static final String DOCKER_CONTEXT_DIR_VAR = "DOCKER_CONTEXT_DIR";
    private static final String DOCKER_IMAGE_NAME_VAR = "DOCKER_IMAGE_NAME";
    private static final String BUILD_ALWAYS_VAR = "BUILD_ALWAYS";
    private static final String REUSE_IMAGE_VAR = "REUSE_IMAGE";
    private static final String BUILD_TIMEOUT_VAR = "BUILD_TIMEOUT";

    private HttpFeatureInfo httpFeatureInfo = null;
    private static final int UNDEFINED_PORT = -1;
    private String dockerContainerTag;
    private String dockerImage;
    private String dockerContainerId;
    private String dockerBindAddress = "0.0.0.0";
    private boolean reuseContainer = false;
    private boolean useSudo = false;
    private DockerClient dockerClient;
    private int maxBuildLockRetries = 20;
    private int buildLockRetryPause = 10;
    private boolean shouldBuild = false;

    public boolean isHttpEnabled() {
        return (httpFeatureInfo != null ? httpFeatureInfo.isHttpEnabled() : false);
    }

    public boolean isHttpsEnabled() {
        return (httpFeatureInfo != null ? httpFeatureInfo.isHttpsEnabled() : false);
    }

    public int getMaxBuildLockRetries() {
        return maxBuildLockRetries;
    }

    public void setMaxBuildLockRetries(int maxBuildLockRetries) {
        this.maxBuildLockRetries = maxBuildLockRetries;
    }

    public int getBuildLockRetryPause() {
        return buildLockRetryPause;
    }

    public void setBuildLockRetryPause(int buildLockRetryPause) {
        this.buildLockRetryPause = buildLockRetryPause;
    }

    @Override
    protected void doInit(List<RuntimeContextVariable> additionalVariables) throws Exception {
        // Initialization done here before running Docker container, including building
        // Dockerfile
        getEngineLogger().fine("Invoking doInit()...");
        validateHttpFeature();
        dockerClient = DockerClient.getInstance();
        dockerContainerTag = resolveDockerContainerTag();
        dockerImage = resolveDockerImage();
        useSudo = resolveToBoolean(USE_SUDO_VAR);
        reuseContainer = resolveToBoolean(REUSE_CONTAINER_VAR);
        dockerBindAddress = resolveDockerBindingAddress();
        // build image first if needed
        // check if we should build?
        shouldBuild = resolveToBoolean(BUILD_ALWAYS_VAR);
        if (!shouldBuild) {
            getEngineLogger().info("Skipping build..");
            // check if the Docker image exists or not
            boolean image_exist = dockerClient().isImageExist(dockerImage);
            Validate.isTrue(image_exist,
                    "Docker image specified [" + dockerImage + "] is not available on Docker host.");
        }

        additionalVariables.add(new RuntimeContextVariable(DOCKER_CONTAINER_TAG_VAR, dockerContainerTag,
                RuntimeContextVariable.ENVIRONMENT_TYPE));
        additionalVariables.add(new RuntimeContextVariable(DOCKER_CONTAINER_BIND_ADDRESS_VAR, dockerBindAddress,
                RuntimeContextVariable.STRING_TYPE));
        // expose any additional private ports
        buildDockerContainerExposedPorts(additionalVariables);
        // construct a host ports to docker container ports mappings
        buildHostToDockerContainerPortMappings(additionalVariables);
        // construct a host vols to docker container vol mappings
        buildHostToDockerContainerVolumeMappings(additionalVariables);
        // construct env to be set inside docker container
        buildDockerContainerEnvs(additionalVariables);
        // construct other options not related to env,ports or volumes
        buildDockerContainerAuxiliaryOptions(additionalVariables);
        // construct security options
        buildDockerContainerSecurityOptions(additionalVariables);
        super.doInit(additionalVariables);
        getEngineLogger().fine("Invoked doInit()...");
    }

    private void buildDockerContainerExposedPorts(List<RuntimeContextVariable> additionalVariables)
            throws Exception {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < getRuntimeContext().getVariableCount(); i++) {
            RuntimeContextVariable var = getRuntimeContext().getVariable(i);
            if (var.getTypeInt() != RuntimeContextVariable.OBJECT_TYPE) {
                String name = var.getName();
                if (!PORT_EXPOSE.prefix(name)) {
                    continue;
                }
                String currentValue = resolveAndSet(var);
                if (currentValue.isEmpty()) {
                    continue;
                }
                sb.append(" --expose ").append(currentValue);
            }
        }

        additionalVariables.add(new RuntimeContextVariable(DOCKER_PORTS_EXPOSED_VAR, sb.toString(),
                RuntimeContextVariable.ENVIRONMENT_TYPE));
    }

    private void buildHostToDockerContainerPortMappings(List<RuntimeContextVariable> additionalVariables)
            throws Exception {
        StringBuilder sb = new StringBuilder();
        Map<String, String> badPortMap = new HashMap<String, String>();
        for (int i = 0; i < getRuntimeContext().getVariableCount(); i++) {
            RuntimeContextVariable var = getRuntimeContext().getVariable(i);
            if (var.getTypeInt() != RuntimeContextVariable.OBJECT_TYPE) {
                String name = var.getName();
                if (!PORT_MAP.prefix(name)) {
                    continue;
                }
                String currentValue = resolveAndSet(var);
                if (currentValue.isEmpty()) {
                    continue;
                }
                if (!isHostPortVacant(name, currentValue)) {
                    badPortMap.put(name, currentValue);
                }
                String newValue = "${DOCKER_CONTAINER_BIND_ADDRESS}:" + currentValue;
                sb.append(" -p ").append(newValue);
            }
        }
        if (!badPortMap.isEmpty()) {
            throw new Exception("Bad host-to-docker container port mappings detected :\n" + badPortMap);
        }
        additionalVariables.add(new RuntimeContextVariable(DOCKER_PORT_MAPPINGS_VAR,
                resolveVariables(sb.toString()), RuntimeContextVariable.ENVIRONMENT_TYPE));
    }

    private boolean isHostPortVacant(final String varName, final String varValue) {
        String host_port_str = StringUtils.substringBefore(varValue, ":");
        int host_port = NumberUtils.createInteger(host_port_str);
        if (host_port < 1024) {
            throw new RuntimeException("Illegal Host port [" + host_port
                    + "] specified in the [0-1023] well-known TCP/UDP ports range.");
        }
        if (checkServerPortInUse(host_port)) {
            getEngineLogger().warning("External host port [" + host_port_str
                    + "] associated with runtime context variable [" + varName + "] is already in use.");
            return false;
        }
        return true;

    }

    private void buildHostToDockerContainerVolumeMappings(List<RuntimeContextVariable> additionalVariables)
            throws Exception {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < getRuntimeContext().getVariableCount(); i++) {
            RuntimeContextVariable var = getRuntimeContext().getVariable(i);
            if (var.getTypeInt() != RuntimeContextVariable.OBJECT_TYPE) {
                String name = var.getName();
                if (!VOL_MAP.prefix(name)) {
                    continue;
                }
                String currentValue = resolveAndSet(var);
                if (currentValue.isEmpty()) {
                    continue;
                }
                sb.append(" -v ").append(currentValue);
            }
        }
        additionalVariables.add(new RuntimeContextVariable(DOCKER_VOL_MAPPINGS_VAR, sb.toString(),
                RuntimeContextVariable.ENVIRONMENT_TYPE));
    }

    private void buildDockerContainerEnvs(List<RuntimeContextVariable> additionalVariables) throws Exception {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < getRuntimeContext().getVariableCount(); i++) {
            RuntimeContextVariable var = getRuntimeContext().getVariable(i);
            if (var.getTypeInt() != RuntimeContextVariable.OBJECT_TYPE) {
                String name = var.getName();
                if (!(ENV_VAR.prefix(name) || ENV_FILE.prefix(name))) {
                    continue;
                }

                String currentValue = resolveAndSet(var);
                if (currentValue.isEmpty()) {
                    continue;
                }
                if (ENV_VAR.prefix(name)) {
                    sb.append(" -e ").append(currentValue);
                } else if (ENV_FILE.prefix(name)) {
                    sb.append(" --env-file ").append(currentValue);
                }
            }
        }
        additionalVariables.add(new RuntimeContextVariable(DOCKER_ENVS_VAR, sb.toString(),
                RuntimeContextVariable.ENVIRONMENT_TYPE));
    }

    private void buildDockerContainerAuxiliaryOptions(List<RuntimeContextVariable> additionalVariables)
            throws Exception {
        String options = RunCmdAuxiliaryOptions.buildAll(getRuntimeContext());
        additionalVariables.add(new RuntimeContextVariable(DOCKER_AUXILIARY_OPTIONS_VAR, resolveVariables(options),
                RuntimeContextVariable.ENVIRONMENT_TYPE));
    }

    private void buildDockerContainerSecurityOptions(List<RuntimeContextVariable> additionalVariables)
            throws Exception {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < getRuntimeContext().getVariableCount(); i++) {
            RuntimeContextVariable var = getRuntimeContext().getVariable(i);
            if (var.getTypeInt() != RuntimeContextVariable.OBJECT_TYPE) {
                String name = var.getName();
                if (!SEC_OPT.prefix(name)) {
                    continue;
                }
                String currentValue = resolveAndSet(var);
                if (currentValue.isEmpty()) {
                    continue;
                }
                sb.append(" --security-opt ").append(currentValue);
            }
        }
        additionalVariables.add(new RuntimeContextVariable(DOCKER_SECURITY_OPTIONS_VAR, sb.toString(),
                RuntimeContextVariable.ENVIRONMENT_TYPE));
    }

    @Override
    protected void doStart() throws Exception {
        // run the Docker container here
        getEngineLogger().fine("Invoking doStart()...");
        if (shouldBuild) {
            getEngineLogger().info("Doing Dockerfile build first...");
            File dockerFile = resolveDockerfile();
            Optional<DockerfileBuildLock> lock = DockerfileBuildLock.acquire(dockerImage, dockerFile,
                    getMaxBuildLockRetries(), getBuildLockRetryPause());
            if (lock.isPresent()) {
                buildDockerImage();
                lock.get().release();
            } else {
                throw new Exception("Can't build from Docker file due to failure to acquire build lock!");
            }
        }
        // remove existing container if exists
        removeExistingContainer();
        super.doStart();
        getEngineLogger().fine("doStart() invoked");
    }

    @Override
    protected void doInstall(ActivationInfo info) throws Exception {
        // things to do after starting docker container
        getEngineLogger().fine("Invoking doInstall()...");
        // Add any additional Docker container inspection info here
        dockerContainerId = DockerActivationInfo.inject(info, this.dockerContainerTag, this.dockerClient())
                .getDockerProperty(Entry.docker_id).orNull();
        if (isHttpEnabled()) {
            info.setProperty(HttpFeatureInfo.HTTP_PORT_VAR, String.valueOf(getHttpPort()));
        }
        if (isHttpEnabled()) {
            info.setProperty(HttpFeatureInfo.HTTPS_PORT_VAR, String.valueOf(getHttpsPort()));
        }
        super.doInstall(info);
        // log the docker inspect container to the engine logger
        logDockerInspectMetadata(dockerContainerId);
        // inject helper processes into activated parent Docker container
        injectDockerHelperProcesses();
        getEngineLogger().fine("doInstall() invoked");
    }

    // this logs the docker container inspect metadata into the engine log
    private void logDockerInspectMetadata(final String dockerContainerId) throws Exception {
        getEngineLogger().info(Strings.repeat("-", 10) + "inspect metadata for [" + dockerContainerId + "]"
                + Strings.repeat("-", 10));
        String command = "docker inspect " + dockerContainerId;
        if (useSudo) {
            command = "sudo " + command;
        }
        File workDir = new File(getWorkDir());
        ProcessWrapper p = null;
        try {
            String engineOS = getEngineProperty(EngineProperties.OS);
            p = getProcessWrapper(command, workDir, engineOS + "_inspect.pid");
            Object lock = p.getLock();
            p.exec();
            synchronized (lock) {
                try {
                    if (p.isRunning()) {
                        lock.wait(3000); // 3 secs
                        if (p.isRunning()) {
                            p.destroy();
                        }
                    }
                } catch (InterruptedException e) {
                    getEngineLogger().warning("logDockerInspectMetadata() thread was interrupted");
                }
            }
        } catch (Exception ex) {
            getEngineLogger().log(Level.SEVERE, "while inspecting docker container [" + dockerContainerId + "]",
                    ex);
        }
    }

    private void injectDockerHelperProcesses() throws Exception {
        getEngineLogger()
                .info(Strings.repeat("-", 10) + "injecting post-activation helper processes into docker container ["
                        + dockerContainerId + "]" + Strings.repeat("-", 10));
        boolean inject = resolveToBoolean(EXEC_CMD_ENABLED_VAR);
        if (inject) {
            File cmdFile = resolveToFile(EXEC_CMD_FILE_VAR);
            Validate.isTrue(cmdFile.exists(), "The command file for 'docker exec' use does not exist at path ["
                    + cmdFile.getCanonicalPath() + "]");
            int delay = resolveToInteger(EXEC_CMD_DELAY_VAR);
            ExecCmdProcessInjector.exec(this, cmdFile.toURI().toURL(), delay);
        } else {
            getEngineLogger().info("Skipping 'docker exec' since " + EXEC_CMD_ENABLED_VAR + " is disabled.");
        }
    }

    public ProcessWrapper getExecCmdProcessWrapper(String execCmd) throws Exception {
        String command = "docker exec -d " + dockerContainerTag + " " + execCmd;
        if (useSudo) {
            command = "sudo " + command;
        }
        File workDir = new File(getWorkDir());
        HashFunction hf = Hashing.md5();
        HashCode hc = hf.newHasher().putString(command, Charsets.UTF_8).hash();
        String cmd_pid = "_cmd_" + BaseEncoding.base64Url().encode(hc.asBytes()) + ".pid";
        try {
            String engineOS = getEngineProperty(EngineProperties.OS);
            ProcessWrapper p = getProcessWrapper(command, workDir, engineOS + cmd_pid);
            return p;
        } catch (Exception ex) {
            getEngineLogger().log(Level.SEVERE,
                    "while getting a process wrapper for 'docker exec' command [" + execCmd + "]", ex);
            throw ex;
        }
    }

    private void buildDockerImage() throws Exception {
        getEngineLogger()
                .info(Strings.repeat("-", 10) + "building [" + dockerImage + "]" + Strings.repeat("-", 10));
        // check if we should reuse existing image?
        boolean reuse_image = resolveToBoolean(REUSE_IMAGE_VAR);
        boolean image_exist = dockerClient().isImageExist(dockerImage);
        if (reuse_image) {
            if (image_exist) {
                getEngineLogger().info("Existing docker image [" + dockerImage + "] exists and will be reused.");
                return;
            }
        } else {
            // delete existing image before build
            if (image_exist) {
                getEngineLogger().info("Deleting existing docker image [" + dockerImage + "] before build.");
                dockerClient().deleteImageByTag(dockerImage);
            }
        }
        // check if Docker file exits?
        File dockerContextDir = resolveToFile(DOCKER_CONTEXT_DIR_VAR);
        File dockerFile = new File(dockerContextDir, "Dockerfile");
        Validate.isTrue(dockerFile.exists(),
                "Required Dockerfile for build does not exist at path [" + dockerFile.getCanonicalPath() + "]");
        // construct build options
        String command = "docker build " + BuildCmdOptions.buildAll(getRuntimeContext()) + " -t " + dockerImage
                + " . ";
        if (useSudo) {
            command = "sudo " + command;
        }
        getEngineLogger().info(Strings.repeat("-", 10) + " begin build command " + Strings.repeat("-", 10));
        getEngineLogger().info(command);
        getEngineLogger().info(Strings.repeat("-", 10) + " end build command " + Strings.repeat("-", 10));
        ProcessWrapper p = null;
        int build_timeout = resolveToInteger(BUILD_TIMEOUT_VAR);// in secs
        try {
            String engineOS = getEngineProperty(EngineProperties.OS);
            p = getProcessWrapper(command, dockerContextDir, engineOS + "_build.pid");
            Object lock = p.getLock();
            p.exec();
            synchronized (lock) {
                try {
                    if (p.isRunning()) {
                        lock.wait(build_timeout * 1000);
                        if (p.isRunning()) {
                            p.destroy();
                        }
                    }
                } catch (InterruptedException e) {
                    getEngineLogger().warning("buildDockerImage() thread was interrupted");
                }
            }
        } catch (Exception ex) {
            getEngineLogger().log(Level.SEVERE, "while building docker image [" + dockerImage + "]", ex);
            throw ex;
        }
        // check for image

        image_exist = dockerClient().isImageExist(dockerImage);
        if (!image_exist) {
            throw new Exception("Build failed for image [" + dockerImage + "]");
        } else {
            getEngineLogger().info("Build succeeded for image [" + dockerImage + "]");
        }
    }

    @Override
    protected void doUninstall() throws Exception {
        // things to do before stopping docker container
        super.doUninstall();
    }

    @Override
    protected void doShutdown() throws Exception {
        getEngineLogger().fine("Invoking doShutdown...");
        getEngineLogger().info("Stopping docker container : " + dockerContainerInfo() + "...");
        long shutdownStart = System.currentTimeMillis();
        if ((getProcess() != null) && (getProcess().isRunning())) {
            getEngineLogger().info("Doing blocking process shutdown...");
            super.doShutdown();
            waitForShutdown(shutdownStart);
        } else {
            // we may have a docker container started in detach mode with "-d" switch
            getEngineLogger().info("Doing non-blocking process shutdown...");
            super.doShutdown();
            ContainerPredicates.awaitStopped(dockerClient().remoteApi());
        }
        // stop monitoring for unexpected container crash now.
        setRunCrashMonitor(false);
        getEngineLogger().fine("doShutdown invoked");
    }

    @Override
    public void cleanup() throws Exception {
        getEngineLogger().fine("Invoking container cleanup()...");
        // delete docker container first
        boolean deleteWorkDir = Boolean.valueOf(getStringVariableValue(DELETE_RUNTIME_DIR_ON_SHUTDOWN_VAR, "true"))
                .booleanValue();
        if (deleteWorkDir) {
            getEngineLogger().info("Removing docker container : " + dockerContainerInfo() + "...");
            dockerClient().removeContainer(dockerContainerId(), null);
        }
        // next delete work area
        super.cleanup();
        getEngineLogger().fine("Invoked container cleanup()...");
    }

    private void removeExistingContainer() {
        if (!reuseContainer) {
            dockerClient().removeContainer(dockerContainerId(), null);
        }
    }

    private File resolveDockerfile() throws Exception {
        File dockerContextDir = resolveToFile(DOCKER_CONTEXT_DIR_VAR);
        File dockerFile = new File(dockerContextDir, "Dockerfile");
        Validate.isTrue(dockerFile.exists(),
                "Required Dockerfile for build does not exist at path [" + dockerFile.getCanonicalPath() + "]");
        return dockerFile;
    }

    // try and open a server socket. If we can then close it and return false
    // otherwise assume its in use and return true
    private boolean checkServerPortInUse(int port) {
        if (port == UNDEFINED_PORT) {
            return false;
        }
        ServerSocket srv = null;
        boolean inUse = false;
        try {
            srv = new ServerSocket(port);
            srv.close();
        } catch (Exception e) {
            getEngineLogger().finest("serverPortInUse: debug exception: " + e);
            inUse = true;
        }
        return inUse;
    }

    private int getHttpPort() throws Exception {
        return Integer.valueOf(getStringVariableValue(HttpFeatureInfo.HTTP_PORT_VAR, null));
    }

    private int getHttpsPort() throws Exception {
        return Integer.valueOf(getStringVariableValue(HttpFeatureInfo.HTTPS_PORT_VAR, null));
    }

    private String createDockerContainerTag() {
        try {
            Joiner joiner = Joiner.on("_").skipNulls();
            String componentName = getComponentName();
            getEngineLogger().info("component name=[" + componentName + "]");
            String username = getEngineUsername();
            getEngineLogger().info("username=[" + username + "]");
            String instance = getEngineInstanceId();
            return joiner.join(componentName, username, instance).toLowerCase();
        } catch (Exception ex) {

        }
        return UUID.randomUUID().toString();
    }

    private String getEngineInstanceId() throws Exception {
        return getEngineProperty(EngineProperties.INSTANCE);
    }

    private String getEngineUsername() throws Exception {
        String username = getEngineProperty(EngineProperties.USERNAME);
        username = StringUtils.replaceChars(username, '-', '_');
        return username;
    }

    private String getComponentName() {
        String componentName = getCurrentDomain().getName();
        if (componentName != null) {
            Iterable<String> parts = Splitter.on(" ").omitEmptyStrings().trimResults().split(componentName);
            Joiner joiner = Joiner.on("_").skipNulls();
            componentName = joiner.join(parts);
        }
        componentName = StringUtils.replaceChars(componentName, '-', '_');
        return componentName;
    }

    private String resolveDockerBindingAddress() throws Exception {
        String bindAllStr = getStringVariableValue(BIND_ON_ALL_LOCAL_ADDRESSES_VAR, "true");
        boolean bindAll = Boolean.valueOf(bindAllStr);
        String bindAddress = bindAll ? "0.0.0.0" : getStringVariableValue(LISTEN_ADDRESS_VAR);
        return bindAddress;
    }

    private String resolveDockerImage() throws Exception {
        String image = resolveToString(DOCKER_IMAGE_NAME_VAR);
        Validate.notEmpty(image, DOCKER_IMAGE_NAME_VAR + " must be specified.");
        return image;
    }

    private String resolveDockerContainerTag() throws Exception {
        String tag = resolveToString(DOCKER_CONTAINER_NAME_VAR);
        if (tag.isEmpty() || NumberUtils.isDigits(tag)) {
            tag = createDockerContainerTag();
            getEngineLogger().info("Auto-generate Docker container name tag to [" + tag + "]");
        }
        return tag;
    }

    private String resolveToString(String runtimeContextVariableName) throws Exception {
        String val = SpecialDirective.resolveStringValue(this,
                StringUtils.trimToEmpty(getStringVariableValue(runtimeContextVariableName)));
        return val;
    }

    private int resolveToInteger(String runtimeContextVariableName) throws Exception {
        String val = SpecialDirective.resolveStringValue(this,
                StringUtils.trimToEmpty(getStringVariableValue(runtimeContextVariableName)));
        return NumberUtils.toInt(val);
    }

    private boolean resolveToBoolean(String runtimeContextVariableName) throws Exception {
        String val = SpecialDirective.resolveStringValue(this,
                StringUtils.trimToEmpty(getStringVariableValue(runtimeContextVariableName)));
        return BooleanUtils.toBoolean(val);
    }

    private File resolveToFile(String runtimeContextVariableName) throws Exception {
        String val = SpecialDirective.resolveStringValue(this,
                StringUtils.trimToEmpty(getStringVariableValue(runtimeContextVariableName)));
        return new File(val).getCanonicalFile();
    }

    private String resolveAndSet(RuntimeContextVariable var) throws Exception {
        String name = var.getName();
        String currentValue = StringUtils.trimToEmpty(getStringVariableValue(name));
        getEngineLogger().fine("Runtime context variable [" + name + "] has current value [" + currentValue + "].");
        String newValue = resolveToString(name);
        getEngineLogger().fine("Runtime context variable [" + name + "] has resolved value [" + newValue + "].");
        if (!newValue.equals(currentValue)) {
            getEngineLogger().fine(
                    "Setting runtime context variable [" + name + "] to new resolved value [" + newValue + "].");
            var.setValue(newValue);
        }
        return newValue;
    }

    private void validateHttpFeature() throws Exception {
        httpFeatureInfo = (HttpFeatureInfo) ContainerUtils.getFeatureInfo(Feature.HTTP_FEATURE_NAME, this,
                getCurrentDomain());
        if (httpFeatureInfo != null) {
            boolean httpEnabled = isHttpEnabled();
            boolean httpsEnabled = isHttpsEnabled();
            if (!httpEnabled && !httpsEnabled) {
                throw new Exception("HTTP or HTTPS must be enabled in the Domain");
            }
            if (httpEnabled && !DynamicVarsUtils.validateIntegerVariable(this, HttpFeatureInfo.HTTP_PORT_VAR)) {
                throw new Exception("HTTP is enabled but the " + HttpFeatureInfo.HTTP_PORT_VAR
                        + " runtime context variable is not set");
            }
            if (httpsEnabled && !DynamicVarsUtils.validateIntegerVariable(this, HttpFeatureInfo.HTTPS_PORT_VAR)) {
                throw new Exception("HTTPS is enabled but the " + HttpFeatureInfo.HTTPS_PORT_VAR
                        + " runtime context variable is not set");
            }
        } else {
            getEngineLogger().warning("HTTP Feature is disabled");
        }
    }

    Logger logger() {
        return getEngineLogger();
    }

    DockerClient dockerClient() {
        return this.dockerClient;
    }

    public String dockerContainerId() {
        return this.dockerContainerId;
    }

    public String dockerContainerName() {
        return this.dockerContainerTag;
    }

    public String dockerContainerInfo() {
        return "[" + dockerContainerName() + "][" + dockerContainerId() + "]";
    }

    // If ArchiveManagementFeatureInfo is supported, then ArchiveManagement methods must scripted
    // Otherwise it got intercepted here and UnsupportedOperationException is thrown.
    public void archiveDeploy(String archiveName, List<ArchiveLocator> archiveLocators, Properties properties)
            throws Exception {
        throw new UnsupportedOperationException(
                "Method archiveDeploy(...) is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");
    }

    public ArchiveActivationInfo archiveStart(String archiveName, Properties properties) throws Exception {
        throw new UnsupportedOperationException(
                "Method archiveStart(...) is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");
    }

    public ArchiveActivationInfo archiveScaleUp(String archiveName, List<ArchiveLocator> archiveLocators)
            throws Exception {
        throw new UnsupportedOperationException(
                "Method archiveScaleUp(...) is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");
    }

    public void archiveScaleDown(String archiveName, String archiveId) throws Exception {
        throw new UnsupportedOperationException(
                "Method archiveScaleDown(...) is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");

    }

    public void archiveStop(String archiveName, String archiveId, Properties properties) throws Exception {
        throw new UnsupportedOperationException(
                "Method archiveStop(...) is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");
    }

    public void archiveUndeploy(String archiveName, Properties properties) throws Exception {
        throw new UnsupportedOperationException(
                "Method archiveUndeploy(...) is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");
    }

    public ArchiveDetail[] archiveDetect() throws Exception {
        throw new UnsupportedOperationException(
                "Method archiveDetect() is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");
    }

    public String[] urlDetect() throws Exception {
        throw new UnsupportedOperationException(
                "Method urlDetect() is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveManagement.class.getName() + "' in the SDK API.");
    }

    // For Continuous Deployment
    public File getArchive(String archiveName) throws Exception {
        throw new UnsupportedOperationException(
                "Method getArchive(...) is not implemented in scripting or this Enabler. Please see interface '"
                        + ArchiveProvider.class.getName() + "' in the SDK API.");
    }

    @Override
    public File resolveArchive(String deploymentArchiveName, String url, Properties properties,
            String componentName, File destDir) throws Exception {
        throw new UnsupportedOperationException(
                "Method resolveArchive(...) is not implemented in scripting or this Enabler. Please see abstract class '"
                        + AbstractContainer.class.getName() + "' in the SDK API.");
    }

    // For ComponentNotification
    @Override
    public void componentNotification(String componentName, List<NotificationEngineInfo> notificationEngineInfo) {
        throw new UnsupportedOperationException(
                "Method componentNotification(...) is not implemented in scripting or this Enabler. Please see abstract class '"
                        + AbstractContainer.class.getName() + "' in the SDK API.");
    }

}