/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.runner.webapps; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.util.CommandLine; import org.eclipse.che.api.core.util.ProcessUtil; import org.eclipse.che.api.core.util.StreamPump; import org.eclipse.che.api.runner.RunnerException; import org.eclipse.che.api.runner.internal.ApplicationLogger; import org.eclipse.che.api.runner.internal.ApplicationLogsPublisher; import org.eclipse.che.api.runner.internal.ApplicationProcess; import org.eclipse.che.api.runner.internal.DeploymentSources; import com.google.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; /** * {@code ApplicationServer} implementation to deploy application to Apache Tomcat servlet container for *nix like system. * * @author Artem Zatsarynnyy * @author Roman Nikitenko */ @Singleton public class UnixTomcatServer extends BaseTomcatServer { private static final Logger LOG = LoggerFactory.getLogger(UnixTomcatServer.class); @Inject public UnixTomcatServer(@Named(MEM_SIZE_PARAMETER) int memSize, @Named(TOMCAT_HOME_PARAMETER) File tomcatHome, EventService eventService) { super(memSize, tomcatHome, eventService); } @Override public ApplicationProcess deploy(File appDir, DeploymentSources toDeploy, ApplicationServerRunnerConfiguration runnerConfiguration, ApplicationProcess.Callback callback) throws RunnerException { prepare(appDir, toDeploy, runnerConfiguration); final File logsDir = new File(appDir, "logs"); final File startUpScriptFile; try { startUpScriptFile = generateStartUpScript(appDir, runnerConfiguration); Files.createDirectory(logsDir.toPath()); } catch (IOException e) { throw new RunnerException(e); } final List<File> logFiles = new ArrayList<>(1); logFiles.add(new File(logsDir, "output.log")); return new TomcatProcess(appDir, startUpScriptFile, logFiles, runnerConfiguration, callback, eventService); } private File generateStartUpScript(File appDir, ApplicationServerRunnerConfiguration runnerConfiguration) throws IOException { final String startupScript = "#!/bin/sh\n" + exportEnvVariables(runnerConfiguration) + "cd tomcat\n" + "chmod +x bin/*.sh\n" + catalinaUnix(runnerConfiguration) + "PID=$!\n" + "echo \"$PID\" > ../run.pid\n" + "wait $PID"; final File startUpScriptFile = new File(appDir, "startup.sh"); Files.write(startUpScriptFile.toPath(), startupScript.getBytes()); if (!startUpScriptFile.setExecutable(true, false)) { throw new IOException("Unable to update attributes of the startup script"); } return startUpScriptFile; } private String exportEnvVariables(ApplicationServerRunnerConfiguration runnerConfiguration) { int memory = runnerConfiguration.getMemory(); if (memory <= 0) { memory = getMemSize(); } final String catalinaOpts = String.format("export CATALINA_OPTS=\"-Xms%dm -Xmx%dm\"%n", memory, memory); final int debugPort = runnerConfiguration.getDebugPort(); if (debugPort <= 0) { return catalinaOpts; } /* From catalina.sh: -agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND */ return catalinaOpts + String.format("export JPDA_ADDRESS=%d%n", debugPort) + String.format("export JPDA_TRANSPORT=%s%n", "dt_socket") + String.format("export JPDA_SUSPEND=%s%n", runnerConfiguration.isDebugSuspend() ? "y" : "n"); } private String catalinaUnix(ApplicationServerRunnerConfiguration runnerConfiguration) { final boolean debug = runnerConfiguration.getDebugPort() > 0; if (debug) { return "./bin/catalina.sh jpda run 2>&1 | tee ../logs/output.log &\n"; } return "./bin/catalina.sh run 2>&1 | tee ../logs/output.log &\n"; } private static class TomcatProcess extends ApplicationProcess { final int httpPort; final List<File> logFiles; final int debugPort; final File startUpScriptFile; final File workDir; final Callback callback; final EventService eventService; final String workspace; final String project; final long id; ApplicationLogger logger; Process process; StreamPump output; TomcatProcess(File appDir, File startUpScriptFile, List<File> logFiles, ApplicationServerRunnerConfiguration runnerConfiguration, Callback callback, EventService eventService) { this.httpPort = runnerConfiguration.getHttpPort(); this.logFiles = logFiles; this.debugPort = runnerConfiguration.getDebugPort(); this.startUpScriptFile = startUpScriptFile; this.workDir = appDir; this.callback = callback; this.eventService = eventService; this.workspace = runnerConfiguration.getRequest().getWorkspace(); this.project = runnerConfiguration.getRequest().getProject(); this.id = runnerConfiguration.getRequest().getId(); } @Override public synchronized void start() throws RunnerException { if (process != null && ProcessUtil.isAlive(process)) { throw new IllegalStateException("Process is already started"); } try { process = Runtime.getRuntime().exec(new CommandLine(startUpScriptFile.getAbsolutePath()).toShellCommand(), null, workDir); logger = new ApplicationLogsPublisher(new TomcatLogger(logFiles), eventService, id, workspace, project); output = new StreamPump(); output.start(process, logger); LOG.debug("Start Tomcat at port {}, application {}", httpPort, workDir); } catch (IOException e) { throw new RunnerException(e); } } @Override public synchronized void stop() throws RunnerException { if (process == null) { throw new IllegalStateException("Process is not started yet"); } // Use ProcessUtil.kill(process) because java.lang.Process.destroy() method doesn't // kill all child processes (see http://bugs.sun.com/view_bug.do?bug_id=4770092). ProcessUtil.kill(process); if (output != null) { output.stop(); } callback.stopped(); LOG.debug("Stop Tomcat at port {}, application {}", httpPort, workDir); } @Override public int waitFor() throws RunnerException { synchronized (this) { if (process == null) { throw new IllegalStateException("Process is not started yet"); } } try { process.waitFor(); } catch (InterruptedException e) { Thread.interrupted(); ProcessUtil.kill(process); } finally { if (output != null) { output.stop(); } } return process.exitValue(); } @Override public synchronized int exitCode() throws RunnerException { if (process == null || ProcessUtil.isAlive(process)) { return -1; } return process.exitValue(); } @Override public synchronized boolean isRunning() throws RunnerException { return process != null && ProcessUtil.isAlive(process); } @Override public synchronized ApplicationLogger getLogger() throws RunnerException { if (logger == null) { // is not started yet return ApplicationLogger.DUMMY; } return logger; } } }