All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.oneandone.stool.templates.Tomcat Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 *
 * 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 net.oneandone.stool.templates;

import net.oneandone.inline.ArgumentException;
import net.oneandone.inline.Console;
import net.oneandone.stool.cli.Main;
import net.oneandone.stool.configuration.StageConfiguration;
import net.oneandone.stool.stage.Stage;
import net.oneandone.stool.util.Ports;
import net.oneandone.stool.util.Session;
import net.oneandone.stool.util.Vhost;
import net.oneandone.sushi.fs.file.FileNode;
import net.oneandone.sushi.launcher.Launcher;
import net.oneandone.sushi.util.Separator;
import net.oneandone.sushi.util.Strings;
import net.oneandone.sushi.util.Substitution;
import net.oneandone.sushi.util.SubstitutionException;
import net.oneandone.sushi.xml.XmlException;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Tomcat {
    private final Stage stage;
    private final FileNode context;
    private final StageConfiguration configuration;
    private final Session session;
    private final Console console;
    private final Ports ports;

    public Tomcat(Stage stage, FileNode context, Session session, Ports ports) {
        this.stage = stage;
        this.context = context;
        this.configuration = stage.config();
        this.session = session;
        this.console = session.console;
        this.ports = ports;
    }

    //-- public interface

    /** null for disabled */
    public String major(String version) {
        return version.substring(0, version.indexOf('.'));
    }

    /** empty string if app does not need any secrets */
    public String fault() throws IOException {
        Launcher launcher;
        List projects;
        FileNode token;
        FileNode list;

        list = session.world.getTemp().createTempFile();
        launcher = context.launcher("fault");
        if (console.getVerbose()) {
            launcher.arg("-v");
        }
        launcher.arg("-maven-repository=" + stage.localRepository().getAbsolute());
        launcher.arg("resolve", "-output", list.getAbsolute());
        for (String p : stage.faultProjects()) {
            launcher.arg(p);
        }
        launcher.getBuilder().inheritIO();
        console.verbose.println("exec " + launcher);
        launcher.exec();
        projects = list.readLines();
        if (projects.isEmpty()) {
            return "";
        }
        token = context.join(".fault-token");
        token.writeString("");
        session.world.onShutdown().deleteAtExit(token);
        token.setPermissions("rw-------");
        token.appendString(session.world.getHome().join(token.getName()).readString());
        return Separator.SPACE.join(projects);
    }

    /* also checks sha1 digest - https://run-jira.tool.1and1.com/browse/CISOOPS-2406 */
    public void download(String downloadUrl, String version) throws IOException {
        FileNode download;
        FileNode dest;

        download = session.downloads().join(tomcatName(version) + ".tar.gz");
        if (!download.exists()) {
            downloadFile(subst(downloadUrl, version), download);
            download.checkFile();
        }
        dest = tomcatTarGz(version);
        dest.getParent().mkdirsOpt();
        download.copyFile(dest);
    }

    public void serverXml(String version, String cookiesStr, String keystorePassword) throws IOException, SAXException, XmlException {
        FileNode tomcatTarGz;
        CookieMode cookies;
        ServerXml serverXml;
        FileNode tomcat;
        FileNode serverXmlDest;

        cookies = CookieMode.valueOf(cookiesStr.toUpperCase());
        tomcatTarGz = tomcatTarGz(version);
        tomcat = tomcatTarGz.getParent();
        serverXmlDest = serverXml();
        tar(tomcat, "zxf", tomcatTarGz.getName(), "--strip-components=2", tomcatName(version) + "/conf/server.xml");

        serverXml = ServerXml.load(serverXmlDest, stage.getName(), session.configuration.hostname);
        serverXml.stripComments();
        serverXml.configure(ports, configuration.url, keystorePassword, cookies, legacyVersion(version));
        serverXml.save(serverXmlDest);
    }

    public String catalinaOpts(String extraOpts, boolean debug, boolean suspend) {
        List opts;
        String tomcatOpts;

        opts = new ArrayList<>();

        tomcatOpts = escape(stage.macros().replace(extraOpts));
        opts.addAll(Separator.SPACE.split(tomcatOpts));

        opts.add("-Xmx" + stage.config().memory * 3 / 4 + "m");

        // see http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html
        opts.add("-Dcom.sun.management.jmxremote.authenticate=false");
        opts.add("-Dcom.sun.management.jmxremote.port=" + ports.jmx());
        opts.add("-Dcom.sun.management.jmxremote.rmi.port=" + ports.jmx());
        opts.add("-Djava.rmi.server.hostname=" + session.configuration.hostname); // needed for jmx access - see https://forums.docker.com/t/enable-jmx-rmi-access-to-a-docker-container/625/2
        opts.add("-Dcom.sun.management.jmxremote.ssl=false");

        if (stage.isSystem()) {
            opts.add("-Dstool.cp=" + Main.stoolCp(session.world).getAbsolute());
            opts.add("-Dstool.home=" + session.home.getAbsolute());
            opts.add("-Dstool.idlink=" + session.backstageLink(stage.getId()).getAbsolute());
        }

        if (debug || suspend) {
            opts.add("-Xdebug");
            opts.add("-Xnoagent");
            opts.add("-Djava.compiler=NONE");
            opts.add("-Xrunjdwp:transport=dt_socket,server=y,address=" + ports.debug() + ",suspend=" + (suspend ? "y" : "n"));
        }
        return Separator.SPACE.join(opts);
    }

    /* for shell processing in dockerfile and eval call in catalina.sh: | -> \\| */
    private static String escape(String str) {
        return str.replace("|", "\\\\|");
    }

    public void contextParameters(boolean logroot, String ... additionals) throws IOException, SAXException, XmlException {
        ServerXml serverXml;

        serverXml = ServerXml.load(serverXml(), stage.getName(), session.configuration.hostname);
        serverXml.addContextParameters(stage, logroot, Strings.toMap(additionals));
        serverXml.save(serverXml());
    }

    //--

    private FileNode tomcatTarGz(String version) {
        return stage.backstage.join("context/tomcat/apache-tomcat-" + version + ".tar.gz");
    }

    private FileNode serverXml() {
        return stage.backstage.join("context/tomcat/server.xml");
    }

    /** @return true for 8.0.x and older */
    private static boolean legacyVersion(String version) {
        int idx;
        int major;

        if (version.startsWith("8.0.")) {
            return true;
        }
        idx = version.indexOf('.');
        if (idx == -1) {
            throw new IllegalArgumentException(version);
        }
        major = Integer.parseInt(version.substring(0, idx));
        return major < 8;
    }

    private String certhost() {
        if (session.configuration.vhosts) {
            return "*." + stage.getName() + "." + session.configuration.hostname;
        } else {
            return session.configuration.hostname;
        }
    }

    //--

    private static String tomcatName(String version) {
        return "apache-tomcat-" + version;
    }

    private String subst(String pattern, String version) {
        Map variables;

        variables = new HashMap<>();
        variables.put("version", version);
        variables.put("major", major(version));
        try {
            return Substitution.ant().apply(pattern, variables);
        } catch (SubstitutionException e) {
            throw new ArgumentException("invalid url pattern: " + pattern, e);
        }
    }

    private void downloadFile(String url, FileNode dest) throws IOException {
        String sha512Expected;
        String sha512Found;

        console.info.println("downloading " + url + " ...");
        try {
            dest.getWorld().validNode(url).copyFile(dest);
        } catch (IOException e) {
            dest.deleteFileOpt();
            throw new IOException("download failed: " + url
                    + "\nAs a work-around, you can download it manually an place it at " + dest.getAbsolute()
                    + "\nDetails: " + e.getMessage(), e);
        }
        try {
            sha512Expected = dest.getWorld().validNode(url + ".sha512").readString();
        } catch (IOException e) {
            dest.deleteFile();
            throw new IOException("failed to download " + url + ".sha512", e);
        }
        sha512Expected = sha512Expected.substring(0, sha512Expected.indexOf(' '));
        try {
            sha512Found = dest.digest("SHA-512");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }
        if (!sha512Expected.equals(sha512Found)) {
            dest.deleteFile();
            throw new IOException("sha512 digest mismatch: " + sha512Expected + " vs" + sha512Found);
        }
    }

    private void tar(FileNode directory, String... args) throws IOException {
        String output;

        output = directory.exec(Strings.cons("tar", args));
        if (!output.trim().isEmpty()) {
            throw new IOException("unexpected output by tar command: " + output);
        }
    }

    //--

    // TODO: placing this in a separate Fitnesse class didn't work
    public void fitnesse() throws IOException {
        Launcher launcher;
        FileNode dir;
        FileNode deps;

        dir = stage.getDirectory().join("target/fitnesse");
        dir.mkdirsOpt();
        dir.join("fitnesse.sh").writeString(fitnesseSh());
        dir.join("fitnesse.sh").setPermissions("rwxrwxrwx");
        // mark as source stage
        context.join(".source").writeBytes();

        for (Vhost vhost : ports.vhosts()) {
            if (vhost.isWebapp()) {
                deps = fitnesseProject(vhost).join("target/fitnesse/dependencies");
                deps.deleteTreeOpt();
                deps.mkdirsOpt();
                launcher = stage.launcher("mvn", "dependency::copy-dependencies",
                        "-DoutputDirectory=" + deps.getAbsolute(), "-DexcludeScope=system");
                launcher.dir(vhostProject(vhost));
                launcher.exec(stage.session.console.verbose);
            }
        }
    }

    private FileNode vhostProject(Vhost vhost) {
        String path;

        path = vhost.docroot.getAbsolute();
        return session.world.file(path.substring(0, path.lastIndexOf("/target/")));
    }

    private String fitnesseSh() {
        StringBuilder result;

        result = new StringBuilder();
        result.append("#!/bin/bash\n");
        result.append("umask 0002");
        for (Vhost vhost : ports.vhosts()) {
            if (vhost.isWebapp()) {
                result.append("\n");
                result.append("# vhost " + vhost.name + "\n");
                result.append("cd /stage/" + fitnesseProject(vhost).getRelative(stage.getDirectory()) + "\n");
                result.append("deps=target/fitnesse/dependencies\n");
                result.append("cp=target/test-classes:target/classes\n");
                result.append("for file in $(ls $deps/*.jar); do\n");
                result.append("  cp=\"$cp:$file\"\n");
                result.append("done\n");
                result.append("export CLASSPATH=$cp\n");
                result.append("java -jar $HOME/fitnesse-standalone.jar -v -r src/test/fitnesse -p " + vhost.httpPort() + " -e 0 >/var/log/stool/fitnesse-" + vhost.name + ".log 2>&1 &\n");
                result.append(pidvar(vhost) + "=$!\n");
            }
        }
        result.append("trap 'kill -TERM " + allPids() + "' TERM\n");
        for (Vhost vhost : ports.vhosts()) {
            if (vhost.isWebapp()) {
                result.append("wait $" + pidvar(vhost) + "\n");
            }
        }
        return result.toString();
    }

    private FileNode fitnesseProject(Vhost fitnesseHost) {
        FileNode dir;;


        dir = ports.lookup(fitnesseHost.name).docroot;
        dir = dir.getParent();
        if (!dir.getName().equals("target")) {
            throw new IllegalStateException(dir.getAbsolute());
        }
        return dir.getParent();
    }

    private String allPids() {
        StringBuilder result;

        result = new StringBuilder();
        for (Vhost vhost : ports.vhosts()) {
            if (vhost.isWebapp()) {
                result.append('$').append(pidvar(vhost));
            }
        }
        return result.toString();
    }

    private static String pidvar(Vhost vhost) {
        return "pid_" + vhost.httpPort();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy