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:
394
spring-boot-project/spring-boot-cli/pom.xml
Normal file
394
spring-boot-project/spring-boot-cli/pom.xml
Normal file
@@ -0,0 +1,394 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../spring-boot-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-boot-cli</artifactId>
|
||||
<name>Spring Boot CLI</name>
|
||||
<description>Spring Boot CLI</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<start-class>org.springframework.boot.cli.SpringCli</start-class>
|
||||
<spring.profiles.active>default</spring.profiles.active>
|
||||
<generated.pom.dir>${project.build.directory}/generated-resources/org/springframework/boot/cli/compiler/dependencies</generated.pom.dir>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>integration</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<spring.profiles.active>integration</spring.profiles.active>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-loader-tools</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vaadin.external.google</groupId>
|
||||
<artifactId>android-json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-aether-provider</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>org.eclipse.sisu.plexus</artifactId>
|
||||
<groupId>org.eclipse.sisu</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-settings-builder</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-component-api</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-connector-basic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-file</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-http</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-util</artifactId>
|
||||
</dependency>
|
||||
<!-- Provided -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-templates</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.build.directory}/generated-resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${basedir}/src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<additionalClasspathElements>
|
||||
<additionalClasspathElement>${project.build.directory}/generated-resources</additionalClasspathElement>
|
||||
</additionalClasspathElements>
|
||||
<systemPropertyVariables>
|
||||
<spring.profiles.active>${spring.profiles.active}</spring.profiles.active>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Build an executable JAR manually since we can't easily depend on
|
||||
a maven plugin that is part of the reactor -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-effective-pom</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>effective-pom</type>
|
||||
<overWrite>true</overWrite>
|
||||
<outputDirectory>${generated.pom.dir}</outputDirectory>
|
||||
<destFileName>effective-pom.xml</destFileName>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>unpack</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-loader</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>jar</type>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<outputDirectory>${project.build.directory}/assembly</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/assembly/BOOT-INF/lib</outputDirectory>
|
||||
<includeScope>runtime</includeScope>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jar-with-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/jar-with-dependencies.xml</descriptor>
|
||||
</descriptors>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
<mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Start-Class>${start-class}</Start-Class>
|
||||
<Class-Loader>groovy.lang.GroovyClassLoader</Class-Loader>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>bin-package</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/bin-package.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ant-contrib</groupId>
|
||||
<artifactId>ant-contrib</artifactId>
|
||||
<version>1.0b3</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>ant</groupId>
|
||||
<artifactId>ant</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.ant</groupId>
|
||||
<artifactId>ant-nodeps</artifactId>
|
||||
<version>1.8.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.tigris.antelope</groupId>
|
||||
<artifactId>antelopetasks</artifactId>
|
||||
<version>3.2.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>homebrew</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
<inherited>false</inherited>
|
||||
<configuration>
|
||||
<target>
|
||||
<taskdef resource="net/sf/antcontrib/antcontrib.properties" />
|
||||
<taskdef name="stringutil" classname="ise.antelope.tasks.StringUtilTask" />
|
||||
<var name="version-type" value="${project.version}" />
|
||||
<propertyregex property="version-type" override="true" input="${version-type}" regexp=".*\.(.*)" replace="\1" />
|
||||
<propertyregex property="version-type" override="true" input="${version-type}" regexp="(M)\d+" replace="MILESTONE" />
|
||||
<propertyregex property="version-type" override="true" input="${version-type}" regexp="(RC)\d+" replace="MILESTONE" />
|
||||
<propertyregex property="version-type" override="true" input="${version-type}" regexp="BUILD-(.*)" replace="SNAPSHOT" />
|
||||
<stringutil string="${version-type}" property="repo">
|
||||
<lowercase />
|
||||
</stringutil>
|
||||
<checksum algorithm="sha-256" file="${project.build.directory}/spring-boot-cli-${project.version}-bin.tar.gz" property="checksum" />
|
||||
<echo message="Customizing homebrew for ${project.version} with checksum ${checksum} in ${repo} repo" />
|
||||
<copy file="${basedir}/src/main/homebrew/springboot.rb" tofile="${project.build.directory}/springboot.rb" overwrite="true">
|
||||
<filterchain>
|
||||
<expandproperties />
|
||||
</filterchain>
|
||||
</copy>
|
||||
<attachartifact file="${project.build.directory}/springboot.rb" classifier="homebrew" type="rb" />
|
||||
</target>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-test-source</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>add-test-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/it/java</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>
|
||||
org.apache.maven.plugins
|
||||
</groupId>
|
||||
<artifactId>
|
||||
maven-dependency-plugin
|
||||
</artifactId>
|
||||
<versionRange>
|
||||
[1.0.0,)
|
||||
</versionRange>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<ignore />
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
12
spring-boot-project/spring-boot-cli/samples/actuator.groovy
Normal file
12
spring-boot-project/spring-boot-cli/samples/actuator.groovy
Normal file
@@ -0,0 +1,12 @@
|
||||
package org.test
|
||||
|
||||
@Grab("spring-boot-starter-actuator")
|
||||
|
||||
@RestController
|
||||
class SampleController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public def hello() {
|
||||
[message: "Hello World!"]
|
||||
}
|
||||
}
|
||||
23
spring-boot-project/spring-boot-cli/samples/app.groovy
Normal file
23
spring-boot-project/spring-boot-cli/samples/app.groovy
Normal file
@@ -0,0 +1,23 @@
|
||||
package org.test
|
||||
|
||||
@Component
|
||||
class Example implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private MyService myService
|
||||
|
||||
void run(String... args) {
|
||||
println "Hello ${this.myService.sayWorld()} From ${getClass().getClassLoader().getResource('samples/app.groovy')}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Service
|
||||
class MyService {
|
||||
|
||||
String sayWorld() {
|
||||
return "World!"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
spring-boot-project/spring-boot-cli/samples/beans.groovy
Normal file
15
spring-boot-project/spring-boot-cli/samples/beans.groovy
Normal file
@@ -0,0 +1,15 @@
|
||||
@RestController
|
||||
class Application {
|
||||
|
||||
@Autowired
|
||||
String foo
|
||||
|
||||
@RequestMapping("/")
|
||||
String home() {
|
||||
"Hello ${foo}!"
|
||||
}
|
||||
}
|
||||
|
||||
beans {
|
||||
foo String, "World"
|
||||
}
|
||||
47
spring-boot-project/spring-boot-cli/samples/caching.groovy
Normal file
47
spring-boot-project/spring-boot-cli/samples/caching.groovy
Normal file
@@ -0,0 +1,47 @@
|
||||
package org.test
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
class Sample {
|
||||
|
||||
@Bean CacheManager cacheManager() {
|
||||
new ConcurrentMapCacheManager()
|
||||
}
|
||||
|
||||
@Component
|
||||
static class MyClient implements CommandLineRunner {
|
||||
|
||||
private final MyService myService
|
||||
|
||||
@Autowired
|
||||
MyClient(MyService myService) {
|
||||
this.myService = myService
|
||||
}
|
||||
|
||||
void run(String... args) {
|
||||
long counter = myService.get('someKey')
|
||||
long counter2 = myService.get('someKey')
|
||||
if (counter == counter2) {
|
||||
println 'Hello World'
|
||||
} else {
|
||||
println 'Something went wrong with the cache setup'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
static class MyService {
|
||||
|
||||
private final AtomicLong counter = new AtomicLong()
|
||||
|
||||
@Cacheable('foo')
|
||||
Long get(String id) {
|
||||
return counter.getAndIncrement()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
21
spring-boot-project/spring-boot-cli/samples/device.groovy
Normal file
21
spring-boot-project/spring-boot-cli/samples/device.groovy
Normal file
@@ -0,0 +1,21 @@
|
||||
package org.test
|
||||
|
||||
@Controller
|
||||
@EnableDeviceResolver
|
||||
class Example {
|
||||
|
||||
@RequestMapping("/")
|
||||
@ResponseBody
|
||||
String helloWorld(Device device) {
|
||||
if (device.isNormal()) {
|
||||
"Hello Normal Device!"
|
||||
} else if (device.isMobile()) {
|
||||
"Hello Mobile Device!"
|
||||
} else if (device.isTablet()) {
|
||||
"Hello Tablet Device!"
|
||||
} else {
|
||||
"Hello Unknown Device!"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
24
spring-boot-project/spring-boot-cli/samples/http.groovy
Normal file
24
spring-boot-project/spring-boot-cli/samples/http.groovy
Normal file
@@ -0,0 +1,24 @@
|
||||
package org.test
|
||||
|
||||
@Grab("org.codehaus.groovy.modules.http-builder:http-builder:0.5.2") // This one just to test dependency resolution
|
||||
import groovyx.net.http.*
|
||||
|
||||
@Controller
|
||||
class Example implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@RequestMapping("/")
|
||||
@ResponseBody
|
||||
public String helloWorld() {
|
||||
return "World!"
|
||||
}
|
||||
|
||||
void run(String... args) {
|
||||
def port = context.webServer.port;
|
||||
def world = new RESTClient("http://localhost:" + port).get(path:"/").data.text
|
||||
print "Hello " + world
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.test
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
class SpringIntegrationExample implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Bean
|
||||
DirectChannel input() {
|
||||
new DirectChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
void run(String... args) {
|
||||
println()
|
||||
println '>>>> ' + new MessagingTemplate(input()).convertSendAndReceive("World", String) + ' <<<<'
|
||||
println()
|
||||
/*
|
||||
* Since this is a simple application that we want to exit right away,
|
||||
* close the context. For an active integration application, with pollers
|
||||
* etc, you can either suspend the main thread here (e.g. with System.in.read()),
|
||||
* or exit the run() method without closing the context, and stop the
|
||||
* application later using some other technique (kill, JMX etc).
|
||||
*/
|
||||
context.close()
|
||||
}
|
||||
}
|
||||
|
||||
@MessageEndpoint
|
||||
class HelloTransformer {
|
||||
|
||||
@Transformer(inputChannel="input")
|
||||
String transform(String payload) {
|
||||
"Hello, ${payload}"
|
||||
}
|
||||
|
||||
}
|
||||
33
spring-boot-project/spring-boot-cli/samples/jms.groovy
Normal file
33
spring-boot-project/spring-boot-cli/samples/jms.groovy
Normal file
@@ -0,0 +1,33 @@
|
||||
package org.test
|
||||
|
||||
@Grab("spring-boot-starter-artemis")
|
||||
@Grab("artemis-jms-server")
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@Log
|
||||
@Configuration
|
||||
@EnableJms
|
||||
class JmsExample implements CommandLineRunner {
|
||||
|
||||
private CountDownLatch latch = new CountDownLatch(1)
|
||||
|
||||
@Autowired
|
||||
JmsTemplate jmsTemplate
|
||||
|
||||
void run(String... args) {
|
||||
def messageCreator = { session ->
|
||||
session.createObjectMessage("Greetings from Spring Boot via Artemis")
|
||||
} as MessageCreator
|
||||
log.info "Sending JMS message..."
|
||||
jmsTemplate.send("spring-boot", messageCreator)
|
||||
log.info "Send JMS message, waiting..."
|
||||
latch.await()
|
||||
}
|
||||
|
||||
@JmsListener(destination = 'spring-boot')
|
||||
def receive(String message) {
|
||||
log.info "Received ${message}"
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
}
|
||||
33
spring-boot-project/spring-boot-cli/samples/job.groovy
Normal file
33
spring-boot-project/spring-boot-cli/samples/job.groovy
Normal file
@@ -0,0 +1,33 @@
|
||||
package org.test
|
||||
|
||||
@Grab("hsqldb")
|
||||
@Configuration
|
||||
@EnableBatchProcessing
|
||||
class JobConfig {
|
||||
|
||||
@Autowired
|
||||
private JobBuilderFactory jobs
|
||||
|
||||
@Autowired
|
||||
private StepBuilderFactory steps
|
||||
|
||||
@Bean
|
||||
protected Tasklet tasklet() {
|
||||
return new Tasklet() {
|
||||
@Override
|
||||
RepeatStatus execute(StepContribution contribution, ChunkContext context) {
|
||||
return RepeatStatus.FINISHED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
Job job() throws Exception {
|
||||
return jobs.get("job").start(step1()).build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected Step step1() throws Exception {
|
||||
return steps.get("step1").tasklet(tasklet()).build()
|
||||
}
|
||||
}
|
||||
32
spring-boot-project/spring-boot-cli/samples/rabbit.groovy
Normal file
32
spring-boot-project/spring-boot-cli/samples/rabbit.groovy
Normal file
@@ -0,0 +1,32 @@
|
||||
package org.test
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@Log
|
||||
@Configuration
|
||||
@EnableRabbit
|
||||
class RabbitExample implements CommandLineRunner {
|
||||
|
||||
private CountDownLatch latch = new CountDownLatch(1)
|
||||
|
||||
@Autowired
|
||||
RabbitTemplate rabbitTemplate
|
||||
|
||||
void run(String... args) {
|
||||
log.info "Sending RabbitMQ message..."
|
||||
rabbitTemplate.convertAndSend("spring-boot", "Greetings from Spring Boot via RabbitMQ")
|
||||
latch.await()
|
||||
}
|
||||
|
||||
@RabbitListener(queues = 'spring-boot')
|
||||
def receive(String message) {
|
||||
log.info "Received ${message}"
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
@Bean
|
||||
Queue queue() {
|
||||
new Queue("spring-boot", false)
|
||||
}
|
||||
|
||||
}
|
||||
28
spring-boot-project/spring-boot-cli/samples/retry.groovy
Normal file
28
spring-boot-project/spring-boot-cli/samples/retry.groovy
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.test
|
||||
|
||||
@EnableRetry
|
||||
@Component
|
||||
class Example implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private MyService myService
|
||||
|
||||
void run(String... args) {
|
||||
println "Hello ${this.myService.sayWorld()} From ${getClass().getClassLoader().getResource('samples/retry.groovy')}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Service
|
||||
class MyService {
|
||||
|
||||
static int count = 0
|
||||
|
||||
@Retryable
|
||||
String sayWorld() {
|
||||
if (count++==0) {
|
||||
throw new IllegalStateException("Planned")
|
||||
}
|
||||
return "World!"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.test
|
||||
|
||||
class Runner implements CommandLineRunner {
|
||||
|
||||
void run(String... args) {
|
||||
print "Hello World!"
|
||||
}
|
||||
}
|
||||
9
spring-boot-project/spring-boot-cli/samples/runner.xml
Normal file
9
spring-boot-project/spring-boot-cli/samples/runner.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="runner" class="org.test.Runner"/>
|
||||
|
||||
</beans>
|
||||
|
||||
13
spring-boot-project/spring-boot-cli/samples/secure.groovy
Normal file
13
spring-boot-project/spring-boot-cli/samples/secure.groovy
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.test
|
||||
|
||||
@Grab("spring-boot-starter-security")
|
||||
@Grab("spring-boot-starter-actuator")
|
||||
|
||||
@RestController
|
||||
class SampleController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public def hello() {
|
||||
[message: "Hello World!"]
|
||||
}
|
||||
}
|
||||
24
spring-boot-project/spring-boot-cli/samples/template.groovy
Normal file
24
spring-boot-project/spring-boot-cli/samples/template.groovy
Normal file
@@ -0,0 +1,24 @@
|
||||
package org.test
|
||||
|
||||
import static org.springframework.boot.groovy.GroovyTemplate.*;
|
||||
|
||||
@Component
|
||||
class Example implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private MyService myService
|
||||
|
||||
@Override
|
||||
void run(String... args) {
|
||||
print template("test.txt", ["message":myService.sayWorld()])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Service
|
||||
class MyService {
|
||||
|
||||
String sayWorld() {
|
||||
return "World"
|
||||
}
|
||||
}
|
||||
17
spring-boot-project/spring-boot-cli/samples/tx.groovy
Normal file
17
spring-boot-project/spring-boot-cli/samples/tx.groovy
Normal file
@@ -0,0 +1,17 @@
|
||||
package org.test
|
||||
|
||||
@Grab("hsqldb")
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
class Example implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
JdbcTemplate jdbcTemplate
|
||||
|
||||
@Transactional
|
||||
void run(String... args) {
|
||||
println "Foo count=" + jdbcTemplate.queryForObject("SELECT COUNT(*) from FOO", Integer)
|
||||
}
|
||||
}
|
||||
|
||||
33
spring-boot-project/spring-boot-cli/samples/ui.groovy
Normal file
33
spring-boot-project/spring-boot-cli/samples/ui.groovy
Normal file
@@ -0,0 +1,33 @@
|
||||
package app
|
||||
|
||||
@Grab("thymeleaf-spring5")
|
||||
@Controller
|
||||
class Example {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String helloWorld(Map<String,Object> model) {
|
||||
model.putAll([title: "My Page", date: new Date(), message: "Hello World"])
|
||||
return "home";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Log
|
||||
class MvcConfiguration extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
void addInterceptors(InterceptorRegistry registry) {
|
||||
log.info "Registering interceptor"
|
||||
registry.addInterceptor(interceptor())
|
||||
}
|
||||
|
||||
@Bean
|
||||
HandlerInterceptor interceptor() {
|
||||
log.info "Creating interceptor"
|
||||
[
|
||||
postHandle: { request, response, handler, mav ->
|
||||
log.info "Intercepted: model=" + mav.model
|
||||
}
|
||||
] as HandlerInterceptorAdapter
|
||||
}
|
||||
}
|
||||
21
spring-boot-project/spring-boot-cli/samples/web.groovy
Normal file
21
spring-boot-project/spring-boot-cli/samples/web.groovy
Normal file
@@ -0,0 +1,21 @@
|
||||
@Controller
|
||||
class Example {
|
||||
|
||||
@Autowired
|
||||
private MyService myService;
|
||||
|
||||
@RequestMapping("/")
|
||||
@ResponseBody
|
||||
public String helloWorld() {
|
||||
return myService.sayWorld();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Service
|
||||
class MyService {
|
||||
|
||||
public String sayWorld() {
|
||||
return "World!";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user