/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.test.infrastructure.process; import static org.mule.runtime.core.util.FileUtils.newFile; import static java.lang.Integer.parseInt; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.StandardOpenOption.APPEND; import static java.util.Arrays.asList; import static org.apache.commons.io.FileUtils.copyDirectoryToDirectory; import static org.apache.commons.io.FileUtils.copyFileToDirectory; import static org.apache.commons.io.FileUtils.forceDelete; import static org.apache.commons.io.FileUtils.listFiles; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteException; import org.apache.commons.exec.ExecuteWatchdog; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.exec.util.StringUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class Controller { private static Logger logger = LoggerFactory.getLogger(Controller.class); protected static final String ANCHOR_SUFFIX = "-anchor.txt"; private static final IOFileFilter ANCHOR_FILTER = FileFilterUtils.suffixFileFilter(ANCHOR_SUFFIX); protected static final String STATUS = "Mule Enterprise Edition is running \\(([0-9]+)\\)\\."; protected static final Pattern STATUS_PATTERN = Pattern.compile(STATUS); private static final int DEFAULT_TIMEOUT = 30000; private static final String MULE_HOME_VARIABLE = "MULE_HOME"; private static final String DOMAIN_DEPLOY_ERROR = "Error deploying domain %s."; private static final String ANCHOR_DELETE_ERROR = "Could not delete anchor file [%s] when stopping Mule Runtime."; private static final String ADD_LIBRARY_ERROR = "Error copying jar file [%s] to lib directory [%s]."; private static final int IS_RUNNING_STATUS_CODE = 0; private static final Pattern pattern = Pattern.compile("wrapper\\.java\\.additional\\.(\\d*)="); protected String muleHome; protected String muleBin; protected File domainsDir; protected File appsDir; protected File libsDir; protected File internalRepository; protected Path wrapperConf; protected int timeout; public Controller(String muleHome, int timeout) { this.muleHome = muleHome; this.muleBin = getMuleBin(); this.domainsDir = new File(muleHome + "/domains"); this.appsDir = new File(muleHome + "/apps/"); this.libsDir = new File(muleHome + "/lib/user"); this.internalRepository = new File(muleHome, "repository"); this.wrapperConf = Paths.get(muleHome + "/conf/wrapper.conf"); this.timeout = timeout != 0 ? timeout : DEFAULT_TIMEOUT; } public abstract String getMuleBin(); public void start(String[] args) { checkRepositoryLocationAndUpdateInternalRepoPropertyIfPresent(args); int error = runSync("start", args); if (error != 0) { throw new MuleControllerException("The mule instance couldn't be started"); } } protected void checkRepositoryLocationAndUpdateInternalRepoPropertyIfPresent(String... args) { Optional<String> repoVar = asList(args).stream().filter(arg -> arg.contains("-M-DmuleRuntimeConfig.maven.repositoryLocation=")).findFirst(); if (repoVar.isPresent()) { this.internalRepository = new File(repoVar.get().split("=")[1]); } } public void stop(String[] args) { int error = runSync("stop", args); verify(error == 0, "The mule instance couldn't be stopped"); deleteAnchors(); } public abstract int status(String... args); public abstract int getProcessId(); public void restart(String[] args) { int error = runSync("restart", args); if (error != 0) { throw new MuleControllerException("The mule instance couldn't be restarted"); } } protected int runSync(String command, String... args) { Map<Object, Object> newEnv = copyEnvironmentVariables(); return executeSyncCommand(command, args, newEnv, timeout); } private int executeSyncCommand(String command, String[] args, Map<Object, Object> newEnv, int timeout) throws MuleControllerException { CommandLine commandLine = new CommandLine(muleBin); commandLine.addArgument(command); commandLine.addArguments(args); DefaultExecutor executor = new DefaultExecutor(); ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout); executor.setWatchdog(watchdog); executor.setStreamHandler(new PumpStreamHandler()); return doExecution(executor, commandLine, newEnv); } protected int doExecution(DefaultExecutor executor, CommandLine commandLine, Map<Object, Object> env) { try { logger.info("Executing: " + StringUtils.toString(commandLine.toStrings(), " ")); return executor.execute(commandLine, env); } catch (ExecuteException e) { return e.getExitValue(); } catch (Exception e) { throw new MuleControllerException("Error executing [" + commandLine.getExecutable() + " " + commandLine.getArguments() + "]", e); } } protected Map<Object, Object> copyEnvironmentVariables() { Map<String, String> env = System.getenv(); Map<Object, Object> newEnv = new HashMap<Object, Object>(); for (Map.Entry<String, String> it : env.entrySet()) { newEnv.put(it.getKey(), it.getValue()); } newEnv.put(MULE_HOME_VARIABLE, muleHome); return newEnv; } protected void verify(boolean condition, String message, Object... args) { if (!condition) { throw new MuleControllerException(String.format(message, args)); } } protected void deployDomain(String domain) { File domainFile = new File(domain); verify(domainFile.exists(), "Domain does not exist: %s", domain); try { if (domainFile.isDirectory()) { copyDirectoryToDirectory(domainFile, this.domainsDir); } else { copyFileToDirectory(domainFile, this.domainsDir); } } catch (IOException e) { throw new MuleControllerException(String.format(DOMAIN_DEPLOY_ERROR, domain), e); } } protected void addLibrary(File jar) { verify(jar.exists(), "Jar file does not exist: %s", jar); verify("jar".equals(FilenameUtils.getExtension(jar.getAbsolutePath())), "Library [%s] don't have .jar extension.", jar); verify(jar.canRead(), "Cannot read jar file: %s", jar); verify(libsDir.canWrite(), "Cannot write on lib dir: %", libsDir); try { copyFileToDirectory(jar, libsDir); } catch (IOException e) { throw new MuleControllerException(String.format(ADD_LIBRARY_ERROR, jar, libsDir), e); } } protected void deleteAnchors() { @SuppressWarnings("unchecked") Collection<File> anchors = listFiles(appsDir, ANCHOR_FILTER, null); for (File anchor : anchors) { try { forceDelete(anchor); } catch (IOException e) { throw new MuleControllerException(String.format(ANCHOR_DELETE_ERROR, anchor), e); } } } public void deploy(String path) { File app = new File(path); verify(app.exists(), "File does not exists: %s", app); verify(app.canRead(), "Cannot read file: %s", app); try { if (app.isFile()) { copyFileToDirectory(app, appsDir); } else { copyDirectoryToDirectory(app, appsDir); } } catch (IOException e) { throw new MuleControllerException("Could not deploy app [" + path + "] to [" + appsDir + "]", e); } } public boolean isRunning() { return IS_RUNNING_STATUS_CODE == status(); } public void undeploy(String application) { if (!new File(appsDir, application + ANCHOR_SUFFIX).exists()) { throw new MuleControllerException("Couldn't undeploy application [" + application + "]. Application is not deployed"); } if (!new File(appsDir, application + ANCHOR_SUFFIX).delete()) { throw new MuleControllerException("Couldn't undeploy application [" + application + "]"); } } public void undeployDomain(String domain) { if (!new File(domainsDir, domain + ANCHOR_SUFFIX).exists()) { new MuleControllerException("Couldn't undeploy domain [" + domain + "]. Domain is not deployed"); } if (!new File(domainsDir, domain + ANCHOR_SUFFIX).delete()) { new MuleControllerException("Couldn't undeploy domain [" + domain + "]"); } } public void undeployAll() { for (File file : appsDir.listFiles()) { try { forceDelete(file); } catch (IOException e) { throw new MuleControllerException("Could not delete directory [" + file.getAbsolutePath() + "]", e); } } } public void installLicense(String path) { this.runSync(null, "--installLicense", path); } public void uninstallLicense() { this.runSync(null, "-unInstallLicense"); } protected boolean isDeployed(String appName) { return new File(appsDir, appName + ANCHOR_SUFFIX).exists(); } protected boolean isDomainDeployed(String domainName) { return new File(domainsDir, domainName + ANCHOR_SUFFIX).exists(); } /** * @param artifactName * @return the directory of the internal repository for the artifact with the given name. */ protected File getArtifactInternalRepository(String artifactName) { return new File(new File(appsDir, artifactName), "repository"); } /** * @return the directory of the internal repository for the Mule runtime. */ protected File getRuntimeInternalRepository() { return this.internalRepository; } public File getLog() { File logEE = newFile(muleHome + "/logs/mule_ee.log"); File logCE = newFile(muleHome + "/logs/mule.log"); if (logCE.exists() && logCE.isFile()) { return logCE; } if (logEE.exists() && logEE.isFile()) { return logEE; } throw new MuleControllerException(String.format("There is no mule log available at %s/logs/", muleHome)); } public File getLog(String appName) { File log = newFile(String.format("%s/logs/mule-app-%s.log", muleHome, appName)); if (log.exists() && log.isFile()) { return log; } throw new MuleControllerException(String.format("There is no mule log available at %s/logs/", muleHome)); } public void addConfProperty(String value) { try { int maxOrder = getMaxPropertyOrder(wrapperConf); String line = format("wrapper.java.additional.%d=%s\n", maxOrder + 1, value); Files.write(wrapperConf, line.getBytes(UTF_8), APPEND); } catch (IOException e) { throw new UncheckedIOException("Couldn't add wrapper.conf property", e); } } private int getOrderNumber(String line) { Matcher matcher = pattern.matcher(line); matcher.find(); return parseInt(matcher.group(1)); } private Integer getMaxPropertyOrder(Path path) throws IOException { return Files.lines(path) .filter(line -> pattern.matcher(line).find()) .map(line -> getOrderNumber(line)) .max(Integer::compare).get(); } }