Package ch.rasc.embeddedtc.runner

Source Code of ch.rasc.embeddedtc.runner.Runner

/**
* Copyright 2013 Ralph Schaer <ralphschaer@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.rasc.embeddedtc.runner;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.servlet.ServletException;

import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.deploy.ApplicationParameter;
import org.apache.catalina.deploy.ContextEnvironment;
import org.apache.catalina.deploy.ContextResource;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.ContextConfig;
import org.apache.catalina.startup.Tomcat;
import org.apache.juli.ClassLoaderLogManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.log.SystemLogHandler;
import org.yaml.snakeyaml.Yaml;

import ch.rasc.embeddedtc.runner.CheckConfig.CheckConfigOptions;
import ch.rasc.embeddedtc.runner.ObfuscateUtil.ObfuscateOptions;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;

public class Runner {

  public static final String TIMESTAMP_FILENAME = "WAR_TIMESTAMP";

  public static final String EXTRA_RESOURCES_DIR = "extra";

  private static Tomcat tomcat;

  private static Thread shutdownHook;

  /**
   * This method is called from the procrun service
   */
  public static void start(String... args) throws Exception {
    main(args);
  }

  /**
   * This method is called from the procrun service
   */
  public static void stop(@SuppressWarnings("unused") String... args) throws LifecycleException {
    tomcat.stop();
  }

  public static void main(String... args) throws Exception {
    String[] arguments;
    if (args.length == 0) {
      arguments = new String[] { "start" };
    } else {
      if ("obfuscate".equals(args[0]) || "start".equals(args[0]) || "stop".equals(args[0])
          || "checkConfig".equals(args[0])) {
        arguments = args;
      } else {
        List<String> argumentsList = new ArrayList<>(Arrays.asList(args));
        argumentsList.add(0, "start");
        arguments = argumentsList.toArray(new String[argumentsList.size()]);
      }
    }

    JCommander commander = new JCommander();
    ObfuscateOptions obfuscateOptions = new ObfuscateOptions();
    commander.addCommand("obfuscate", obfuscateOptions);

    StartOptions startOptions = new StartOptions();
    commander.addCommand("start", startOptions);

    StopOptions stopOptions = new StopOptions();
    commander.addCommand("stop", stopOptions);

    CheckConfigOptions checkConfigOptions = new CheckConfigOptions();
    commander.addCommand("checkConfig", checkConfigOptions);

    commander.parse(arguments);

    switch (commander.getParsedCommand()) {
    case "start":
      startTc(startOptions);
      break;
    case "stop":
      stopTc(stopOptions);
      break;
    case "obfuscate":
      ObfuscateUtil.obfuscate(obfuscateOptions);
      break;
    case "checkConfig":
      CheckConfig.check(checkConfigOptions);
      break;
    default:
      commander.usage();
      break;
    }

  }

  @SuppressWarnings("resource")
  private static void startTc(StartOptions startOptions) throws URISyntaxException, IOException, Exception,
      ServletException, LifecycleException {

    final Config config = readConfig(startOptions.configFile != null && !startOptions.configFile.isEmpty() ? startOptions.configFile
        .get(0) : null);

    for (Map.Entry<String, Object> entry : config.getSystemProperties().entrySet()) {
      String value = entry.getValue().toString();
      value = ObfuscateUtil.toPlaintext(value, startOptions.password);
      System.setProperty(entry.getKey(), value);
    }

    Path configuredPathToExtractDir = Paths.get(config.getExtractDirectory());
    final Path extractDir;
    if (!configuredPathToExtractDir.isAbsolute()) {
      extractDir = config.getMyJarDirectory().resolve(configuredPathToExtractDir);
    } else {
      extractDir = configuredPathToExtractDir;
    }

    boolean extractWar = true;

    if (Files.exists(extractDir) && !startOptions.clean) {
      Path timestampFile = extractDir.resolve(TIMESTAMP_FILENAME);
      if (Files.exists(timestampFile)) {
        byte[] extractTimestampBytes = Files.readAllBytes(timestampFile);
        String extractTimestamp = new String(extractTimestampBytes, StandardCharsets.UTF_8);

        String timestamp = null;
        try (InputStream is = Runner.class.getResourceAsStream("/" + TIMESTAMP_FILENAME);
            ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

          copy(is, bos);
          timestamp = new String(bos.toByteArray(), StandardCharsets.UTF_8);
        }

        if (Long.valueOf(timestamp) <= Long.valueOf(extractTimestamp)) {
          extractWar = false;
        }

      }
    }

    boolean isWin = System.getProperty("os.name").toLowerCase().contains("win");

    Path loggingPropertyFile = extractDir.resolve("logging.properties");
    Path loggingDir = extractDir.resolve("logs");
    Path tempDir = extractDir.resolve("temp");
    final Path defaultWebxmlFile = extractDir.resolve("web.xml");

    if (extractWar || startOptions.clean) {

      if (Files.exists(extractDir)) {
        Files.walkFileTree(extractDir, new DeleteDirectory());
      }

      Files.createDirectories(extractDir);
      Files.createDirectory(tempDir);
      Files.createDirectory(loggingDir);

      CodeSource src = Runner.class.getProtectionDomain().getCodeSource();
      List<String> warList = new ArrayList<>();
      List<String> extraList = new ArrayList<>();

      if (src != null) {
        URL jar = src.getLocation();
        ZipInputStream zip = new ZipInputStream(jar.openStream());
        ZipEntry ze = null;

        String extraDirName = EXTRA_RESOURCES_DIR + "/";
        while ((ze = zip.getNextEntry()) != null) {
          String entryName = ze.getName();
          if (entryName.startsWith(extraDirName)) {
            extraList.add(entryName);
          } else if (entryName.endsWith(".war")) {
            warList.add(entryName);
          }
        }
      }

      for (String war : warList) {
        Path warFile = extractDir.resolve(war);
        try (InputStream is = Runner.class.getResourceAsStream("/" + war)) {
          Files.copy(is, warFile);
        }
      }

      try (InputStream is = Runner.class.getResourceAsStream("/conf/web.xml")) {
        Files.copy(is, defaultWebxmlFile);
      }

      try (InputStream is = Runner.class.getResourceAsStream("/conf/logging.properties")) {
        Files.copy(is, loggingPropertyFile);
      }

      if (!extraList.isEmpty()) {
        for (String extra : extraList) {
          Path extraFile = extractDir.resolve(extra);
          Files.createDirectories(extraFile.getParent());
          try (InputStream is = Runner.class.getResourceAsStream("/" + extra)) {
            Files.copy(is, extraFile);
          }

          System.setProperty("EXTRA_RESOURCES_DIR", extractDir.resolve(EXTRA_RESOURCES_DIR).toAbsolutePath()
              .toString());
        }
      }

      Path timestampFile = extractDir.resolve(TIMESTAMP_FILENAME);
      try (InputStream is = Runner.class.getResourceAsStream("/" + TIMESTAMP_FILENAME)) {
        Files.copy(is, timestampFile);
      }

      if (isWin) {
        if (System.getProperty("os.arch").contains("64")) {
          try (InputStream is = Runner.class.getResourceAsStream("/tcnative-1.dll.64")) {
            if (is != null) {
              Files.copy(is, extractDir.resolve("tcnative-1.dll"));
            }
          }
        } else {
          try (InputStream is = Runner.class.getResourceAsStream("/tcnative-1.dll.32")) {
            if (is != null) {
              Files.copy(is, extractDir.resolve("tcnative-1.dll"));
            }
          }
        }
      }

    }

    if (isWin) {
      String libraryPath = System.getProperty("java.library.path");
      libraryPath = extractDir.toString() + ";" + libraryPath;
      System.setProperty("java.library.path", libraryPath);

      Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
      fieldSysPath.setAccessible(true);
      fieldSysPath.set(null, null);
    }

    List<String> absolutePathsToEmbeddedWars = new ArrayList<>();

    try (DirectoryStream<Path> wars = Files.newDirectoryStream(extractDir, "*.war")) {
      for (Path war : wars) {
        absolutePathsToEmbeddedWars.add(war.toAbsolutePath().toString());
      }
    }

    System.setProperty("java.io.tmpdir", tempDir.toAbsolutePath().toString());
    System.setProperty("log.dir", loggingDir.toAbsolutePath().toString());
    System.setProperty("java.util.logging.config.file", loggingPropertyFile.toAbsolutePath().toString());
    System.setProperty("java.util.logging.manager", "org.apache.juli.ClassLoaderLogManager");

    List<Connector> connectors = config.createConnectorObjects();
    for (Connector connector : connectors) {
      try {
        try (ServerSocket srv = new ServerSocket(connector.getPort())) {
          // nothing here
        }
      } catch (IOException e) {
        String msg = "PORT " + connector.getPort() + " ALREADY IN USE";
        System.err.println(msg);
        getLogger().error(msg);
        return;
      }
    }

    tomcat = new Tomcat() {

      @Override
      public Context addWebapp(@SuppressWarnings("hiding") Host host, String url, String name, String path) {
        String base = "org.apache.catalina.core.ContainerBase.[default].[";
        if (host == null) {
          base += getHost().getName();
        } else {
          base += host.getName();
        }
        base += "].[";
        base += url;
        base += "]";
        if (config.isSilent()) {
          Logger.getLogger(base).setLevel(Level.WARNING);
        } else {
          Logger.getLogger(base).setLevel(Level.INFO);
        }

        Context ctx = new StandardContext();
        ctx.setName(name);
        ctx.setPath(url);
        ctx.setDocBase(path);

        ContextConfig ctxCfg = new ContextConfig();
        ctx.addLifecycleListener(ctxCfg);
        ctxCfg.setDefaultWebXml(defaultWebxmlFile.toAbsolutePath().toString());
        getHost().addChild(ctx);

        return ctx;
      }

    };

    tomcat.setBaseDir(extractDir.toAbsolutePath().toString());
    tomcat.setSilent(config.isSilent());

    for (String s : new String[] { "org.apache.coyote.http11.Http11NioProtocol",
        "org.apache.tomcat.util.net.NioSelectorPool", Runner.class.getName() }) {
      if (config.isSilent()) {
        Logger.getLogger(s).setLevel(Level.WARNING);
      } else {
        Logger.getLogger(s).setLevel(Level.INFO);
      }
    }

    // Create all server objects;
    tomcat.getHost();

    // Install the listeners
    for (String listenerClassName : config.getListeners()) {
      @SuppressWarnings("unchecked")
      Class<LifecycleListener> listener = (Class<LifecycleListener>) Class.forName(listenerClassName);
      tomcat.getServer().addLifecycleListener(listener.newInstance());
    }

    for (Connector connector : connectors) {
      tomcat.setConnector(connector);
      tomcat.getService().addConnector(connector);
    }

    if (config.getJvmRoute() != null && !config.getJvmRoute().isEmpty()) {
      tomcat.getEngine().setJvmRoute(config.getJvmRoute());
    }

    for (Valve valve : config.createValveObjects()) {
      tomcat.getHost().getPipeline().addValve(valve);
    }

    if (config.isEnableNaming()) {
      tomcat.enableNaming();
    }

    // No context configured. Create a default context.
    if (config.getContexts().isEmpty()) {
      ch.rasc.embeddedtc.runner.Context ctx = new ch.rasc.embeddedtc.runner.Context();
      ctx.setContextPath("");
      ctx.setEmbeddedWar(absolutePathsToEmbeddedWars.iterator().next());
      config.setContexts(Collections.singletonList(ctx));
    }

    List<Context> contextsWithoutSessionPersistence = new ArrayList<>();
    for (ch.rasc.embeddedtc.runner.Context configuredContext : config.getContexts()) {
      configuredContext.decryptPasswords(startOptions.password);

      String contextPath = configuredContext.getContextPath();
      if (contextPath == null) {
        contextPath = "";
      }

      String warPath = null;

      if (configuredContext.getEmbeddedWar() != null) {
        if (configuredContext.getEmbeddedWar().contains("*")) {
          String regex = ".*?"
              + configuredContext.getEmbeddedWar().replace("\\", "\\\\").replace(".", "\\.")
                  .replace("*", ".*?") + "$";
          Pattern pattern = Pattern.compile(regex);

          for (String warAbsolutePath : absolutePathsToEmbeddedWars) {
            Matcher matcher = pattern.matcher(warAbsolutePath);
            if (matcher.matches()) {
              warPath = warAbsolutePath;
              break;
            }
          }
        } else {
          warPath = configuredContext.getEmbeddedWar();
        }

        if (warPath == null) {
          warPath = absolutePathsToEmbeddedWars.iterator().next();
        }
      } else if (configuredContext.getExternalWar() != null) {
        Path externWarPath = Paths.get(configuredContext.getExternalWar());
        if (externWarPath.isAbsolute()) {
          warPath = configuredContext.getExternalWar();
        } else {
          warPath = config.getMyJarDirectory().resolve(configuredContext.getExternalWar()).toString();
        }
      } else {
        // As a default, if no war is specified, take the first war
        // that's embedded in our jar
        warPath = absolutePathsToEmbeddedWars.iterator().next();
      }

      Context ctx = tomcat.addWebapp(contextPath, warPath);
      ctx.setSwallowOutput(true);

      for (ContextEnvironment env : configuredContext.getEnvironments()) {
        ctx.getNamingResources().addEnvironment(env);
      }

      for (ContextResource res : configuredContext.createContextResourceObjects()) {
        ctx.getNamingResources().addResource(res);
      }

      for (ApplicationParameter param : configuredContext.getParameters()) {
        ctx.addApplicationParameter(param);
      }

      if (configuredContext.getContextFile() != null) {
        Path contextFilePath = Paths.get(configuredContext.getContextFile());
        if (Files.exists(contextFilePath)) {
          try {
            URL contextFileURL = contextFilePath.toUri().toURL();
            ctx.setConfigFile(contextFileURL);
          } catch (Exception e) {
            getLogger().error("Problem with the context file: " + e.getMessage());
          }
        }
      } else {
        URL contextFileURL = getContextXml(warPath);
        if (contextFileURL != null) {
          ctx.setConfigFile(contextFileURL);
        }
      }

      // Shutdown tomcat if a failure occurs during startup
      ctx.addLifecycleListener(new LifecycleListener() {
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
          if (event.getLifecycle().getState() == LifecycleState.FAILED) {
            ((StandardServer) tomcat.getServer()).stopAwait();
          }
        }
      });

      if (!configuredContext.isSessionPersistence()) {
        contextsWithoutSessionPersistence.add(ctx);
      }
    }

    System.setOut(new SystemLogHandler(System.out));
    System.setErr(new SystemLogHandler(System.err));

    boolean useShutdownPort = config.getShutdown() != null && config.getShutdown().getPort() != null;
    if (useShutdownPort) {
      tomcat.getServer().setPort(config.getShutdown().getPort());
      tomcat.getServer().setShutdown(config.getShutdown().getCommand());
    }

    tomcat.start();

    // Disable session persistence support
    for (Context ctx : contextsWithoutSessionPersistence) {
      ((StandardManager) ctx.getManager()).setPathname(null);
    }

    if (config.isUseShutdownHook()) {
      if (shutdownHook == null) {
        shutdownHook = new RunnerShutdownHook();
      }
      Runtime.getRuntime().addShutdownHook(shutdownHook);

      LogManager logManager = LogManager.getLogManager();
      if (logManager instanceof ClassLoaderLogManager) {
        ((ClassLoaderLogManager) logManager).setUseShutdownHook(false);
      }
    }

    tomcat.getServer().await();

    if (useShutdownPort) {
      tomcat.stop();
    }
  }

  private static void stopTc(StopOptions stopOptions) throws URISyntaxException, IOException {
    Config config = readConfig(stopOptions.configFile != null && !stopOptions.configFile.isEmpty() ? stopOptions.configFile
        .get(0) : null);
    if (config.getShutdown() != null && config.getShutdown().getPort() != null) {

      // send shutdown command
      try (Socket socket = new Socket("localhost", config.getShutdown().getPort());
          final OutputStream stream = socket.getOutputStream()) {

        String command = config.getShutdown().getCommand();
        for (int i = 0; i < command.length(); i++) {
          stream.write(command.charAt(i));
        }

        stream.flush();
      }

    }
  }

  private static Config readConfig(String pathToConfigFile) throws URISyntaxException, IOException {
    URL myJarLocationURL = Runner.class.getProtectionDomain().getCodeSource().getLocation();
    Path myJar = Paths.get(myJarLocationURL.toURI());
    Path myJarDir = myJar.getParent();

    Path configFile;

    if (pathToConfigFile != null) {
      configFile = Paths.get(pathToConfigFile);
      if (!configFile.isAbsolute()) {
        configFile = myJarDir.resolve(pathToConfigFile);
      }
    } else {
      configFile = myJarDir.resolve("config.yaml");
    }

    Config config;
    if (Files.exists(configFile)) {
      try (InputStream is = Files.newInputStream(configFile)) {
        Yaml yaml = new Yaml();
        config = yaml.loadAs(is, Config.class);
      }
    } else {
      config = new Config();
    }

    config.setMyJarDirectory(myJarDir);

    return config;
  }

  private static URL getContextXml(String warPath) throws IOException {
    String urlStr = "jar:file:" + warPath + "!/META-INF/context.xml";
    URL url = new URL(urlStr);
    try (InputStream is = url.openConnection().getInputStream()) {
      if (is != null) {
        return url;
      }
    } catch (FileNotFoundException e) {
      // ignore this exception
    }

    return null;
  }

  public static Log getLogger() {
    return LogFactory.getLog(Runner.class);
  }

  private static void copy(InputStream source, OutputStream sink) throws IOException {
    byte[] buf = new byte[8192];
    int n;
    while ((n = source.read(buf)) > 0) {
      sink.write(buf, 0, n);
    }
  }

  private static class RunnerShutdownHook extends Thread {
    @Override
    public void run() {
      try {
        if (tomcat != null) {
          tomcat.stop();
        }
      } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        getLogger().error("shutdownHook failed", e);
      } finally {
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
          ((ClassLoaderLogManager) logManager).shutdown();
        }
      }
    }
  }

  @Parameters(commandDescription = "Starts Tomcat")
  private static class StartOptions {
    @Parameter(required = false, arity = 1, description = "absolutePathToConfigFile")
    private List<String> configFile;

    @Parameter(names = { "-p", "--password" }, description = "The password")
    private String password;

    @Parameter(names = { "-c", "--clean" }, description = "Force deletion of extraction directory at startup")
    private boolean clean;
  }

  @Parameters(commandDescription = "Stops Tomcat")
  private static class StopOptions {
    @Parameter(required = false, arity = 1, description = "absolutePathToConfigFile")
    private List<String> configFile;
  }
}
TOP

Related Classes of ch.rasc.embeddedtc.runner.Runner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.