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:
Phillip Webb
2017-09-19 14:29:46 -07:00
parent 0419d42b7c
commit 0ba4830b4f
4023 changed files with 0 additions and 0 deletions

View 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>

View File

@@ -0,0 +1,12 @@
package org.test
@Grab("spring-boot-starter-actuator")
@RestController
class SampleController {
@RequestMapping("/")
public def hello() {
[message: "Hello World!"]
}
}

View 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!"
}
}

View File

@@ -0,0 +1,15 @@
@RestController
class Application {
@Autowired
String foo
@RequestMapping("/")
String home() {
"Hello ${foo}!"
}
}
beans {
foo String, "World"
}

View 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()
}
}
}

View 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!"
}
}
}

View 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
}
}

View File

@@ -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}"
}
}

View 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()
}
}

View 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()
}
}

View 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)
}
}

View 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!"
}
}

View File

@@ -0,0 +1,8 @@
package org.test
class Runner implements CommandLineRunner {
void run(String... args) {
print "Hello World!"
}
}

View 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>

View 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!"]
}
}

View 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"
}
}

View 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)
}
}

View 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
}
}

View 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!";
}
}

View File

@@ -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:"));
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
@GrabResolver(name='clojars.org', root='http://clojars.org/repo')
@Grab('redis.embedded:embedded-redis:0.2')
@Component
class EmbeddedRedis {
}

View File

@@ -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!'
}
}

View File

@@ -0,0 +1 @@
Goodbye ${world}

View File

@@ -0,0 +1,10 @@
package org.test
@Component
class Example implements CommandLineRunner {
void run(String... args) {
print "Ssshh"
}
}

View File

@@ -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")
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 "$@"

View 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 "$@"

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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()]);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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] &lt;file&gt;'. 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;
}

View File

@@ -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
}
}

View File

@@ -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();
}

View File

@@ -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("");
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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()));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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()]);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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