Java tutorial
/** * 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; import net.oneandone.stool.configuration.Until; import net.oneandone.stool.stage.Stage; import net.oneandone.stool.util.Files; import net.oneandone.stool.util.Ports; import net.oneandone.stool.util.ServerXml; import net.oneandone.stool.util.Session; import net.oneandone.sushi.cli.ArgumentException; import net.oneandone.sushi.cli.Console; import net.oneandone.sushi.cli.Option; import net.oneandone.sushi.fs.GetLastModifiedException; import net.oneandone.sushi.fs.Node; import net.oneandone.sushi.fs.file.FileNode; import net.oneandone.sushi.io.OS; import net.oneandone.sushi.util.Separator; import net.oneandone.sushi.util.Strings; import org.springframework.http.HttpMethod; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.xml.sax.SAXException; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; public class Start extends StageCommand { @Option("debug") private boolean debug = false; @Option("suspend") private boolean suspend = false; @Option("tail") private boolean tail = false; public Start(Session session, boolean debug, boolean suspend) throws IOException { super(session); this.debug = debug; this.suspend = suspend; } public static String tomcatName(String version) { return "apache-tomcat-" + version; } @Override public void doInvoke(Stage stage) throws Exception { FileNode download; Ports ports; serviceWrapperOpt(stage.config().tomcatService); download = tomcatOpt(stage.config().tomcatVersion); timeStart(); checkUntil(stage.config().until); checkCommitted(stage); checkNotStarted(stage); ports = Ports.allocate(stage); copyTemplate(stage, ports); copyTomcatBaseOpt(download, stage.shared(), stage.config().tomcatVersion); if (session.bedroom.stages().contains(stage.getName())) { console.info.println("leaving sleeping state"); session.bedroom.remove(stage.getName()); } if (debug || suspend) { console.info.println("debugging enabled on port " + ports.debug()); } stage.start(console, ports); ping(stage); stage.buildStats().start(executionTime()); stage.buildStats().save(); if (tail) { doTail(stage); } } private void doTail(Stage stage) throws IOException { List<Node> logs; int c; Node log; logs = stage.shared().find("tomcat/logs/catalina*.log"); if (logs.size() == 0) { throw new IOException("no log filesfound"); } Collections.sort(logs, new Comparator<Node>() { @Override public int compare(Node left, Node right) { try { return (int) (right.getLastModified() - left.getLastModified()); } catch (GetLastModifiedException e) { throw new IllegalStateException(e); } } }); log = logs.get(0); console.info.println("tail " + log); console.info.println("Press Ctrl-C to abort."); try (InputStream src = log.createInputStream()) { while (true) { if (src.available() == 0) { try { Thread.sleep(500); } catch (InterruptedException e) { console.info.println("[interrupted]"); break; } continue; } c = src.read(); if (c == -1) { console.info.println("[closed]"); break; } console.info.print((char) c); } } } private void checkNotStarted(Stage stage) throws IOException { if (stage.state().equals(Stage.State.UP)) { throw new IOException("Stage is already running."); } } private void ping(Stage stage) throws IOException, SAXException, URISyntaxException, InterruptedException { URI uri; SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setConnectTimeout(500); console.info.println("Ping'n Applications."); Thread.sleep(2000); for (String url : stage.urlMap().values()) { if (url.startsWith("http://")) { uri = new URI(url); console.verbose.println("Opening connection to " + url); try { requestFactory.createRequest(uri, HttpMethod.GET).execute(); } catch (IOException e) { console.verbose.println("Opening connection failed. " + e.getCause()); } } } } //TODO: work-around for sushi http problem with proxies // TODO: race condition for simultaneous downloads public static void downloadFile(Console console, String url, FileNode dest) throws IOException { console.info.print("downloading " + dest + " from " + url + " ..."); try { if (OS.CURRENT != OS.MAC) { // don't use sushi, it's not proxy-aware dest.getParent().exec("wget", "--tries=1", "--connect-timeout=5", "-q", "-O", dest.getName(), url); } else { // wget not available on Mac, but Mac usually have no proxy dest.getWorld().validNode(url).copyFile(dest); } } catch (IOException e) { throw new IOException(url + ": download failed: " + e.getMessage(), e); } console.info.println(" done"); } public void copyTemplate(Stage stage, Ports ports) throws Exception { FileNode shared; shared = stage.shared(); Files.template(world.resource("templates/stage"), shared, variables(stage, ports)); // manually create empty subdirectories, because git doesn't know them for (String dir : new String[] { "ssl", "run" }) { Files.stoolDirectory(shared.join(dir).mkdirOpt()); } } public FileNode tomcatOpt(String version) throws IOException { IOException failed; FileNode download; String name; FileNode base; name = tomcatName(version); download = session.home.join("downloads", name + ".tar.gz"); if (!download.exists()) { console.info.println("downloading tomcat ..."); try { downloadFile(console, "http://archive.apache.org/dist/tomcat/tomcat-7/v" + version + "/bin/" + name + ".tar.gz", download); } catch (IOException e) { failed = new IOException( "Cannot download Tomcat " + version + ". Please provide it manually at " + download); failed.addSuppressed(e); throw failed; } download.checkFile(); } base = session.home.join("tomcat/" + name); if (!base.exists()) { tar(base.getParent(), "zxf", download.getAbsolute(), name + "/lib", name + "/bin"); base.checkDirectory(); } return download; } public void serviceWrapperOpt(String version) throws IOException { FileNode download; String name; FileNode base; name = serviceWrapperName(version); download = session.home.join("downloads", name + ".tar.gz"); if (!download.exists()) { downloadFile(console, "http://wrapper.tanukisoftware.com/download/" + version + "/" + name + ".tar.gz", download); download.checkFile(); } base = session.home.join("service-wrapper/" + name); if (!base.exists()) { tar(base.getParent(), "zxf", download.getAbsolute()); base.checkDirectory(); } } public static String serviceWrapperName(String version) { String platform; String name; platform = (OS.CURRENT == OS.LINUX) ? "linux-x86-64" : "macosx-universal-64"; name = "wrapper-" + platform + "-" + version; return name; } 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); } } public void copyTomcatBaseOpt(FileNode download, FileNode shared, String version) throws IOException, SAXException { String name; FileNode src; FileNode dest; ServerXml serverXml; FileNode file; name = tomcatName(version); dest = shared.join("tomcat"); if (!dest.exists()) { tar(shared, "zxf", download.getAbsolute(), "--exclude", name + "/lib", "--exclude", name + "/bin", "--exclude", name + "/webapps"); src = shared.join(name); src.move(dest); file = dest.join("conf/server.xml"); serverXml = ServerXml.load(file); serverXml.stripComments(); serverXml.save(dest.join("conf/server.xml.template")); file.deleteFile(); dest.join("conf/logging.properties").appendLines("", "# appended by Stool: make sure we see application output in catalina.out", "org.apache.catalina.core.ContainerBase.[Catalina].level = INFO", "org.apache.catalina.core.ContainerBase.[Catalina].handlers = 1catalina.org.apache.juli.FileHandler"); Files.stoolTree(dest); } } private Map<String, String> variables(Stage stage, Ports ports) { Map<String, String> result; result = new HashMap<>(); result.put("java.home", stage.config().javaHome); result.put("wrapper.port", Integer.toString(ports.wrapper())); result.put("wrapper.java.additional", wrapperJavaAdditional(ports, stage)); return result; } private String wrapperJavaAdditional(Ports ports, Stage stage) { String tomcatOpts; List<String> opts; StringBuilder result; int i; opts = new ArrayList<>(); // for tomcat opts.add("-Djava.endorsed.dirs=%CATALINA_HOME%/endorsed"); opts.add("-Djava.io.tmpdir=%CATALINA_BASE%/temp"); opts.add("-Djava.util.logging.config.file=%CATALINA_BASE%/conf/logging.properties"); opts.add("-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"); opts.add("-Dcatalina.base=%CATALINA_BASE%"); opts.add("-Dcatalina.home=%CATALINA_HOME%"); tomcatOpts = stage.macros().replace(stage.config().tomcatOpts); opts.addAll(Separator.SPACE.split(tomcatOpts)); opts.add("-Xmx" + stage.config().tomcatHeap + "m"); opts.add("-XX:MaxPermSize=" + stage.config().tomcatHeap + "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("-Dcom.sun.management.jmxremote.ssl=false"); 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")); } i = 1; result = new StringBuilder(); for (String opt : opts) { result.append("wrapper.java.additional."); result.append(i); result.append('='); result.append(opt); result.append('\n'); i++; } return result.toString(); } private void checkUntil(Until until) { if (until.expired()) { throw new ArgumentException("'until' date has expired: " + until); } } @Override protected void checkCommitted(Stage stage) throws IOException { if (session.configuration.security.isPearl() || session.configuration.security.isWaterloo()) { try { super.checkCommitted(stage); } catch (IOException e) { throw new IOException( "Due to AC1-restrictions it's only allowed to run/build committed states on this machine. \n" + "Please commit your modified files in order to start/build the stage."); } } } }