Relocate projects to spring-boot-project
Move projects to better reflect the way that Spring Boot is released. The following projects are under `spring-boot-project`: - `spring-boot` - `spring-boot-autoconfigure` - `spring-boot-tools` - `spring-boot-starters` - `spring-boot-actuator` - `spring-boot-actuator-autoconfigure` - `spring-boot-test` - `spring-boot-test-autoconfigure` - `spring-boot-devtools` - `spring-boot-cli` - `spring-boot-docs` See gh-9316
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.cli.infrastructure.CommandLineInvoker;
|
||||
import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Integration Tests for the command line application.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class CommandLineIT {
|
||||
|
||||
private final CommandLineInvoker cli = new CommandLineInvoker();
|
||||
|
||||
@Test
|
||||
public void hintProducesListOfValidCommands()
|
||||
throws IOException, InterruptedException {
|
||||
Invocation cli = this.cli.invoke("hint");
|
||||
assertThat(cli.await(), equalTo(0));
|
||||
assertThat("Unexpected error: \n" + cli.getErrorOutput(),
|
||||
cli.getErrorOutput().length(), equalTo(0));
|
||||
assertThat(cli.getStandardOutputLines().size(), equalTo(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokingWithNoArgumentsDisplaysHelp()
|
||||
throws IOException, InterruptedException {
|
||||
Invocation cli = this.cli.invoke();
|
||||
assertThat(cli.await(), equalTo(1));
|
||||
assertThat(cli.getErrorOutput().length(), equalTo(0));
|
||||
assertThat(cli.getStandardOutput(), startsWith("usage:"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrecognizedCommandsAreHandledGracefully()
|
||||
throws IOException, InterruptedException {
|
||||
Invocation cli = this.cli.invoke("not-a-real-command");
|
||||
assertThat(cli.await(), equalTo(1));
|
||||
assertThat(cli.getErrorOutput(),
|
||||
containsString("'not-a-real-command' is not a valid command"));
|
||||
assertThat(cli.getStandardOutput().length(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void version() throws IOException, InterruptedException {
|
||||
Invocation cli = this.cli.invoke("version");
|
||||
assertThat(cli.await(), equalTo(0));
|
||||
assertThat(cli.getErrorOutput().length(), equalTo(0));
|
||||
assertThat(cli.getStandardOutput(), startsWith("Spring CLI v"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void help() throws IOException, InterruptedException {
|
||||
Invocation cli = this.cli.invoke("help");
|
||||
assertThat(cli.await(), equalTo(1));
|
||||
assertThat(cli.getErrorOutput().length(), equalTo(0));
|
||||
assertThat(cli.getStandardOutput(), startsWith("usage:"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.cli.command.archive.JarCommand;
|
||||
import org.springframework.boot.cli.infrastructure.CommandLineInvoker;
|
||||
import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation;
|
||||
import org.springframework.boot.loader.tools.JavaExecutable;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Integration test for {@link JarCommand}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class JarCommandIT {
|
||||
|
||||
private static final boolean JAVA_9_OR_LATER = isClassPresent(
|
||||
"java.security.cert.URICertStoreParameters");
|
||||
|
||||
private final CommandLineInvoker cli = new CommandLineInvoker(
|
||||
new File("src/it/resources/jar-command"));
|
||||
|
||||
@Test
|
||||
public void noArguments() throws Exception {
|
||||
Invocation invocation = this.cli.invoke("jar");
|
||||
invocation.await();
|
||||
assertThat(invocation.getStandardOutput(), equalTo(""));
|
||||
assertThat(invocation.getErrorOutput(), containsString("The name of the "
|
||||
+ "resulting jar and at least one source file must be specified"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSources() throws Exception {
|
||||
Invocation invocation = this.cli.invoke("jar", "test-app.jar");
|
||||
invocation.await();
|
||||
assertThat(invocation.getStandardOutput(), equalTo(""));
|
||||
assertThat(invocation.getErrorOutput(), containsString("The name of the "
|
||||
+ "resulting jar and at least one source file must be specified"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jarCreationWithGrabResolver() throws Exception {
|
||||
File jar = new File("target/test-app.jar");
|
||||
Invocation invocation = this.cli.invoke("run", jar.getAbsolutePath(),
|
||||
"bad.groovy");
|
||||
invocation.await();
|
||||
if (!JAVA_9_OR_LATER) {
|
||||
assertThat(invocation.getErrorOutput(), equalTo(""));
|
||||
}
|
||||
invocation = this.cli.invoke("jar", jar.getAbsolutePath(), "bad.groovy");
|
||||
invocation.await();
|
||||
if (!JAVA_9_OR_LATER) {
|
||||
assertEquals(invocation.getErrorOutput(), 0,
|
||||
invocation.getErrorOutput().length());
|
||||
}
|
||||
assertTrue(jar.exists());
|
||||
|
||||
Process process = new JavaExecutable()
|
||||
.processBuilder("-jar", jar.getAbsolutePath()).start();
|
||||
invocation = new Invocation(process);
|
||||
invocation.await();
|
||||
|
||||
if (!JAVA_9_OR_LATER) {
|
||||
assertThat(invocation.getErrorOutput(), equalTo(""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jarCreation() throws Exception {
|
||||
File jar = new File("target/test-app.jar");
|
||||
Invocation invocation = this.cli.invoke("jar", jar.getAbsolutePath(),
|
||||
"jar.groovy");
|
||||
invocation.await();
|
||||
if (!JAVA_9_OR_LATER) {
|
||||
assertEquals(invocation.getErrorOutput(), 0,
|
||||
invocation.getErrorOutput().length());
|
||||
}
|
||||
assertTrue(jar.exists());
|
||||
|
||||
Process process = new JavaExecutable()
|
||||
.processBuilder("-jar", jar.getAbsolutePath()).start();
|
||||
invocation = new Invocation(process);
|
||||
invocation.await();
|
||||
|
||||
if (!JAVA_9_OR_LATER) {
|
||||
assertThat(invocation.getErrorOutput(), equalTo(""));
|
||||
}
|
||||
assertThat(invocation.getStandardOutput(), containsString("Hello World!"));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
containsString("/BOOT-INF/classes!/public/public.txt"));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
containsString("/BOOT-INF/classes!/resources/resource.txt"));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
containsString("/BOOT-INF/classes!/static/static.txt"));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
containsString("/BOOT-INF/classes!/templates/template.txt"));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
containsString("/BOOT-INF/classes!/root.properties"));
|
||||
assertThat(invocation.getStandardOutput(), containsString("Goodbye Mama"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jarCreationWithIncludes() throws Exception {
|
||||
File jar = new File("target/test-app.jar");
|
||||
Invocation invocation = this.cli.invoke("jar", jar.getAbsolutePath(), "--include",
|
||||
"-public/**,-resources/**", "jar.groovy");
|
||||
invocation.await();
|
||||
if (!JAVA_9_OR_LATER) {
|
||||
assertEquals(invocation.getErrorOutput(), 0,
|
||||
invocation.getErrorOutput().length());
|
||||
}
|
||||
assertTrue(jar.exists());
|
||||
|
||||
Process process = new JavaExecutable()
|
||||
.processBuilder("-jar", jar.getAbsolutePath()).start();
|
||||
invocation = new Invocation(process);
|
||||
invocation.await();
|
||||
|
||||
if (!JAVA_9_OR_LATER) {
|
||||
assertThat(invocation.getErrorOutput(), equalTo(""));
|
||||
}
|
||||
assertThat(invocation.getStandardOutput(), containsString("Hello World!"));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
not(containsString("/public/public.txt")));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
not(containsString("/resources/resource.txt")));
|
||||
assertThat(invocation.getStandardOutput(), containsString("/static/static.txt"));
|
||||
assertThat(invocation.getStandardOutput(),
|
||||
containsString("/templates/template.txt"));
|
||||
assertThat(invocation.getStandardOutput(), containsString("Goodbye Mama"));
|
||||
}
|
||||
|
||||
private static boolean isClassPresent(String name) {
|
||||
try {
|
||||
Class.forName(name);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.cli.command.archive.WarCommand;
|
||||
import org.springframework.boot.cli.infrastructure.CommandLineInvoker;
|
||||
import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation;
|
||||
import org.springframework.boot.loader.tools.JavaExecutable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration test for {@link WarCommand}.
|
||||
*
|
||||
* @author Andrey Stolyarov
|
||||
* @author Henri Kerola
|
||||
*/
|
||||
public class WarCommandIT {
|
||||
|
||||
private final CommandLineInvoker cli = new CommandLineInvoker(
|
||||
new File("src/it/resources/war-command"));
|
||||
|
||||
@Test
|
||||
public void warCreation() throws Exception {
|
||||
File war = new File("target/test-app.war");
|
||||
Invocation invocation = this.cli.invoke("war", war.getAbsolutePath(),
|
||||
"war.groovy");
|
||||
invocation.await();
|
||||
assertThat(war.exists()).isTrue();
|
||||
Process process = new JavaExecutable()
|
||||
.processBuilder("-jar", war.getAbsolutePath(), "--server.port=0").start();
|
||||
invocation = new Invocation(process);
|
||||
invocation.await();
|
||||
assertThat(invocation.getOutput()).contains("onStart error");
|
||||
assertThat(invocation.getOutput()).contains("Tomcat started");
|
||||
assertThat(invocation.getOutput())
|
||||
.contains("/WEB-INF/lib-provided/tomcat-embed-core");
|
||||
assertThat(invocation.getOutput()).contains("WEB-INF/classes!/root.properties");
|
||||
process.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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.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((pathname) -> pathname.getName().endsWith("-bin.zip"))[0];
|
||||
try (ZipInputStream input = new ZipInputStream(new FileInputStream(zip))) {
|
||||
ZipEntry entry;
|
||||
while ((entry = input.getNextEntry()) != null) {
|
||||
File file = new File(unpacked, entry.getName());
|
||||
if (entry.isDirectory()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
else {
|
||||
file.getParentFile().mkdirs();
|
||||
try (FileOutputStream output = new FileOutputStream(file)) {
|
||||
StreamUtils.copy(input, output);
|
||||
if (entry.getName().endsWith("/bin/spring")) {
|
||||
file.setExecutable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@GrabResolver(name='clojars.org', root='http://clojars.org/repo')
|
||||
@Grab('redis.embedded:embedded-redis:0.2')
|
||||
|
||||
@Component
|
||||
class EmbeddedRedis {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.test
|
||||
|
||||
@EnableGroovyTemplates
|
||||
@Component
|
||||
class Example implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private MyService myService
|
||||
|
||||
void run(String... args) {
|
||||
println "Hello ${this.myService.sayWorld()}"
|
||||
println getClass().getResource('/public/public.txt')
|
||||
println getClass().getResource('/resources/resource.txt')
|
||||
println getClass().getResource('/static/static.txt')
|
||||
println getClass().getResource('/templates/template.txt')
|
||||
println getClass().getResource('/root.properties')
|
||||
println template('template.txt', [world:'Mama'])
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
class MyService {
|
||||
|
||||
String sayWorld() {
|
||||
return 'World!'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Goodbye ${world}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.test
|
||||
|
||||
@Component
|
||||
class Example implements CommandLineRunner {
|
||||
|
||||
void run(String... args) {
|
||||
print "Ssshh"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.test
|
||||
|
||||
@RestController
|
||||
class WarExample implements CommandLineRunner {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String hello() {
|
||||
return "Hello"
|
||||
}
|
||||
|
||||
void run(String... args) {
|
||||
println getClass().getResource('/org/apache/tomcat/InstanceManager.class')
|
||||
println getClass().getResource('/root.properties')
|
||||
throw new RuntimeException("onStart error")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<assembly>
|
||||
<id>bin</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<baseDirectory>spring-${project.version}</baseDirectory>
|
||||
<includeBaseDirectory>true</includeBaseDirectory>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>src/main/content</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<useDefaultExcludes>true</useDefaultExcludes>
|
||||
<fileMode>644</fileMode>
|
||||
<directoryMode>755</directoryMode>
|
||||
<filtered>true</filtered>
|
||||
<includes>
|
||||
<include>INSTALL.txt</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>src/main/content</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<useDefaultExcludes>true</useDefaultExcludes>
|
||||
<fileMode>644</fileMode>
|
||||
<directoryMode>755</directoryMode>
|
||||
<excludes>
|
||||
<exclude>INSTALL.txt</exclude>
|
||||
</excludes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>src/main/executablecontent</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<useDefaultExcludes>true</useDefaultExcludes>
|
||||
<fileMode>755</fileMode>
|
||||
<directoryMode>755</directoryMode>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
<files>
|
||||
<file>
|
||||
<source>${project.build.directory}/${project.artifactId}-${project.version}-full.jar</source>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<destName>${project.build.finalName}.jar</destName>
|
||||
</file>
|
||||
</files>
|
||||
</assembly>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||
<id>full</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<useProjectArtifact />
|
||||
<includes>
|
||||
<include>${project.groupId}:${project.artifactId}</include>
|
||||
</includes>
|
||||
<unpack>true</unpack>
|
||||
<outputDirectory>BOOT-INF/classes/</outputDirectory>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/assembly</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,44 @@
|
||||
SPRING BOOT CLI - INSTALLATION
|
||||
==============================
|
||||
|
||||
Thank you for downloading the Spring Boot CLI tool. Please follow these instructions
|
||||
in order to complete your installation.
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
Spring Boot CLI requires Java JDK v1.8 or above in order to run. Groovy v${groovy.version}
|
||||
is packaged as part of this distribution, and therefore does not need to be installed (any
|
||||
existing Groovy installation is ignored).
|
||||
|
||||
The CLI will use whatever JDK it finds on your path, to check that you have an appropriate
|
||||
version you should run:
|
||||
|
||||
java -version
|
||||
|
||||
Alternatively, you can set the JAVA_HOME environment variable to point an suitable JDK.
|
||||
|
||||
|
||||
Environment Variables
|
||||
---------------------
|
||||
No specific environment variables are required to run the CLI, however, you may want to
|
||||
set SPRING_HOME to point to a specific installation. You should also add SPRING_HOME/bin
|
||||
to your PATH environment variable.
|
||||
|
||||
|
||||
Shell Completion
|
||||
----------------
|
||||
Shell auto-completion scripts are provided for BASH and ZSH. Add symlinks to the appropriate
|
||||
location for your environment. For example, something like:
|
||||
|
||||
ln -s ./shell-completion/bash/spring /etc/bash_completion.d/spring
|
||||
ln -s ./shell-completion/zsh/_spring /usr/local/share/zsh/site-functions/_spring
|
||||
|
||||
|
||||
Checking Your Installation
|
||||
--------------------------
|
||||
To test if you have successfully install the CLI you can run the following command:
|
||||
|
||||
spring --version
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
Copyright 2012-2013 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.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
:setSpringHome
|
||||
@rem Setup SPRING_HOME if not already defined
|
||||
if defined SPRING_HOME goto setJavaHome
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set SPRING_HOME=%DIRNAME%\..
|
||||
|
||||
:setJavaHome
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto runSpring
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
if exist "%JAVA_EXE%" goto runSpring
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:runSpring
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%SPRING_HOME%\lib\*
|
||||
"%JAVA_EXE%" %JAVA_OPTS% -cp "%CLASSPATH%" org.springframework.boot.loader.JarLauncher %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable SPRING_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%SPRING_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
open_source_licenses.txt
|
||||
|
||||
Spring Boot CLI
|
||||
==================================================================
|
||||
|
||||
Pivotal makes available all content in this download ("Content").
|
||||
Unless otherwise indicated below, the Content is provided to you under
|
||||
the terms and conditions of the Apache License 2.0 (the "License"). A
|
||||
copy of the license is available in the file called LICENSE.txt or you
|
||||
may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
The following copyright statements and licenses apply to various open
|
||||
source software packages (or portions thereof) that are distributed with
|
||||
this content.
|
||||
|
||||
|
||||
=================================================================
|
||||
TABLE OF CONTENTS
|
||||
=================================================================
|
||||
|
||||
|
||||
The following is a listing of the open source components detailed in this
|
||||
document. This list is provided for your convenience; please read further if
|
||||
you wish to review the copyright notice(s) and the full text of the license
|
||||
associated with each component.
|
||||
|
||||
|
||||
SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES
|
||||
|
||||
>>> JLine (jline:jline)
|
||||
>>> JOpt Simple (net.sf.jopt-simple:jopt-simple)
|
||||
>>> ASM 4.0 (org.ow2.asm:asm)
|
||||
|
||||
|
||||
SECTION 2: Apache License, V2.0
|
||||
|
||||
>>> JSON library from Android SDK (com.vaadin.external.google:android-json)
|
||||
>>> Apache Commons Codec (commons-codec:commons-codec)
|
||||
>>> Apache HttpClient (org.apache.httpcomponents:httpclient)
|
||||
>>> Apache HttpCore (org.apache.httpcomponents:httpcore)
|
||||
>>> Plexus Cipher: encryption/decryption Component (org.sonatype.plexus:plexus-cipher)
|
||||
>>> Plexus Security Dispatcher Component (org.sonatype.plexus:plexus-sec-dispatcher)
|
||||
>>> Apache Commons Logging (commons-logging:commons-logging)
|
||||
>>> Apache Groovy (org.codehaus.groovy:groovy)
|
||||
>>> Maven Aether Provider (org.apache.maven:maven-aether-provider)
|
||||
>>> Maven Model (org.apache.maven:maven-model)
|
||||
>>> Maven Model Builder (org.apache.maven:maven-model-builder)
|
||||
>>> Maven Repository Metadata Model (org.apache.maven:maven-repository-metadata)
|
||||
>>> Maven Settings (org.apache.maven:maven-settings)
|
||||
>>> Maven Settings Builder (org.apache.maven:maven-settings-builder)
|
||||
>>> Plexus :: Component Annotations (org.codehaus.plexus:plexus-component-annotations)
|
||||
>>> Plexus Common Utilities (org.codehaus.plexus:plexus-utils)
|
||||
>>> Plexus Component API (org.codehaus.plexus:plexus-component-api)
|
||||
>>> Plexus Interpolation API (org.codehaus.plexus:plexus-interpolation)
|
||||
|
||||
|
||||
SECTION 3: Eclipse Public License, Version 1.0
|
||||
|
||||
>>> Aether API (org.eclipse.aether:aether-api)
|
||||
>>> Aether Connector Basic (org.eclipse.aether:aether-connector-basic)
|
||||
>>> Aether Implementation (org.eclipse.aether:aether-impl)
|
||||
>>> Aether SPI (org.eclipse.aether:aether-spi)
|
||||
>>> Aether Transport File (org.eclipse.aether:aether-transport-file)
|
||||
>>> Aether Transport HTTP (org.eclipse.aether:aether-transport-http)
|
||||
>>> Aether Utilities (org.eclipse.aether:aether-util)
|
||||
|
||||
|
||||
|
||||
--------------- SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES ----------
|
||||
|
||||
BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES are applicable to the following component(s).
|
||||
|
||||
|
||||
>>> JLine (jline:jline)
|
||||
|
||||
Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with
|
||||
the distribution.
|
||||
|
||||
Neither the name of JLine nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
>>> net.sf.jopt-simple:jopt-simple:4.5
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
>>> org.ow2.asm:asm
|
||||
|
||||
Copyright (c) 2000-2011 INRIA, France Telecom
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
--------------- SECTION 2: Apache License, V2.0 ----------
|
||||
|
||||
Apache License, V2.0 is applicable to the following component(s).
|
||||
|
||||
|
||||
>>> org.apache.httpcomponents:httpclient
|
||||
>>> org.apache.httpcomponents:httpcore
|
||||
>>> org.sonatype.plexus:plexus-cipher
|
||||
>>> org.sonatype.plexus:plexus-sec-dispatcher
|
||||
>>> commons-logging:commons-logging
|
||||
>>> org.codehaus.groovy:groovy
|
||||
>>> org.apache.maven:maven-aether-provider
|
||||
>>> org.apache.maven:maven-model
|
||||
>>> org.apache.maven:maven-model-builder
|
||||
>>> org.apache.maven:maven-repository-metadata
|
||||
>>> org.apache.maven:maven-settings
|
||||
>>> org.apache.maven:maven-settings-builder
|
||||
>>> org.codehaus.plexus:plexus-component-annotations
|
||||
>>> org.codehaus.plexus:plexus-utils
|
||||
>>> org.codehaus.plexus:plexus-component-api
|
||||
>>> org.codehaus.plexus:plexus-interpolation
|
||||
|
||||
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
|
||||
|
||||
>>> CGLIB 3.0 (cglib:cglib:3.0):
|
||||
|
||||
Per the LICENSE file in the CGLIB JAR distribution downloaded from
|
||||
http://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download,
|
||||
CGLIB 3.0 is licensed under the Apache License, version 2.0, the text of which
|
||||
is included above.
|
||||
|
||||
|
||||
--------------- SECTION 3: Eclipse Public License, Version 1.0 ----------
|
||||
|
||||
Eclipse Public License, Version 1.0 is applicable to the following component(s).
|
||||
|
||||
>>> org.eclipse.aether:aether-api
|
||||
>>> org.eclipse.aether:aether-connector-basic
|
||||
>>> org.eclipse.aether:aether-impl
|
||||
>>> org.eclipse.aether:aether-spi
|
||||
>>> org.eclipse.aether:aether-transport-file
|
||||
>>> org.eclipse.aether:aether-transport-http
|
||||
>>> org.eclipse.aether:aether-util
|
||||
|
||||
The Eclipse Foundation makes available all content in this plug-in ("Content").
|
||||
Unless otherwise indicated below, the Content is provided to you under the terms
|
||||
and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the
|
||||
EPL is available at http://www.eclipse.org/legal/epl-v10.html.
|
||||
|
||||
For purposes of the EPL, "Program" will mean the Content.
|
||||
|
||||
If you did not receive this Content directly from the Eclipse Foundation, the
|
||||
Content is being redistributed by another party ("Redistributor") and different
|
||||
terms and conditions may apply to your use of any object code in the Content.
|
||||
Check the Redistributor's license that was provided with the Content. If no such
|
||||
license exists, contact the Redistributor. Unless otherwise indicated below, the
|
||||
terms and conditions of the EPL still apply to any source code in the Content and
|
||||
such source code may be obtained at http://www.eclipse.org/
|
||||
|
||||
|
||||
|
||||
===========================================================================
|
||||
|
||||
To the extent any open source subcomponents are licensed under the EPL and/or
|
||||
other similar licenses that require the source code and/or modifications to
|
||||
source code to be made available (as would be noted above), you may obtain a
|
||||
copy of the source code corresponding to the binaries for such open source
|
||||
components and modifications thereto, if any, (the "Source Files"), by
|
||||
downloading the Source Files from https://github.com/spring-projects/spring-boot,
|
||||
or by sending a request, with your name and address to:
|
||||
|
||||
Pivotal, Inc., 875 Howard St,
|
||||
San Francisco, CA 94103
|
||||
United States of America
|
||||
|
||||
or email info@pivotal.io. All such requests should clearly specify:
|
||||
|
||||
OPEN SOURCE FILES REQUEST
|
||||
Attention General Counsel
|
||||
@@ -0,0 +1,22 @@
|
||||
# bash completion for spring
|
||||
# Installation: source this file locally in a terminal or from
|
||||
# ~/.bashrc or put it in /etc/bash_completions.d (debian)
|
||||
|
||||
_spring()
|
||||
{
|
||||
local cur prev help helps words cword command commands i
|
||||
|
||||
_get_comp_words_by_ref cur prev words cword
|
||||
|
||||
COMPREPLY=()
|
||||
|
||||
while read -r line; do
|
||||
reply=`echo "$line" | awk '{print $1;}'`
|
||||
COMPREPLY+=("$reply")
|
||||
done < <(spring hint ${cword} ${words[*]})
|
||||
|
||||
if [ $cword -ne 1 ]; then
|
||||
_filedir
|
||||
fi
|
||||
|
||||
} && complete -F _spring spring
|
||||
@@ -0,0 +1,29 @@
|
||||
#compdef spring 'spring'
|
||||
#autoload
|
||||
|
||||
_spring() {
|
||||
|
||||
local cword
|
||||
let cword=CURRENT-1
|
||||
|
||||
local hints
|
||||
hints=()
|
||||
|
||||
local reply
|
||||
while read -r line; do
|
||||
reply=`echo "$line" | awk '{printf $1 ":"; for (i=2; i<NF; i++) printf $i " "; print $NF}'`
|
||||
hints+=("$reply")
|
||||
done < <(spring hint ${cword} ${words[*]})
|
||||
|
||||
if ((cword == 1)) {
|
||||
_describe -t commands 'commands' hints
|
||||
return 0
|
||||
}
|
||||
|
||||
_describe -t options 'options' hints
|
||||
_files
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_spring "$@"
|
||||
95
spring-boot-project/spring-boot-cli/src/main/executablecontent/bin/spring
Executable file
95
spring-boot-project/spring-boot-cli/src/main/executablecontent/bin/spring
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
case "`uname`" in
|
||||
CYGWIN*)
|
||||
cygwin=true
|
||||
;;
|
||||
|
||||
Darwin*)
|
||||
darwin=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to find JAVA_HOME if not already set
|
||||
if [ -z "${JAVA_HOME}" ]; then
|
||||
if $darwin ; then
|
||||
[ -z "$JAVA_HOME" -a -f "/usr/libexec/java_home" ] && export JAVA_HOME=`/usr/libexec/java_home`
|
||||
[ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
|
||||
[ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
|
||||
else
|
||||
javaExecutable="`which javac`"
|
||||
[ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && echo "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME." && exit 1
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
[ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && echo "JAVA_HOME not set and readlink not available, please set JAVA_HOME." && exit 1
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
|
||||
# Sanity check that we have java
|
||||
if [ ! -f "${JAVA_HOME}/bin/java" ]; then
|
||||
echo ""
|
||||
echo "======================================================================================================"
|
||||
echo " Please ensure that your JAVA_HOME points to a valid Java SDK."
|
||||
echo " You are currently pointing to:"
|
||||
echo ""
|
||||
echo " ${JAVA_HOME}"
|
||||
echo ""
|
||||
echo " This does not seem to be valid. Please rectify and restart."
|
||||
echo "======================================================================================================"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Attempt to find SPRING_HOME if not already set
|
||||
if [ -z "${SPRING_HOME}" ]; then
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/../" >&-
|
||||
export SPRING_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
fi
|
||||
|
||||
if [ ! -d "${SPRING_HOME}" ]; then
|
||||
echo "Not a directory: SPRING_HOME=${SPRING_HOME}"
|
||||
echo "Please rectify and restart."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
CLASSPATH=.:${SPRING_HOME}/bin
|
||||
if [ -d ${SPRING_HOME}/ext ]; then
|
||||
CLASSPATH=$CLASSPATH:${SPRING_HOME}/ext
|
||||
fi
|
||||
for f in ${SPRING_HOME}/lib/*; do
|
||||
CLASSPATH=$CLASSPATH:$f
|
||||
done
|
||||
|
||||
if $cygwin; then
|
||||
SPRING_HOME=`cygpath --path --mixed "$SPRING_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
"${JAVA_HOME}/bin/java" ${JAVA_OPTS} -cp "$CLASSPATH" org.springframework.boot.loader.JarLauncher "$@"
|
||||
@@ -0,0 +1,27 @@
|
||||
require 'formula'
|
||||
|
||||
class Springboot < Formula
|
||||
homepage 'http://projects.spring.io/spring-boot/'
|
||||
url 'https://repo.spring.io/${repo}/org/springframework/boot/spring-boot-cli/${project.version}/spring-boot-cli-${project.version}-bin.tar.gz'
|
||||
version '${project.version}'
|
||||
sha256 '${checksum}'
|
||||
head 'https://github.com/spring-projects/spring-boot.git'
|
||||
|
||||
if build.head?
|
||||
depends_on 'maven' => :build
|
||||
end
|
||||
|
||||
def install
|
||||
if build.head?
|
||||
Dir.chdir('spring-boot-cli') { system 'mvn -U -DskipTests=true package' }
|
||||
root = 'spring-boot-cli/target/spring-boot-cli-*-bin/spring-*'
|
||||
else
|
||||
root = '.'
|
||||
end
|
||||
|
||||
bin.install Dir["#{root}/bin/spring"]
|
||||
lib.install Dir["#{root}/lib/spring-boot-cli-*.jar"]
|
||||
bash_completion.install Dir["#{root}/shell-completion/bash/spring"]
|
||||
zsh_completion.install Dir["#{root}/shell-completion/zsh/_spring"]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.CommandFactory;
|
||||
import org.springframework.boot.cli.command.archive.JarCommand;
|
||||
import org.springframework.boot.cli.command.archive.WarCommand;
|
||||
import org.springframework.boot.cli.command.core.VersionCommand;
|
||||
import org.springframework.boot.cli.command.grab.GrabCommand;
|
||||
import org.springframework.boot.cli.command.init.InitCommand;
|
||||
import org.springframework.boot.cli.command.install.InstallCommand;
|
||||
import org.springframework.boot.cli.command.install.UninstallCommand;
|
||||
import org.springframework.boot.cli.command.run.RunCommand;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link CommandFactory}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DefaultCommandFactory implements CommandFactory {
|
||||
|
||||
private static final List<Command> defaultCommands = Arrays.<Command>asList(
|
||||
new VersionCommand(), new RunCommand(), new GrabCommand(), new JarCommand(),
|
||||
new WarCommand(), new InstallCommand(), new UninstallCommand(),
|
||||
new InitCommand());
|
||||
|
||||
@Override
|
||||
public Collection<Command> getCommands() {
|
||||
return defaultCommands;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.springframework.boot.cli.command.CommandFactory;
|
||||
import org.springframework.boot.cli.command.CommandRunner;
|
||||
import org.springframework.boot.cli.command.core.HelpCommand;
|
||||
import org.springframework.boot.cli.command.core.HintCommand;
|
||||
import org.springframework.boot.cli.command.core.VersionCommand;
|
||||
import org.springframework.boot.cli.command.shell.ShellCommand;
|
||||
import org.springframework.boot.loader.tools.LogbackInitializer;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* Spring Command Line Interface. This is the main entry-point for the Spring command line
|
||||
* application.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @see #main(String...)
|
||||
* @see CommandRunner
|
||||
*/
|
||||
public final class SpringCli {
|
||||
|
||||
private SpringCli() {
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
System.setProperty("java.awt.headless", Boolean.toString(true));
|
||||
LogbackInitializer.initialize();
|
||||
|
||||
CommandRunner runner = new CommandRunner("spring");
|
||||
ClassUtils.overrideThreadContextClassLoader(createExtendedClassLoader(runner));
|
||||
runner.addCommand(new HelpCommand(runner));
|
||||
addServiceLoaderCommands(runner);
|
||||
runner.addCommand(new ShellCommand());
|
||||
runner.addCommand(new HintCommand(runner));
|
||||
runner.setOptionCommands(HelpCommand.class, VersionCommand.class);
|
||||
runner.setHiddenCommands(HintCommand.class);
|
||||
|
||||
int exitCode = runner.runAndHandleErrors(args);
|
||||
if (exitCode != 0) {
|
||||
// If successful, leave it to run in case it's a server app
|
||||
System.exit(exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addServiceLoaderCommands(CommandRunner runner) {
|
||||
ServiceLoader<CommandFactory> factories = ServiceLoader
|
||||
.load(CommandFactory.class);
|
||||
for (CommandFactory factory : factories) {
|
||||
runner.addCommands(factory.getCommands());
|
||||
}
|
||||
}
|
||||
|
||||
private static URLClassLoader createExtendedClassLoader(CommandRunner runner) {
|
||||
return new URLClassLoader(getExtensionURLs(), runner.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
private static URL[] getExtensionURLs() {
|
||||
List<URL> urls = new ArrayList<>();
|
||||
String home = SystemPropertyUtils
|
||||
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
|
||||
File extDirectory = new File(new File(home, "lib"), "ext");
|
||||
if (extDirectory.isDirectory()) {
|
||||
for (File file : extDirectory.listFiles()) {
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
try {
|
||||
urls.add(file.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return urls.toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.app;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A launcher for {@code SpringApplication} or a {@code SpringApplication} subclass. The
|
||||
* class that is used can be configured using the System property
|
||||
* {@code spring.application.class.name} or the {@code SPRING_APPLICATION_CLASS_NAME}
|
||||
* environment variable. Uses reflection to allow the launching code to exist in a
|
||||
* separate ClassLoader from the application code.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
* @see System#getProperty(String)
|
||||
* @see System#getenv(String)
|
||||
*/
|
||||
public class SpringApplicationLauncher {
|
||||
|
||||
private static final String DEFAULT_SPRING_APPLICATION_CLASS = "org.springframework.boot.SpringApplication";
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* Creates a new launcher that will use the given {@code classLoader} to load the
|
||||
* configured {@code SpringApplication} class.
|
||||
* @param classLoader the {@code ClassLoader} to use
|
||||
*/
|
||||
public SpringApplicationLauncher(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the application created using the given {@code sources}. The application
|
||||
* is launched with the given {@code args}.
|
||||
* @param sources The sources for the application
|
||||
* @param args The args for the application
|
||||
* @return The application's {@code ApplicationContext}
|
||||
* @throws Exception if the launch fails
|
||||
*/
|
||||
public Object launch(Class<?>[] sources, String[] args) throws Exception {
|
||||
Map<String, Object> defaultProperties = new HashMap<>();
|
||||
defaultProperties.put("spring.groovy.template.check-template-location", "false");
|
||||
Class<?> applicationClass = this.classLoader
|
||||
.loadClass(getSpringApplicationClassName());
|
||||
Constructor<?> constructor = applicationClass.getConstructor(Class[].class);
|
||||
Object application = constructor.newInstance((Object) sources);
|
||||
applicationClass.getMethod("setDefaultProperties", Map.class).invoke(application,
|
||||
defaultProperties);
|
||||
Method method = applicationClass.getMethod("run", String[].class);
|
||||
return method.invoke(application, (Object) args);
|
||||
}
|
||||
|
||||
private String getSpringApplicationClassName() {
|
||||
String className = System.getProperty("spring.application.class.name");
|
||||
if (className == null) {
|
||||
className = getEnvironmentVariable("SPRING_APPLICATION_CLASS_NAME");
|
||||
}
|
||||
if (className == null) {
|
||||
className = DEFAULT_SPRING_APPLICATION_CLASS;
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
protected String getEnvironmentVariable(String name) {
|
||||
return System.getenv(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.app;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
|
||||
/**
|
||||
* {@link SpringBootServletInitializer} for CLI packaged WAR files.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class SpringApplicationWebApplicationInitializer
|
||||
extends SpringBootServletInitializer {
|
||||
|
||||
/**
|
||||
* The entry containing the source class.
|
||||
*/
|
||||
public static final String SOURCE_ENTRY = "Spring-Application-Source-Classes";
|
||||
|
||||
private String[] sources;
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
try {
|
||||
this.sources = getSources(servletContext);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
super.onStartup(servletContext);
|
||||
}
|
||||
|
||||
private String[] getSources(ServletContext servletContext) throws IOException {
|
||||
Manifest manifest = getManifest(servletContext);
|
||||
if (manifest == null) {
|
||||
throw new IllegalStateException("Unable to read manifest");
|
||||
}
|
||||
String sources = manifest.getMainAttributes().getValue(SOURCE_ENTRY);
|
||||
return sources.split(",");
|
||||
}
|
||||
|
||||
private Manifest getManifest(ServletContext servletContext) throws IOException {
|
||||
InputStream stream = servletContext.getResourceAsStream("/META-INF/MANIFEST.MF");
|
||||
Manifest manifest = (stream == null ? null : new Manifest(stream));
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
|
||||
try {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Class<?>[] sourceClasses = new Class<?>[this.sources.length];
|
||||
for (int i = 0; i < this.sources.length; i++) {
|
||||
sourceClasses[i] = classLoader.loadClass(this.sources[i]);
|
||||
}
|
||||
return builder.sources(sourceClasses)
|
||||
.properties("spring.groovy.template.check-template-location=false");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.archive;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.springframework.boot.cli.app.SpringApplicationLauncher;
|
||||
|
||||
/**
|
||||
* A launcher for a CLI application that has been compiled and packaged as a jar file.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public final class PackagedSpringApplicationLauncher {
|
||||
|
||||
/**
|
||||
* The entry containing the source class.
|
||||
*/
|
||||
public static final String SOURCE_ENTRY = "Spring-Application-Source-Classes";
|
||||
|
||||
/**
|
||||
* The entry containing the start class.
|
||||
*/
|
||||
public static final String START_CLASS_ENTRY = "Start-Class";
|
||||
|
||||
private PackagedSpringApplicationLauncher() {
|
||||
}
|
||||
|
||||
private void run(String[] args) throws Exception {
|
||||
URLClassLoader classLoader = (URLClassLoader) Thread.currentThread()
|
||||
.getContextClassLoader();
|
||||
new SpringApplicationLauncher(classLoader).launch(getSources(classLoader), args);
|
||||
}
|
||||
|
||||
private Class<?>[] getSources(URLClassLoader classLoader) throws Exception {
|
||||
Enumeration<URL> urls = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (urls.hasMoreElements()) {
|
||||
URL url = urls.nextElement();
|
||||
Manifest manifest = new Manifest(url.openStream());
|
||||
if (isCliPackaged(manifest)) {
|
||||
String sources = manifest.getMainAttributes().getValue(SOURCE_ENTRY);
|
||||
return loadClasses(classLoader, sources.split(","));
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Cannot locate " + SOURCE_ENTRY + " in MANIFEST.MF");
|
||||
}
|
||||
|
||||
private boolean isCliPackaged(Manifest manifest) {
|
||||
Attributes attributes = manifest.getMainAttributes();
|
||||
String startClass = attributes.getValue(START_CLASS_ENTRY);
|
||||
return getClass().getName().equals(startClass);
|
||||
}
|
||||
|
||||
private Class<?>[] loadClasses(ClassLoader classLoader, String[] names)
|
||||
throws ClassNotFoundException {
|
||||
Class<?>[] classes = new Class<?>[names.length];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
classes[i] = classLoader.loadClass(names[i]);
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new PackagedSpringApplicationLauncher().run(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class that are packaged as part of CLI generated JARs.
|
||||
* @see org.springframework.boot.cli.command.archive.JarCommand
|
||||
*/
|
||||
package org.springframework.boot.cli.archive;
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.cli.command.options.OptionHelp;
|
||||
|
||||
/**
|
||||
* Abstract {@link Command} implementation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public abstract class AbstractCommand implements Command {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractCommand} instance.
|
||||
* @param name the name of the command
|
||||
* @param description the command description
|
||||
*/
|
||||
protected AbstractCommand(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OptionHelp> getOptionsHelp() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<HelpExample> getExamples() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.command;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.cli.command.options.OptionHelp;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
|
||||
/**
|
||||
* A single command that can be run from the CLI.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @see #run(String...)
|
||||
*/
|
||||
public interface Command {
|
||||
|
||||
/**
|
||||
* Returns the name of the command.
|
||||
* @return the command's name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns a description of the command.
|
||||
* @return the command's description
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Returns usage help for the command. This should be a simple one-line string
|
||||
* describing basic usage. e.g. '[options] <file>'. Do not include the name of
|
||||
* the command in this string.
|
||||
* @return the command's usage help
|
||||
*/
|
||||
String getUsageHelp();
|
||||
|
||||
/**
|
||||
* Gets full help text for the command, e.g. a longer description and one line per
|
||||
* option.
|
||||
* @return the command's help text
|
||||
*/
|
||||
String getHelp();
|
||||
|
||||
/**
|
||||
* Returns help for each supported option.
|
||||
* @return help for each of the command's options
|
||||
*/
|
||||
Collection<OptionHelp> getOptionsHelp();
|
||||
|
||||
/**
|
||||
* Return some examples for the command.
|
||||
* @return the command's examples
|
||||
*/
|
||||
Collection<HelpExample> getExamples();
|
||||
|
||||
/**
|
||||
* Run the command.
|
||||
* @param args command arguments (this will not include the command itself)
|
||||
* @return the outcome of the command
|
||||
* @throws Exception if the command fails
|
||||
*/
|
||||
ExitStatus run(String... args) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.command;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Runtime exception wrapper that defines additional {@link Option}s that are understood
|
||||
* by the {@link CommandRunner}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class CommandException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final EnumSet<Option> options;
|
||||
|
||||
/**
|
||||
* Create a new {@link CommandException} with the specified options.
|
||||
* @param options the exception options
|
||||
*/
|
||||
public CommandException(Option... options) {
|
||||
this.options = asEnumSet(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CommandException} with the specified options.
|
||||
* @param message the exception message to display to the user
|
||||
* @param options the exception options
|
||||
*/
|
||||
public CommandException(String message, Option... options) {
|
||||
super(message);
|
||||
this.options = asEnumSet(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CommandException} with the specified options.
|
||||
* @param message the exception message to display to the user
|
||||
* @param cause the underlying cause
|
||||
* @param options the exception options
|
||||
*/
|
||||
public CommandException(String message, Throwable cause, Option... options) {
|
||||
super(message, cause);
|
||||
this.options = asEnumSet(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CommandException} with the specified options.
|
||||
* @param cause the underlying cause
|
||||
* @param options the exception options
|
||||
*/
|
||||
public CommandException(Throwable cause, Option... options) {
|
||||
super(cause);
|
||||
this.options = asEnumSet(options);
|
||||
}
|
||||
|
||||
private EnumSet<Option> asEnumSet(Option[] options) {
|
||||
if (options == null || options.length == 0) {
|
||||
return EnumSet.noneOf(Option.class);
|
||||
}
|
||||
return EnumSet.copyOf(Arrays.asList(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of options that are understood by the {@link CommandRunner}.
|
||||
* @return the options understood by the runner
|
||||
*/
|
||||
public Set<Option> getOptions() {
|
||||
return Collections.unmodifiableSet(this.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific options understood by the {@link CommandRunner}.
|
||||
*/
|
||||
public enum Option {
|
||||
|
||||
/**
|
||||
* Hide the exception message.
|
||||
*/
|
||||
HIDE_MESSAGE,
|
||||
|
||||
/**
|
||||
* Print basic CLI usage information.
|
||||
*/
|
||||
SHOW_USAGE,
|
||||
|
||||
/**
|
||||
* Print the stack-trace of the exception.
|
||||
*/
|
||||
STACK_TRACE,
|
||||
|
||||
/**
|
||||
* Re-throw the exception rather than dealing with it.
|
||||
*/
|
||||
RETHROW
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.command;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Factory used to create CLI {@link Command}s. Intended for use with a Java
|
||||
* {@link ServiceLoader}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CommandFactory {
|
||||
|
||||
/**
|
||||
* Returns the CLI {@link Command}s.
|
||||
* @return The commands
|
||||
*/
|
||||
Collection<Command> getCommands();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Main class used to run {@link Command}s.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @see #addCommand(Command)
|
||||
* @see CommandRunner#runAndHandleErrors(String[])
|
||||
*/
|
||||
public class CommandRunner implements Iterable<Command> {
|
||||
|
||||
private static final Set<CommandException.Option> NO_EXCEPTION_OPTIONS = EnumSet
|
||||
.noneOf(CommandException.Option.class);
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<Command> commands = new ArrayList<>();
|
||||
|
||||
private Class<?>[] optionCommandClasses = {};
|
||||
|
||||
private Class<?>[] hiddenCommandClasses = {};
|
||||
|
||||
/**
|
||||
* Create a new {@link CommandRunner} instance.
|
||||
* @param name the name of the runner or {@code null}
|
||||
*/
|
||||
public CommandRunner(String name) {
|
||||
this.name = (StringUtils.hasLength(name) ? name + " " : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the runner or an empty string. Non-empty names will include a
|
||||
* trailing space character so that they can be used as a prefix.
|
||||
* @return the name of the runner
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified commands.
|
||||
* @param commands the commands to add
|
||||
*/
|
||||
public void addCommands(Iterable<Command> commands) {
|
||||
Assert.notNull(commands, "Commands must not be null");
|
||||
for (Command command : commands) {
|
||||
addCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified command.
|
||||
* @param command the command to add.
|
||||
*/
|
||||
public void addCommand(Command command) {
|
||||
Assert.notNull(command, "Command must not be null");
|
||||
this.commands.add(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command classes which should be considered option commands. An option
|
||||
* command is a special type of command that usually makes more sense to present as if
|
||||
* it is an option. For example '--version'.
|
||||
* @param commandClasses the classes of option commands.
|
||||
* @see #isOptionCommand(Command)
|
||||
*/
|
||||
public void setOptionCommands(Class<?>... commandClasses) {
|
||||
Assert.notNull(commandClasses, "CommandClasses must not be null");
|
||||
this.optionCommandClasses = commandClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command classes which should be hidden (i.e. executed but not displayed in
|
||||
* the available commands list).
|
||||
* @param commandClasses the classes of hidden commands
|
||||
*/
|
||||
public void setHiddenCommands(Class<?>... commandClasses) {
|
||||
Assert.notNull(commandClasses, "CommandClasses must not be null");
|
||||
this.hiddenCommandClasses = commandClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the specified command is an option command.
|
||||
* @param command the command to test
|
||||
* @return {@code true} if the command is an option command
|
||||
* @see #setOptionCommands(Class...)
|
||||
*/
|
||||
public boolean isOptionCommand(Command command) {
|
||||
return isCommandInstanceOf(command, this.optionCommandClasses);
|
||||
}
|
||||
|
||||
private boolean isHiddenCommand(Command command) {
|
||||
return isCommandInstanceOf(command, this.hiddenCommandClasses);
|
||||
}
|
||||
|
||||
private boolean isCommandInstanceOf(Command command, Class<?>[] commandClasses) {
|
||||
for (Class<?> commandClass : commandClasses) {
|
||||
if (commandClass.isInstance(command)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Command> iterator() {
|
||||
return getCommands().iterator();
|
||||
}
|
||||
|
||||
protected final List<Command> getCommands() {
|
||||
return Collections.unmodifiableList(this.commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a command by name.
|
||||
* @param name the name of the command
|
||||
* @return the command or {@code null} if not found
|
||||
*/
|
||||
public Command findCommand(String name) {
|
||||
for (Command candidate : this.commands) {
|
||||
String candidateName = candidate.getName();
|
||||
if (candidateName.equals(name) || (isOptionCommand(candidate)
|
||||
&& ("--" + candidateName).equals(name))) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the appropriate and handle and errors.
|
||||
* @param args the input arguments
|
||||
* @return a return status code (non boot is used to indicate an error)
|
||||
*/
|
||||
public int runAndHandleErrors(String... args) {
|
||||
String[] argsWithoutDebugFlags = removeDebugFlags(args);
|
||||
boolean debug = argsWithoutDebugFlags.length != args.length;
|
||||
if (debug) {
|
||||
System.setProperty("debug", "true");
|
||||
}
|
||||
try {
|
||||
ExitStatus result = run(argsWithoutDebugFlags);
|
||||
// The caller will hang up if it gets a non-zero status
|
||||
if (result != null && result.isHangup()) {
|
||||
return (result.getCode() > 0 ? result.getCode() : 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
catch (NoArgumentsException ex) {
|
||||
showUsage();
|
||||
return 1;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return handleError(debug, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] removeDebugFlags(String[] args) {
|
||||
List<String> rtn = new ArrayList<>(args.length);
|
||||
boolean appArgsDetected = false;
|
||||
for (String arg : args) {
|
||||
// Allow apps to have a -d argument
|
||||
appArgsDetected |= "--".equals(arg);
|
||||
if (("-d".equals(arg) || "--debug".equals(arg)) && !appArgsDetected) {
|
||||
continue;
|
||||
}
|
||||
rtn.add(arg);
|
||||
}
|
||||
return rtn.toArray(new String[rtn.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the arguments and run a suitable command.
|
||||
* @param args the arguments
|
||||
* @return the outcome of the command
|
||||
* @throws Exception if the command fails
|
||||
*/
|
||||
protected ExitStatus run(String... args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
throw new NoArgumentsException();
|
||||
}
|
||||
String commandName = args[0];
|
||||
String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
|
||||
Command command = findCommand(commandName);
|
||||
if (command == null) {
|
||||
throw new NoSuchCommandException(commandName);
|
||||
}
|
||||
beforeRun(command);
|
||||
try {
|
||||
return command.run(commandArguments);
|
||||
}
|
||||
finally {
|
||||
afterRun(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook called before a command is run.
|
||||
* @param command the command about to run
|
||||
*/
|
||||
protected void beforeRun(Command command) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook called after a command has run.
|
||||
* @param command the command that has run
|
||||
*/
|
||||
protected void afterRun(Command command) {
|
||||
}
|
||||
|
||||
private int handleError(boolean debug, Exception ex) {
|
||||
Set<CommandException.Option> options = NO_EXCEPTION_OPTIONS;
|
||||
if (ex instanceof CommandException) {
|
||||
options = ((CommandException) ex).getOptions();
|
||||
if (options.contains(CommandException.Option.RETHROW)) {
|
||||
throw (CommandException) ex;
|
||||
}
|
||||
}
|
||||
boolean couldNotShowMessage = false;
|
||||
if (!options.contains(CommandException.Option.HIDE_MESSAGE)) {
|
||||
couldNotShowMessage = !errorMessage(ex.getMessage());
|
||||
}
|
||||
if (options.contains(CommandException.Option.SHOW_USAGE)) {
|
||||
showUsage();
|
||||
}
|
||||
if (debug || couldNotShowMessage
|
||||
|| options.contains(CommandException.Option.STACK_TRACE)) {
|
||||
printStackTrace(ex);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected boolean errorMessage(String message) {
|
||||
Log.error(message == null ? "Unexpected error" : message);
|
||||
return message != null;
|
||||
}
|
||||
|
||||
protected void showUsage() {
|
||||
Log.infoPrint("usage: " + this.name);
|
||||
for (Command command : this.commands) {
|
||||
if (isOptionCommand(command)) {
|
||||
Log.infoPrint("[--" + command.getName() + "] ");
|
||||
}
|
||||
}
|
||||
Log.info("");
|
||||
Log.info(" <command> [<args>]");
|
||||
Log.info("");
|
||||
Log.info("Available commands are:");
|
||||
for (Command command : this.commands) {
|
||||
if (!isOptionCommand(command) && !isHiddenCommand(command)) {
|
||||
String usageHelp = command.getUsageHelp();
|
||||
String description = command.getDescription();
|
||||
Log.info(String.format("%n %1$s %2$-15s%n %3$s", command.getName(),
|
||||
(usageHelp == null ? "" : usageHelp),
|
||||
(description == null ? "" : description)));
|
||||
}
|
||||
}
|
||||
Log.info("");
|
||||
Log.info("Common options:");
|
||||
Log.info(String.format("%n %1$s %2$-15s%n %3$s", "-d, --debug",
|
||||
"Verbose mode",
|
||||
"Print additional status information for the command you are running"));
|
||||
Log.info("");
|
||||
Log.info("");
|
||||
Log.info("See '" + this.name
|
||||
+ "help <command>' for more information on a specific command.");
|
||||
}
|
||||
|
||||
protected void printStackTrace(Exception ex) {
|
||||
Log.error("");
|
||||
Log.error(ex);
|
||||
Log.error("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.command;
|
||||
|
||||
/**
|
||||
* An example that can be displayed in the help.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class HelpExample {
|
||||
|
||||
private final String description;
|
||||
|
||||
private final String example;
|
||||
|
||||
/**
|
||||
* Create a new {@link HelpExample} instance.
|
||||
* @param description The description (in the form "to ....")
|
||||
* @param example the example
|
||||
*/
|
||||
public HelpExample(String description, String example) {
|
||||
this.description = description;
|
||||
this.example = example;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public String getExample() {
|
||||
return this.example;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.command;
|
||||
|
||||
/**
|
||||
* Exception used to indicate that no arguments were specified.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class NoArgumentsException extends CommandException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command;
|
||||
|
||||
/**
|
||||
* Exception used to when the help command is called without arguments.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class NoHelpCommandArgumentsException extends CommandException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NoHelpCommandArgumentsException() {
|
||||
super(Option.SHOW_USAGE, Option.HIDE_MESSAGE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command;
|
||||
|
||||
/**
|
||||
* Exception used when a command is not found.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class NoSuchCommandException extends CommandException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NoSuchCommandException(String name) {
|
||||
super(String.format("'%1$s' is not a valid command. See 'help'.", name));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.cli.command.options.OptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionHelp;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
|
||||
/**
|
||||
* Base class for a {@link Command} that parse options using an {@link OptionHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @see OptionHandler
|
||||
*/
|
||||
public abstract class OptionParsingCommand extends AbstractCommand {
|
||||
|
||||
private final OptionHandler handler;
|
||||
|
||||
protected OptionParsingCommand(String name, String description,
|
||||
OptionHandler handler) {
|
||||
super(name, description);
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelp() {
|
||||
return this.handler.getHelp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OptionHelp> getOptionsHelp() {
|
||||
return this.handler.getOptionsHelp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ExitStatus run(String... args) throws Exception {
|
||||
return this.handler.run(args);
|
||||
}
|
||||
|
||||
protected OptionHandler getHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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.command.archive;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import groovy.lang.Grab;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotatedNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
import org.springframework.boot.cli.app.SpringApplicationLauncher;
|
||||
import org.springframework.boot.cli.archive.PackagedSpringApplicationLauncher;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.archive.ResourceMatcher.MatchedResource;
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.command.options.SourceOptions;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
import org.springframework.boot.loader.tools.JarWriter;
|
||||
import org.springframework.boot.loader.tools.Layout;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
import org.springframework.boot.loader.tools.Repackager;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract {@link Command} to create a self-contained executable archive file from a CLI
|
||||
* application.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Andrey Stolyarov
|
||||
* @author Henri Kerola
|
||||
*/
|
||||
abstract class ArchiveCommand extends OptionParsingCommand {
|
||||
|
||||
protected ArchiveCommand(String name, String description,
|
||||
OptionHandler optionHandler) {
|
||||
super(name, description, optionHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <" + getName() + "-name> <files>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base {@link CompilerOptionHandler} for archive commands.
|
||||
*/
|
||||
protected abstract static class ArchiveOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
private final String type;
|
||||
|
||||
private final Layout layout;
|
||||
|
||||
private OptionSpec<String> includeOption;
|
||||
|
||||
private OptionSpec<String> excludeOption;
|
||||
|
||||
public ArchiveOptionHandler(String type, Layout layout) {
|
||||
this.type = type;
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
protected Layout getLayout() {
|
||||
return this.layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOptions() {
|
||||
this.includeOption = option("include",
|
||||
"Pattern applied to directories on the classpath to find files to "
|
||||
+ "include in the resulting ").withRequiredArg()
|
||||
.withValuesSeparatedBy(",").defaultsTo("");
|
||||
this.excludeOption = option("exclude",
|
||||
"Pattern applied to directories on the classpath to find files to "
|
||||
+ "exclude from the resulting " + this.type).withRequiredArg()
|
||||
.withValuesSeparatedBy(",").defaultsTo("");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
List<?> nonOptionArguments = new ArrayList<Object>(
|
||||
options.nonOptionArguments());
|
||||
Assert.isTrue(nonOptionArguments.size() >= 2, "The name of the resulting "
|
||||
+ this.type + " and at least one source file must be specified");
|
||||
|
||||
File output = new File((String) nonOptionArguments.remove(0));
|
||||
Assert.isTrue(output.getName().toLowerCase().endsWith("." + this.type),
|
||||
"The output '" + output + "' is not a " + this.type.toUpperCase()
|
||||
+ " file.");
|
||||
deleteIfExists(output);
|
||||
|
||||
GroovyCompiler compiler = createCompiler(options);
|
||||
|
||||
List<URL> classpath = getClassPathUrls(compiler);
|
||||
List<MatchedResource> classpathEntries = findMatchingClasspathEntries(
|
||||
classpath, options);
|
||||
|
||||
String[] sources = new SourceOptions(nonOptionArguments).getSourcesArray();
|
||||
Class<?>[] compiledClasses = compiler.compile(sources);
|
||||
|
||||
List<URL> dependencies = getClassPathUrls(compiler);
|
||||
dependencies.removeAll(classpath);
|
||||
|
||||
writeJar(output, compiledClasses, classpathEntries, dependencies);
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
private void deleteIfExists(File file) {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to delete existing file " + file.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private GroovyCompiler createCompiler(OptionSet options) {
|
||||
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
|
||||
.createDefaultRepositoryConfiguration();
|
||||
GroovyCompilerConfiguration configuration = new OptionSetGroovyCompilerConfiguration(
|
||||
options, this, repositoryConfiguration);
|
||||
GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
|
||||
groovyCompiler.getAstTransformations().add(0, new GrabAnnotationTransform());
|
||||
return groovyCompiler;
|
||||
}
|
||||
|
||||
private List<URL> getClassPathUrls(GroovyCompiler compiler) {
|
||||
return new ArrayList<>(Arrays.asList(compiler.getLoader().getURLs()));
|
||||
}
|
||||
|
||||
private List<MatchedResource> findMatchingClasspathEntries(List<URL> classpath,
|
||||
OptionSet options) throws IOException {
|
||||
ResourceMatcher matcher = new ResourceMatcher(
|
||||
options.valuesOf(this.includeOption),
|
||||
options.valuesOf(this.excludeOption));
|
||||
List<File> roots = new ArrayList<>();
|
||||
for (URL classpathEntry : classpath) {
|
||||
roots.add(new File(URI.create(classpathEntry.toString())));
|
||||
}
|
||||
return matcher.find(roots);
|
||||
}
|
||||
|
||||
private void writeJar(File file, Class<?>[] compiledClasses,
|
||||
List<MatchedResource> classpathEntries, List<URL> dependencies)
|
||||
throws FileNotFoundException, IOException, URISyntaxException {
|
||||
final List<Library> libraries;
|
||||
try (JarWriter writer = new JarWriter(file)) {
|
||||
addManifest(writer, compiledClasses);
|
||||
addCliClasses(writer);
|
||||
for (Class<?> compiledClass : compiledClasses) {
|
||||
addClass(writer, compiledClass);
|
||||
}
|
||||
libraries = addClasspathEntries(writer, classpathEntries);
|
||||
}
|
||||
libraries.addAll(createLibraries(dependencies));
|
||||
Repackager repackager = new Repackager(file);
|
||||
repackager.setMainClass(PackagedSpringApplicationLauncher.class.getName());
|
||||
repackager.repackage((callback) -> {
|
||||
for (Library library : libraries) {
|
||||
callback.library(library);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<Library> createLibraries(List<URL> dependencies)
|
||||
throws URISyntaxException {
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
for (URL dependency : dependencies) {
|
||||
File file = new File(dependency.toURI());
|
||||
libraries.add(new Library(file, getLibraryScope(file)));
|
||||
}
|
||||
return libraries;
|
||||
}
|
||||
|
||||
private void addManifest(JarWriter writer, Class<?>[] compiledClasses)
|
||||
throws IOException {
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
|
||||
manifest.getMainAttributes().putValue(
|
||||
PackagedSpringApplicationLauncher.SOURCE_ENTRY,
|
||||
commaDelimitedClassNames(compiledClasses));
|
||||
writer.writeManifest(manifest);
|
||||
}
|
||||
|
||||
private String commaDelimitedClassNames(Class<?>[] classes) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < classes.length; i++) {
|
||||
builder.append(i == 0 ? "" : ",");
|
||||
builder.append(classes[i].getName());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
protected void addCliClasses(JarWriter writer) throws IOException {
|
||||
addClass(writer, PackagedSpringApplicationLauncher.class);
|
||||
addClass(writer, SpringApplicationLauncher.class);
|
||||
Resource[] resources = new PathMatchingResourcePatternResolver()
|
||||
.getResources("org/springframework/boot/groovy/**");
|
||||
for (Resource resource : resources) {
|
||||
String url = resource.getURL().toString();
|
||||
addResource(writer, resource,
|
||||
url.substring(url.indexOf("org/springframework/boot/groovy/")));
|
||||
}
|
||||
}
|
||||
|
||||
protected final void addClass(JarWriter writer, Class<?> sourceClass)
|
||||
throws IOException {
|
||||
addClass(writer, sourceClass.getClassLoader(), sourceClass.getName());
|
||||
}
|
||||
|
||||
protected final void addClass(JarWriter writer, ClassLoader classLoader,
|
||||
String sourceClass) throws IOException {
|
||||
if (classLoader == null) {
|
||||
classLoader = Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
String name = sourceClass.replace('.', '/') + ".class";
|
||||
InputStream stream = classLoader.getResourceAsStream(name);
|
||||
writer.writeEntry(this.layout.getClassesLocation() + name, stream);
|
||||
}
|
||||
|
||||
private void addResource(JarWriter writer, Resource resource, String name)
|
||||
throws IOException {
|
||||
InputStream stream = resource.getInputStream();
|
||||
writer.writeEntry(name, stream);
|
||||
}
|
||||
|
||||
private List<Library> addClasspathEntries(JarWriter writer,
|
||||
List<MatchedResource> entries) throws IOException {
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
for (MatchedResource entry : entries) {
|
||||
if (entry.isRoot()) {
|
||||
libraries.add(new Library(entry.getFile(), LibraryScope.COMPILE));
|
||||
}
|
||||
else {
|
||||
writeClasspathEntry(writer, entry);
|
||||
}
|
||||
}
|
||||
return libraries;
|
||||
}
|
||||
|
||||
protected void writeClasspathEntry(JarWriter writer, MatchedResource entry)
|
||||
throws IOException {
|
||||
writer.writeEntry(entry.getName(), new FileInputStream(entry.getFile()));
|
||||
}
|
||||
|
||||
protected abstract LibraryScope getLibraryScope(File file);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to change {@code @Grab} annotation values.
|
||||
*/
|
||||
private static class GrabAnnotationTransform implements ASTTransformation {
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
for (ASTNode node : nodes) {
|
||||
if (node instanceof ModuleNode) {
|
||||
visitModule((ModuleNode) node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void visitModule(ModuleNode module) {
|
||||
for (ClassNode classNode : module.getClasses()) {
|
||||
AnnotationNode annotation = new AnnotationNode(new ClassNode(Grab.class));
|
||||
annotation.addMember("value", new ConstantExpression("groovy"));
|
||||
classNode.addAnnotation(annotation);
|
||||
// We only need to do it at most once
|
||||
break;
|
||||
}
|
||||
// Disable the addition of a static initializer that calls Grape.addResolver
|
||||
// because all the dependencies are local now
|
||||
disableGrabResolvers(module.getClasses());
|
||||
disableGrabResolvers(module.getImports());
|
||||
}
|
||||
|
||||
private void disableGrabResolvers(List<? extends AnnotatedNode> nodes) {
|
||||
for (AnnotatedNode classNode : nodes) {
|
||||
List<AnnotationNode> annotations = classNode.getAnnotations();
|
||||
for (AnnotationNode node : new ArrayList<>(annotations)) {
|
||||
if (node.getClassNode().getNameWithoutPackage()
|
||||
.equals("GrabResolver")) {
|
||||
node.setMember("initClass", new ConstantExpression(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.command.archive;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.loader.tools.Layouts;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
|
||||
/**
|
||||
* {@link Command} to create a self-contained executable jar file from a CLI application.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class JarCommand extends ArchiveCommand {
|
||||
|
||||
public JarCommand() {
|
||||
super("jar", "Create a self-contained executable jar "
|
||||
+ "file from a Spring Groovy script", new JarOptionHandler());
|
||||
}
|
||||
|
||||
private static final class JarOptionHandler extends ArchiveOptionHandler {
|
||||
|
||||
JarOptionHandler() {
|
||||
super("jar", new Layouts.Jar());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LibraryScope getLibraryScope(File file) {
|
||||
return LibraryScope.COMPILE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.command.archive;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Used to match resources for inclusion in a CLI application's jar file.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class ResourceMatcher {
|
||||
|
||||
private static final String[] DEFAULT_INCLUDES = { "public/**", "resources/**",
|
||||
"static/**", "templates/**", "META-INF/**", "*" };
|
||||
|
||||
private static final String[] DEFAULT_EXCLUDES = { ".*", "repository/**", "build/**",
|
||||
"target/**", "**/*.jar", "**/*.groovy" };
|
||||
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private final List<String> includes;
|
||||
|
||||
private final List<String> excludes;
|
||||
|
||||
ResourceMatcher(List<String> includes, List<String> excludes) {
|
||||
this.includes = getOptions(includes, DEFAULT_INCLUDES);
|
||||
this.excludes = getOptions(excludes, DEFAULT_EXCLUDES);
|
||||
}
|
||||
|
||||
public List<MatchedResource> find(List<File> roots) throws IOException {
|
||||
List<MatchedResource> matchedResources = new ArrayList<>();
|
||||
for (File root : roots) {
|
||||
if (root.isFile()) {
|
||||
matchedResources.add(new MatchedResource(root));
|
||||
}
|
||||
else {
|
||||
matchedResources.addAll(findInFolder(root));
|
||||
}
|
||||
}
|
||||
return matchedResources;
|
||||
}
|
||||
|
||||
private List<MatchedResource> findInFolder(File folder) throws IOException {
|
||||
List<MatchedResource> matchedResources = new ArrayList<>();
|
||||
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
|
||||
new FolderResourceLoader(folder));
|
||||
|
||||
for (String include : this.includes) {
|
||||
for (Resource candidate : resolver.getResources(include)) {
|
||||
File file = candidate.getFile();
|
||||
if (file.isFile()) {
|
||||
MatchedResource matchedResource = new MatchedResource(folder, file);
|
||||
if (!isExcluded(matchedResource)) {
|
||||
matchedResources.add(matchedResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchedResources;
|
||||
}
|
||||
|
||||
private boolean isExcluded(MatchedResource matchedResource) {
|
||||
for (String exclude : this.excludes) {
|
||||
if (this.pathMatcher.match(exclude, matchedResource.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<String> getOptions(List<String> values, String[] defaults) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
Set<String> minus = new LinkedHashSet<>();
|
||||
boolean deltasFound = false;
|
||||
for (String value : values) {
|
||||
if (value.startsWith("+")) {
|
||||
deltasFound = true;
|
||||
value = value.substring(1);
|
||||
result.add(value);
|
||||
}
|
||||
else if (value.startsWith("-")) {
|
||||
deltasFound = true;
|
||||
value = value.substring(1);
|
||||
minus.add(value);
|
||||
}
|
||||
else if (!value.trim().isEmpty()) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
for (String value : defaults) {
|
||||
if (!minus.contains(value) || !deltasFound) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResourceLoader} to get load resource from a folder.
|
||||
*/
|
||||
private static class FolderResourceLoader extends DefaultResourceLoader {
|
||||
|
||||
private final File rootFolder;
|
||||
|
||||
FolderResourceLoader(File root) throws MalformedURLException {
|
||||
super(new FolderClassLoader(root));
|
||||
this.rootFolder = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource getResourceByPath(String path) {
|
||||
return new FileSystemResource(new File(this.rootFolder, path));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClassLoader} backed by a folder.
|
||||
*/
|
||||
private static class FolderClassLoader extends URLClassLoader {
|
||||
|
||||
FolderClassLoader(File rootFolder) throws MalformedURLException {
|
||||
super(new URL[] { rootFolder.toURI().toURL() });
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
return findResources(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
return findResource(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A single matched resource.
|
||||
*/
|
||||
public static final class MatchedResource {
|
||||
|
||||
private final File file;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final boolean root;
|
||||
|
||||
private MatchedResource(File file) {
|
||||
this.name = file.getName();
|
||||
this.file = file;
|
||||
this.root = this.name.endsWith(".jar");
|
||||
}
|
||||
|
||||
private MatchedResource(File rootFolder, File file) {
|
||||
this.name = StringUtils.cleanPath(file.getAbsolutePath()
|
||||
.substring(rootFolder.getAbsolutePath().length() + 1));
|
||||
this.file = file;
|
||||
this.root = false;
|
||||
}
|
||||
|
||||
private MatchedResource(File resourceFile, String path, boolean root) {
|
||||
this.file = resourceFile;
|
||||
this.name = path;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
public boolean isRoot() {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.file.getAbsolutePath();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.command.archive;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.loader.tools.JarWriter;
|
||||
import org.springframework.boot.loader.tools.Layouts;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
|
||||
/**
|
||||
* {@link Command} to create a self-contained executable jar file from a CLI application.
|
||||
*
|
||||
* @author Andrey Stolyarov
|
||||
* @author Phillip Webb
|
||||
* @author Henri Kerola
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class WarCommand extends ArchiveCommand {
|
||||
|
||||
public WarCommand() {
|
||||
super("war", "Create a self-contained executable war "
|
||||
+ "file from a Spring Groovy script", new WarOptionHandler());
|
||||
}
|
||||
|
||||
private static final class WarOptionHandler extends ArchiveOptionHandler {
|
||||
|
||||
WarOptionHandler() {
|
||||
super("war", new Layouts.War());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LibraryScope getLibraryScope(File file) {
|
||||
String fileName = file.getName();
|
||||
if (fileName.contains("tomcat-embed")
|
||||
|| fileName.contains("spring-boot-starter-tomcat")) {
|
||||
return LibraryScope.PROVIDED;
|
||||
}
|
||||
return LibraryScope.COMPILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCliClasses(JarWriter writer) throws IOException {
|
||||
addClass(writer, null, "org.springframework.boot."
|
||||
+ "cli.app.SpringApplicationWebApplicationInitializer");
|
||||
super.addCliClasses(writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeClasspathEntry(JarWriter writer,
|
||||
ResourceMatcher.MatchedResource entry) throws IOException {
|
||||
writer.writeEntry(getLayout().getClassesLocation() + entry.getName(),
|
||||
new FileInputStream(entry.getFile()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.command.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.CommandRunner;
|
||||
import org.springframework.boot.cli.command.HelpExample;
|
||||
import org.springframework.boot.cli.command.NoHelpCommandArgumentsException;
|
||||
import org.springframework.boot.cli.command.NoSuchCommandException;
|
||||
import org.springframework.boot.cli.command.options.OptionHelp;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* Internal {@link Command} used for 'help' requests.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HelpCommand extends AbstractCommand {
|
||||
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
public HelpCommand(CommandRunner commandRunner) {
|
||||
super("help", "Get help on commands");
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "command";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OptionHelp> getOptionsHelp() {
|
||||
List<OptionHelp> help = new ArrayList<>();
|
||||
for (final Command command : this.commandRunner) {
|
||||
if (isHelpShown(command)) {
|
||||
help.add(new OptionHelp() {
|
||||
|
||||
@Override
|
||||
public Set<String> getOptions() {
|
||||
return Collections.singleton(command.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return command.getDescription();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
return help;
|
||||
}
|
||||
|
||||
private boolean isHelpShown(Command command) {
|
||||
if (command instanceof HelpCommand || command instanceof HintCommand) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
throw new NoHelpCommandArgumentsException();
|
||||
}
|
||||
String commandName = args[0];
|
||||
for (Command command : this.commandRunner) {
|
||||
if (command.getName().equals(commandName)) {
|
||||
Log.info(this.commandRunner.getName() + command.getName() + " - "
|
||||
+ command.getDescription());
|
||||
Log.info("");
|
||||
if (command.getUsageHelp() != null) {
|
||||
Log.info("usage: " + this.commandRunner.getName() + command.getName()
|
||||
+ " " + command.getUsageHelp());
|
||||
Log.info("");
|
||||
}
|
||||
if (command.getHelp() != null) {
|
||||
Log.info(command.getHelp());
|
||||
}
|
||||
Collection<HelpExample> examples = command.getExamples();
|
||||
if (examples != null) {
|
||||
Log.info(examples.size() == 1 ? "example:" : "examples:");
|
||||
Log.info("");
|
||||
for (HelpExample example : examples) {
|
||||
Log.info(" " + example.getDescription() + ":");
|
||||
Log.info(" $ " + example.getExample());
|
||||
Log.info("");
|
||||
}
|
||||
Log.info("");
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
throw new NoSuchCommandException(commandName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.command.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.CommandRunner;
|
||||
import org.springframework.boot.cli.command.options.OptionHelp;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* Internal {@link Command} to provide hints for shell auto-completion. Expects to be
|
||||
* called with the current index followed by a list of arguments already typed.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HintCommand extends AbstractCommand {
|
||||
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
public HintCommand(CommandRunner commandRunner) {
|
||||
super("hint", "Provides hints for shell auto-completion");
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) throws Exception {
|
||||
try {
|
||||
int index = (args.length == 0 ? 0 : Integer.valueOf(args[0]) - 1);
|
||||
List<String> arguments = new ArrayList<>(args.length);
|
||||
for (int i = 2; i < args.length; i++) {
|
||||
arguments.add(args[i]);
|
||||
}
|
||||
String starting = "";
|
||||
if (index < arguments.size()) {
|
||||
starting = arguments.remove(index);
|
||||
}
|
||||
if (index == 0) {
|
||||
showCommandHints(starting);
|
||||
}
|
||||
else if (!arguments.isEmpty() && !starting.isEmpty()) {
|
||||
String command = arguments.remove(0);
|
||||
showCommandOptionHints(command, Collections.unmodifiableList(arguments),
|
||||
starting);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Swallow and provide no hints
|
||||
return ExitStatus.ERROR;
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
private void showCommandHints(String starting) {
|
||||
for (Command command : this.commandRunner) {
|
||||
if (isHintMatch(command, starting)) {
|
||||
Log.info(command.getName() + " " + command.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHintMatch(Command command, String starting) {
|
||||
if (command instanceof HintCommand) {
|
||||
return false;
|
||||
}
|
||||
return command.getName().startsWith(starting)
|
||||
|| (this.commandRunner.isOptionCommand(command)
|
||||
&& ("--" + command.getName()).startsWith(starting));
|
||||
}
|
||||
|
||||
private void showCommandOptionHints(String commandName,
|
||||
List<String> specifiedArguments, String starting) {
|
||||
Command command = this.commandRunner.findCommand(commandName);
|
||||
if (command != null) {
|
||||
for (OptionHelp help : command.getOptionsHelp()) {
|
||||
if (!alreadyUsed(help, specifiedArguments)) {
|
||||
for (String option : help.getOptions()) {
|
||||
if (option.startsWith(starting)) {
|
||||
Log.info(option + " " + help.getUsageHelp());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean alreadyUsed(OptionHelp help, List<String> specifiedArguments) {
|
||||
for (String argument : specifiedArguments) {
|
||||
if (help.getOptions().contains(argument)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.core;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* {@link Command} to display the 'version' number.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class VersionCommand extends AbstractCommand {
|
||||
|
||||
public VersionCommand() {
|
||||
super("version", "Show the version");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) {
|
||||
Log.info("Spring CLI v" + getClass().getPackage().getImplementationVersion());
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.grab;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.command.options.SourceOptions;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
|
||||
/**
|
||||
* {@link Command} to grab the dependencies of one or more Groovy scripts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class GrabCommand extends OptionParsingCommand {
|
||||
|
||||
public GrabCommand() {
|
||||
super("grab", "Download a spring groovy script's dependencies to ./repository",
|
||||
new GrabOptionHandler());
|
||||
}
|
||||
|
||||
private static final class GrabOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
@Override
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
SourceOptions sourceOptions = new SourceOptions(options);
|
||||
|
||||
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
|
||||
.createDefaultRepositoryConfiguration();
|
||||
|
||||
GroovyCompilerConfiguration configuration = new OptionSetGroovyCompilerConfiguration(
|
||||
options, this, repositoryConfiguration);
|
||||
|
||||
if (System.getProperty("grape.root") == null) {
|
||||
System.setProperty("grape.root", ".");
|
||||
}
|
||||
|
||||
GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
|
||||
groovyCompiler.compile(sourceOptions.getSourcesArray());
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.init;
|
||||
|
||||
/**
|
||||
* Provide some basic information about a dependency.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
final class Dependency {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String description;
|
||||
|
||||
Dependency(String id, String name, String description) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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.command.init;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.HelpExample;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.OptionHandler;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Command} that initializes a project using Spring initializr.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class InitCommand extends OptionParsingCommand {
|
||||
|
||||
public InitCommand() {
|
||||
this(new InitOptionHandler(new InitializrService()));
|
||||
}
|
||||
|
||||
public InitCommand(InitOptionHandler handler) {
|
||||
super("init",
|
||||
"Initialize a new project using Spring " + "Initializr (start.spring.io)",
|
||||
handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] [location]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<HelpExample> getExamples() {
|
||||
List<HelpExample> examples = new ArrayList<>();
|
||||
examples.add(new HelpExample("To list all the capabilities of the service",
|
||||
"spring init --list"));
|
||||
examples.add(new HelpExample("To creates a default project", "spring init"));
|
||||
examples.add(new HelpExample("To create a web my-app.zip",
|
||||
"spring init -d=web my-app.zip"));
|
||||
examples.add(new HelpExample("To create a web/data-jpa gradle project unpacked",
|
||||
"spring init -d=web,jpa --build=gradle my-dir"));
|
||||
return examples;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link OptionHandler} for {@link InitCommand}.
|
||||
*/
|
||||
static class InitOptionHandler extends OptionHandler {
|
||||
|
||||
private final ServiceCapabilitiesReportGenerator serviceCapabilitiesReport;
|
||||
|
||||
private final ProjectGenerator projectGenerator;
|
||||
|
||||
private OptionSpec<String> target;
|
||||
|
||||
private OptionSpec<Void> listCapabilities;
|
||||
|
||||
private OptionSpec<String> groupId;
|
||||
|
||||
private OptionSpec<String> artifactId;
|
||||
|
||||
private OptionSpec<String> version;
|
||||
|
||||
private OptionSpec<String> name;
|
||||
|
||||
private OptionSpec<String> description;
|
||||
|
||||
private OptionSpec<String> packageName;
|
||||
|
||||
private OptionSpec<String> type;
|
||||
|
||||
private OptionSpec<String> packaging;
|
||||
|
||||
private OptionSpec<String> build;
|
||||
|
||||
private OptionSpec<String> format;
|
||||
|
||||
private OptionSpec<String> javaVersion;
|
||||
|
||||
private OptionSpec<String> language;
|
||||
|
||||
private OptionSpec<String> bootVersion;
|
||||
|
||||
private OptionSpec<String> dependencies;
|
||||
|
||||
private OptionSpec<Void> extract;
|
||||
|
||||
private OptionSpec<Void> force;
|
||||
|
||||
InitOptionHandler(InitializrService initializrService) {
|
||||
this.serviceCapabilitiesReport = new ServiceCapabilitiesReportGenerator(
|
||||
initializrService);
|
||||
this.projectGenerator = new ProjectGenerator(initializrService);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void options() {
|
||||
this.target = option(Arrays.asList("target"), "URL of the service to use")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(ProjectGenerationRequest.DEFAULT_SERVICE_URL);
|
||||
this.listCapabilities = option(Arrays.asList("list", "l"),
|
||||
"List the capabilities of the service. Use it to discover the "
|
||||
+ "dependencies and the types that are available");
|
||||
projectGenerationOptions();
|
||||
otherOptions();
|
||||
}
|
||||
|
||||
private void projectGenerationOptions() {
|
||||
this.groupId = option(Arrays.asList("groupId", "g"),
|
||||
"Project coordinates (for example 'org.test')").withRequiredArg();
|
||||
this.artifactId = option(Arrays.asList("artifactId", "a"),
|
||||
"Project coordinates; infer archive name (for example 'test')")
|
||||
.withRequiredArg();
|
||||
this.version = option(Arrays.asList("version", "v"),
|
||||
"Project version (for example '0.0.1-SNAPSHOT')").withRequiredArg();
|
||||
this.name = option(Arrays.asList("name", "n"),
|
||||
"Project name; infer application name").withRequiredArg();
|
||||
this.description = option("description", "Project description")
|
||||
.withRequiredArg();
|
||||
this.packageName = option("package-name", "Package name").withRequiredArg();
|
||||
this.type = option(Arrays.asList("type", "t"),
|
||||
"Project type. Not normally needed if you use --build "
|
||||
+ "and/or --format. Check the capabilities of the service "
|
||||
+ "(--list) for more details").withRequiredArg();
|
||||
this.packaging = option(Arrays.asList("packaging", "p"),
|
||||
"Project packaging (for example 'jar')").withRequiredArg();
|
||||
this.build = option("build",
|
||||
"Build system to use (for example 'maven' or 'gradle')")
|
||||
.withRequiredArg().defaultsTo("maven");
|
||||
this.format = option("format",
|
||||
"Format of the generated content (for example 'build' for a build file, "
|
||||
+ "'project' for a project archive)").withRequiredArg()
|
||||
.defaultsTo("project");
|
||||
this.javaVersion = option(Arrays.asList("java-version", "j"),
|
||||
"Language level (for example '1.8')").withRequiredArg();
|
||||
this.language = option(Arrays.asList("language", "l"),
|
||||
"Programming language (for example 'java')").withRequiredArg();
|
||||
this.bootVersion = option(Arrays.asList("boot-version", "b"),
|
||||
"Spring Boot version (for example '1.2.0.RELEASE')")
|
||||
.withRequiredArg();
|
||||
this.dependencies = option(Arrays.asList("dependencies", "d"),
|
||||
"Comma-separated list of dependency identifiers to include in the "
|
||||
+ "generated project").withRequiredArg();
|
||||
}
|
||||
|
||||
private void otherOptions() {
|
||||
this.extract = option(Arrays.asList("extract", "x"),
|
||||
"Extract the project archive. Inferred if a location is specified without an extension");
|
||||
this.force = option(Arrays.asList("force", "f"),
|
||||
"Force overwrite of existing files");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
try {
|
||||
if (options.has(this.listCapabilities)) {
|
||||
generateReport(options);
|
||||
}
|
||||
else {
|
||||
generateProject(options);
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
catch (ReportableException ex) {
|
||||
Log.error(ex.getMessage());
|
||||
return ExitStatus.ERROR;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.error(ex);
|
||||
return ExitStatus.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private void generateReport(OptionSet options) throws IOException {
|
||||
Log.info(this.serviceCapabilitiesReport
|
||||
.generate(options.valueOf(this.target)));
|
||||
}
|
||||
|
||||
protected void generateProject(OptionSet options) throws IOException {
|
||||
ProjectGenerationRequest request = createProjectGenerationRequest(options);
|
||||
this.projectGenerator.generateProject(request, options.has(this.force));
|
||||
}
|
||||
|
||||
protected ProjectGenerationRequest createProjectGenerationRequest(
|
||||
OptionSet options) {
|
||||
|
||||
List<?> nonOptionArguments = new ArrayList<Object>(
|
||||
options.nonOptionArguments());
|
||||
Assert.isTrue(nonOptionArguments.size() <= 1,
|
||||
"Only the target location may be specified");
|
||||
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
request.setServiceUrl(options.valueOf(this.target));
|
||||
if (options.has(this.bootVersion)) {
|
||||
request.setBootVersion(options.valueOf(this.bootVersion));
|
||||
}
|
||||
if (options.has(this.dependencies)) {
|
||||
for (String dep : options.valueOf(this.dependencies).split(",")) {
|
||||
request.getDependencies().add(dep.trim());
|
||||
}
|
||||
}
|
||||
if (options.has(this.javaVersion)) {
|
||||
request.setJavaVersion(options.valueOf(this.javaVersion));
|
||||
}
|
||||
if (options.has(this.packageName)) {
|
||||
request.setPackageName(options.valueOf(this.packageName));
|
||||
}
|
||||
request.setBuild(options.valueOf(this.build));
|
||||
request.setFormat(options.valueOf(this.format));
|
||||
request.setDetectType(options.has(this.build) || options.has(this.format));
|
||||
if (options.has(this.type)) {
|
||||
request.setType(options.valueOf(this.type));
|
||||
}
|
||||
if (options.has(this.packaging)) {
|
||||
request.setPackaging(options.valueOf(this.packaging));
|
||||
}
|
||||
if (options.has(this.language)) {
|
||||
request.setLanguage(options.valueOf(this.language));
|
||||
}
|
||||
if (options.has(this.groupId)) {
|
||||
request.setGroupId(options.valueOf(this.groupId));
|
||||
}
|
||||
if (options.has(this.artifactId)) {
|
||||
request.setArtifactId(options.valueOf(this.artifactId));
|
||||
}
|
||||
if (options.has(this.name)) {
|
||||
request.setName(options.valueOf(this.name));
|
||||
}
|
||||
if (options.has(this.version)) {
|
||||
request.setVersion(options.valueOf(this.version));
|
||||
}
|
||||
if (options.has(this.description)) {
|
||||
request.setDescription(options.valueOf(this.description));
|
||||
}
|
||||
request.setExtract(options.has(this.extract));
|
||||
if (nonOptionArguments.size() == 1) {
|
||||
String output = (String) nonOptionArguments.get(0);
|
||||
request.setOutput(output);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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.command.init;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Invokes the initializr service over HTTP.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class InitializrService {
|
||||
|
||||
private static final String FILENAME_HEADER_PREFIX = "filename=\"";
|
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
/**
|
||||
* Accept header to use to retrieve the json meta-data.
|
||||
*/
|
||||
public static final String ACCEPT_META_DATA = "application/vnd.initializr.v2.1+"
|
||||
+ "json,application/vnd.initializr.v2+json";
|
||||
|
||||
/**
|
||||
* Accept header to use to retrieve the service capabilities of the service. If the
|
||||
* service does not offer such feature, the json meta-data are retrieved instead.
|
||||
*/
|
||||
public static final String ACCEPT_SERVICE_CAPABILITIES = "text/plain,"
|
||||
+ ACCEPT_META_DATA;
|
||||
|
||||
/**
|
||||
* Late binding HTTP client.
|
||||
*/
|
||||
private CloseableHttpClient http;
|
||||
|
||||
InitializrService() {
|
||||
}
|
||||
|
||||
InitializrService(CloseableHttpClient http) {
|
||||
this.http = http;
|
||||
}
|
||||
|
||||
protected CloseableHttpClient getHttp() {
|
||||
if (this.http == null) {
|
||||
this.http = HttpClientBuilder.create().useSystemProperties().build();
|
||||
}
|
||||
return this.http;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a project based on the specified {@link ProjectGenerationRequest}.
|
||||
* @param request the generation request
|
||||
* @return an entity defining the project
|
||||
* @throws IOException if generation fails
|
||||
*/
|
||||
public ProjectGenerationResponse generate(ProjectGenerationRequest request)
|
||||
throws IOException {
|
||||
Log.info("Using service at " + request.getServiceUrl());
|
||||
InitializrServiceMetadata metadata = loadMetadata(request.getServiceUrl());
|
||||
URI url = request.generateUrl(metadata);
|
||||
CloseableHttpResponse httpResponse = executeProjectGenerationRequest(url);
|
||||
HttpEntity httpEntity = httpResponse.getEntity();
|
||||
validateResponse(httpResponse, request.getServiceUrl());
|
||||
return createResponse(httpResponse, httpEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the {@link InitializrServiceMetadata} at the specified url.
|
||||
* @param serviceUrl to url of the initializer service
|
||||
* @return the metadata describing the service
|
||||
* @throws IOException if the service's metadata cannot be loaded
|
||||
*/
|
||||
public InitializrServiceMetadata loadMetadata(String serviceUrl) throws IOException {
|
||||
CloseableHttpResponse httpResponse = executeInitializrMetadataRetrieval(
|
||||
serviceUrl);
|
||||
validateResponse(httpResponse, serviceUrl);
|
||||
return parseJsonMetadata(httpResponse.getEntity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the service capabilities of the service at the specified URL. If the service
|
||||
* supports generating a textual representation of the capabilities, it is returned,
|
||||
* otherwise {@link InitializrServiceMetadata} is returned.
|
||||
* @param serviceUrl to url of the initializer service
|
||||
* @return the service capabilities (as a String) or the
|
||||
* {@link InitializrServiceMetadata} describing the service
|
||||
* @throws IOException if the service capabilities cannot be loaded
|
||||
*/
|
||||
public Object loadServiceCapabilities(String serviceUrl) throws IOException {
|
||||
HttpGet request = new HttpGet(serviceUrl);
|
||||
request.setHeader(
|
||||
new BasicHeader(HttpHeaders.ACCEPT, ACCEPT_SERVICE_CAPABILITIES));
|
||||
CloseableHttpResponse httpResponse = execute(request, serviceUrl,
|
||||
"retrieve help");
|
||||
validateResponse(httpResponse, serviceUrl);
|
||||
HttpEntity httpEntity = httpResponse.getEntity();
|
||||
ContentType contentType = ContentType.getOrDefault(httpEntity);
|
||||
if (contentType.getMimeType().equals("text/plain")) {
|
||||
return getContent(httpEntity);
|
||||
}
|
||||
return parseJsonMetadata(httpEntity);
|
||||
}
|
||||
|
||||
private InitializrServiceMetadata parseJsonMetadata(HttpEntity httpEntity)
|
||||
throws IOException {
|
||||
try {
|
||||
return new InitializrServiceMetadata(getContentAsJson(httpEntity));
|
||||
}
|
||||
catch (JSONException ex) {
|
||||
throw new ReportableException(
|
||||
"Invalid content received from server (" + ex.getMessage() + ")", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateResponse(CloseableHttpResponse httpResponse, String serviceUrl) {
|
||||
if (httpResponse.getEntity() == null) {
|
||||
throw new ReportableException(
|
||||
"No content received from server '" + serviceUrl + "'");
|
||||
}
|
||||
if (httpResponse.getStatusLine().getStatusCode() != 200) {
|
||||
throw createException(serviceUrl, httpResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private ProjectGenerationResponse createResponse(CloseableHttpResponse httpResponse,
|
||||
HttpEntity httpEntity) throws IOException {
|
||||
ProjectGenerationResponse response = new ProjectGenerationResponse(
|
||||
ContentType.getOrDefault(httpEntity));
|
||||
response.setContent(FileCopyUtils.copyToByteArray(httpEntity.getContent()));
|
||||
String fileName = extractFileName(
|
||||
httpResponse.getFirstHeader("Content-Disposition"));
|
||||
if (fileName != null) {
|
||||
response.setFileName(fileName);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the creation of the project using the specified URL.
|
||||
* @param url the URL
|
||||
* @return the response
|
||||
*/
|
||||
private CloseableHttpResponse executeProjectGenerationRequest(URI url) {
|
||||
return execute(new HttpGet(url), url, "generate project");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the meta-data of the service at the specified URL.
|
||||
* @param url the URL
|
||||
* @return the response
|
||||
*/
|
||||
private CloseableHttpResponse executeInitializrMetadataRetrieval(String url) {
|
||||
HttpGet request = new HttpGet(url);
|
||||
request.setHeader(new BasicHeader(HttpHeaders.ACCEPT, ACCEPT_META_DATA));
|
||||
return execute(request, url, "retrieve metadata");
|
||||
}
|
||||
|
||||
private CloseableHttpResponse execute(HttpUriRequest request, Object url,
|
||||
String description) {
|
||||
try {
|
||||
request.addHeader("User-Agent", "SpringBootCli/"
|
||||
+ getClass().getPackage().getImplementationVersion());
|
||||
return getHttp().execute(request);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new ReportableException("Failed to " + description
|
||||
+ " from service at '" + url + "' (" + ex.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private ReportableException createException(String url,
|
||||
CloseableHttpResponse httpResponse) {
|
||||
String message = "Initializr service call failed using '" + url
|
||||
+ "' - service returned "
|
||||
+ httpResponse.getStatusLine().getReasonPhrase();
|
||||
String error = extractMessage(httpResponse.getEntity());
|
||||
if (StringUtils.hasText(error)) {
|
||||
message += ": '" + error + "'";
|
||||
}
|
||||
else {
|
||||
int statusCode = httpResponse.getStatusLine().getStatusCode();
|
||||
message += " (unexpected " + statusCode + " error)";
|
||||
}
|
||||
throw new ReportableException(message);
|
||||
}
|
||||
|
||||
private String extractMessage(HttpEntity entity) {
|
||||
if (entity != null) {
|
||||
try {
|
||||
JSONObject error = getContentAsJson(entity);
|
||||
if (error.has("message")) {
|
||||
return error.getString("message");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private JSONObject getContentAsJson(HttpEntity entity)
|
||||
throws IOException, JSONException {
|
||||
return new JSONObject(getContent(entity));
|
||||
}
|
||||
|
||||
private String getContent(HttpEntity entity) throws IOException {
|
||||
ContentType contentType = ContentType.getOrDefault(entity);
|
||||
Charset charset = contentType.getCharset();
|
||||
charset = (charset != null ? charset : UTF_8);
|
||||
byte[] content = FileCopyUtils.copyToByteArray(entity.getContent());
|
||||
return new String(content, charset);
|
||||
}
|
||||
|
||||
private String extractFileName(Header header) {
|
||||
if (header != null) {
|
||||
String value = header.getValue();
|
||||
int start = value.indexOf(FILENAME_HEADER_PREFIX);
|
||||
if (start != -1) {
|
||||
value = value.substring(start + FILENAME_HEADER_PREFIX.length());
|
||||
int end = value.indexOf("\"");
|
||||
if (end != -1) {
|
||||
return value.substring(0, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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.command.init;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Define the metadata available for a particular service instance.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class InitializrServiceMetadata {
|
||||
|
||||
private static final String DEPENDENCIES_EL = "dependencies";
|
||||
|
||||
private static final String TYPE_EL = "type";
|
||||
|
||||
private static final String VALUES_EL = "values";
|
||||
|
||||
private static final String NAME_ATTRIBUTE = "name";
|
||||
|
||||
private static final String ID_ATTRIBUTE = "id";
|
||||
|
||||
private static final String DESCRIPTION_ATTRIBUTE = "description";
|
||||
|
||||
private static final String ACTION_ATTRIBUTE = "action";
|
||||
|
||||
private static final String DEFAULT_ATTRIBUTE = "default";
|
||||
|
||||
private final Map<String, Dependency> dependencies;
|
||||
|
||||
private final MetadataHolder<String, ProjectType> projectTypes;
|
||||
|
||||
private final Map<String, String> defaults;
|
||||
|
||||
/**
|
||||
* Creates a new instance using the specified root {@link JSONObject}.
|
||||
* @param root the root JSONObject
|
||||
* @throws JSONException on JSON parsing failure
|
||||
*/
|
||||
InitializrServiceMetadata(JSONObject root) throws JSONException {
|
||||
this.dependencies = parseDependencies(root);
|
||||
this.projectTypes = parseProjectTypes(root);
|
||||
this.defaults = Collections.unmodifiableMap(parseDefaults(root));
|
||||
}
|
||||
|
||||
InitializrServiceMetadata(ProjectType defaultProjectType) {
|
||||
this.dependencies = new HashMap<>();
|
||||
this.projectTypes = new MetadataHolder<>();
|
||||
this.projectTypes.getContent().put(defaultProjectType.getId(),
|
||||
defaultProjectType);
|
||||
this.projectTypes.setDefaultItem(defaultProjectType);
|
||||
this.defaults = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dependencies supported by the service.
|
||||
* @return the supported dependencies
|
||||
*/
|
||||
public Collection<Dependency> getDependencies() {
|
||||
return this.dependencies.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dependency with the specified id or {@code null} if no such dependency
|
||||
* exists.
|
||||
* @param id the id
|
||||
* @return the dependency or {@code null}
|
||||
*/
|
||||
public Dependency getDependency(String id) {
|
||||
return this.dependencies.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the project types supported by the service.
|
||||
* @return the supported project types
|
||||
*/
|
||||
public Map<String, ProjectType> getProjectTypes() {
|
||||
return this.projectTypes.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default type to use or {@code null} if the metadata does not define any
|
||||
* default.
|
||||
* @return the default project type or {@code null}
|
||||
*/
|
||||
public ProjectType getDefaultType() {
|
||||
if (this.projectTypes.getDefaultItem() != null) {
|
||||
return this.projectTypes.getDefaultItem();
|
||||
}
|
||||
String defaultTypeId = getDefaults().get("type");
|
||||
if (defaultTypeId != null) {
|
||||
return this.projectTypes.getContent().get(defaultTypeId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the defaults applicable to the service.
|
||||
* @return the defaults of the service
|
||||
*/
|
||||
public Map<String, String> getDefaults() {
|
||||
return this.defaults;
|
||||
}
|
||||
|
||||
private Map<String, Dependency> parseDependencies(JSONObject root)
|
||||
throws JSONException {
|
||||
Map<String, Dependency> result = new HashMap<>();
|
||||
if (!root.has(DEPENDENCIES_EL)) {
|
||||
return result;
|
||||
}
|
||||
JSONObject dependencies = root.getJSONObject(DEPENDENCIES_EL);
|
||||
JSONArray array = dependencies.getJSONArray(VALUES_EL);
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject group = array.getJSONObject(i);
|
||||
parseGroup(group, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private MetadataHolder<String, ProjectType> parseProjectTypes(JSONObject root)
|
||||
throws JSONException {
|
||||
MetadataHolder<String, ProjectType> result = new MetadataHolder<>();
|
||||
if (!root.has(TYPE_EL)) {
|
||||
return result;
|
||||
}
|
||||
JSONObject type = root.getJSONObject(TYPE_EL);
|
||||
JSONArray array = type.getJSONArray(VALUES_EL);
|
||||
String defaultType = type.has(DEFAULT_ATTRIBUTE)
|
||||
? type.getString(DEFAULT_ATTRIBUTE) : null;
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject typeJson = array.getJSONObject(i);
|
||||
ProjectType projectType = parseType(typeJson, defaultType);
|
||||
result.getContent().put(projectType.getId(), projectType);
|
||||
if (projectType.isDefaultType()) {
|
||||
result.setDefaultItem(projectType);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, String> parseDefaults(JSONObject root) throws JSONException {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
Iterator<?> keys = root.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = (String) keys.next();
|
||||
Object o = root.get(key);
|
||||
if (o instanceof JSONObject) {
|
||||
JSONObject child = (JSONObject) o;
|
||||
if (child.has(DEFAULT_ATTRIBUTE)) {
|
||||
result.put(key, child.getString(DEFAULT_ATTRIBUTE));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void parseGroup(JSONObject group, Map<String, Dependency> dependencies)
|
||||
throws JSONException {
|
||||
if (group.has(VALUES_EL)) {
|
||||
JSONArray content = group.getJSONArray(VALUES_EL);
|
||||
for (int i = 0; i < content.length(); i++) {
|
||||
Dependency dependency = parseDependency(content.getJSONObject(i));
|
||||
dependencies.put(dependency.getId(), dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dependency parseDependency(JSONObject object) throws JSONException {
|
||||
String id = getStringValue(object, ID_ATTRIBUTE, null);
|
||||
String name = getStringValue(object, NAME_ATTRIBUTE, null);
|
||||
String description = getStringValue(object, DESCRIPTION_ATTRIBUTE, null);
|
||||
return new Dependency(id, name, description);
|
||||
}
|
||||
|
||||
private ProjectType parseType(JSONObject object, String defaultId)
|
||||
throws JSONException {
|
||||
String id = getStringValue(object, ID_ATTRIBUTE, null);
|
||||
String name = getStringValue(object, NAME_ATTRIBUTE, null);
|
||||
String action = getStringValue(object, ACTION_ATTRIBUTE, null);
|
||||
boolean defaultType = id.equals(defaultId);
|
||||
Map<String, String> tags = new HashMap<>();
|
||||
if (object.has("tags")) {
|
||||
JSONObject jsonTags = object.getJSONObject("tags");
|
||||
tags.putAll(parseStringItems(jsonTags));
|
||||
}
|
||||
return new ProjectType(id, name, action, defaultType, tags);
|
||||
}
|
||||
|
||||
private String getStringValue(JSONObject object, String name, String defaultValue)
|
||||
throws JSONException {
|
||||
return object.has(name) ? object.getString(name) : defaultValue;
|
||||
}
|
||||
|
||||
private Map<String, String> parseStringItems(JSONObject json) throws JSONException {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (Iterator<?> iterator = json.keys(); iterator.hasNext();) {
|
||||
String key = (String) iterator.next();
|
||||
Object value = json.get(key);
|
||||
if (value instanceof String) {
|
||||
result.put(key, (String) value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private final static class MetadataHolder<K, T> {
|
||||
|
||||
private final Map<K, T> content;
|
||||
|
||||
private T defaultItem;
|
||||
|
||||
private MetadataHolder() {
|
||||
this.content = new HashMap<>();
|
||||
}
|
||||
|
||||
public Map<K, T> getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public T getDefaultItem() {
|
||||
return this.defaultItem;
|
||||
}
|
||||
|
||||
public void setDefaultItem(T defaultItem) {
|
||||
this.defaultItem = defaultItem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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.command.init;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Represent the settings to apply to generating the project.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class ProjectGenerationRequest {
|
||||
|
||||
public static final String DEFAULT_SERVICE_URL = "https://start.spring.io";
|
||||
|
||||
private String serviceUrl = DEFAULT_SERVICE_URL;
|
||||
|
||||
private String output;
|
||||
|
||||
private boolean extract;
|
||||
|
||||
private String groupId;
|
||||
|
||||
private String artifactId;
|
||||
|
||||
private String version;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private String packageName;
|
||||
|
||||
private String type;
|
||||
|
||||
private String packaging;
|
||||
|
||||
private String build;
|
||||
|
||||
private String format;
|
||||
|
||||
private boolean detectType;
|
||||
|
||||
private String javaVersion;
|
||||
|
||||
private String language;
|
||||
|
||||
private String bootVersion;
|
||||
|
||||
private List<String> dependencies = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The URL of the service to use.
|
||||
* @return the service URL
|
||||
* @see #DEFAULT_SERVICE_URL
|
||||
*/
|
||||
public String getServiceUrl() {
|
||||
return this.serviceUrl;
|
||||
}
|
||||
|
||||
public void setServiceUrl(String serviceUrl) {
|
||||
this.serviceUrl = serviceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* The location of the generated project.
|
||||
* @return the location of the generated project
|
||||
*/
|
||||
public String getOutput() {
|
||||
return this.output;
|
||||
}
|
||||
|
||||
public void setOutput(String output) {
|
||||
if (output != null && output.endsWith("/")) {
|
||||
this.output = output.substring(0, output.length() - 1);
|
||||
this.extract = true;
|
||||
}
|
||||
else {
|
||||
this.output = output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the project archive should be extracted in the output location. If
|
||||
* the {@link #getOutput() output} ends with "/", the project is extracted
|
||||
* automatically.
|
||||
* @return {@code true} if the archive should be extracted, otherwise {@code false}
|
||||
*/
|
||||
public boolean isExtract() {
|
||||
return this.extract;
|
||||
}
|
||||
|
||||
public void setExtract(boolean extract) {
|
||||
this.extract = extract;
|
||||
}
|
||||
|
||||
/**
|
||||
* The groupId to use or {@code null} if it should not be customized.
|
||||
* @return the groupId or {@code null}
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The artifactId to use or {@code null} if it should not be customized.
|
||||
* @return the artifactId or {@code null}
|
||||
*/
|
||||
public String getArtifactId() {
|
||||
return this.artifactId;
|
||||
}
|
||||
|
||||
public void setArtifactId(String artifactId) {
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The artifact version to use or {@code null} if it should not be customized.
|
||||
* @return the artifact version or {@code null}
|
||||
*/
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name to use or {@code null} if it should not be customized.
|
||||
* @return the name or {@code null}
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The description to use or {@code null} if it should not be customized.
|
||||
* @return the description or {@code null}
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the package name or {@code null} if it should not be customized.
|
||||
* @return the package name or {@code null}
|
||||
*/
|
||||
public String getPackageName() {
|
||||
return this.packageName;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of project to generate. Should match one of the advertized type that the
|
||||
* service supports. If not set, the default is retrieved from the service metadata.
|
||||
* @return the project type
|
||||
*/
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The packaging type or {@code null} if it should not be customized.
|
||||
* @return the packaging type or {@code null}
|
||||
*/
|
||||
public String getPackaging() {
|
||||
return this.packaging;
|
||||
}
|
||||
|
||||
public void setPackaging(String packaging) {
|
||||
this.packaging = packaging;
|
||||
}
|
||||
|
||||
/**
|
||||
* The build type to use. Ignored if a type is set. Can be used alongside the
|
||||
* {@link #getFormat() format} to identify the type to use.
|
||||
* @return the build type
|
||||
*/
|
||||
public String getBuild() {
|
||||
return this.build;
|
||||
}
|
||||
|
||||
public void setBuild(String build) {
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
/**
|
||||
* The project format to use. Ignored if a type is set. Can be used alongside the
|
||||
* {@link #getBuild() build} to identify the type to use.
|
||||
* @return the project format
|
||||
*/
|
||||
public String getFormat() {
|
||||
return this.format;
|
||||
}
|
||||
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the type should be detected based on the build and format value.
|
||||
* @return {@code true} if type detection will be performed, otherwise {@code false}
|
||||
*/
|
||||
public boolean isDetectType() {
|
||||
return this.detectType;
|
||||
}
|
||||
|
||||
public void setDetectType(boolean detectType) {
|
||||
this.detectType = detectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Java version to use or {@code null} if it should not be customized.
|
||||
* @return the Java version or {@code null}
|
||||
*/
|
||||
public String getJavaVersion() {
|
||||
return this.javaVersion;
|
||||
}
|
||||
|
||||
public void setJavaVersion(String javaVersion) {
|
||||
this.javaVersion = javaVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* The programming language to use or {@code null} if it should not be customized.
|
||||
* @return the programming language or {@code null}
|
||||
*/
|
||||
public String getLanguage() {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Spring Boot version to use or {@code null} if it should not be customized.
|
||||
* @return the Spring Boot version or {@code null}
|
||||
*/
|
||||
public String getBootVersion() {
|
||||
return this.bootVersion;
|
||||
}
|
||||
|
||||
public void setBootVersion(String bootVersion) {
|
||||
this.bootVersion = bootVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* The identifiers of the dependencies to include in the project.
|
||||
* @return the dependency identifiers
|
||||
*/
|
||||
public List<String> getDependencies() {
|
||||
return this.dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the URI to use to generate a project represented by this request.
|
||||
* @param metadata the metadata that describes the service
|
||||
* @return the project generation URI
|
||||
*/
|
||||
URI generateUrl(InitializrServiceMetadata metadata) {
|
||||
try {
|
||||
URIBuilder builder = new URIBuilder(this.serviceUrl);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (builder.getPath() != null) {
|
||||
sb.append(builder.getPath());
|
||||
}
|
||||
|
||||
ProjectType projectType = determineProjectType(metadata);
|
||||
this.type = projectType.getId();
|
||||
sb.append(projectType.getAction());
|
||||
builder.setPath(sb.toString());
|
||||
|
||||
if (!this.dependencies.isEmpty()) {
|
||||
builder.setParameter("dependencies",
|
||||
StringUtils.collectionToCommaDelimitedString(this.dependencies));
|
||||
}
|
||||
|
||||
if (this.groupId != null) {
|
||||
builder.setParameter("groupId", this.groupId);
|
||||
}
|
||||
String resolvedArtifactId = resolveArtifactId();
|
||||
if (resolvedArtifactId != null) {
|
||||
builder.setParameter("artifactId", resolvedArtifactId);
|
||||
}
|
||||
if (this.version != null) {
|
||||
builder.setParameter("version", this.version);
|
||||
}
|
||||
if (this.name != null) {
|
||||
builder.setParameter("name", this.name);
|
||||
}
|
||||
if (this.description != null) {
|
||||
builder.setParameter("description", this.description);
|
||||
}
|
||||
if (this.packageName != null) {
|
||||
builder.setParameter("packageName", this.packageName);
|
||||
}
|
||||
if (this.type != null) {
|
||||
builder.setParameter("type", projectType.getId());
|
||||
}
|
||||
if (this.packaging != null) {
|
||||
builder.setParameter("packaging", this.packaging);
|
||||
}
|
||||
if (this.javaVersion != null) {
|
||||
builder.setParameter("javaVersion", this.javaVersion);
|
||||
}
|
||||
if (this.language != null) {
|
||||
builder.setParameter("language", this.language);
|
||||
}
|
||||
if (this.bootVersion != null) {
|
||||
builder.setParameter("bootVersion", this.bootVersion);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new ReportableException("Invalid service URL (" + e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
protected ProjectType determineProjectType(InitializrServiceMetadata metadata) {
|
||||
if (this.type != null) {
|
||||
ProjectType result = metadata.getProjectTypes().get(this.type);
|
||||
if (result == null) {
|
||||
throw new ReportableException(("No project type with id '" + this.type
|
||||
+ "' - check the service capabilities (--list)"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (isDetectType()) {
|
||||
Map<String, ProjectType> types = new HashMap<>(metadata.getProjectTypes());
|
||||
if (this.build != null) {
|
||||
filter(types, "build", this.build);
|
||||
}
|
||||
if (this.format != null) {
|
||||
filter(types, "format", this.format);
|
||||
}
|
||||
if (types.size() == 1) {
|
||||
return types.values().iterator().next();
|
||||
}
|
||||
else if (types.isEmpty()) {
|
||||
throw new ReportableException("No type found with build '" + this.build
|
||||
+ "' and format '" + this.format
|
||||
+ "' check the service capabilities (--list)");
|
||||
}
|
||||
else {
|
||||
throw new ReportableException("Multiple types found with build '"
|
||||
+ this.build + "' and format '" + this.format
|
||||
+ "' use --type with a more specific value " + types.keySet());
|
||||
}
|
||||
}
|
||||
else {
|
||||
ProjectType defaultType = metadata.getDefaultType();
|
||||
if (defaultType == null) {
|
||||
throw new ReportableException(
|
||||
("No project type is set and no default is defined. "
|
||||
+ "Check the service capabilities (--list)"));
|
||||
}
|
||||
return defaultType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the artifactId to use or {@code null} if it should not be customized.
|
||||
* @return the artifactId
|
||||
*/
|
||||
protected String resolveArtifactId() {
|
||||
if (this.artifactId != null) {
|
||||
return this.artifactId;
|
||||
}
|
||||
if (this.output != null) {
|
||||
int i = this.output.lastIndexOf('.');
|
||||
return (i == -1 ? this.output : this.output.substring(0, i));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void filter(Map<String, ProjectType> projects, String tag,
|
||||
String tagValue) {
|
||||
for (Iterator<Map.Entry<String, ProjectType>> it = projects.entrySet()
|
||||
.iterator(); it.hasNext();) {
|
||||
Map.Entry<String, ProjectType> entry = it.next();
|
||||
String value = entry.getValue().getTags().get(tag);
|
||||
if (!tagValue.equals(value)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.command.init;
|
||||
|
||||
import org.apache.http.entity.ContentType;
|
||||
|
||||
/**
|
||||
* Represent the response of a {@link ProjectGenerationRequest}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class ProjectGenerationResponse {
|
||||
|
||||
private final ContentType contentType;
|
||||
|
||||
private byte[] content;
|
||||
|
||||
private String fileName;
|
||||
|
||||
ProjectGenerationResponse(ContentType contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ContentType} of this instance.
|
||||
* @return the content type
|
||||
*/
|
||||
public ContentType getContentType() {
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The generated project archive or file.
|
||||
* @return the content
|
||||
*/
|
||||
public byte[] getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public void setContent(byte[] content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* The preferred file name to use to store the entity on disk or {@code null} if no
|
||||
* preferred value has been set.
|
||||
* @return the file name, or {@code null}
|
||||
*/
|
||||
public String getFileName() {
|
||||
return this.fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.command.init;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* Helper class used to generate the project.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ProjectGenerator {
|
||||
|
||||
private static final String ZIP_MIME_TYPE = "application/zip";
|
||||
|
||||
private final InitializrService initializrService;
|
||||
|
||||
ProjectGenerator(InitializrService initializrService) {
|
||||
this.initializrService = initializrService;
|
||||
}
|
||||
|
||||
public void generateProject(ProjectGenerationRequest request, boolean force)
|
||||
throws IOException {
|
||||
ProjectGenerationResponse response = this.initializrService.generate(request);
|
||||
String fileName = (request.getOutput() != null ? request.getOutput()
|
||||
: response.getFileName());
|
||||
if (shouldExtract(request, response)) {
|
||||
if (isZipArchive(response)) {
|
||||
extractProject(response, request.getOutput(), force);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
Log.info("Could not extract '" + response.getContentType() + "'");
|
||||
// Use value from the server since we can't extract it
|
||||
fileName = response.getFileName();
|
||||
}
|
||||
}
|
||||
if (fileName == null) {
|
||||
throw new ReportableException(
|
||||
"Could not save the project, the server did not set a preferred "
|
||||
+ "file name and no location was set. Specify the output location "
|
||||
+ "for the project.");
|
||||
}
|
||||
writeProject(response, fileName, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the project should be extracted.
|
||||
* @param request the generation request
|
||||
* @param response the generation response
|
||||
* @return if the project should be extracted
|
||||
*/
|
||||
private boolean shouldExtract(ProjectGenerationRequest request,
|
||||
ProjectGenerationResponse response) {
|
||||
if (request.isExtract()) {
|
||||
return true;
|
||||
}
|
||||
// explicit name hasn't been provided for an archive and there is no extension
|
||||
if (isZipArchive(response) && request.getOutput() != null
|
||||
&& !request.getOutput().contains(".")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isZipArchive(ProjectGenerationResponse entity) {
|
||||
if (entity.getContentType() != null) {
|
||||
try {
|
||||
return ZIP_MIME_TYPE.equals(entity.getContentType().getMimeType());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void extractProject(ProjectGenerationResponse entity, String output,
|
||||
boolean overwrite) throws IOException {
|
||||
File outputFolder = (output != null ? new File(output)
|
||||
: new File(System.getProperty("user.dir")));
|
||||
if (!outputFolder.exists()) {
|
||||
outputFolder.mkdirs();
|
||||
}
|
||||
try (ZipInputStream zipStream = new ZipInputStream(
|
||||
new ByteArrayInputStream(entity.getContent()))) {
|
||||
extractFromStream(zipStream, overwrite, outputFolder);
|
||||
fixExecutableFlag(outputFolder, "mvnw");
|
||||
fixExecutableFlag(outputFolder, "gradlew");
|
||||
Log.info("Project extracted to '" + outputFolder.getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private void extractFromStream(ZipInputStream zipStream, boolean overwrite,
|
||||
File outputFolder) throws IOException {
|
||||
ZipEntry entry = zipStream.getNextEntry();
|
||||
while (entry != null) {
|
||||
File file = new File(outputFolder, entry.getName());
|
||||
if (file.exists() && !overwrite) {
|
||||
throw new ReportableException((file.isDirectory() ? "Directory" : "File")
|
||||
+ " '" + file.getName()
|
||||
+ "' already exists. Use --force if you want to overwrite or "
|
||||
+ "specify an alternate location.");
|
||||
}
|
||||
if (!entry.isDirectory()) {
|
||||
FileCopyUtils.copy(StreamUtils.nonClosing(zipStream),
|
||||
new FileOutputStream(file));
|
||||
}
|
||||
else {
|
||||
file.mkdir();
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
entry = zipStream.getNextEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeProject(ProjectGenerationResponse entity, String output,
|
||||
boolean overwrite) throws IOException {
|
||||
File outputFile = new File(output);
|
||||
if (outputFile.exists()) {
|
||||
if (!overwrite) {
|
||||
throw new ReportableException("File '" + outputFile.getName()
|
||||
+ "' already exists. Use --force if you want to "
|
||||
+ "overwrite or specify an alternate location.");
|
||||
}
|
||||
if (!outputFile.delete()) {
|
||||
throw new ReportableException(
|
||||
"Failed to delete existing file " + outputFile.getPath());
|
||||
}
|
||||
}
|
||||
FileCopyUtils.copy(entity.getContent(), outputFile);
|
||||
Log.info("Content saved to '" + output + "'");
|
||||
}
|
||||
|
||||
private void fixExecutableFlag(File dir, String fileName) {
|
||||
File f = new File(dir, fileName);
|
||||
if (f.exists()) {
|
||||
f.setExecutable(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.command.init;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represent a project type that is supported by a service.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class ProjectType {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String action;
|
||||
|
||||
private final boolean defaultType;
|
||||
|
||||
private final Map<String, String> tags = new HashMap<>();
|
||||
|
||||
ProjectType(String id, String name, String action, boolean defaultType,
|
||||
Map<String, String> tags) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.action = action;
|
||||
this.defaultType = defaultType;
|
||||
if (tags != null) {
|
||||
this.tags.putAll(tags);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return this.action;
|
||||
}
|
||||
|
||||
public boolean isDefaultType() {
|
||||
return this.defaultType;
|
||||
}
|
||||
|
||||
public Map<String, String> getTags() {
|
||||
return Collections.unmodifiableMap(this.tags);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.init;
|
||||
|
||||
/**
|
||||
* Exception with a message that can be reported to the user.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ReportableException extends RuntimeException {
|
||||
|
||||
public ReportableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ReportableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.command.init;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* A helper class generating a report from the meta-data of a particular service.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class ServiceCapabilitiesReportGenerator {
|
||||
|
||||
private static final String NEW_LINE = System.getProperty("line.separator");
|
||||
|
||||
private final InitializrService initializrService;
|
||||
|
||||
/**
|
||||
* Creates an instance using the specified {@link InitializrService}.
|
||||
* @param initializrService the initializr service
|
||||
*/
|
||||
ServiceCapabilitiesReportGenerator(InitializrService initializrService) {
|
||||
this.initializrService = initializrService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a report for the specified service. The report contains the available
|
||||
* capabilities as advertised by the root endpoint.
|
||||
* @param url the url of the service
|
||||
* @return the report that describes the service
|
||||
* @throws IOException if the report cannot be generated
|
||||
*/
|
||||
public String generate(String url) throws IOException {
|
||||
Object content = this.initializrService.loadServiceCapabilities(url);
|
||||
if (content instanceof InitializrServiceMetadata) {
|
||||
return generateHelp(url, (InitializrServiceMetadata) content);
|
||||
}
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
private String generateHelp(String url, InitializrServiceMetadata metadata) {
|
||||
String header = "Capabilities of " + url;
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.append(repeat("=", header.length()) + NEW_LINE);
|
||||
report.append(header + NEW_LINE);
|
||||
report.append(repeat("=", header.length()) + NEW_LINE);
|
||||
report.append(NEW_LINE);
|
||||
reportAvailableDependencies(metadata, report);
|
||||
report.append(NEW_LINE);
|
||||
reportAvailableProjectTypes(metadata, report);
|
||||
report.append(NEW_LINE);
|
||||
reportDefaults(report, metadata);
|
||||
return report.toString();
|
||||
}
|
||||
|
||||
private void reportAvailableDependencies(InitializrServiceMetadata metadata,
|
||||
StringBuilder report) {
|
||||
report.append("Available dependencies:" + NEW_LINE);
|
||||
report.append("-----------------------" + NEW_LINE);
|
||||
List<Dependency> dependencies = getSortedDependencies(metadata);
|
||||
for (Dependency dependency : dependencies) {
|
||||
report.append(dependency.getId() + " - " + dependency.getName());
|
||||
if (dependency.getDescription() != null) {
|
||||
report.append(": " + dependency.getDescription());
|
||||
}
|
||||
report.append(NEW_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Dependency> getSortedDependencies(InitializrServiceMetadata metadata) {
|
||||
ArrayList<Dependency> dependencies = new ArrayList<>(metadata.getDependencies());
|
||||
dependencies.sort(Comparator.comparing(Dependency::getId));
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private void reportAvailableProjectTypes(InitializrServiceMetadata metadata,
|
||||
StringBuilder report) {
|
||||
report.append("Available project types:" + NEW_LINE);
|
||||
report.append("------------------------" + NEW_LINE);
|
||||
SortedSet<Entry<String, ProjectType>> entries = new TreeSet<>(
|
||||
Comparator.comparing(Entry::getKey));
|
||||
entries.addAll(metadata.getProjectTypes().entrySet());
|
||||
for (Entry<String, ProjectType> entry : entries) {
|
||||
ProjectType type = entry.getValue();
|
||||
report.append(entry.getKey() + " - " + type.getName());
|
||||
if (!type.getTags().isEmpty()) {
|
||||
reportTags(report, type);
|
||||
}
|
||||
if (type.isDefaultType()) {
|
||||
report.append(" (default)");
|
||||
}
|
||||
report.append(NEW_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
private void reportTags(StringBuilder report, ProjectType type) {
|
||||
Map<String, String> tags = type.getTags();
|
||||
Iterator<Map.Entry<String, String>> iterator = tags.entrySet().iterator();
|
||||
report.append(" [");
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, String> entry = iterator.next();
|
||||
report.append(entry.getKey() + ":" + entry.getValue());
|
||||
if (iterator.hasNext()) {
|
||||
report.append(", ");
|
||||
}
|
||||
}
|
||||
report.append("]");
|
||||
}
|
||||
|
||||
private void reportDefaults(StringBuilder report,
|
||||
InitializrServiceMetadata metadata) {
|
||||
report.append("Defaults:" + NEW_LINE);
|
||||
report.append("---------" + NEW_LINE);
|
||||
List<String> defaultsKeys = new ArrayList<>(metadata.getDefaults().keySet());
|
||||
Collections.sort(defaultsKeys);
|
||||
for (String defaultsKey : defaultsKeys) {
|
||||
String defaultsValue = metadata.getDefaults().get(defaultsKey);
|
||||
report.append(defaultsKey + ": " + defaultsValue + NEW_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
private static String repeat(String s, int count) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) {
|
||||
sb.append(s);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Resolve artifact identifiers (typically in the form {@literal group:artifact:version})
|
||||
* to {@link File}s.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface DependencyResolver {
|
||||
|
||||
/**
|
||||
* Resolves the given {@code artifactIdentifiers}, typically in the form
|
||||
* "group:artifact:version", and their dependencies.
|
||||
* @param artifactIdentifiers The artifacts to resolve
|
||||
* @return The {@code File}s for the resolved artifacts
|
||||
* @throws Exception if dependency resolution fails
|
||||
*/
|
||||
List<File> resolve(List<String> artifactIdentifiers) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
|
||||
/**
|
||||
* A {@code DependencyResolver} implemented using Groovy's {@code @Grab}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class GroovyGrabDependencyResolver implements DependencyResolver {
|
||||
|
||||
private final GroovyCompilerConfiguration configuration;
|
||||
|
||||
GroovyGrabDependencyResolver(GroovyCompilerConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<File> resolve(List<String> artifactIdentifiers)
|
||||
throws CompilationFailedException, IOException {
|
||||
GroovyCompiler groovyCompiler = new GroovyCompiler(this.configuration);
|
||||
List<File> artifactFiles = new ArrayList<>();
|
||||
if (!artifactIdentifiers.isEmpty()) {
|
||||
List<URL> initialUrls = getClassPathUrls(groovyCompiler);
|
||||
groovyCompiler.compile(createSources(artifactIdentifiers));
|
||||
List<URL> artifactUrls = getClassPathUrls(groovyCompiler);
|
||||
artifactUrls.removeAll(initialUrls);
|
||||
for (URL artifactUrl : artifactUrls) {
|
||||
artifactFiles.add(toFile(artifactUrl));
|
||||
}
|
||||
}
|
||||
return artifactFiles;
|
||||
}
|
||||
|
||||
private List<URL> getClassPathUrls(GroovyCompiler compiler) {
|
||||
return new ArrayList<>(Arrays.asList(compiler.getLoader().getURLs()));
|
||||
}
|
||||
|
||||
private String createSources(List<String> artifactIdentifiers) throws IOException {
|
||||
File file = File.createTempFile("SpringCLIDependency", ".groovy");
|
||||
file.deleteOnExit();
|
||||
try (OutputStreamWriter stream = new OutputStreamWriter(
|
||||
new FileOutputStream(file), "UTF-8")) {
|
||||
for (String artifactIdentifier : artifactIdentifiers) {
|
||||
stream.write("@Grab('" + artifactIdentifier + "')");
|
||||
}
|
||||
// Dummy class to force compiler to do grab
|
||||
stream.write("class Installer {}");
|
||||
}
|
||||
// Windows paths get tricky unless you work with URI
|
||||
return file.getAbsoluteFile().toURI().toString();
|
||||
}
|
||||
|
||||
private File toFile(URL url) {
|
||||
try {
|
||||
return new File(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
return new File(url.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.command.install;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Command} to install additional dependencies into the CLI.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class InstallCommand extends OptionParsingCommand {
|
||||
|
||||
public InstallCommand() {
|
||||
super("install", "Install dependencies to the lib/ext directory",
|
||||
new InstallOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <coordinates>";
|
||||
}
|
||||
|
||||
private static final class InstallOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
List<String> args = (List<String>) options.nonOptionArguments();
|
||||
Assert.notEmpty(args, "Please specify at least one "
|
||||
+ "dependency, in the form group:artifact:version, to install");
|
||||
try {
|
||||
new Installer(options, this).install(args);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
String message = ex.getMessage();
|
||||
Log.error(message != null ? message : ex.getClass().toString());
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* Shared logic for the {@link InstallCommand} and {@link UninstallCommand}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class Installer {
|
||||
|
||||
private final DependencyResolver dependencyResolver;
|
||||
|
||||
private final Properties installCounts;
|
||||
|
||||
Installer(OptionSet options, CompilerOptionHandler compilerOptionHandler)
|
||||
throws IOException {
|
||||
this(new GroovyGrabDependencyResolver(
|
||||
createCompilerConfiguration(options, compilerOptionHandler)));
|
||||
}
|
||||
|
||||
Installer(DependencyResolver resolver) throws IOException {
|
||||
this.dependencyResolver = resolver;
|
||||
this.installCounts = loadInstallCounts();
|
||||
}
|
||||
|
||||
private static GroovyCompilerConfiguration createCompilerConfiguration(
|
||||
OptionSet options, CompilerOptionHandler compilerOptionHandler) {
|
||||
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
|
||||
.createDefaultRepositoryConfiguration();
|
||||
return new OptionSetGroovyCompilerConfiguration(options, compilerOptionHandler,
|
||||
repositoryConfiguration) {
|
||||
@Override
|
||||
public boolean isAutoconfigure() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Properties loadInstallCounts() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
File installed = getInstalled();
|
||||
if (installed.exists()) {
|
||||
FileReader reader = new FileReader(installed);
|
||||
properties.load(reader);
|
||||
reader.close();
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void saveInstallCounts() throws IOException {
|
||||
try (FileWriter writer = new FileWriter(getInstalled())) {
|
||||
this.installCounts.store(writer, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void install(List<String> artifactIdentifiers) throws Exception {
|
||||
File extDirectory = getDefaultExtDirectory();
|
||||
extDirectory.mkdirs();
|
||||
Log.info("Installing into: " + extDirectory);
|
||||
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
|
||||
for (File artifactFile : artifactFiles) {
|
||||
int installCount = getInstallCount(artifactFile);
|
||||
if (installCount == 0) {
|
||||
FileCopyUtils.copy(artifactFile,
|
||||
new File(extDirectory, artifactFile.getName()));
|
||||
}
|
||||
setInstallCount(artifactFile, installCount + 1);
|
||||
}
|
||||
saveInstallCounts();
|
||||
}
|
||||
|
||||
private int getInstallCount(File file) {
|
||||
String countString = this.installCounts.getProperty(file.getName());
|
||||
if (countString == null) {
|
||||
return 0;
|
||||
}
|
||||
return Integer.valueOf(countString);
|
||||
}
|
||||
|
||||
private void setInstallCount(File file, int count) {
|
||||
if (count == 0) {
|
||||
this.installCounts.remove(file.getName());
|
||||
}
|
||||
else {
|
||||
this.installCounts.setProperty(file.getName(), Integer.toString(count));
|
||||
}
|
||||
}
|
||||
|
||||
public void uninstall(List<String> artifactIdentifiers) throws Exception {
|
||||
File extDirectory = getDefaultExtDirectory();
|
||||
Log.info("Uninstalling from: " + extDirectory);
|
||||
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
|
||||
for (File artifactFile : artifactFiles) {
|
||||
int installCount = getInstallCount(artifactFile);
|
||||
if (installCount <= 1) {
|
||||
new File(extDirectory, artifactFile.getName()).delete();
|
||||
}
|
||||
setInstallCount(artifactFile, installCount - 1);
|
||||
}
|
||||
saveInstallCounts();
|
||||
}
|
||||
|
||||
public void uninstallAll() throws Exception {
|
||||
File extDirectory = getDefaultExtDirectory();
|
||||
Log.info("Uninstalling from: " + extDirectory);
|
||||
for (String name : this.installCounts.stringPropertyNames()) {
|
||||
new File(extDirectory, name).delete();
|
||||
}
|
||||
this.installCounts.clear();
|
||||
saveInstallCounts();
|
||||
}
|
||||
|
||||
private File getDefaultExtDirectory() {
|
||||
String home = SystemPropertyUtils
|
||||
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
|
||||
File extDirectory = new File(new File(home, "lib"), "ext");
|
||||
if (!extDirectory.isDirectory()) {
|
||||
if (!extDirectory.mkdirs()) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to create ext directory " + extDirectory);
|
||||
}
|
||||
}
|
||||
return extDirectory;
|
||||
}
|
||||
|
||||
private File getInstalled() {
|
||||
return new File(getDefaultExtDirectory(), ".installed");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.command.install;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* {@link Command} to uninstall dependencies from the CLI's lib/ext directory.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class UninstallCommand extends OptionParsingCommand {
|
||||
|
||||
public UninstallCommand() {
|
||||
super("uninstall", "Uninstall dependencies from the lib/ext directory",
|
||||
new UninstallOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <coordinates>";
|
||||
}
|
||||
|
||||
private static class UninstallOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
private OptionSpec<Void> allOption;
|
||||
|
||||
@Override
|
||||
protected void doOptions() {
|
||||
this.allOption = option("all", "Uninstall all");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
List<String> args = (List<String>) options.nonOptionArguments();
|
||||
try {
|
||||
if (options.has(this.allOption)) {
|
||||
if (!args.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Please use --all without specifying any dependencies");
|
||||
}
|
||||
new Installer(options, this).uninstallAll();
|
||||
}
|
||||
if (args.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Please specify at least one dependency, in the form group:artifact:version, to uninstall");
|
||||
}
|
||||
new Installer(options, this).uninstall(args);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
String message = ex.getMessage();
|
||||
Log.error(message != null ? message : ex.getClass().toString());
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.command.options;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
/**
|
||||
* An {@link OptionHandler} for commands that result in the compilation of one or more
|
||||
* Groovy scripts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class CompilerOptionHandler extends OptionHandler {
|
||||
|
||||
private OptionSpec<Void> noGuessImportsOption;
|
||||
|
||||
private OptionSpec<Void> noGuessDependenciesOption;
|
||||
|
||||
private OptionSpec<Boolean> autoconfigureOption;
|
||||
|
||||
private OptionSpec<String> classpathOption;
|
||||
|
||||
@Override
|
||||
protected final void options() {
|
||||
this.noGuessImportsOption = option("no-guess-imports",
|
||||
"Do not attempt to guess imports");
|
||||
this.noGuessDependenciesOption = option("no-guess-dependencies",
|
||||
"Do not attempt to guess dependencies");
|
||||
this.autoconfigureOption = option("autoconfigure",
|
||||
"Add autoconfigure compiler transformations").withOptionalArg()
|
||||
.ofType(Boolean.class).defaultsTo(true);
|
||||
this.classpathOption = option(Arrays.asList("classpath", "cp"),
|
||||
"Additional classpath entries").withRequiredArg();
|
||||
doOptions();
|
||||
}
|
||||
|
||||
protected void doOptions() {
|
||||
}
|
||||
|
||||
public OptionSpec<Void> getNoGuessImportsOption() {
|
||||
return this.noGuessImportsOption;
|
||||
}
|
||||
|
||||
public OptionSpec<Void> getNoGuessDependenciesOption() {
|
||||
return this.noGuessDependenciesOption;
|
||||
}
|
||||
|
||||
public OptionSpec<String> getClasspathOption() {
|
||||
return this.classpathOption;
|
||||
}
|
||||
|
||||
public OptionSpec<Boolean> getAutoconfigureOption() {
|
||||
return this.autoconfigureOption;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.command.options;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import joptsimple.BuiltinHelpFormatter;
|
||||
import joptsimple.HelpFormatter;
|
||||
import joptsimple.OptionDescriptor;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpecBuilder;
|
||||
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
|
||||
/**
|
||||
* Delegate used by {@link OptionParsingCommand} to parse options and run the command.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @see OptionParsingCommand
|
||||
* @see #run(OptionSet)
|
||||
*/
|
||||
public class OptionHandler {
|
||||
|
||||
private OptionParser parser;
|
||||
|
||||
private String help;
|
||||
|
||||
private Collection<OptionHelp> optionHelp;
|
||||
|
||||
public OptionSpecBuilder option(String name, String description) {
|
||||
return getParser().accepts(name, description);
|
||||
}
|
||||
|
||||
public OptionSpecBuilder option(Collection<String> aliases, String description) {
|
||||
return getParser().acceptsAll(aliases, description);
|
||||
}
|
||||
|
||||
public OptionParser getParser() {
|
||||
if (this.parser == null) {
|
||||
this.parser = new OptionParser();
|
||||
options();
|
||||
}
|
||||
return this.parser;
|
||||
}
|
||||
|
||||
protected void options() {
|
||||
}
|
||||
|
||||
public final ExitStatus run(String... args) throws Exception {
|
||||
String[] argsToUse = args.clone();
|
||||
for (int i = 0; i < argsToUse.length; i++) {
|
||||
if ("-cp".equals(argsToUse[i])) {
|
||||
argsToUse[i] = "--cp";
|
||||
}
|
||||
}
|
||||
OptionSet options = getParser().parse(argsToUse);
|
||||
return run(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command using the specified parsed {@link OptionSet}.
|
||||
* @param options the parsed option set
|
||||
* @return an ExitStatus
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
public String getHelp() {
|
||||
if (this.help == null) {
|
||||
getParser().formatHelpWith(new BuiltinHelpFormatter(80, 2));
|
||||
OutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
getParser().printHelpOn(out);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return "Help not available";
|
||||
}
|
||||
this.help = out.toString().replace(" --cp ", " -cp ");
|
||||
}
|
||||
return this.help;
|
||||
}
|
||||
|
||||
public Collection<OptionHelp> getOptionsHelp() {
|
||||
if (this.optionHelp == null) {
|
||||
OptionHelpFormatter formatter = new OptionHelpFormatter();
|
||||
getParser().formatHelpWith(formatter);
|
||||
try {
|
||||
getParser().printHelpOn(new ByteArrayOutputStream());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore and provide no hints
|
||||
}
|
||||
this.optionHelp = formatter.getOptionHelp();
|
||||
}
|
||||
return this.optionHelp;
|
||||
}
|
||||
|
||||
private static class OptionHelpFormatter implements HelpFormatter {
|
||||
|
||||
private final List<OptionHelp> help = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String format(Map<String, ? extends OptionDescriptor> options) {
|
||||
Comparator<OptionDescriptor> comparator = Comparator.comparing(
|
||||
(optionDescriptor) -> optionDescriptor.options().iterator().next());
|
||||
Set<OptionDescriptor> sorted = new TreeSet<>(comparator);
|
||||
sorted.addAll(options.values());
|
||||
for (OptionDescriptor descriptor : sorted) {
|
||||
if (!descriptor.representsNonOptions()) {
|
||||
this.help.add(new OptionHelpAdapter(descriptor));
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public Collection<OptionHelp> getOptionHelp() {
|
||||
return Collections.unmodifiableList(this.help);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class OptionHelpAdapter implements OptionHelp {
|
||||
|
||||
private final LinkedHashSet<String> options;
|
||||
|
||||
private final String description;
|
||||
|
||||
OptionHelpAdapter(OptionDescriptor descriptor) {
|
||||
this.options = new LinkedHashSet<>();
|
||||
for (String option : descriptor.options()) {
|
||||
this.options.add((option.length() == 1 ? "-" : "--") + option);
|
||||
}
|
||||
if (this.options.contains("--cp")) {
|
||||
this.options.remove("--cp");
|
||||
this.options.add("-cp");
|
||||
}
|
||||
this.description = descriptor.description();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.command.options;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Help for a specific option.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public interface OptionHelp {
|
||||
|
||||
/**
|
||||
* Returns the set of options that are mutually synonymous.
|
||||
* @return the options
|
||||
*/
|
||||
Set<String> getOptions();
|
||||
|
||||
/**
|
||||
* Returns usage help for the option.
|
||||
* @return the usage help
|
||||
*/
|
||||
String getUsageHelp();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.command.options;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
|
||||
/**
|
||||
* Simple adapter class to present an {@link OptionSet} as a
|
||||
* {@link GroovyCompilerConfiguration}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class OptionSetGroovyCompilerConfiguration implements GroovyCompilerConfiguration {
|
||||
|
||||
private final OptionSet options;
|
||||
|
||||
private final CompilerOptionHandler optionHandler;
|
||||
|
||||
private final List<RepositoryConfiguration> repositoryConfiguration;
|
||||
|
||||
protected OptionSetGroovyCompilerConfiguration(OptionSet optionSet,
|
||||
CompilerOptionHandler compilerOptionHandler) {
|
||||
this(optionSet, compilerOptionHandler,
|
||||
RepositoryConfigurationFactory.createDefaultRepositoryConfiguration());
|
||||
}
|
||||
|
||||
public OptionSetGroovyCompilerConfiguration(OptionSet optionSet,
|
||||
CompilerOptionHandler compilerOptionHandler,
|
||||
List<RepositoryConfiguration> repositoryConfiguration) {
|
||||
this.options = optionSet;
|
||||
this.optionHandler = compilerOptionHandler;
|
||||
this.repositoryConfiguration = repositoryConfiguration;
|
||||
}
|
||||
|
||||
protected OptionSet getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroovyCompilerScope getScope() {
|
||||
return GroovyCompilerScope.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuessImports() {
|
||||
return !this.options.has(this.optionHandler.getNoGuessImportsOption());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuessDependencies() {
|
||||
return !this.options.has(this.optionHandler.getNoGuessDependenciesOption());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoconfigure() {
|
||||
return this.optionHandler.getAutoconfigureOption().value(this.options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClasspath() {
|
||||
OptionSpec<String> classpathOption = this.optionHandler.getClasspathOption();
|
||||
if (this.options.has(classpathOption)) {
|
||||
return this.options.valueOf(classpathOption).split(":");
|
||||
}
|
||||
return DEFAULT_CLASSPATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RepositoryConfiguration> getRepositoryConfiguration() {
|
||||
return this.repositoryConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isQuiet() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.command.options;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import org.springframework.boot.cli.util.ResourceUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Extract source file options (anything following '--' in an {@link OptionSet}).
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Greg Turnquist
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class SourceOptions {
|
||||
|
||||
private final List<String> sources;
|
||||
|
||||
private final List<?> args;
|
||||
|
||||
/**
|
||||
* Create a new {@link SourceOptions} instance.
|
||||
* @param options the source option set
|
||||
*/
|
||||
public SourceOptions(OptionSet options) {
|
||||
this(options, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SourceOptions} instance.
|
||||
* @param arguments the source arguments
|
||||
*/
|
||||
public SourceOptions(List<?> arguments) {
|
||||
this(arguments, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SourceOptions} instance. If it is an error to pass options that
|
||||
* specify non-existent sources, but the default paths are allowed not to exist (the
|
||||
* paths are tested before use). If default paths are provided and the option set
|
||||
* contains no source file arguments it is not an error even if none of the default
|
||||
* paths exist).
|
||||
* @param optionSet the source option set
|
||||
* @param classLoader an optional classloader used to try and load files that are not
|
||||
* found in the local filesystem
|
||||
*/
|
||||
public SourceOptions(OptionSet optionSet, ClassLoader classLoader) {
|
||||
this(optionSet.nonOptionArguments(), classLoader);
|
||||
}
|
||||
|
||||
private SourceOptions(List<?> nonOptionArguments, ClassLoader classLoader) {
|
||||
List<String> sources = new ArrayList<>();
|
||||
int sourceArgCount = 0;
|
||||
for (Object option : nonOptionArguments) {
|
||||
if (option instanceof String) {
|
||||
String filename = (String) option;
|
||||
if ("--".equals(filename)) {
|
||||
break;
|
||||
}
|
||||
List<String> urls = new ArrayList<>();
|
||||
File fileCandidate = new File(filename);
|
||||
if (fileCandidate.isFile()) {
|
||||
urls.add(fileCandidate.getAbsoluteFile().toURI().toString());
|
||||
}
|
||||
else if (!isAbsoluteWindowsFile(fileCandidate)) {
|
||||
urls.addAll(ResourceUtils.getUrls(filename, classLoader));
|
||||
}
|
||||
for (String url : urls) {
|
||||
if (isSource(url)) {
|
||||
sources.add(url);
|
||||
}
|
||||
}
|
||||
if (isSource(filename)) {
|
||||
if (urls.isEmpty()) {
|
||||
throw new IllegalArgumentException("Can't find " + filename);
|
||||
}
|
||||
else {
|
||||
sourceArgCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.args = Collections.unmodifiableList(
|
||||
nonOptionArguments.subList(sourceArgCount, nonOptionArguments.size()));
|
||||
Assert.isTrue(!sources.isEmpty(), "Please specify at least one file");
|
||||
this.sources = Collections.unmodifiableList(sources);
|
||||
}
|
||||
|
||||
private boolean isAbsoluteWindowsFile(File file) {
|
||||
return isWindows() && file.isAbsolute();
|
||||
}
|
||||
|
||||
private boolean isWindows() {
|
||||
return File.separatorChar == '\\';
|
||||
}
|
||||
|
||||
private boolean isSource(String name) {
|
||||
return name.endsWith(".java") || name.endsWith(".groovy");
|
||||
}
|
||||
|
||||
public List<?> getArgs() {
|
||||
return this.args;
|
||||
}
|
||||
|
||||
public String[] getArgsArray() {
|
||||
return this.args.toArray(new String[this.args.size()]);
|
||||
}
|
||||
|
||||
public List<String> getSources() {
|
||||
return this.sources;
|
||||
}
|
||||
|
||||
public String[] getSourcesArray() {
|
||||
return this.sources.toArray(new String[this.sources.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.command.run;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.command.options.SourceOptions;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
|
||||
/**
|
||||
* {@link Command} to 'run' a groovy script or scripts.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @see SpringApplicationRunner
|
||||
*/
|
||||
public class RunCommand extends OptionParsingCommand {
|
||||
|
||||
public RunCommand() {
|
||||
super("run", "Run a spring groovy script", new RunOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <files> [--] [args]";
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (this.getHandler() != null) {
|
||||
((RunOptionHandler) this.getHandler()).stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RunOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private OptionSpec<Void> watchOption;
|
||||
|
||||
private OptionSpec<Void> verboseOption;
|
||||
|
||||
private OptionSpec<Void> quietOption;
|
||||
|
||||
private SpringApplicationRunner runner;
|
||||
|
||||
@Override
|
||||
protected void doOptions() {
|
||||
this.watchOption = option("watch", "Watch the specified file for changes");
|
||||
this.verboseOption = option(Arrays.asList("verbose", "v"),
|
||||
"Verbose logging of dependency resolution");
|
||||
this.quietOption = option(Arrays.asList("quiet", "q"), "Quiet logging");
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
synchronized (this.monitor) {
|
||||
if (this.runner != null) {
|
||||
this.runner.stop();
|
||||
}
|
||||
this.runner = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized ExitStatus run(OptionSet options) throws Exception {
|
||||
synchronized (this.monitor) {
|
||||
if (this.runner != null) {
|
||||
throw new RuntimeException(
|
||||
"Already running. Please stop the current application before running another (use the 'stop' command).");
|
||||
}
|
||||
|
||||
SourceOptions sourceOptions = new SourceOptions(options);
|
||||
|
||||
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
|
||||
.createDefaultRepositoryConfiguration();
|
||||
repositoryConfiguration.add(0, new RepositoryConfiguration("local",
|
||||
new File("repository").toURI(), true));
|
||||
|
||||
SpringApplicationRunnerConfiguration configuration = new SpringApplicationRunnerConfigurationAdapter(
|
||||
options, this, repositoryConfiguration);
|
||||
|
||||
this.runner = new SpringApplicationRunner(configuration,
|
||||
sourceOptions.getSourcesArray(), sourceOptions.getArgsArray());
|
||||
this.runner.compileAndRun();
|
||||
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple adapter class to present the {@link OptionSet} as a
|
||||
* {@link SpringApplicationRunnerConfiguration}.
|
||||
*/
|
||||
private class SpringApplicationRunnerConfigurationAdapter
|
||||
extends OptionSetGroovyCompilerConfiguration
|
||||
implements SpringApplicationRunnerConfiguration {
|
||||
|
||||
SpringApplicationRunnerConfigurationAdapter(OptionSet options,
|
||||
CompilerOptionHandler optionHandler,
|
||||
List<RepositoryConfiguration> repositoryConfiguration) {
|
||||
super(options, optionHandler, repositoryConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroovyCompilerScope getScope() {
|
||||
return GroovyCompilerScope.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWatchForFileChanges() {
|
||||
return getOptions().has(RunOptionHandler.this.watchOption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level getLogLevel() {
|
||||
if (isQuiet()) {
|
||||
return Level.OFF;
|
||||
}
|
||||
if (getOptions().has(RunOptionHandler.this.verboseOption)) {
|
||||
return Level.FINEST;
|
||||
}
|
||||
return Level.INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isQuiet() {
|
||||
return getOptions().has(RunOptionHandler.this.quietOption);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* 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.command.run;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.springframework.boot.cli.app.SpringApplicationLauncher;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Compiles Groovy code running the resulting classes using a {@code SpringApplication}.
|
||||
* Takes care of threading and class-loading issues and can optionally monitor sources for
|
||||
* changes.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class SpringApplicationRunner {
|
||||
|
||||
private static int watcherCounter = 0;
|
||||
|
||||
private static int runnerCounter = 0;
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final SpringApplicationRunnerConfiguration configuration;
|
||||
|
||||
private final String[] sources;
|
||||
|
||||
private final String[] args;
|
||||
|
||||
private final GroovyCompiler compiler;
|
||||
|
||||
private RunThread runThread;
|
||||
|
||||
private FileWatchThread fileWatchThread;
|
||||
|
||||
/**
|
||||
* Create a new {@link SpringApplicationRunner} instance.
|
||||
* @param configuration the configuration
|
||||
* @param sources the files to compile/watch
|
||||
* @param args input arguments
|
||||
*/
|
||||
SpringApplicationRunner(final SpringApplicationRunnerConfiguration configuration,
|
||||
String[] sources, String... args) {
|
||||
this.configuration = configuration;
|
||||
this.sources = sources.clone();
|
||||
this.args = args.clone();
|
||||
this.compiler = new GroovyCompiler(configuration);
|
||||
int level = configuration.getLogLevel().intValue();
|
||||
if (level <= Level.FINER.intValue()) {
|
||||
System.setProperty(
|
||||
"org.springframework.boot.cli.compiler.grape.ProgressReporter",
|
||||
"detail");
|
||||
System.setProperty("trace", "true");
|
||||
}
|
||||
else if (level <= Level.FINE.intValue()) {
|
||||
System.setProperty("debug", "true");
|
||||
}
|
||||
else if (level == Level.OFF.intValue()) {
|
||||
System.setProperty("spring.main.banner-mode", "OFF");
|
||||
System.setProperty("logging.level.ROOT", "OFF");
|
||||
System.setProperty(
|
||||
"org.springframework.boot.cli.compiler.grape.ProgressReporter",
|
||||
"none");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and run the application.
|
||||
* @throws Exception on error
|
||||
*/
|
||||
public void compileAndRun() throws Exception {
|
||||
synchronized (this.monitor) {
|
||||
try {
|
||||
stop();
|
||||
Class<?>[] compiledSources = compile();
|
||||
monitorForChanges();
|
||||
// Run in new thread to ensure that the context classloader is setup
|
||||
this.runThread = new RunThread(compiledSources);
|
||||
this.runThread.start();
|
||||
this.runThread.join();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (this.fileWatchThread == null) {
|
||||
throw ex;
|
||||
}
|
||||
else {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
synchronized (this.monitor) {
|
||||
if (this.runThread != null) {
|
||||
this.runThread.shutdown();
|
||||
this.runThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?>[] compile() throws IOException {
|
||||
Class<?>[] compiledSources = this.compiler.compile(this.sources);
|
||||
if (compiledSources.length == 0) {
|
||||
throw new RuntimeException(
|
||||
"No classes found in '" + Arrays.toString(this.sources) + "'");
|
||||
}
|
||||
return compiledSources;
|
||||
}
|
||||
|
||||
private void monitorForChanges() {
|
||||
if (this.fileWatchThread == null && this.configuration.isWatchForFileChanges()) {
|
||||
this.fileWatchThread = new FileWatchThread();
|
||||
this.fileWatchThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread used to launch the Spring Application with the correct context classloader.
|
||||
*/
|
||||
private class RunThread extends Thread {
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final Class<?>[] compiledSources;
|
||||
|
||||
private Object applicationContext;
|
||||
|
||||
/**
|
||||
* Create a new {@link RunThread} instance.
|
||||
* @param compiledSources the sources to launch
|
||||
*/
|
||||
RunThread(Class<?>... compiledSources) {
|
||||
super("runner-" + (runnerCounter++));
|
||||
this.compiledSources = compiledSources;
|
||||
if (compiledSources.length != 0) {
|
||||
setContextClassLoader(compiledSources[0].getClassLoader());
|
||||
}
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (this.monitor) {
|
||||
try {
|
||||
this.applicationContext = new SpringApplicationLauncher(
|
||||
getContextClassLoader()).launch(this.compiledSources,
|
||||
SpringApplicationRunner.this.args);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the thread, closing any previously opened application context.
|
||||
*/
|
||||
public void shutdown() {
|
||||
synchronized (this.monitor) {
|
||||
if (this.applicationContext != null) {
|
||||
try {
|
||||
Method method = this.applicationContext.getClass()
|
||||
.getMethod("close");
|
||||
method.invoke(this.applicationContext);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// Not an application context that we can close
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
this.applicationContext = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread to watch for file changes and trigger recompile/reload.
|
||||
*/
|
||||
private class FileWatchThread extends Thread {
|
||||
|
||||
private long previous;
|
||||
|
||||
private List<File> sources;
|
||||
|
||||
FileWatchThread() {
|
||||
super("filewatcher-" + (watcherCounter++));
|
||||
this.previous = 0;
|
||||
this.sources = getSourceFiles();
|
||||
for (File file : this.sources) {
|
||||
if (file.exists()) {
|
||||
long current = file.lastModified();
|
||||
if (current > this.previous) {
|
||||
this.previous = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
setDaemon(false);
|
||||
}
|
||||
|
||||
private List<File> getSourceFiles() {
|
||||
List<File> sources = new ArrayList<>();
|
||||
for (String source : SpringApplicationRunner.this.sources) {
|
||||
List<String> paths = ResourceUtils.getUrls(source,
|
||||
SpringApplicationRunner.this.compiler.getLoader());
|
||||
for (String path : paths) {
|
||||
try {
|
||||
URL url = new URL(path);
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
sources.add(new File(url.getFile()));
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
|
||||
for (File file : this.sources) {
|
||||
if (file.exists()) {
|
||||
long current = file.lastModified();
|
||||
if (this.previous < current) {
|
||||
this.previous = current;
|
||||
compileAndRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Swallow, will be reported by compileAndRun
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.command.run;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
|
||||
/**
|
||||
* Configuration for the {@link SpringApplicationRunner}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public interface SpringApplicationRunnerConfiguration
|
||||
extends GroovyCompilerConfiguration {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the source file should be monitored for changes and
|
||||
* automatically recompiled.
|
||||
* @return {@code true} if file watching should be performed, otherwise {@code false}
|
||||
*/
|
||||
boolean isWatchForFileChanges();
|
||||
|
||||
/**
|
||||
* Returns the logging level to use.
|
||||
* @return the logging level
|
||||
*/
|
||||
Level getLogLevel();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.command.shell;
|
||||
|
||||
import jline.Terminal;
|
||||
import org.fusesource.jansi.Ansi;
|
||||
import org.fusesource.jansi.AnsiRenderer.Code;
|
||||
|
||||
/**
|
||||
* Simple utility class to build an ANSI string when supported by the {@link Terminal}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AnsiString {
|
||||
|
||||
private final Terminal terminal;
|
||||
|
||||
private final StringBuilder value = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Create a new {@link AnsiString} for the given {@link Terminal}.
|
||||
* @param terminal the terminal used to test if {@link Terminal#isAnsiSupported() ANSI
|
||||
* is supported}.
|
||||
*/
|
||||
AnsiString(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append text with the given ANSI codes.
|
||||
* @param text the text to append
|
||||
* @param codes the ANSI codes
|
||||
* @return this string
|
||||
*/
|
||||
AnsiString append(String text, Code... codes) {
|
||||
if (codes.length == 0 || !isAnsiSupported()) {
|
||||
this.value.append(text);
|
||||
return this;
|
||||
}
|
||||
Ansi ansi = Ansi.ansi();
|
||||
for (Code code : codes) {
|
||||
ansi = applyCode(ansi, code);
|
||||
}
|
||||
this.value.append(ansi.a(text).reset().toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
private Ansi applyCode(Ansi ansi, Code code) {
|
||||
if (code.isColor()) {
|
||||
if (code.isBackground()) {
|
||||
return ansi.bg(code.getColor());
|
||||
}
|
||||
return ansi.fg(code.getColor());
|
||||
}
|
||||
return ansi.a(code.getAttribute());
|
||||
}
|
||||
|
||||
private boolean isAnsiSupported() {
|
||||
return this.terminal.isAnsiSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.shell;
|
||||
|
||||
import jline.console.ConsoleReader;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
|
||||
/**
|
||||
* Clear the {@link Shell} screen.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ClearCommand extends AbstractCommand {
|
||||
|
||||
private final ConsoleReader consoleReader;
|
||||
|
||||
ClearCommand(ConsoleReader consoleReader) {
|
||||
super("clear", "Clear the screen");
|
||||
this.consoleReader = consoleReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) throws Exception {
|
||||
this.consoleReader.setPrompt("");
|
||||
this.consoleReader.clearScreen();
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.command.shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jline.console.ConsoleReader;
|
||||
import jline.console.completer.AggregateCompleter;
|
||||
import jline.console.completer.ArgumentCompleter;
|
||||
import jline.console.completer.ArgumentCompleter.ArgumentDelimiter;
|
||||
import jline.console.completer.Completer;
|
||||
import jline.console.completer.FileNameCompleter;
|
||||
import jline.console.completer.StringsCompleter;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.options.OptionHelp;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* JLine {@link Completer} for Spring Boot {@link Command}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class CommandCompleter extends StringsCompleter {
|
||||
|
||||
private final Map<String, Completer> commandCompleters = new HashMap<>();
|
||||
|
||||
private final List<Command> commands = new ArrayList<>();
|
||||
|
||||
private final ConsoleReader console;
|
||||
|
||||
public CommandCompleter(ConsoleReader consoleReader,
|
||||
ArgumentDelimiter argumentDelimiter, Iterable<Command> commands) {
|
||||
this.console = consoleReader;
|
||||
List<String> names = new ArrayList<>();
|
||||
for (Command command : commands) {
|
||||
this.commands.add(command);
|
||||
names.add(command.getName());
|
||||
List<String> options = new ArrayList<>();
|
||||
for (OptionHelp optionHelp : command.getOptionsHelp()) {
|
||||
options.addAll(optionHelp.getOptions());
|
||||
}
|
||||
AggregateCompleter argumentCompleters = new AggregateCompleter(
|
||||
new StringsCompleter(options), new FileNameCompleter());
|
||||
ArgumentCompleter argumentCompleter = new ArgumentCompleter(argumentDelimiter,
|
||||
argumentCompleters);
|
||||
argumentCompleter.setStrict(false);
|
||||
this.commandCompleters.put(command.getName(), argumentCompleter);
|
||||
}
|
||||
getStrings().addAll(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
int completionIndex = super.complete(buffer, cursor, candidates);
|
||||
int spaceIndex = buffer.indexOf(' ');
|
||||
String commandName = (spaceIndex == -1) ? "" : buffer.substring(0, spaceIndex);
|
||||
if (!"".equals(commandName.trim())) {
|
||||
for (Command command : this.commands) {
|
||||
if (command.getName().equals(commandName)) {
|
||||
if (cursor == buffer.length() && buffer.endsWith(" ")) {
|
||||
printUsage(command);
|
||||
break;
|
||||
}
|
||||
Completer completer = this.commandCompleters.get(command.getName());
|
||||
if (completer != null) {
|
||||
completionIndex = completer.complete(buffer, cursor, candidates);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return completionIndex;
|
||||
}
|
||||
|
||||
private void printUsage(Command command) {
|
||||
try {
|
||||
int maxOptionsLength = 0;
|
||||
List<OptionHelpLine> optionHelpLines = new ArrayList<>();
|
||||
for (OptionHelp optionHelp : command.getOptionsHelp()) {
|
||||
OptionHelpLine optionHelpLine = new OptionHelpLine(optionHelp);
|
||||
optionHelpLines.add(optionHelpLine);
|
||||
maxOptionsLength = Math.max(maxOptionsLength,
|
||||
optionHelpLine.getOptions().length());
|
||||
}
|
||||
|
||||
this.console.println();
|
||||
this.console.println("Usage:");
|
||||
this.console.println(command.getName() + " " + command.getUsageHelp());
|
||||
for (OptionHelpLine optionHelpLine : optionHelpLines) {
|
||||
this.console.println(String.format("\t%" + maxOptionsLength + "s: %s",
|
||||
optionHelpLine.getOptions(), optionHelpLine.getUsage()));
|
||||
}
|
||||
this.console.drawLine();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
Log.error(ex.getMessage() + " (" + ex.getClass().getName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulated options and usage help.
|
||||
*/
|
||||
private static class OptionHelpLine {
|
||||
|
||||
private final String options;
|
||||
|
||||
private final String usage;
|
||||
|
||||
OptionHelpLine(OptionHelp optionHelp) {
|
||||
StringBuilder options = new StringBuilder();
|
||||
for (String option : optionHelp.getOptions()) {
|
||||
options.append(options.length() == 0 ? "" : ", ");
|
||||
options.append(option);
|
||||
}
|
||||
this.options = options.toString();
|
||||
this.usage = optionHelp.getUsageHelp();
|
||||
}
|
||||
|
||||
public String getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public String getUsage() {
|
||||
return this.usage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.shell;
|
||||
|
||||
import jline.console.completer.ArgumentCompleter.ArgumentList;
|
||||
import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter;
|
||||
|
||||
/**
|
||||
* Escape aware variant of {@link WhitespaceArgumentDelimiter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class EscapeAwareWhiteSpaceArgumentDelimiter extends WhitespaceArgumentDelimiter {
|
||||
|
||||
@Override
|
||||
public boolean isEscaped(CharSequence buffer, int pos) {
|
||||
return (isEscapeChar(buffer, pos - 1));
|
||||
}
|
||||
|
||||
private boolean isEscapeChar(CharSequence buffer, int pos) {
|
||||
if (pos >= 0) {
|
||||
for (char c : getEscapeChars()) {
|
||||
if (buffer.charAt(pos) == c) {
|
||||
return !isEscapeChar(buffer, pos - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isQuoted(CharSequence buffer, int pos) {
|
||||
int closingQuote = searchBackwards(buffer, pos - 1, getQuoteChars());
|
||||
if (closingQuote == -1) {
|
||||
return false;
|
||||
}
|
||||
int openingQuote = searchBackwards(buffer, closingQuote - 1,
|
||||
buffer.charAt(closingQuote));
|
||||
if (openingQuote == -1) {
|
||||
return true;
|
||||
}
|
||||
return isQuoted(buffer, openingQuote - 1);
|
||||
}
|
||||
|
||||
private int searchBackwards(CharSequence buffer, int pos, char... chars) {
|
||||
while (pos >= 0) {
|
||||
for (char c : chars) {
|
||||
if (buffer.charAt(pos) == c && !isEscaped(buffer, pos)) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String[] parseArguments(String line) {
|
||||
ArgumentList delimit = delimit(line, 0);
|
||||
return cleanArguments(delimit.getArguments());
|
||||
}
|
||||
|
||||
private String[] cleanArguments(String[] arguments) {
|
||||
String[] cleanArguments = new String[arguments.length];
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
cleanArguments[i] = cleanArgument(arguments[i]);
|
||||
}
|
||||
return cleanArguments;
|
||||
}
|
||||
|
||||
private String cleanArgument(String argument) {
|
||||
for (char c : getQuoteChars()) {
|
||||
String quote = String.valueOf(c);
|
||||
if (argument.startsWith(quote) && argument.endsWith(quote)) {
|
||||
return replaceEscapes(argument.substring(1, argument.length() - 1));
|
||||
}
|
||||
}
|
||||
return replaceEscapes(argument);
|
||||
}
|
||||
|
||||
private String replaceEscapes(String string) {
|
||||
string = string.replace("\\ ", " ");
|
||||
string = string.replace("\\\\", "\\");
|
||||
string = string.replace("\\t", "\t");
|
||||
string = string.replace("\\\"", "\"");
|
||||
string = string.replace("\\\'", "\'");
|
||||
return string;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.shell;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
|
||||
/**
|
||||
* {@link Command} to quit the {@link Shell}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ExitCommand extends AbstractCommand {
|
||||
|
||||
ExitCommand() {
|
||||
super("exit", "Quit the embedded shell");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) throws Exception {
|
||||
throw new ShellExitException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.command.shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.options.OptionHelp;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.loader.tools.JavaExecutable;
|
||||
|
||||
/**
|
||||
* Decorate an existing command to run it by forking the current java process.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ForkProcessCommand extends RunProcessCommand {
|
||||
|
||||
private static final String MAIN_CLASS = "org.springframework.boot.loader.JarLauncher";
|
||||
|
||||
private final Command command;
|
||||
|
||||
ForkProcessCommand(Command command) {
|
||||
super(new JavaExecutable().toString());
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.command.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.command.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return this.command.getUsageHelp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelp() {
|
||||
return this.command.getHelp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OptionHelp> getOptionsHelp() {
|
||||
return this.command.getOptionsHelp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) throws Exception {
|
||||
List<String> fullArgs = new ArrayList<>();
|
||||
fullArgs.add("-cp");
|
||||
fullArgs.add(System.getProperty("java.class.path"));
|
||||
fullArgs.add(MAIN_CLASS);
|
||||
fullArgs.add(this.command.getName());
|
||||
fullArgs.addAll(Arrays.asList(args));
|
||||
run(fullArgs);
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.shell;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
|
||||
/**
|
||||
* {@link Command} to change the {@link Shell} prompt.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class PromptCommand extends AbstractCommand {
|
||||
|
||||
private final ShellPrompts prompts;
|
||||
|
||||
public PromptCommand(ShellPrompts shellPrompts) {
|
||||
super("prompt", "Change the prompt used with the current 'shell' command. "
|
||||
+ "Execute with no arguments to return to the previous value.");
|
||||
this.prompts = shellPrompts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... strings) throws Exception {
|
||||
if (strings.length > 0) {
|
||||
for (String string : strings) {
|
||||
this.prompts.pushPrompt(string + " ");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.prompts.popPrompt();
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.command.shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.loader.tools.RunProcess;
|
||||
|
||||
/**
|
||||
* Special {@link Command} used to run a process from the shell. NOTE: this command is not
|
||||
* directly installed into the shell.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class RunProcessCommand extends AbstractCommand {
|
||||
|
||||
private final String[] command;
|
||||
|
||||
private volatile RunProcess process;
|
||||
|
||||
RunProcessCommand(String... command) {
|
||||
super(null, null);
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) throws Exception {
|
||||
return run(Arrays.asList(args));
|
||||
}
|
||||
|
||||
protected ExitStatus run(Collection<String> args) throws IOException {
|
||||
this.process = new RunProcess(this.command);
|
||||
int code = this.process.run(true, args.toArray(new String[args.size()]));
|
||||
if (code == 0) {
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
else {
|
||||
return new ExitStatus(code, "EXTERNAL_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleSigInt() {
|
||||
return this.process.handleSigInt();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.command.shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
import jline.console.ConsoleReader;
|
||||
import jline.console.completer.CandidateListCompletionHandler;
|
||||
import org.fusesource.jansi.AnsiRenderer.Code;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.CommandFactory;
|
||||
import org.springframework.boot.cli.command.CommandRunner;
|
||||
import org.springframework.boot.cli.command.core.HelpCommand;
|
||||
import org.springframework.boot.cli.command.core.VersionCommand;
|
||||
import org.springframework.boot.loader.tools.SignalUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A shell for Spring Boot. Drops the user into an event loop (REPL) where command line
|
||||
* completion and history are available without relying on OS shell features.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class Shell {
|
||||
|
||||
private static final Set<Class<?>> NON_FORKED_COMMANDS;
|
||||
|
||||
static {
|
||||
Set<Class<?>> nonForked = new HashSet<>();
|
||||
nonForked.add(VersionCommand.class);
|
||||
NON_FORKED_COMMANDS = Collections.unmodifiableSet(nonForked);
|
||||
}
|
||||
|
||||
private final ShellCommandRunner commandRunner;
|
||||
|
||||
private final ConsoleReader consoleReader;
|
||||
|
||||
private final EscapeAwareWhiteSpaceArgumentDelimiter argumentDelimiter = new EscapeAwareWhiteSpaceArgumentDelimiter();
|
||||
|
||||
private final ShellPrompts prompts = new ShellPrompts();
|
||||
|
||||
/**
|
||||
* Create a new {@link Shell} instance.
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
Shell() throws IOException {
|
||||
attachSignalHandler();
|
||||
this.consoleReader = new ConsoleReader();
|
||||
this.commandRunner = createCommandRunner();
|
||||
initializeConsoleReader();
|
||||
}
|
||||
|
||||
private ShellCommandRunner createCommandRunner() {
|
||||
ShellCommandRunner runner = new ShellCommandRunner();
|
||||
runner.addCommand(new HelpCommand(runner));
|
||||
runner.addCommands(getCommands());
|
||||
runner.addAliases("exit", "quit");
|
||||
runner.addAliases("help", "?");
|
||||
runner.addAliases("clear", "cls");
|
||||
return runner;
|
||||
}
|
||||
|
||||
private Iterable<Command> getCommands() {
|
||||
List<Command> commands = new ArrayList<>();
|
||||
ServiceLoader<CommandFactory> factories = ServiceLoader.load(CommandFactory.class,
|
||||
getClass().getClassLoader());
|
||||
for (CommandFactory factory : factories) {
|
||||
for (Command command : factory.getCommands()) {
|
||||
commands.add(convertToForkCommand(command));
|
||||
}
|
||||
}
|
||||
commands.add(new PromptCommand(this.prompts));
|
||||
commands.add(new ClearCommand(this.consoleReader));
|
||||
commands.add(new ExitCommand());
|
||||
return commands;
|
||||
}
|
||||
|
||||
private Command convertToForkCommand(Command command) {
|
||||
for (Class<?> nonForked : NON_FORKED_COMMANDS) {
|
||||
if (nonForked.isInstance(command)) {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
return new ForkProcessCommand(command);
|
||||
}
|
||||
|
||||
private void initializeConsoleReader() {
|
||||
this.consoleReader.setHistoryEnabled(true);
|
||||
this.consoleReader.setBellEnabled(false);
|
||||
this.consoleReader.setExpandEvents(false);
|
||||
this.consoleReader.addCompleter(new CommandCompleter(this.consoleReader,
|
||||
this.argumentDelimiter, this.commandRunner));
|
||||
this.consoleReader.setCompletionHandler(new CandidateListCompletionHandler());
|
||||
}
|
||||
|
||||
private void attachSignalHandler() {
|
||||
SignalUtils.attachSignalHandler(this::handleSigInt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the shell until the user exists.
|
||||
* @throws Exception on error
|
||||
*/
|
||||
public void run() throws Exception {
|
||||
printBanner();
|
||||
try {
|
||||
runInputLoop();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (!(ex instanceof ShellExitException)) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printBanner() {
|
||||
String version = getClass().getPackage().getImplementationVersion();
|
||||
version = (version == null ? "" : " (v" + version + ")");
|
||||
System.out.println(ansi("Spring Boot", Code.BOLD).append(version, Code.FAINT));
|
||||
System.out.println(ansi("Hit TAB to complete. Type 'help' and hit "
|
||||
+ "RETURN for help, and 'exit' to quit."));
|
||||
}
|
||||
|
||||
private void runInputLoop() throws Exception {
|
||||
String line;
|
||||
while ((line = this.consoleReader.readLine(getPrompt())) != null) {
|
||||
while (line.endsWith("\\")) {
|
||||
line = line.substring(0, line.length() - 1);
|
||||
line += this.consoleReader.readLine("> ");
|
||||
}
|
||||
if (StringUtils.hasLength(line)) {
|
||||
String[] args = this.argumentDelimiter.parseArguments(line);
|
||||
this.commandRunner.runAndHandleErrors(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getPrompt() {
|
||||
String prompt = this.prompts.getPrompt();
|
||||
return ansi(prompt, Code.FG_BLUE).toString();
|
||||
}
|
||||
|
||||
private AnsiString ansi(String text, Code... codes) {
|
||||
return new AnsiString(this.consoleReader.getTerminal()).append(text, codes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Final handle an interrupt signal (CTRL-C).
|
||||
*/
|
||||
protected void handleSigInt() {
|
||||
if (this.commandRunner.handleSigInt()) {
|
||||
return;
|
||||
}
|
||||
System.out.println(String.format("%nThanks for using Spring Boot"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of {@link CommandRunner} to deal with {@link RunProcessCommand}s and
|
||||
* aliases.
|
||||
*/
|
||||
private class ShellCommandRunner extends CommandRunner {
|
||||
|
||||
private volatile Command lastCommand;
|
||||
|
||||
private final Map<String, String> aliases = new HashMap<>();
|
||||
|
||||
ShellCommandRunner() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public void addAliases(String command, String... aliases) {
|
||||
for (String alias : aliases) {
|
||||
this.aliases.put(alias, command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command findCommand(String name) {
|
||||
if (name.startsWith("!")) {
|
||||
return new RunProcessCommand(name.substring(1));
|
||||
}
|
||||
if (this.aliases.containsKey(name)) {
|
||||
name = this.aliases.get(name);
|
||||
}
|
||||
return super.findCommand(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeRun(Command command) {
|
||||
this.lastCommand = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterRun(Command command) {
|
||||
}
|
||||
|
||||
public boolean handleSigInt() {
|
||||
Command command = this.lastCommand;
|
||||
if (command != null && command instanceof RunProcessCommand) {
|
||||
return ((RunProcessCommand) command).handleSigInt();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.shell;
|
||||
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
|
||||
/**
|
||||
* {@link Command} to start a nested REPL shell.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @see Shell
|
||||
*/
|
||||
public class ShellCommand extends AbstractCommand {
|
||||
|
||||
public ShellCommand() {
|
||||
super("shell", "Start a nested shell");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus run(String... args) throws Exception {
|
||||
new Shell().run();
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.shell;
|
||||
|
||||
import org.springframework.boot.cli.command.CommandException;
|
||||
|
||||
/**
|
||||
* Exception used to stop the {@link Shell}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ShellExitException extends CommandException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ShellExitException() {
|
||||
super(Option.RETHROW);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.command.shell;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Abstraction to manage a stack of prompts.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ShellPrompts {
|
||||
|
||||
private static final String DEFAULT_PROMPT = "$ ";
|
||||
|
||||
private final Stack<String> prompts = new Stack<>();
|
||||
|
||||
/**
|
||||
* Push a new prompt to be used by the shell.
|
||||
* @param prompt the prompt
|
||||
* @see #popPrompt()
|
||||
*/
|
||||
public void pushPrompt(String prompt) {
|
||||
this.prompts.push(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop a previously pushed prompt, returning to the previous value.
|
||||
* @see #pushPrompt(String)
|
||||
*/
|
||||
public void popPrompt() {
|
||||
if (!this.prompts.isEmpty()) {
|
||||
this.prompts.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current prompt.
|
||||
* @return the current prompt
|
||||
*/
|
||||
public String getPrompt() {
|
||||
return this.prompts.isEmpty() ? DEFAULT_PROMPT : this.prompts.peek();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.command.status;
|
||||
|
||||
/**
|
||||
* Encapsulation of the outcome of a command.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @see ExitStatus#OK
|
||||
* @see ExitStatus#ERROR
|
||||
*
|
||||
*/
|
||||
public final class ExitStatus {
|
||||
|
||||
/**
|
||||
* Generic "OK" exit status with zero exit code and {@literal hangup=false}.
|
||||
*/
|
||||
public static ExitStatus OK = new ExitStatus(0, "OK");
|
||||
|
||||
/**
|
||||
* Generic "not OK" exit status with non-zero exit code and {@literal hangup=true}.
|
||||
*/
|
||||
public static ExitStatus ERROR = new ExitStatus(-1, "ERROR", true);
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final boolean hangup;
|
||||
|
||||
/**
|
||||
* Create a new {@link ExitStatus} instance.
|
||||
* @param code the exit code
|
||||
* @param name the name
|
||||
*/
|
||||
public ExitStatus(int code, String name) {
|
||||
this(code, name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExitStatus} instance.
|
||||
* @param code the exit code
|
||||
* @param name the name
|
||||
* @param hangup true if it is OK for the caller to hangup
|
||||
*/
|
||||
public ExitStatus(int code, String name, boolean hangup) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.hangup = hangup;
|
||||
}
|
||||
|
||||
/**
|
||||
* An exit code appropriate for use in {@code System.exit()}.
|
||||
* @return an exit code
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* A name describing the outcome.
|
||||
* @return a name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to signal that the caller can (or should) hangup. A server process with
|
||||
* non-daemon threads should set this to false.
|
||||
* @return the flag
|
||||
*/
|
||||
public boolean isHangup() {
|
||||
return this.hangup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the existing code to a hangup.
|
||||
* @return a new ExitStatus with hangup=true
|
||||
*/
|
||||
public ExitStatus hangup() {
|
||||
return new ExitStatus(this.code, this.name, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + ":" + getCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotatedNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ImportNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
/**
|
||||
* A base class for {@link ASTTransformation AST transformations} that are solely
|
||||
* interested in {@link AnnotatedNode AnnotatedNodes}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class AnnotatedNodeASTTransformation implements ASTTransformation {
|
||||
|
||||
private final Set<String> interestingAnnotationNames;
|
||||
|
||||
private final boolean removeAnnotations;
|
||||
|
||||
private SourceUnit sourceUnit;
|
||||
|
||||
protected AnnotatedNodeASTTransformation(Set<String> interestingAnnotationNames,
|
||||
boolean removeAnnotations) {
|
||||
this.interestingAnnotationNames = interestingAnnotationNames;
|
||||
this.removeAnnotations = removeAnnotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
this.sourceUnit = source;
|
||||
List<AnnotationNode> annotationNodes = new ArrayList<>();
|
||||
ClassVisitor classVisitor = new ClassVisitor(source, annotationNodes);
|
||||
for (ASTNode node : nodes) {
|
||||
if (node instanceof ModuleNode) {
|
||||
ModuleNode module = (ModuleNode) node;
|
||||
visitAnnotatedNode(module.getPackage(), annotationNodes);
|
||||
for (ImportNode importNode : module.getImports()) {
|
||||
visitAnnotatedNode(importNode, annotationNodes);
|
||||
}
|
||||
for (ImportNode importNode : module.getStarImports()) {
|
||||
visitAnnotatedNode(importNode, annotationNodes);
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue(), annotationNodes);
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticStarImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue(), annotationNodes);
|
||||
}
|
||||
for (ClassNode classNode : module.getClasses()) {
|
||||
visitAnnotatedNode(classNode, annotationNodes);
|
||||
classNode.visitContents(classVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
processAnnotationNodes(annotationNodes);
|
||||
}
|
||||
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.sourceUnit;
|
||||
}
|
||||
|
||||
protected abstract void processAnnotationNodes(List<AnnotationNode> annotationNodes);
|
||||
|
||||
private void visitAnnotatedNode(AnnotatedNode annotatedNode,
|
||||
List<AnnotationNode> annotatedNodes) {
|
||||
if (annotatedNode != null) {
|
||||
Iterator<AnnotationNode> annotationNodes = annotatedNode.getAnnotations()
|
||||
.iterator();
|
||||
while (annotationNodes.hasNext()) {
|
||||
AnnotationNode annotationNode = annotationNodes.next();
|
||||
if (this.interestingAnnotationNames
|
||||
.contains(annotationNode.getClassNode().getName())) {
|
||||
annotatedNodes.add(annotationNode);
|
||||
if (this.removeAnnotations) {
|
||||
annotationNodes.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassVisitor extends ClassCodeVisitorSupport {
|
||||
|
||||
private final SourceUnit source;
|
||||
|
||||
private List<AnnotationNode> annotationNodes;
|
||||
|
||||
ClassVisitor(SourceUnit source, List<AnnotationNode> annotationNodes) {
|
||||
this.source = source;
|
||||
this.annotationNodes = annotationNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnnotations(AnnotatedNode node) {
|
||||
visitAnnotatedNode(node, this.annotationNodes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.groovy.ast.AnnotatedNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.FieldNode;
|
||||
import org.codehaus.groovy.ast.MethodNode;
|
||||
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
|
||||
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.ast.expr.MethodCallExpression;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||
import org.codehaus.groovy.ast.stmt.Statement;
|
||||
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
|
||||
/**
|
||||
* General purpose AST utilities.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public abstract class AstUtils {
|
||||
|
||||
/**
|
||||
* Determine if a {@link ClassNode} has one or more of the specified annotations on
|
||||
* the class or any of its methods. N.B. the type names are not normally fully
|
||||
* qualified.
|
||||
* @param node the class to examine
|
||||
* @param annotations the annotations to look for
|
||||
* @return {@code true} if at least one of the annotations is found, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public static boolean hasAtLeastOneAnnotation(ClassNode node, String... annotations) {
|
||||
if (hasAtLeastOneAnnotation((AnnotatedNode) node, annotations)) {
|
||||
return true;
|
||||
}
|
||||
for (MethodNode method : node.getMethods()) {
|
||||
if (hasAtLeastOneAnnotation(method, annotations)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an {@link AnnotatedNode} has one or more of the specified annotations.
|
||||
* N.B. the annotation type names are not normally fully qualified.
|
||||
* @param node the node to examine
|
||||
* @param annotations the annotations to look for
|
||||
* @return {@code true} if at least one of the annotations is found, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public static boolean hasAtLeastOneAnnotation(AnnotatedNode node,
|
||||
String... annotations) {
|
||||
for (AnnotationNode annotationNode : node.getAnnotations()) {
|
||||
for (String annotation : annotations) {
|
||||
if (PatternMatchUtils.simpleMatch(annotation,
|
||||
annotationNode.getClassNode().getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a {@link ClassNode} has one or more fields of the specified types or
|
||||
* method returning one or more of the specified types. N.B. the type names are not
|
||||
* normally fully qualified.
|
||||
* @param node the class to examine
|
||||
* @param types the types to look for
|
||||
* @return {@code true} if at least one of the types is found, otherwise {@code false}
|
||||
*/
|
||||
public static boolean hasAtLeastOneFieldOrMethod(ClassNode node, String... types) {
|
||||
Set<String> typesSet = new HashSet<>(Arrays.asList(types));
|
||||
for (FieldNode field : node.getFields()) {
|
||||
if (typesSet.contains(field.getType().getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (MethodNode method : node.getMethods()) {
|
||||
if (typesSet.contains(method.getReturnType().getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a {@link ClassNode} subclasses any of the specified types N.B. the
|
||||
* type names are not normally fully qualified.
|
||||
* @param node the class to examine
|
||||
* @param types the types that may have been sub-classed
|
||||
* @return {@code true} if the class subclasses any of the specified types, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public static boolean subclasses(ClassNode node, String... types) {
|
||||
for (String type : types) {
|
||||
if (node.getSuperClass().getName().equals(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean hasAtLeastOneInterface(ClassNode classNode, String... types) {
|
||||
Set<String> typesSet = new HashSet<>(Arrays.asList(types));
|
||||
for (ClassNode inter : classNode.getInterfaces()) {
|
||||
if (typesSet.contains(inter.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a top-level {@code name} closure from inside this block if there is one,
|
||||
* optionally removing it from the block at the same time.
|
||||
* @param block a block statement (class definition)
|
||||
* @param name the name to look for
|
||||
* @param remove whether or not the extracted closure should be removed
|
||||
* @return a beans Closure if one can be found, null otherwise
|
||||
*/
|
||||
public static ClosureExpression getClosure(BlockStatement block, String name,
|
||||
boolean remove) {
|
||||
for (ExpressionStatement statement : getExpressionStatements(block)) {
|
||||
Expression expression = statement.getExpression();
|
||||
if (expression instanceof MethodCallExpression) {
|
||||
ClosureExpression closure = getClosure(name,
|
||||
(MethodCallExpression) expression);
|
||||
if (closure != null) {
|
||||
if (remove) {
|
||||
block.getStatements().remove(statement);
|
||||
}
|
||||
return closure;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<ExpressionStatement> getExpressionStatements(
|
||||
BlockStatement block) {
|
||||
ArrayList<ExpressionStatement> statements = new ArrayList<>();
|
||||
for (Statement statement : block.getStatements()) {
|
||||
if (statement instanceof ExpressionStatement) {
|
||||
statements.add((ExpressionStatement) statement);
|
||||
}
|
||||
}
|
||||
return statements;
|
||||
}
|
||||
|
||||
private static ClosureExpression getClosure(String name,
|
||||
MethodCallExpression expression) {
|
||||
Expression method = expression.getMethod();
|
||||
if (method instanceof ConstantExpression) {
|
||||
if (name.equals(((ConstantExpression) method).getValue())) {
|
||||
return (ClosureExpression) ((ArgumentListExpression) expression
|
||||
.getArguments()).getExpression(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ClosureExpression getClosure(BlockStatement block, String name) {
|
||||
return getClosure(block, name, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.compiler;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.classgen.GeneratorContext;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.CompilePhase;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
|
||||
/**
|
||||
* Strategy that can be used to apply some auto-configuration during the
|
||||
* {@link CompilePhase#CONVERSION} Groovy compile phase.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public abstract class CompilerAutoConfiguration {
|
||||
|
||||
/**
|
||||
* Strategy method used to determine when compiler auto-configuration should be
|
||||
* applied. Defaults to always.
|
||||
* @param classNode the class node
|
||||
* @return {@code true} if the compiler should be auto configured using this class. If
|
||||
* this method returns {@code false} no other strategy methods will be called.
|
||||
*/
|
||||
public boolean matches(ClassNode classNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any dependency customizations. This method will only be called if
|
||||
* {@link #matches} returns {@code true}.
|
||||
* @param dependencies dependency customizer
|
||||
* @throws CompilationFailedException if the dependencies cannot be applied
|
||||
*/
|
||||
public void applyDependencies(DependencyCustomizer dependencies)
|
||||
throws CompilationFailedException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any import customizations. This method will only be called if
|
||||
* {@link #matches} returns {@code true}.
|
||||
* @param imports import customizer
|
||||
* @throws CompilationFailedException if the imports cannot be applied
|
||||
*/
|
||||
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any customizations to the main class. This method will only be called if
|
||||
* {@link #matches} returns {@code true}. This method is useful when a groovy file
|
||||
* defines more than one class but customization only applies to the first class.
|
||||
* @param loader the class loader being used during compilation
|
||||
* @param configuration the compiler configuration
|
||||
* @param generatorContext the current context
|
||||
* @param source the source unit
|
||||
* @param classNode the main class
|
||||
* @throws CompilationFailedException if the customizations cannot be applied
|
||||
*/
|
||||
public void applyToMainClass(GroovyClassLoader loader,
|
||||
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
|
||||
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any additional configuration.
|
||||
* @param loader the class loader being used during compilation
|
||||
* @param configuration the compiler configuration
|
||||
* @param generatorContext the current context
|
||||
* @param source the source unit
|
||||
* @param classNode the class
|
||||
* @throws CompilationFailedException if the configuration cannot be applied
|
||||
*/
|
||||
public void apply(GroovyClassLoader loader, GroovyCompilerConfiguration configuration,
|
||||
GeneratorContext generatorContext, SourceUnit source, ClassNode classNode)
|
||||
throws CompilationFailedException {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.compiler;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to apply
|
||||
* {@link CompilerAutoConfiguration#applyDependencies(DependencyCustomizer) dependency
|
||||
* auto-configuration}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Order(DependencyAutoConfigurationTransformation.ORDER)
|
||||
public class DependencyAutoConfigurationTransformation implements ASTTransformation {
|
||||
|
||||
/**
|
||||
* The order of the transformation.
|
||||
*/
|
||||
public static final int ORDER = DependencyManagementBomTransformation.ORDER + 100;
|
||||
|
||||
private final GroovyClassLoader loader;
|
||||
|
||||
private final DependencyResolutionContext dependencyResolutionContext;
|
||||
|
||||
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
|
||||
|
||||
public DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
|
||||
DependencyResolutionContext dependencyResolutionContext,
|
||||
Iterable<CompilerAutoConfiguration> compilerAutoConfigurations) {
|
||||
this.loader = loader;
|
||||
this.dependencyResolutionContext = dependencyResolutionContext;
|
||||
this.compilerAutoConfigurations = compilerAutoConfigurations;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
for (ASTNode astNode : nodes) {
|
||||
if (astNode instanceof ModuleNode) {
|
||||
visitModule((ModuleNode) astNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void visitModule(ModuleNode module) {
|
||||
DependencyCustomizer dependencies = new DependencyCustomizer(this.loader, module,
|
||||
this.dependencyResolutionContext);
|
||||
for (ClassNode classNode : module.getClasses()) {
|
||||
for (CompilerAutoConfiguration autoConfiguration : this.compilerAutoConfigurations) {
|
||||
if (autoConfiguration.matches(classNode)) {
|
||||
autoConfiguration.applyDependencies(dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.compiler;
|
||||
|
||||
import groovy.lang.Grab;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
|
||||
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
|
||||
|
||||
/**
|
||||
* Customizer that allows dependencies to be added during compilation. Adding a dependency
|
||||
* results in a {@link Grab @Grab} annotation being added to the primary {@link ClassNode
|
||||
* class} is the {@link ModuleNode module} that's being customized.
|
||||
* <p>
|
||||
* This class provides a fluent API for conditionally adding dependencies. For example:
|
||||
* {@code dependencies.ifMissing("com.corp.SomeClass").add(module)}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class DependencyCustomizer {
|
||||
|
||||
private final GroovyClassLoader loader;
|
||||
|
||||
private final ClassNode classNode;
|
||||
|
||||
private final DependencyResolutionContext dependencyResolutionContext;
|
||||
|
||||
/**
|
||||
* Create a new {@link DependencyCustomizer} instance.
|
||||
* @param loader the current classloader
|
||||
* @param moduleNode the current module
|
||||
* @param dependencyResolutionContext the context for dependency resolution
|
||||
*/
|
||||
public DependencyCustomizer(GroovyClassLoader loader, ModuleNode moduleNode,
|
||||
DependencyResolutionContext dependencyResolutionContext) {
|
||||
this.loader = loader;
|
||||
this.classNode = moduleNode.getClasses().get(0);
|
||||
this.dependencyResolutionContext = dependencyResolutionContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new nested {@link DependencyCustomizer}.
|
||||
* @param parent the parent customizer
|
||||
*/
|
||||
protected DependencyCustomizer(DependencyCustomizer parent) {
|
||||
this.loader = parent.loader;
|
||||
this.classNode = parent.classNode;
|
||||
this.dependencyResolutionContext = parent.dependencyResolutionContext;
|
||||
}
|
||||
|
||||
public String getVersion(String artifactId) {
|
||||
return getVersion(artifactId, "");
|
||||
|
||||
}
|
||||
|
||||
public String getVersion(String artifactId, String defaultVersion) {
|
||||
String version = this.dependencyResolutionContext.getArtifactCoordinatesResolver()
|
||||
.getVersion(artifactId);
|
||||
if (version == null) {
|
||||
version = defaultVersion;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a nested {@link DependencyCustomizer} that only applies if any of the
|
||||
* specified class names are not on the class path.
|
||||
* @param classNames the class names to test
|
||||
* @return a nested {@link DependencyCustomizer}
|
||||
*/
|
||||
public DependencyCustomizer ifAnyMissingClasses(final String... classNames) {
|
||||
return new DependencyCustomizer(this) {
|
||||
@Override
|
||||
protected boolean canAdd() {
|
||||
for (String className : classNames) {
|
||||
try {
|
||||
DependencyCustomizer.this.loader.loadClass(className);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a nested {@link DependencyCustomizer} that only applies if all of the
|
||||
* specified class names are not on the class path.
|
||||
* @param classNames the class names to test
|
||||
* @return a nested {@link DependencyCustomizer}
|
||||
*/
|
||||
public DependencyCustomizer ifAllMissingClasses(final String... classNames) {
|
||||
return new DependencyCustomizer(this) {
|
||||
@Override
|
||||
protected boolean canAdd() {
|
||||
for (String className : classNames) {
|
||||
try {
|
||||
DependencyCustomizer.this.loader.loadClass(className);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// swallow exception and continue
|
||||
}
|
||||
}
|
||||
return DependencyCustomizer.this.canAdd();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a nested {@link DependencyCustomizer} that only applies if the specified
|
||||
* paths are on the class path.
|
||||
* @param paths the paths to test
|
||||
* @return a nested {@link DependencyCustomizer}
|
||||
*/
|
||||
public DependencyCustomizer ifAllResourcesPresent(final String... paths) {
|
||||
return new DependencyCustomizer(this) {
|
||||
@Override
|
||||
protected boolean canAdd() {
|
||||
for (String path : paths) {
|
||||
try {
|
||||
if (DependencyCustomizer.this.loader.getResource(path) == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// swallow exception and continue
|
||||
}
|
||||
}
|
||||
return DependencyCustomizer.this.canAdd();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a nested {@link DependencyCustomizer} that only applies at least one of the
|
||||
* specified paths is on the class path.
|
||||
* @param paths the paths to test
|
||||
* @return a nested {@link DependencyCustomizer}
|
||||
*/
|
||||
public DependencyCustomizer ifAnyResourcesPresent(final String... paths) {
|
||||
return new DependencyCustomizer(this) {
|
||||
@Override
|
||||
protected boolean canAdd() {
|
||||
for (String path : paths) {
|
||||
try {
|
||||
if (DependencyCustomizer.this.loader.getResource(path) != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// swallow exception and continue
|
||||
}
|
||||
}
|
||||
return DependencyCustomizer.this.canAdd();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dependencies and all of their dependencies. The group ID and version of the
|
||||
* dependencies are resolved from the modules using the customizer's
|
||||
* {@link ArtifactCoordinatesResolver}.
|
||||
* @param modules The module IDs
|
||||
* @return this {@link DependencyCustomizer} for continued use
|
||||
*/
|
||||
public DependencyCustomizer add(String... modules) {
|
||||
for (String module : modules) {
|
||||
add(module, null, null, true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single dependency and, optionally, all of its dependencies. The group ID and
|
||||
* version of the dependency are resolved from the module using the customizer's
|
||||
* {@link ArtifactCoordinatesResolver}.
|
||||
* @param module The module ID
|
||||
* @param transitive {@code true} if the transitive dependencies should also be added,
|
||||
* otherwise {@code false}.
|
||||
* @return this {@link DependencyCustomizer} for continued use
|
||||
*/
|
||||
public DependencyCustomizer add(String module, boolean transitive) {
|
||||
return add(module, null, null, transitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single dependency with the specified classifier and type and, optionally, all
|
||||
* of its dependencies. The group ID and version of the dependency are resolved from
|
||||
* the module by using the customizer's {@link ArtifactCoordinatesResolver}.
|
||||
* @param module The module ID
|
||||
* @param classifier The classifier, may be {@code null}
|
||||
* @param type The type, may be {@code null}
|
||||
* @param transitive {@code true} if the transitive dependencies should also be added,
|
||||
* otherwise {@code false}.
|
||||
* @return this {@link DependencyCustomizer} for continued use
|
||||
*/
|
||||
public DependencyCustomizer add(String module, String classifier, String type,
|
||||
boolean transitive) {
|
||||
if (canAdd()) {
|
||||
ArtifactCoordinatesResolver artifactCoordinatesResolver = this.dependencyResolutionContext
|
||||
.getArtifactCoordinatesResolver();
|
||||
this.classNode.addAnnotation(
|
||||
createGrabAnnotation(artifactCoordinatesResolver.getGroupId(module),
|
||||
artifactCoordinatesResolver.getArtifactId(module),
|
||||
artifactCoordinatesResolver.getVersion(module), classifier,
|
||||
type, transitive));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private AnnotationNode createGrabAnnotation(String group, String module,
|
||||
String version, String classifier, String type, boolean transitive) {
|
||||
AnnotationNode annotationNode = new AnnotationNode(new ClassNode(Grab.class));
|
||||
annotationNode.addMember("group", new ConstantExpression(group));
|
||||
annotationNode.addMember("module", new ConstantExpression(module));
|
||||
annotationNode.addMember("version", new ConstantExpression(version));
|
||||
if (classifier != null) {
|
||||
annotationNode.addMember("classifier", new ConstantExpression(classifier));
|
||||
}
|
||||
if (type != null) {
|
||||
annotationNode.addMember("type", new ConstantExpression(type));
|
||||
}
|
||||
annotationNode.addMember("transitive", new ConstantExpression(transitive));
|
||||
annotationNode.addMember("initClass", new ConstantExpression(false));
|
||||
return annotationNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy called to test if dependencies can be added. Subclasses override as
|
||||
* required. Returns {@code true} by default.
|
||||
* @return {@code true} if dependencies can be added, otherwise {@code false}
|
||||
*/
|
||||
protected boolean canAdd() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DependencyResolutionContext}.
|
||||
* @return the dependency resolution context
|
||||
*/
|
||||
public DependencyResolutionContext getDependencyResolutionContext() {
|
||||
return this.dependencyResolutionContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import groovy.grape.Grape;
|
||||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.Repository;
|
||||
import org.apache.maven.model.building.DefaultModelBuilder;
|
||||
import org.apache.maven.model.building.DefaultModelBuilderFactory;
|
||||
import org.apache.maven.model.building.DefaultModelBuildingRequest;
|
||||
import org.apache.maven.model.building.ModelSource;
|
||||
import org.apache.maven.model.building.UrlModelSource;
|
||||
import org.apache.maven.model.resolution.InvalidRepositoryException;
|
||||
import org.apache.maven.model.resolution.ModelResolver;
|
||||
import org.apache.maven.model.resolution.UnresolvableModelException;
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.ast.expr.ListExpression;
|
||||
import org.codehaus.groovy.control.messages.Message;
|
||||
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
|
||||
import org.codehaus.groovy.syntax.SyntaxException;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
import org.springframework.boot.cli.compiler.dependencies.MavenModelDependencyManagement;
|
||||
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
|
||||
import org.springframework.boot.groovy.DependencyManagementBom;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} for processing {@link DependencyManagementBom} annotations.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Order(DependencyManagementBomTransformation.ORDER)
|
||||
public class DependencyManagementBomTransformation
|
||||
extends AnnotatedNodeASTTransformation {
|
||||
|
||||
/**
|
||||
* The order of the transformation.
|
||||
*/
|
||||
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 100;
|
||||
|
||||
private static final Set<String> DEPENDENCY_MANAGEMENT_BOM_ANNOTATION_NAMES = Collections
|
||||
.unmodifiableSet(
|
||||
new HashSet<>(Arrays.asList(DependencyManagementBom.class.getName(),
|
||||
DependencyManagementBom.class.getSimpleName())));
|
||||
|
||||
private final DependencyResolutionContext resolutionContext;
|
||||
|
||||
public DependencyManagementBomTransformation(
|
||||
DependencyResolutionContext resolutionContext) {
|
||||
super(DEPENDENCY_MANAGEMENT_BOM_ANNOTATION_NAMES, true);
|
||||
this.resolutionContext = resolutionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) {
|
||||
if (!annotationNodes.isEmpty()) {
|
||||
if (annotationNodes.size() > 1) {
|
||||
for (AnnotationNode annotationNode : annotationNodes) {
|
||||
handleDuplicateDependencyManagementBomAnnotation(annotationNode);
|
||||
}
|
||||
}
|
||||
else {
|
||||
processDependencyManagementBomAnnotation(annotationNodes.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processDependencyManagementBomAnnotation(AnnotationNode annotationNode) {
|
||||
Expression valueExpression = annotationNode.getMember("value");
|
||||
List<Map<String, String>> bomDependencies = createDependencyMaps(valueExpression);
|
||||
updateDependencyResolutionContext(bomDependencies);
|
||||
}
|
||||
|
||||
private List<Map<String, String>> createDependencyMaps(Expression valueExpression) {
|
||||
Map<String, String> dependency = null;
|
||||
List<ConstantExpression> constantExpressions = getConstantExpressions(
|
||||
valueExpression);
|
||||
List<Map<String, String>> dependencies = new ArrayList<>(
|
||||
constantExpressions.size());
|
||||
for (ConstantExpression expression : constantExpressions) {
|
||||
Object value = expression.getValue();
|
||||
if (value instanceof String) {
|
||||
String[] components = ((String) expression.getValue()).split(":");
|
||||
if (components.length == 3) {
|
||||
dependency = new HashMap<>();
|
||||
dependency.put("group", components[0]);
|
||||
dependency.put("module", components[1]);
|
||||
dependency.put("version", components[2]);
|
||||
dependency.put("type", "pom");
|
||||
dependencies.add(dependency);
|
||||
}
|
||||
else {
|
||||
handleMalformedDependency(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private List<ConstantExpression> getConstantExpressions(Expression valueExpression) {
|
||||
if (valueExpression instanceof ListExpression) {
|
||||
return getConstantExpressions((ListExpression) valueExpression);
|
||||
}
|
||||
if (valueExpression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) valueExpression).getValue() instanceof String) {
|
||||
return Arrays.asList((ConstantExpression) valueExpression);
|
||||
}
|
||||
reportError("@DependencyManagementBom requires an inline constant that is a "
|
||||
+ "string or a string array", valueExpression);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<ConstantExpression> getConstantExpressions(
|
||||
ListExpression valueExpression) {
|
||||
List<ConstantExpression> expressions = new ArrayList<>();
|
||||
for (Expression expression : valueExpression.getExpressions()) {
|
||||
if (expression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) expression).getValue() instanceof String) {
|
||||
expressions.add((ConstantExpression) expression);
|
||||
}
|
||||
else {
|
||||
reportError(
|
||||
"Each entry in the array must be an " + "inline string constant",
|
||||
expression);
|
||||
}
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
||||
private void handleMalformedDependency(Expression expression) {
|
||||
Message message = createSyntaxErrorMessage(
|
||||
String.format(
|
||||
"The string must be of the form \"group:module:version\"%n"),
|
||||
expression);
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
|
||||
}
|
||||
|
||||
private void updateDependencyResolutionContext(
|
||||
List<Map<String, String>> bomDependencies) {
|
||||
URI[] uris = Grape.getInstance().resolve(null,
|
||||
bomDependencies.toArray(new Map[bomDependencies.size()]));
|
||||
DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance();
|
||||
for (URI uri : uris) {
|
||||
try {
|
||||
DefaultModelBuildingRequest request = new DefaultModelBuildingRequest();
|
||||
request.setModelResolver(new GrapeModelResolver());
|
||||
request.setModelSource(new UrlModelSource(uri.toURL()));
|
||||
request.setSystemProperties(System.getProperties());
|
||||
Model model = modelBuilder.build(request).getEffectiveModel();
|
||||
this.resolutionContext.addDependencyManagement(
|
||||
new MavenModelDependencyManagement(model));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to build model for '" + uri
|
||||
+ "'. Is it a valid Maven bom?", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDuplicateDependencyManagementBomAnnotation(
|
||||
AnnotationNode annotationNode) {
|
||||
Message message = createSyntaxErrorMessage(
|
||||
"Duplicate @DependencyManagementBom annotation. It must be declared at most once.",
|
||||
annotationNode);
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
|
||||
}
|
||||
|
||||
private void reportError(String message, ASTNode node) {
|
||||
getSourceUnit().getErrorCollector()
|
||||
.addErrorAndContinue(createSyntaxErrorMessage(message, node));
|
||||
}
|
||||
|
||||
private Message createSyntaxErrorMessage(String message, ASTNode node) {
|
||||
return new SyntaxErrorMessage(
|
||||
new SyntaxException(message, node.getLineNumber(), node.getColumnNumber(),
|
||||
node.getLastLineNumber(), node.getLastColumnNumber()),
|
||||
getSourceUnit());
|
||||
}
|
||||
|
||||
private static class GrapeModelResolver implements ModelResolver {
|
||||
|
||||
@Override
|
||||
public ModelSource resolveModel(String groupId, String artifactId, String version)
|
||||
throws UnresolvableModelException {
|
||||
Map<String, String> dependency = new HashMap<>();
|
||||
dependency.put("group", groupId);
|
||||
dependency.put("module", artifactId);
|
||||
dependency.put("version", version);
|
||||
dependency.put("type", "pom");
|
||||
try {
|
||||
return new UrlModelSource(
|
||||
Grape.getInstance().resolve(null, dependency)[0].toURL());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new UnresolvableModelException(e.getMessage(), groupId, artifactId,
|
||||
version);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRepository(Repository repository)
|
||||
throws InvalidRepositoryException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelResolver newCopy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.control.CompilationUnit;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Extension of the {@link GroovyClassLoader} with support for obtaining '.class' files as
|
||||
* resources.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
||||
|
||||
private static final String SHARED_PACKAGE = "org.springframework.boot.groovy";
|
||||
|
||||
private static final URL[] NO_URLS = new URL[] {};
|
||||
|
||||
private final Map<String, byte[]> classResources = new HashMap<>();
|
||||
|
||||
private final GroovyCompilerScope scope;
|
||||
|
||||
private final CompilerConfiguration configuration;
|
||||
|
||||
public ExtendedGroovyClassLoader(GroovyCompilerScope scope) {
|
||||
this(scope, createParentClassLoader(scope), new CompilerConfiguration());
|
||||
}
|
||||
|
||||
private static ClassLoader createParentClassLoader(GroovyCompilerScope scope) {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (scope == GroovyCompilerScope.DEFAULT) {
|
||||
classLoader = new DefaultScopeParentClassLoader(classLoader);
|
||||
}
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
private ExtendedGroovyClassLoader(GroovyCompilerScope scope, ClassLoader parent,
|
||||
CompilerConfiguration configuration) {
|
||||
super(parent, configuration);
|
||||
this.configuration = configuration;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
try {
|
||||
return super.findClass(name);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
if (this.scope == GroovyCompilerScope.DEFAULT
|
||||
&& name.startsWith(SHARED_PACKAGE)) {
|
||||
Class<?> sharedClass = findSharedClass(name);
|
||||
if (sharedClass != null) {
|
||||
return sharedClass;
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> findSharedClass(String name) {
|
||||
try {
|
||||
String path = name.replace('.', '/').concat(".class");
|
||||
try (InputStream inputStream = getParent().getResourceAsStream(path)) {
|
||||
if (inputStream != null) {
|
||||
return defineClass(name, FileCopyUtils.copyToByteArray(inputStream));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(String name) {
|
||||
InputStream resourceStream = super.getResourceAsStream(name);
|
||||
if (resourceStream == null) {
|
||||
byte[] bytes = this.classResources.get(name);
|
||||
resourceStream = bytes == null ? null : new ByteArrayInputStream(bytes);
|
||||
}
|
||||
return resourceStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
|
||||
InnerLoader loader = AccessController.doPrivileged(getInnerLoader());
|
||||
return new ExtendedClassCollector(loader, unit, su);
|
||||
}
|
||||
|
||||
private PrivilegedAction<InnerLoader> getInnerLoader() {
|
||||
return () -> new InnerLoader(ExtendedGroovyClassLoader.this) {
|
||||
|
||||
// Don't return URLs from the inner loader so that Tomcat only
|
||||
// searches the parent. Fixes 'TLD skipped' issues
|
||||
@Override
|
||||
public URL[] getURLs() {
|
||||
return NO_URLS;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public CompilerConfiguration getConfiguration() {
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner collector class used to track as classes are added.
|
||||
*/
|
||||
protected class ExtendedClassCollector extends ClassCollector {
|
||||
|
||||
protected ExtendedClassCollector(InnerLoader loader, CompilationUnit unit,
|
||||
SourceUnit su) {
|
||||
super(loader, unit, su);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> createClass(byte[] code, ClassNode classNode) {
|
||||
Class<?> createdClass = super.createClass(code, classNode);
|
||||
ExtendedGroovyClassLoader.this.classResources
|
||||
.put(classNode.getName().replace('.', '/') + ".class", code);
|
||||
return createdClass;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ClassLoader used for a parent that filters so that only classes from groovy-all.jar
|
||||
* are exposed.
|
||||
*/
|
||||
private static class DefaultScopeParentClassLoader extends ClassLoader {
|
||||
|
||||
private static final String[] GROOVY_JARS_PREFIXES = { "groovy", "antlr", "asm" };
|
||||
|
||||
private final URLClassLoader groovyOnlyClassLoader;
|
||||
|
||||
DefaultScopeParentClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
this.groovyOnlyClassLoader = new URLClassLoader(getGroovyJars(parent),
|
||||
parent.getParent());
|
||||
}
|
||||
|
||||
private URL[] getGroovyJars(final ClassLoader parent) {
|
||||
Set<URL> urls = new HashSet<>();
|
||||
findGroovyJarsDirectly(parent, urls);
|
||||
if (urls.isEmpty()) {
|
||||
findGroovyJarsFromClassPath(parent, urls);
|
||||
}
|
||||
Assert.state(!urls.isEmpty(), "Unable to find groovy JAR");
|
||||
return new ArrayList<>(urls).toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
private void findGroovyJarsDirectly(ClassLoader classLoader, Set<URL> urls) {
|
||||
while (classLoader != null) {
|
||||
if (classLoader instanceof URLClassLoader) {
|
||||
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
|
||||
if (isGroovyJar(url.toString())) {
|
||||
urls.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
classLoader = classLoader.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
private void findGroovyJarsFromClassPath(ClassLoader parent, Set<URL> urls) {
|
||||
String classpath = System.getProperty("java.class.path");
|
||||
String[] entries = classpath.split(System.getProperty("path.separator"));
|
||||
for (String entry : entries) {
|
||||
if (isGroovyJar(entry)) {
|
||||
File file = new File(entry);
|
||||
if (file.canRead()) {
|
||||
try {
|
||||
urls.add(file.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
// Swallow and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGroovyJar(String entry) {
|
||||
entry = StringUtils.cleanPath(entry);
|
||||
for (String jarPrefix : GROOVY_JARS_PREFIXES) {
|
||||
if (entry.contains("/" + jarPrefix + "-")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve)
|
||||
throws ClassNotFoundException {
|
||||
if (!name.startsWith("java.")) {
|
||||
this.groovyOnlyClassLoader.loadClass(name);
|
||||
}
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotatedNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassHelper;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.ast.PackageNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.ast.expr.ListExpression;
|
||||
import org.codehaus.groovy.control.CompilePhase;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.GroovyASTTransformation;
|
||||
|
||||
import org.springframework.boot.groovy.DependencyManagementBom;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* A base class that lets plugin authors easily add additional BOMs to all apps. All the
|
||||
* dependencies in the BOM (and its transitives) will be added to the dependency
|
||||
* management lookup, so an app can use just the artifact id (e.g. "spring-jdbc") in a
|
||||
* {@code @Grab}. To install, implement the missing methods and list the class in
|
||||
* {@code META-INF/services/org.springframework.boot.cli.compiler.SpringBootAstTransformation}
|
||||
* . The {@link #getOrder()} value needs to be before
|
||||
* {@link DependencyManagementBomTransformation#ORDER}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@GroovyASTTransformation(phase = CompilePhase.CONVERSION)
|
||||
public abstract class GenericBomAstTransformation
|
||||
implements SpringBootAstTransformation, Ordered {
|
||||
|
||||
private static ClassNode BOM = ClassHelper.make(DependencyManagementBom.class);
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
for (ASTNode astNode : nodes) {
|
||||
if (astNode instanceof ModuleNode) {
|
||||
visitModule((ModuleNode) astNode, getBomModule());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The bom to be added to dependency management in compact form:
|
||||
* <code>"<groupId>:<artifactId>:<version>"</code> (like in a
|
||||
* {@code @Grab}).
|
||||
* @return the maven co-ordinates of the BOM to add
|
||||
*/
|
||||
protected abstract String getBomModule();
|
||||
|
||||
private void visitModule(ModuleNode node, String module) {
|
||||
addDependencyManagementBom(node, module);
|
||||
}
|
||||
|
||||
private void addDependencyManagementBom(ModuleNode node, String module) {
|
||||
AnnotatedNode annotated = getAnnotatedNode(node);
|
||||
if (annotated != null) {
|
||||
AnnotationNode bom = getAnnotation(annotated);
|
||||
List<Expression> expressions = new ArrayList<>(
|
||||
getConstantExpressions(bom.getMember("value")));
|
||||
expressions.add(new ConstantExpression(module));
|
||||
bom.setMember("value", new ListExpression(expressions));
|
||||
}
|
||||
}
|
||||
|
||||
private AnnotationNode getAnnotation(AnnotatedNode annotated) {
|
||||
List<AnnotationNode> annotations = annotated.getAnnotations(BOM);
|
||||
if (!annotations.isEmpty()) {
|
||||
return annotations.get(0);
|
||||
}
|
||||
AnnotationNode annotation = new AnnotationNode(BOM);
|
||||
annotated.addAnnotation(annotation);
|
||||
return annotation;
|
||||
}
|
||||
|
||||
private AnnotatedNode getAnnotatedNode(ModuleNode node) {
|
||||
PackageNode packageNode = node.getPackage();
|
||||
if (packageNode != null && !packageNode.getAnnotations(BOM).isEmpty()) {
|
||||
return packageNode;
|
||||
}
|
||||
if (!node.getClasses().isEmpty()) {
|
||||
return node.getClasses().get(0);
|
||||
}
|
||||
return packageNode;
|
||||
}
|
||||
|
||||
private List<ConstantExpression> getConstantExpressions(Expression valueExpression) {
|
||||
if (valueExpression instanceof ListExpression) {
|
||||
return getConstantExpressions((ListExpression) valueExpression);
|
||||
}
|
||||
if (valueExpression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) valueExpression).getValue() instanceof String) {
|
||||
return Arrays.asList((ConstantExpression) valueExpression);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<ConstantExpression> getConstantExpressions(
|
||||
ListExpression valueExpression) {
|
||||
List<ConstantExpression> expressions = new ArrayList<>();
|
||||
for (Expression expression : valueExpression.getExpressions()) {
|
||||
if (expression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) expression).getValue() instanceof String) {
|
||||
expressions.add((ConstantExpression) expression);
|
||||
}
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
|
||||
import org.codehaus.groovy.ast.ClassHelper;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.ast.PropertyNode;
|
||||
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to resolve beans declarations inside application source
|
||||
* files. Users only need to define a <code>beans{}</code> DSL element, and this
|
||||
* transformation will remove it and make it accessible to the Spring application via an
|
||||
* interface.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Order(GroovyBeansTransformation.ORDER)
|
||||
public class GroovyBeansTransformation implements ASTTransformation {
|
||||
|
||||
/**
|
||||
* The order of the transformation.
|
||||
*/
|
||||
public static final int ORDER = DependencyManagementBomTransformation.ORDER + 200;
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
for (ASTNode node : nodes) {
|
||||
if (node instanceof ModuleNode) {
|
||||
ModuleNode module = (ModuleNode) node;
|
||||
for (ClassNode classNode : new ArrayList<>(module.getClasses())) {
|
||||
if (classNode.isScript()) {
|
||||
classNode.visitContents(new ClassVisitor(source, classNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassVisitor extends ClassCodeVisitorSupport {
|
||||
|
||||
private static final String SOURCE_INTERFACE = "org.springframework.boot.BeanDefinitionLoader.GroovyBeanDefinitionSource";
|
||||
|
||||
private static final String BEANS = "beans";
|
||||
|
||||
private final SourceUnit source;
|
||||
|
||||
private final ClassNode classNode;
|
||||
|
||||
private boolean xformed = false;
|
||||
|
||||
ClassVisitor(SourceUnit source, ClassNode classNode) {
|
||||
this.source = source;
|
||||
this.classNode = classNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBlockStatement(BlockStatement block) {
|
||||
if (block.isEmpty() || this.xformed) {
|
||||
return;
|
||||
}
|
||||
ClosureExpression closure = beans(block);
|
||||
if (closure != null) {
|
||||
// Add a marker interface to the current script
|
||||
this.classNode.addInterface(ClassHelper.make(SOURCE_INTERFACE));
|
||||
// Implement the interface by adding a public read-only property with the
|
||||
// same name as the method in the interface (getBeans). Make it return the
|
||||
// closure.
|
||||
this.classNode.addProperty(
|
||||
new PropertyNode(BEANS, Modifier.PUBLIC | Modifier.FINAL,
|
||||
ClassHelper.CLOSURE_TYPE.getPlainNodeReference(),
|
||||
this.classNode, closure, null, null));
|
||||
// Only do this once per class
|
||||
this.xformed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a top-level <code>beans{}</code> closure from inside this block if
|
||||
* there is one. Removes it from the block at the same time.
|
||||
* @param block a block statement (class definition)
|
||||
* @return a beans Closure if one can be found, null otherwise
|
||||
*/
|
||||
private ClosureExpression beans(BlockStatement block) {
|
||||
return AstUtils.getClosure(block, BEANS, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.GroovyClassLoader.ClassCollector;
|
||||
import groovy.lang.GroovyCodeSource;
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.classgen.GeneratorContext;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.CompilationUnit;
|
||||
import org.codehaus.groovy.control.CompilePhase;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.codehaus.groovy.control.Phases;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
import org.codehaus.groovy.transform.ASTTransformationVisitor;
|
||||
|
||||
import org.springframework.boot.cli.compiler.dependencies.SpringBootDependenciesDependencyManagement;
|
||||
import org.springframework.boot.cli.compiler.grape.AetherGrapeEngine;
|
||||
import org.springframework.boot.cli.compiler.grape.AetherGrapeEngineFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
|
||||
import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller;
|
||||
import org.springframework.boot.cli.util.ResourceUtils;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
/**
|
||||
* Compiler for Groovy sources. Primarily a simple Facade for
|
||||
* {@link GroovyClassLoader#parseClass(GroovyCodeSource)} with the following additional
|
||||
* features:
|
||||
* <ul>
|
||||
* <li>{@link CompilerAutoConfiguration} strategies will be read from
|
||||
* {@code META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration}
|
||||
* (per the standard java {@link ServiceLoader} contract) and applied during compilation
|
||||
* </li>
|
||||
*
|
||||
* <li>Multiple classes can be returned if the Groovy source defines more than one Class
|
||||
* </li>
|
||||
*
|
||||
* <li>Generated class files can also be loaded using
|
||||
* {@link ClassLoader#getResource(String)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class GroovyCompiler {
|
||||
|
||||
private final GroovyCompilerConfiguration configuration;
|
||||
|
||||
private final ExtendedGroovyClassLoader loader;
|
||||
|
||||
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
|
||||
|
||||
private final List<ASTTransformation> transformations;
|
||||
|
||||
/**
|
||||
* Create a new {@link GroovyCompiler} instance.
|
||||
* @param configuration the compiler configuration
|
||||
*/
|
||||
public GroovyCompiler(final GroovyCompilerConfiguration configuration) {
|
||||
|
||||
this.configuration = configuration;
|
||||
this.loader = createLoader(configuration);
|
||||
|
||||
DependencyResolutionContext resolutionContext = new DependencyResolutionContext();
|
||||
resolutionContext.addDependencyManagement(
|
||||
new SpringBootDependenciesDependencyManagement());
|
||||
|
||||
AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(this.loader,
|
||||
configuration.getRepositoryConfiguration(), resolutionContext,
|
||||
configuration.isQuiet());
|
||||
|
||||
GrapeEngineInstaller.install(grapeEngine);
|
||||
|
||||
this.loader.getConfiguration()
|
||||
.addCompilationCustomizers(new CompilerAutoConfigureCustomizer());
|
||||
if (configuration.isAutoconfigure()) {
|
||||
this.compilerAutoConfigurations = ServiceLoader
|
||||
.load(CompilerAutoConfiguration.class);
|
||||
}
|
||||
else {
|
||||
this.compilerAutoConfigurations = Collections.emptySet();
|
||||
}
|
||||
|
||||
this.transformations = new ArrayList<>();
|
||||
this.transformations
|
||||
.add(new DependencyManagementBomTransformation(resolutionContext));
|
||||
this.transformations.add(new DependencyAutoConfigurationTransformation(
|
||||
this.loader, resolutionContext, this.compilerAutoConfigurations));
|
||||
this.transformations.add(new GroovyBeansTransformation());
|
||||
if (this.configuration.isGuessDependencies()) {
|
||||
this.transformations.add(
|
||||
new ResolveDependencyCoordinatesTransformation(resolutionContext));
|
||||
}
|
||||
for (ASTTransformation transformation : ServiceLoader
|
||||
.load(SpringBootAstTransformation.class)) {
|
||||
this.transformations.add(transformation);
|
||||
}
|
||||
this.transformations.sort(AnnotationAwareOrderComparator.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a mutable list of the {@link ASTTransformation}s to be applied during
|
||||
* {@link #compile(String...)}.
|
||||
* @return the AST transformations to apply
|
||||
*/
|
||||
public List<ASTTransformation> getAstTransformations() {
|
||||
return this.transformations;
|
||||
}
|
||||
|
||||
public ExtendedGroovyClassLoader getLoader() {
|
||||
return this.loader;
|
||||
}
|
||||
|
||||
private ExtendedGroovyClassLoader createLoader(
|
||||
GroovyCompilerConfiguration configuration) {
|
||||
|
||||
ExtendedGroovyClassLoader loader = new ExtendedGroovyClassLoader(
|
||||
configuration.getScope());
|
||||
|
||||
for (URL url : getExistingUrls()) {
|
||||
loader.addURL(url);
|
||||
}
|
||||
|
||||
for (String classpath : configuration.getClasspath()) {
|
||||
loader.addClasspath(classpath);
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
private URL[] getExistingUrls() {
|
||||
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
|
||||
if (tccl instanceof ExtendedGroovyClassLoader) {
|
||||
return ((ExtendedGroovyClassLoader) tccl).getURLs();
|
||||
}
|
||||
else {
|
||||
return new URL[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void addCompilationCustomizers(CompilationCustomizer... customizers) {
|
||||
this.loader.getConfiguration().addCompilationCustomizers(customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the specified Groovy sources, applying any
|
||||
* {@link CompilerAutoConfiguration}s. All classes defined in the sources will be
|
||||
* returned from this method.
|
||||
* @param sources the sources to compile
|
||||
* @return compiled classes
|
||||
* @throws CompilationFailedException in case of compilation failures
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws CompilationFailedException in case of compilation errors
|
||||
*/
|
||||
public Class<?>[] compile(String... sources)
|
||||
throws CompilationFailedException, IOException {
|
||||
|
||||
this.loader.clearCache();
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
|
||||
CompilerConfiguration configuration = this.loader.getConfiguration();
|
||||
|
||||
CompilationUnit compilationUnit = new CompilationUnit(configuration, null,
|
||||
this.loader);
|
||||
ClassCollector collector = this.loader.createCollector(compilationUnit, null);
|
||||
compilationUnit.setClassgenCallback(collector);
|
||||
|
||||
for (String source : sources) {
|
||||
List<String> paths = ResourceUtils.getUrls(source, this.loader);
|
||||
for (String path : paths) {
|
||||
compilationUnit.addSource(new URL(path));
|
||||
}
|
||||
}
|
||||
|
||||
addAstTransformations(compilationUnit);
|
||||
|
||||
compilationUnit.compile(Phases.CLASS_GENERATION);
|
||||
for (Object loadedClass : collector.getLoadedClasses()) {
|
||||
classes.add((Class<?>) loadedClass);
|
||||
}
|
||||
ClassNode mainClassNode = MainClass.get(compilationUnit);
|
||||
|
||||
Class<?> mainClass = null;
|
||||
for (Class<?> loadedClass : classes) {
|
||||
if (mainClassNode.getName().equals(loadedClass.getName())) {
|
||||
mainClass = loadedClass;
|
||||
}
|
||||
}
|
||||
if (mainClass != null) {
|
||||
classes.remove(mainClass);
|
||||
classes.add(0, mainClass);
|
||||
}
|
||||
|
||||
return classes.toArray(new Class<?>[classes.size()]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void addAstTransformations(CompilationUnit compilationUnit) {
|
||||
LinkedList[] phaseOperations = getPhaseOperations(compilationUnit);
|
||||
processConversionOperations(phaseOperations[Phases.CONVERSION]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private LinkedList[] getPhaseOperations(CompilationUnit compilationUnit) {
|
||||
try {
|
||||
Field field = CompilationUnit.class.getDeclaredField("phaseOperations");
|
||||
field.setAccessible(true);
|
||||
LinkedList[] phaseOperations = (LinkedList[]) field.get(compilationUnit);
|
||||
return phaseOperations;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(
|
||||
"Phase operations not available from compilation unit");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void processConversionOperations(LinkedList conversionOperations) {
|
||||
int index = getIndexOfASTTransformationVisitor(conversionOperations);
|
||||
conversionOperations.add(index, new CompilationUnit.SourceUnitOperation() {
|
||||
@Override
|
||||
public void call(SourceUnit source) throws CompilationFailedException {
|
||||
ASTNode[] nodes = new ASTNode[] { source.getAST() };
|
||||
for (ASTTransformation transformation : GroovyCompiler.this.transformations) {
|
||||
transformation.visit(nodes, source);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int getIndexOfASTTransformationVisitor(LinkedList<?> conversionOperations) {
|
||||
for (int index = 0; index < conversionOperations.size(); index++) {
|
||||
if (conversionOperations.get(index).getClass().getName()
|
||||
.startsWith(ASTTransformationVisitor.class.getName())) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return conversionOperations.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link CompilationCustomizer} to call {@link CompilerAutoConfiguration}s.
|
||||
*/
|
||||
private class CompilerAutoConfigureCustomizer extends CompilationCustomizer {
|
||||
|
||||
CompilerAutoConfigureCustomizer() {
|
||||
super(CompilePhase.CONVERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode)
|
||||
throws CompilationFailedException {
|
||||
|
||||
ImportCustomizer importCustomizer = new SmartImportCustomizer(source, context,
|
||||
classNode);
|
||||
ClassNode mainClassNode = MainClass.get(source.getAST().getClasses());
|
||||
|
||||
// Additional auto configuration
|
||||
for (CompilerAutoConfiguration autoConfiguration : GroovyCompiler.this.compilerAutoConfigurations) {
|
||||
if (autoConfiguration.matches(classNode)) {
|
||||
if (GroovyCompiler.this.configuration.isGuessImports()) {
|
||||
autoConfiguration.applyImports(importCustomizer);
|
||||
importCustomizer.call(source, context, classNode);
|
||||
}
|
||||
if (classNode.equals(mainClassNode)) {
|
||||
autoConfiguration.applyToMainClass(GroovyCompiler.this.loader,
|
||||
GroovyCompiler.this.configuration, context, source,
|
||||
classNode);
|
||||
}
|
||||
autoConfiguration.apply(GroovyCompiler.this.loader,
|
||||
GroovyCompiler.this.configuration, context, source,
|
||||
classNode);
|
||||
}
|
||||
}
|
||||
importCustomizer.call(source, context, classNode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MainClass {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static ClassNode get(CompilationUnit source) {
|
||||
return get(source.getAST().getClasses());
|
||||
}
|
||||
|
||||
public static ClassNode get(List<ClassNode> classes) {
|
||||
for (ClassNode node : classes) {
|
||||
if (AstUtils.hasAtLeastOneAnnotation(node, "Enable*AutoConfiguration")) {
|
||||
return null; // No need to enhance this
|
||||
}
|
||||
if (AstUtils.hasAtLeastOneAnnotation(node, "*Controller", "Configuration",
|
||||
"Component", "*Service", "Repository", "Enable*")) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return (classes.isEmpty() ? null : classes.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
|
||||
/**
|
||||
* Configuration for the {@link GroovyCompiler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public interface GroovyCompilerConfiguration {
|
||||
|
||||
/**
|
||||
* Constant to be used when there is no {@link #getClasspath() classpath}.
|
||||
*/
|
||||
String[] DEFAULT_CLASSPATH = { "." };
|
||||
|
||||
/**
|
||||
* Returns the scope in which the compiler operates.
|
||||
* @return the scope of the compiler
|
||||
*/
|
||||
GroovyCompilerScope getScope();
|
||||
|
||||
/**
|
||||
* Returns if import declarations should be guessed.
|
||||
* @return {@code true} if imports should be guessed, otherwise {@code false}
|
||||
*/
|
||||
boolean isGuessImports();
|
||||
|
||||
/**
|
||||
* Returns if jar dependencies should be guessed.
|
||||
* @return {@code true} if dependencies should be guessed, otherwise {@code false}
|
||||
*/
|
||||
boolean isGuessDependencies();
|
||||
|
||||
/**
|
||||
* Returns true if auto-configuration transformations should be applied.
|
||||
* @return {@code true} if auto-configuration transformations should be applied,
|
||||
* otherwise {@code false}
|
||||
*/
|
||||
boolean isAutoconfigure();
|
||||
|
||||
/**
|
||||
* Returns the classpath for local resources.
|
||||
* @return a path for local resources
|
||||
*/
|
||||
String[] getClasspath();
|
||||
|
||||
/**
|
||||
* Returns the configuration for the repositories that will be used by the compiler to
|
||||
* resolve dependencies.
|
||||
* @return the repository configurations
|
||||
*/
|
||||
List<RepositoryConfiguration> getRepositoryConfiguration();
|
||||
|
||||
/**
|
||||
* Returns if running in quiet mode.
|
||||
* @return {@code true} if running in quiet mode
|
||||
*/
|
||||
boolean isQuiet();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.compiler;
|
||||
|
||||
/**
|
||||
* The scope in which a groovy compiler operates.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public enum GroovyCompilerScope {
|
||||
|
||||
/**
|
||||
* Default scope, exposes groovy.jar (loaded from the parent) and the shared cli
|
||||
* package (loaded via groovy classloader).
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* Extension scope, allows full access to internal CLI classes.
|
||||
*/
|
||||
EXTENSION
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.maven.settings.Profile;
|
||||
import org.apache.maven.settings.Repository;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.Interpolator;
|
||||
import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
|
||||
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
import org.springframework.boot.cli.compiler.maven.MavenSettings;
|
||||
import org.springframework.boot.cli.compiler.maven.MavenSettingsReader;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Factory used to create {@link RepositoryConfiguration}s.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public final class RepositoryConfigurationFactory {
|
||||
|
||||
private static final RepositoryConfiguration MAVEN_CENTRAL = new RepositoryConfiguration(
|
||||
"central", URI.create("http://repo1.maven.org/maven2/"), false);
|
||||
|
||||
private static final RepositoryConfiguration SPRING_MILESTONE = new RepositoryConfiguration(
|
||||
"spring-milestone", URI.create("http://repo.spring.io/milestone"), false);
|
||||
|
||||
private static final RepositoryConfiguration SPRING_SNAPSHOT = new RepositoryConfiguration(
|
||||
"spring-snapshot", URI.create("http://repo.spring.io/snapshot"), true);
|
||||
|
||||
private RepositoryConfigurationFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new default repository configuration.
|
||||
* @return the newly-created default repository configuration
|
||||
*/
|
||||
public static List<RepositoryConfiguration> createDefaultRepositoryConfiguration() {
|
||||
MavenSettings mavenSettings = new MavenSettingsReader().readSettings();
|
||||
List<RepositoryConfiguration> repositoryConfiguration = new ArrayList<>();
|
||||
repositoryConfiguration.add(MAVEN_CENTRAL);
|
||||
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
|
||||
repositoryConfiguration.add(SPRING_MILESTONE);
|
||||
repositoryConfiguration.add(SPRING_SNAPSHOT);
|
||||
}
|
||||
addDefaultCacheAsRepository(mavenSettings.getLocalRepository(),
|
||||
repositoryConfiguration);
|
||||
addActiveProfileRepositories(mavenSettings.getActiveProfiles(),
|
||||
repositoryConfiguration);
|
||||
return repositoryConfiguration;
|
||||
}
|
||||
|
||||
private static void addDefaultCacheAsRepository(String localRepository,
|
||||
List<RepositoryConfiguration> repositoryConfiguration) {
|
||||
RepositoryConfiguration repository = new RepositoryConfiguration("local",
|
||||
getLocalRepositoryDirectory(localRepository).toURI(), true);
|
||||
if (!repositoryConfiguration.contains(repository)) {
|
||||
repositoryConfiguration.add(0, repository);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addActiveProfileRepositories(List<Profile> activeProfiles,
|
||||
List<RepositoryConfiguration> configurations) {
|
||||
for (Profile activeProfile : activeProfiles) {
|
||||
Interpolator interpolator = new RegexBasedInterpolator();
|
||||
interpolator.addValueSource(
|
||||
new PropertiesBasedValueSource(activeProfile.getProperties()));
|
||||
for (Repository repository : activeProfile.getRepositories()) {
|
||||
configurations.add(getRepositoryConfiguration(interpolator, repository));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static RepositoryConfiguration getRepositoryConfiguration(
|
||||
Interpolator interpolator, Repository repository) {
|
||||
String name = interpolate(interpolator, repository.getId());
|
||||
String url = interpolate(interpolator, repository.getUrl());
|
||||
boolean snapshotsEnabled = false;
|
||||
if (repository.getSnapshots() != null) {
|
||||
snapshotsEnabled = repository.getSnapshots().isEnabled();
|
||||
}
|
||||
return new RepositoryConfiguration(name, URI.create(url), snapshotsEnabled);
|
||||
}
|
||||
|
||||
private static String interpolate(Interpolator interpolator, String value) {
|
||||
try {
|
||||
return interpolator.interpolate(value);
|
||||
}
|
||||
catch (InterpolationException ex) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static File getLocalRepositoryDirectory(String localRepository) {
|
||||
if (StringUtils.hasText(localRepository)) {
|
||||
return new File(localRepository);
|
||||
}
|
||||
return new File(getM2HomeDirectory(), "repository");
|
||||
}
|
||||
|
||||
private static File getM2HomeDirectory() {
|
||||
String mavenRoot = System.getProperty("maven.home");
|
||||
if (StringUtils.hasLength(mavenRoot)) {
|
||||
return new File(mavenRoot);
|
||||
}
|
||||
return new File(System.getProperty("user.home"), ".m2");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import groovy.lang.Grab;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to resolve {@link Grab} artifact coordinates.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Order(ResolveDependencyCoordinatesTransformation.ORDER)
|
||||
public class ResolveDependencyCoordinatesTransformation
|
||||
extends AnnotatedNodeASTTransformation {
|
||||
|
||||
/**
|
||||
* The order of the transformation.
|
||||
*/
|
||||
public static final int ORDER = DependencyManagementBomTransformation.ORDER + 300;
|
||||
|
||||
private static final Set<String> GRAB_ANNOTATION_NAMES = Collections
|
||||
.unmodifiableSet(new HashSet<>(
|
||||
Arrays.asList(Grab.class.getName(), Grab.class.getSimpleName())));
|
||||
|
||||
private final DependencyResolutionContext resolutionContext;
|
||||
|
||||
public ResolveDependencyCoordinatesTransformation(
|
||||
DependencyResolutionContext resolutionContext) {
|
||||
super(GRAB_ANNOTATION_NAMES, false);
|
||||
this.resolutionContext = resolutionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) {
|
||||
for (AnnotationNode annotationNode : annotationNodes) {
|
||||
transformGrabAnnotation(annotationNode);
|
||||
}
|
||||
}
|
||||
|
||||
private void transformGrabAnnotation(AnnotationNode grabAnnotation) {
|
||||
grabAnnotation.setMember("initClass", new ConstantExpression(false));
|
||||
String value = getValue(grabAnnotation);
|
||||
if (value != null && !isConvenienceForm(value)) {
|
||||
applyGroupAndVersion(grabAnnotation, value);
|
||||
}
|
||||
}
|
||||
|
||||
private String getValue(AnnotationNode annotation) {
|
||||
Expression expression = annotation.getMember("value");
|
||||
if (expression instanceof ConstantExpression) {
|
||||
Object value = ((ConstantExpression) expression).getValue();
|
||||
return (value instanceof String ? (String) value : null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isConvenienceForm(String value) {
|
||||
return value.contains(":") || value.contains("#");
|
||||
}
|
||||
|
||||
private void applyGroupAndVersion(AnnotationNode annotation, String module) {
|
||||
if (module != null) {
|
||||
setMember(annotation, "module", module);
|
||||
}
|
||||
else {
|
||||
Expression expression = annotation.getMembers().get("module");
|
||||
module = (String) ((ConstantExpression) expression).getValue();
|
||||
}
|
||||
if (annotation.getMember("group") == null) {
|
||||
setMember(annotation, "group", this.resolutionContext
|
||||
.getArtifactCoordinatesResolver().getGroupId(module));
|
||||
}
|
||||
if (annotation.getMember("version") == null) {
|
||||
setMember(annotation, "version", this.resolutionContext
|
||||
.getArtifactCoordinatesResolver().getVersion(module));
|
||||
}
|
||||
}
|
||||
|
||||
private void setMember(AnnotationNode annotation, String name, String value) {
|
||||
ConstantExpression expression = new ConstantExpression(value);
|
||||
annotation.setMember(name, expression);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import org.codehaus.groovy.ast.ClassHelper;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.classgen.GeneratorContext;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
|
||||
/**
|
||||
* Smart extension of {@link ImportCustomizer} that will only add a specific import if a
|
||||
* class with the same name is not already explicitly imported.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.1
|
||||
*/
|
||||
class SmartImportCustomizer extends ImportCustomizer {
|
||||
|
||||
private SourceUnit source;
|
||||
|
||||
SmartImportCustomizer(SourceUnit source, GeneratorContext context,
|
||||
ClassNode classNode) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImportCustomizer addImport(String alias, String className) {
|
||||
if (this.source.getAST()
|
||||
.getImport(ClassHelper.make(className).getNameWithoutPackage()) == null) {
|
||||
super.addImport(alias, className);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImportCustomizer addImports(String... imports) {
|
||||
for (String alias : imports) {
|
||||
if (this.source.getAST()
|
||||
.getImport(ClassHelper.make(alias).getNameWithoutPackage()) == null) {
|
||||
super.addImports(alias);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.compiler;
|
||||
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
/**
|
||||
* Marker interface for AST transformations that should be installed automatically from
|
||||
* {@code META-INF/services}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SpringBootAstTransformation extends ASTTransformation {
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user