/* * Copyright 2012-2017 the original author or authors. * * 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 org.springframework.boot.cli.infrastructure; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** * Utility to invoke the command line in the same way as a user would, i.e. via the shell * script in the package's bin directory. * * @author Andy Wilkinson * @author Phillip Webb */ public final class CommandLineInvoker { private final File workingDirectory; public CommandLineInvoker() { this(new File(".")); } public CommandLineInvoker(File workingDirectory) { this.workingDirectory = workingDirectory; } public Invocation invoke(String... args) throws IOException { return new Invocation(runCliProcess(args)); } private Process runCliProcess(String... args) throws IOException { List<String> command = new ArrayList<>(); command.add(findLaunchScript().getAbsolutePath()); command.addAll(Arrays.asList(args)); ProcessBuilder processBuilder = new ProcessBuilder(command) .directory(this.workingDirectory); processBuilder.environment().remove("JAVA_OPTS"); return processBuilder.start(); } private File findLaunchScript() throws IOException { File unpacked = new File("target/unpacked-cli"); if (!unpacked.isDirectory()) { File zip = new File("target").listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().endsWith("-bin.zip"); } })[0]; ZipInputStream input = new ZipInputStream(new FileInputStream(zip)); try { ZipEntry entry; while ((entry = input.getNextEntry()) != null) { File file = new File(unpacked, entry.getName()); if (entry.isDirectory()) { file.mkdirs(); } else { file.getParentFile().mkdirs(); FileOutputStream output = new FileOutputStream(file); try { StreamUtils.copy(input, output); if (entry.getName().endsWith("/bin/spring")) { file.setExecutable(true); } } finally { output.close(); } } } } finally { input.close(); } } File bin = new File(unpacked.listFiles()[0], "bin"); File launchScript = new File(bin, isWindows() ? "spring.bat" : "spring"); Assert.state(launchScript.exists() && launchScript.isFile(), "Could not find CLI launch script " + launchScript.getAbsolutePath()); return launchScript; } private boolean isWindows() { return File.separatorChar == '\\'; } /** * An ongoing Process invocation. */ public static final class Invocation { private final StringBuffer err = new StringBuffer(); private final StringBuffer out = new StringBuffer(); private final StringBuffer combined = new StringBuffer(); private final Process process; private final List<Thread> streamReaders = new ArrayList<>(); public Invocation(Process process) { this.process = process; this.streamReaders.add(new Thread(new StreamReadingRunnable( this.process.getErrorStream(), this.err, this.combined))); this.streamReaders.add(new Thread(new StreamReadingRunnable( this.process.getInputStream(), this.out, this.combined))); for (Thread streamReader : this.streamReaders) { streamReader.start(); } } public String getOutput() { return postProcessLines(getLines(this.combined)); } public String getErrorOutput() { return postProcessLines(getLines(this.err)); } public String getStandardOutput() { return postProcessLines(getStandardOutputLines()); } public List<String> getStandardOutputLines() { return getLines(this.out); } private String postProcessLines(List<String> lines) { StringWriter out = new StringWriter(); PrintWriter printOut = new PrintWriter(out); for (String line : lines) { if (!line.startsWith("Maven settings decryption failed")) { printOut.println(line); } } return out.toString(); } private List<String> getLines(StringBuffer buffer) { BufferedReader reader = new BufferedReader( new StringReader(buffer.toString())); String line; List<String> lines = new ArrayList<>(); try { while ((line = reader.readLine()) != null) { if (!line.startsWith("Picked up ")) { lines.add(line); } } } catch (IOException ex) { throw new RuntimeException("Failed to read output"); } return lines; } public int await() throws InterruptedException { for (Thread streamReader : this.streamReaders) { streamReader.join(); } return this.process.waitFor(); } /** * {@link Runnable} to copy stream output. */ private final class StreamReadingRunnable implements Runnable { private final InputStream stream; private final StringBuffer[] outputs; private final byte[] buffer = new byte[4096]; private StreamReadingRunnable(InputStream stream, StringBuffer... outputs) { this.stream = stream; this.outputs = outputs; } @Override public void run() { int read; try { while ((read = this.stream.read(this.buffer)) > 0) { for (StringBuffer output : this.outputs) { output.append(new String(this.buffer, 0, read)); } } } catch (IOException ex) { // Allow thread to die } } } } }