/******************************************************************************* * 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.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.jvnet.winp.WinProcess; 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 windows system. * * @author Roman Nikitenko */ @Singleton public class WindowsTomcatServer extends BaseTomcatServer { private static final Logger LOG = LoggerFactory.getLogger(WindowsTomcatServer.class); @Inject public WindowsTomcatServer(@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 { generateSetEnvScript(appDir, runnerConfiguration); 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 void generateSetEnvScript(File appDir, ApplicationServerRunnerConfiguration runnerConfiguration) throws IOException { int memory = runnerConfiguration.getMemory(); if (memory <= 0) { memory = getMemSize(); } final String setEnvScript = "@echo off\r\n" + String.format("set \"CATALINA_OPTS=-server -Xms%dm -Xmx%dm\"\r\n", memory, memory) + "set \"CLASSPATH=%CATALINA_HOME%/conf/;%CATALINA_HOME%/lib/jul-to-slf4j.jar;^%CATALINA_HOME%/lib/slf4j-api.jar;" + "%CATALINA_HOME%/lib/logback-classic.jar\""; final File setEnvScriptFile = new File(appDir.toPath() + "/tomcat/bin", "setenv.bat"); Files.write(setEnvScriptFile.toPath(), setEnvScript.getBytes()); if (!setEnvScriptFile.setExecutable(true, false)) { throw new IOException("Unable to update attributes of the setenv script"); } } private File generateStartUpScript(File appDir, ApplicationServerRunnerConfiguration runnerConfiguration) throws IOException { final String startupScript = "@echo off\r\n" + "setlocal\r\n" + setDebugVariables(runnerConfiguration) + "cd tomcat\r\n" + setCatalinaVariables(runnerConfiguration); final File startUpScriptFile = new File(appDir, "startup.bat"); 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 setDebugVariables(ApplicationServerRunnerConfiguration runnerConfiguration) { final int debugPort = runnerConfiguration.getDebugPort(); if (debugPort > 0) { return "set \"JPDA_ADDRESS=" + debugPort + "\"\r\n" + "set \"JPDA_TRANSPORT=dt_socket\"\r\n" + "set \"JPDA_SUSPEND=" + (runnerConfiguration.isDebugSuspend() ? "y" : "n") + "\"\r\n"; } return ""; } private String setCatalinaVariables(ApplicationServerRunnerConfiguration runnerConfiguration) { String catalinaOpts = "set \"CATALINA_HOME=%cd%\"\r\n" + "set \"CATALINA_BASE=%cd%\"\r\n" + "set \"CATALINA_TMPDIR=%cd%\\temp\"\r\n"; final boolean debug = runnerConfiguration.getDebugPort() > 0; if (debug) { return catalinaOpts + "call bin/catalina.bat jpda run 2>&1\r\n"; } return catalinaOpts + "call bin/catalina.bat run 2>&1\r\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; WinProcess winProcess; 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 && isAlive()) { throw new IllegalStateException("Process is already started"); } try { process = Runtime.getRuntime().exec(new CommandLine(startUpScriptFile.getAbsolutePath()).toShellCommand(), null, workDir); winProcess = new WinProcess(process); 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"); } killProcess(); if (output != null) { output.stop(); } callback.stopped(); LOG.debug("Stop Tomcat at port {}, application {}", httpPort, workDir); } private void killProcess() throws RunnerException { if (isAlive()) { winProcess.killRecursively(); winProcess = null; } } @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(); killProcess(); } finally { if (output != null) { output.stop(); } } return process.exitValue(); } @Override public synchronized int exitCode() throws RunnerException { if (process == null || isAlive()) { return -1; } return process.exitValue(); } public boolean isAlive() { return winProcess != null && winProcess.getPid() > 0; } @Override public synchronized boolean isRunning() throws RunnerException { return process != null && isAlive(); } @Override public synchronized ApplicationLogger getLogger() throws RunnerException { if (logger == null) { // is not started yet return ApplicationLogger.DUMMY; } return logger; } } }