Remove rarely used commands from the CLI

Closes gh-32263
pull/32224/head
Andy Wilkinson 2 years ago
parent e112657e1a
commit 0555dda63d

@ -125,12 +125,6 @@ For example, if you want to get started using Spring and JPA for database access
=== spring-boot-cli
The Spring command-line application compiles and runs Groovy source, allowing you to write the absolute minimum amount of code to get an application running.
Spring CLI can also watch files, automatically recompiling and restarting when they change.
=== spring-boot-actuator
Actuator endpoints let you monitor and interact with your application.
Spring Boot Actuator provides the infrastructure required for actuator endpoints.
@ -170,12 +164,6 @@ Developer tools are automatically disabled when running a fully packaged applica
== Samples
Groovy samples for use with the command line application are available in link:spring-boot-project/spring-boot-cli/samples[spring-boot-cli/samples].
To run the CLI samples, type `spring run <sample>.groovy` from the samples directory.
== Guides
The https://spring.io/[spring.io] site contains several guides that show how to use Spring Boot step-by-step:

@ -18,9 +18,6 @@ configurations {
dependencies {
compileOnlyProject(project(":spring-boot-project:spring-boot"))
compileOnly("jakarta.servlet:jakarta.servlet-api")
compileOnly("org.apache.groovy:groovy-templates")
compileOnly("org.springframework:spring-web")
dependenciesBom(project(path: ":spring-boot-project:spring-boot-dependencies", configuration: "effectiveBom"))
@ -31,30 +28,10 @@ dependencies {
implementation("org.apache.httpcomponents:httpclient") {
exclude group: "commons-logging", module: "commons-logging"
}
implementation("org.apache.maven:maven-model")
implementation("org.apache.maven:maven-resolver-provider") {
exclude group: "com.google.guava", module: "guava"
exclude group: "javax.inject", module: "javax.inject"
}
implementation("org.apache.maven.resolver:maven-resolver-connector-basic")
implementation("org.apache.maven.resolver:maven-resolver-transport-file")
implementation("org.apache.maven.resolver:maven-resolver-transport-http") {
exclude group: "org.slf4j", module: "jcl-over-slf4j"
}
implementation("org.apache.maven:maven-settings-builder") {
exclude group: "javax.inject", module: "javax.inject"
}
implementation("org.apache.groovy:groovy")
implementation("org.slf4j:slf4j-simple")
implementation("org.sonatype.plexus:plexus-sec-dispatcher")
implementation("org.sonatype.sisu:sisu-inject-plexus") {
exclude group: "javax.enterprise", module: "cdi-api"
exclude group: "org.sonatype.sisu", module: "sisu-inject-bean"
}
implementation("org.springframework:spring-core")
implementation("org.springframework.security:spring-security-crypto")
intTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-tools"))
intTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
intTestImplementation("org.assertj:assertj-core")
intTestImplementation("org.junit.jupiter:junit-jupiter")
@ -62,47 +39,14 @@ dependencies {
loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader"))
testCompileOnly("org.apache.tomcat.embed:tomcat-embed-core")
testImplementation(project(":spring-boot-project:spring-boot"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation("org.assertj:assertj-core")
testImplementation("org.apache.groovy:groovy-templates")
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("org.springframework:spring-test")
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-amqp", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-aop", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-artemis", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-batch", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-data-jpa", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-jdbc", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-integration", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-security", configuration: "mavenRepository"))
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-web", configuration: "mavenRepository"))
}
task syncSpringBootDependenciesBom(type: Sync) {
destinationDir = file("${buildDir}/generated-resources/org/springframework/boot/cli/compiler/dependencies")
from configurations.dependenciesBom
}
task syncTestRepository(type: Sync) {
destinationDir = file("${buildDir}/test-repository")
from configurations.testRepository
}
sourceSets {
main {
output.dir("${buildDir}/generated-resources", builtBy: "syncSpringBootDependenciesBom")
}
}
test {
dependsOn syncTestRepository
}
task fullJar(type: Jar) {
@ -124,7 +68,6 @@ task fullJar(type: Jar) {
}
manifest {
attributes(
"Class-Loader": "groovy.lang.GroovyClassLoader",
"Main-Class": "org.springframework.boot.loader.JarLauncher",
"Start-Class": "org.springframework.boot.cli.SpringCli"
)
@ -155,7 +98,7 @@ task zip(type: Zip) {
}
intTest {
dependsOn syncTestRepository, zip
dependsOn zip
}
task tar(type: Tar) {

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

@ -1,23 +0,0 @@
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!"
}
}

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

@ -1,47 +0,0 @@
package org.test
import java.util.concurrent.atomic.AtomicLong
@Configuration(proxyBeanMethods = false)
@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()
}
}
}

@ -1,23 +0,0 @@
package org.test
import org.springframework.web.client.RestTemplate;
@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 RestTemplate().getForObject("http://localhost:" + port + "/", String.class);
print "Hello " + world
}
}

@ -1,39 +0,0 @@
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}"
}
}

@ -1,33 +0,0 @@
package org.test
@Grab("spring-boot-starter-artemis")
@Grab("artemis-jakarta-server")
import java.util.concurrent.CountDownLatch
@Log
@Configuration(proxyBeanMethods = false)
@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()
}
}

@ -1,38 +0,0 @@
package org.test
import org.springframework.transaction.TransactionManager
@Grab("hsqldb")
@Configuration(proxyBeanMethods = false)
@EnableBatchProcessing
class JobConfig {
@Autowired
private JobBuilderFactory jobs
@Autowired
private StepBuilderFactory steps
@Autowired
private TransactionManager transactionManager
@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()).transactionManager(this.transactionManager).build()
}
}

@ -1,32 +0,0 @@
package org.test
import java.util.concurrent.CountDownLatch
@Log
@Configuration(proxyBeanMethods = false)
@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
org.springframework.amqp.core.Queue queue() {
new org.springframework.amqp.core.Queue("spring-boot", false)
}
}

@ -1,28 +0,0 @@
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!"
}
}

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

@ -1,9 +0,0 @@
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="runner" class="org.test.Runner"/>
</beans>

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

@ -1,24 +0,0 @@
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"
}
}

@ -1,17 +0,0 @@
package org.test
@Grab("hsqldb")
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement
class Example implements CommandLineRunner {
@Autowired
JdbcTemplate jdbcTemplate
@Transactional
void run(String... args) {
println "Foo count=" + jdbcTemplate.queryForObject("SELECT COUNT(*) from FOO", Integer)
}
}

@ -1,12 +0,0 @@
package app
@Grab("thymeleaf-spring6")
@Controller
class Example {
@RequestMapping("/")
public String helloWorld(Map<String,Object> model) {
model.putAll([title: "My Page", date: new Date(), message: "Hello World"])
return "home"
}
}

@ -1,21 +0,0 @@
@Controller
class Example {
@Autowired
private MyService myService
@RequestMapping("/")
@ResponseBody
public String helloWorld() {
return myService.sayWorld()
}
}
@Service
class MyService {
public String sayWorld() {
return "World!"
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2022 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.
@ -48,7 +48,7 @@ class CommandLineIT {
Invocation cli = this.cli.invoke("hint");
assertThat(cli.await()).isEqualTo(0);
assertThat(cli.getErrorOutput()).isEmpty();
assertThat(cli.getStandardOutputLines()).hasSize(11);
assertThat(cli.getStandardOutputLines()).hasSize(5);
}
@Test

@ -1,150 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
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.assertj.core.api.Assertions.assertThat;
/**
* Integration test for {@link JarCommand}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
class JarCommandIT {
private static final boolean JAVA_9_OR_LATER = isClassPresent("java.security.cert.URICertStoreParameters");
private CommandLineInvoker cli;
private File tempDir;
@BeforeEach
void setup(@TempDir File tempDir) {
this.cli = new CommandLineInvoker(new File("src/intTest/resources/jar-command"), tempDir);
this.tempDir = tempDir;
}
@Test
void noArguments() throws Exception {
Invocation invocation = this.cli.invoke("jar");
invocation.await();
assertThat(invocation.getStandardOutput()).isEqualTo("");
assertThat(invocation.getErrorOutput())
.contains("The name of the resulting jar and at least one source file must be specified");
}
@Test
void noSources() throws Exception {
Invocation invocation = this.cli.invoke("jar", "test-app.jar");
invocation.await();
assertThat(invocation.getStandardOutput()).isEqualTo("");
assertThat(invocation.getErrorOutput())
.contains("The name of the resulting jar and at least one source file must be specified");
}
@Test
void jarCreationWithGrabResolver() throws Exception {
File jar = new File(this.tempDir, "test-app.jar");
Invocation invocation = this.cli.invoke("run", jar.getAbsolutePath(), "bad.groovy");
invocation.await();
if (!JAVA_9_OR_LATER) {
assertThat(invocation.getErrorOutput()).isEqualTo("");
}
invocation = this.cli.invoke("jar", jar.getAbsolutePath(), "bad.groovy");
invocation.await();
if (!JAVA_9_OR_LATER) {
assertThat(invocation.getErrorOutput()).isEmpty();
}
assertThat(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()).isEqualTo("");
}
}
@Test
void jarCreation() throws Exception {
File jar = new File(this.tempDir, "test-app.jar");
Invocation invocation = this.cli.invoke("jar", jar.getAbsolutePath(), "jar.groovy");
invocation.await();
if (!JAVA_9_OR_LATER) {
assertThat(invocation.getErrorOutput()).isEmpty();
}
assertThat(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()).isEqualTo("");
}
assertThat(invocation.getStandardOutput()).contains("Hello World!")
.contains("/BOOT-INF/classes!/public/public.txt").contains("/BOOT-INF/classes!/resources/resource.txt")
.contains("/BOOT-INF/classes!/static/static.txt").contains("/BOOT-INF/classes!/templates/template.txt")
.contains("/BOOT-INF/classes!/root.properties").contains("Goodbye Mama");
}
@Test
void jarCreationWithIncludes() throws Exception {
File jar = new File(this.tempDir, "test-app.jar");
Invocation invocation = this.cli.invoke("jar", jar.getAbsolutePath(), "--include", "-public/**,-resources/**",
"jar.groovy");
invocation.await();
if (!JAVA_9_OR_LATER) {
assertThat(invocation.getErrorOutput()).isEmpty();
}
assertThat(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()).isEqualTo("");
}
assertThat(invocation.getStandardOutput()).contains("Hello World!").doesNotContain("/public/public.txt")
.doesNotContain("/resources/resource.txt").contains("/static/static.txt")
.contains("/templates/template.txt").contains("Goodbye Mama");
}
private static boolean isClassPresent(String name) {
try {
Class.forName(name);
return true;
}
catch (Exception ex) {
return false;
}
}
}

@ -1,66 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
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
*/
class WarCommandIT {
private CommandLineInvoker cli;
private File tempDir;
@BeforeEach
void setup(@TempDir File tempDir) {
this.cli = new CommandLineInvoker(new File("src/intTest/resources/war-command"), tempDir);
this.tempDir = tempDir;
}
@Test
void warCreation() throws Exception {
File war = new File(this.tempDir, "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();
}
}

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

@ -1,27 +0,0 @@
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!'
}
}

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

@ -1,17 +0,0 @@
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")
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 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.
@ -23,15 +23,9 @@ 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.encodepassword.EncodePasswordCommand;
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}.
@ -46,12 +40,6 @@ public class DefaultCommandFactory implements CommandFactory {
static {
List<Command> defaultCommands = new ArrayList<>();
defaultCommands.add(new VersionCommand());
defaultCommands.add(new RunCommand());
defaultCommands.add(new GrabCommand());
defaultCommands.add(new JarCommand());
defaultCommands.add(new WarCommand());
defaultCommands.add(new InstallCommand());
defaultCommands.add(new UninstallCommand());
defaultCommands.add(new InitCommand());
defaultCommands.add(new EncodePasswordCommand());
DEFAULT_COMMANDS = Collections.unmodifiableList(defaultCommands);

@ -1,86 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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 = Class.forName(getSpringApplicationClassName(), false, this.classLoader);
Constructor<?> constructor = applicationClass.getDeclaredConstructor(Class[].class);
constructor.setAccessible(true);
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);
}
}

@ -1,84 +0,0 @@
/*
* Copyright 2012-2021 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
*
* https://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 jakarta.servlet.ServletContext;
import jakarta.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");
return (stream != null) ? new Manifest(stream) : null;
}
@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] = Class.forName(this.sources[i], false, classLoader);
}
return builder.sources(sourceClasses).properties("spring.groovy.template.check-template-location=false");
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.
*/
/**
* Support classes for CLI applications.
*/
package org.springframework.boot.cli.app;

@ -1,85 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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
* @since 1.0.0
*/
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] = Class.forName(names[i], false, classLoader);
}
return classes;
}
public static void main(String[] args) throws Exception {
new PackagedSpringApplicationLauncher().run(args);
}
}

@ -1,21 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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;

@ -1,317 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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 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.Locale;
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(Locale.ENGLISH).endsWith("." + this.type),
() -> "The output '" + output + "' is not a " + this.type.toUpperCase(Locale.ENGLISH) + " 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 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(null, file, getLibraryScope(file), null, false, false, true));
}
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++) {
if (i != 0) {
builder.append(',');
}
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(null, entry.getFile(), LibraryScope.COMPILE, null, false, false, true));
}
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 moduleNode) {
visitModule(moduleNode);
}
}
}
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));
}
}
}
}
}
}

@ -1,51 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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
* @since 1.3.0
*/
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;
}
}
}

@ -1,222 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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);
}
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(findInDirectory(root));
}
}
return matchedResources;
}
private List<MatchedResource> findInDirectory(File directory) throws IOException {
List<MatchedResource> matchedResources = new ArrayList<>();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
new DirectoryResourceLoader(directory));
for (String include : this.includes) {
for (Resource candidate : resolver.getResources(include)) {
File file = candidate.getFile();
if (file.isFile()) {
MatchedResource matchedResource = new MatchedResource(directory, 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 directory.
*/
private static class DirectoryResourceLoader extends DefaultResourceLoader {
private final File rootDirectory;
DirectoryResourceLoader(File root) throws MalformedURLException {
super(new DirectoryClassLoader(root));
this.rootDirectory = root;
}
@Override
protected Resource getResourceByPath(String path) {
return new FileSystemResource(new File(this.rootDirectory, path));
}
}
/**
* {@link ClassLoader} backed by a directory.
*/
private static class DirectoryClassLoader extends URLClassLoader {
DirectoryClassLoader(File rootDirectory) throws MalformedURLException {
super(new URL[] { rootDirectory.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 rootDirectory, File file) {
String filePath = file.getAbsolutePath();
String rootDirectoryPath = rootDirectory.getAbsolutePath();
this.name = StringUtils.cleanPath(filePath.substring(rootDirectoryPath.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();
}
}
}

@ -1,70 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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()));
}
}
}

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.
*/
/**
* CLI commands for creating jars and wars.
*/
package org.springframework.boot.cli.command.archive;

@ -1,65 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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
* @since 1.0.0
*/
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;
}
}
}

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.
*/
/**
* CLI command for grabbing dependencies.
*/
package org.springframework.boot.cli.command.grab;

@ -1,40 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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
*/
@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;
}

@ -1,92 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.nio.charset.StandardCharsets;
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), StandardCharsets.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());
}
}
}

@ -1,68 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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;
}
}
}

@ -1,155 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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);
}
}
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.parseInt(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));
}
}
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();
}
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() && !extDirectory.mkdirs()) {
throw new IllegalStateException("Failed to create ext directory " + extDirectory);
}
return extDirectory;
}
private File getInstalled() {
return new File(getDefaultExtDirectory(), ".installed");
}
}

@ -1,83 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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;
}
}
}

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.
*/
/**
* CLI commands for installing and uninstalling CLI dependencies.
*/
package org.springframework.boot.cli.command.install;

@ -1,71 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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
* @since 1.0.0
*/
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;
}
}

@ -1,98 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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
* @since 1.0.0
*/
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;
}
}

@ -1,143 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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;
import org.springframework.util.StringUtils;
/**
* Extract source file options (anything following '--' in an {@link OptionSet}).
*
* @author Phillip Webb
* @author Dave Syer
* @author Greg Turnquist
* @author Andy Wilkinson
* @since 1.0.0
*/
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 filename) {
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.stream().map(this::asString).toArray(String[]::new);
}
private String asString(Object arg) {
return (arg != null) ? String.valueOf(arg) : null;
}
public List<String> getSources() {
return this.sources;
}
public String[] getSourcesArray() {
return StringUtils.toStringArray(this.sources);
}
}

@ -1,159 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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
* @since 1.0.0
* @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 (getHandler() != null) {
((RunOptionHandler) 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");
}
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);
}
}
}
}

@ -1,270 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://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
* @since 1.0.0
*/
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(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 set up
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.
*/
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
}
}
}
}
}

@ -1,44 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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
* @since 1.0.0
*/
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();
}

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.
*/
/**
* Classes for running CLI applications.
*/
package org.springframework.boot.cli.command.run;

@ -1,125 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
/**
* A base class for {@link ASTTransformation AST transformations} that are solely
* interested in {@link AnnotatedNode AnnotatedNodes}.
*
* @author Andy Wilkinson
* @since 1.1.0
*/
public abstract class AnnotatedNodeASTTransformation implements ASTTransformation {
private final Set<String> interestingAnnotationNames;
private final boolean removeAnnotations;
private SourceUnit sourceUnit;
protected AnnotatedNodeASTTransformation(Set<String> interestingAnnotationNames, boolean removeAnnotations) {
this.interestingAnnotationNames = interestingAnnotationNames;
this.removeAnnotations = removeAnnotations;
}
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
this.sourceUnit = source;
List<AnnotationNode> annotationNodes = new ArrayList<>();
ClassVisitor classVisitor = new ClassVisitor(source, annotationNodes);
for (ASTNode node : nodes) {
if (node instanceof ModuleNode module) {
visitAnnotatedNode(module.getPackage(), annotationNodes);
for (ImportNode importNode : module.getImports()) {
visitAnnotatedNode(importNode, annotationNodes);
}
for (ImportNode importNode : module.getStarImports()) {
visitAnnotatedNode(importNode, annotationNodes);
}
module.getStaticImports()
.forEach((name, importNode) -> visitAnnotatedNode(importNode, annotationNodes));
module.getStaticStarImports()
.forEach((name, importNode) -> visitAnnotatedNode(importNode, annotationNodes));
for (ClassNode classNode : module.getClasses()) {
visitAnnotatedNode(classNode, annotationNodes);
classNode.visitContents(classVisitor);
}
}
}
processAnnotationNodes(annotationNodes);
}
protected SourceUnit getSourceUnit() {
return this.sourceUnit;
}
protected abstract void processAnnotationNodes(List<AnnotationNode> annotationNodes);
private void visitAnnotatedNode(AnnotatedNode annotatedNode, List<AnnotationNode> annotatedNodes) {
if (annotatedNode != null) {
Iterator<AnnotationNode> annotationNodes = annotatedNode.getAnnotations().iterator();
while (annotationNodes.hasNext()) {
AnnotationNode annotationNode = annotationNodes.next();
if (this.interestingAnnotationNames.contains(annotationNode.getClassNode().getName())) {
annotatedNodes.add(annotationNode);
if (this.removeAnnotations) {
annotationNodes.remove();
}
}
}
}
}
private class ClassVisitor extends ClassCodeVisitorSupport {
private final SourceUnit source;
private final List<AnnotationNode> annotationNodes;
ClassVisitor(SourceUnit source, List<AnnotationNode> annotationNodes) {
this.source = source;
this.annotationNodes = annotationNodes;
}
@Override
protected SourceUnit getSourceUnit() {
return this.source;
}
@Override
public void visitAnnotations(AnnotatedNode node) {
visitAnnotatedNode(node, this.annotationNodes);
}
}
}

@ -1,187 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.springframework.util.PatternMatchUtils;
/**
* General purpose AST utilities.
*
* @author Phillip Webb
* @author Dave Syer
* @author Greg Turnquist
* @since 1.0.0
*/
public abstract class AstUtils {
/**
* Determine if a {@link ClassNode} has one or more of the specified annotations on
* the class or any of its methods. N.B. the type names are not normally fully
* qualified.
* @param node the class to examine
* @param annotations the annotations to look for
* @return {@code true} if at least one of the annotations is found, otherwise
* {@code false}
*/
public static boolean hasAtLeastOneAnnotation(ClassNode node, String... annotations) {
if (hasAtLeastOneAnnotation((AnnotatedNode) node, annotations)) {
return true;
}
for (MethodNode method : node.getMethods()) {
if (hasAtLeastOneAnnotation(method, annotations)) {
return true;
}
}
return false;
}
/**
* Determine if an {@link AnnotatedNode} has one or more of the specified annotations.
* N.B. the annotation type names are not normally fully qualified.
* @param node the node to examine
* @param annotations the annotations to look for
* @return {@code true} if at least one of the annotations is found, otherwise
* {@code false}
*/
public static boolean hasAtLeastOneAnnotation(AnnotatedNode node, String... annotations) {
for (AnnotationNode annotationNode : node.getAnnotations()) {
for (String annotation : annotations) {
if (PatternMatchUtils.simpleMatch(annotation, annotationNode.getClassNode().getName())) {
return true;
}
}
}
return false;
}
/**
* Determine if a {@link ClassNode} has one or more fields of the specified types or
* method returning one or more of the specified types. N.B. the type names are not
* normally fully qualified.
* @param node the class to examine
* @param types the types to look for
* @return {@code true} if at least one of the types is found, otherwise {@code false}
*/
public static boolean hasAtLeastOneFieldOrMethod(ClassNode node, String... types) {
Set<String> typesSet = new HashSet<>(Arrays.asList(types));
for (FieldNode field : node.getFields()) {
if (typesSet.contains(field.getType().getName())) {
return true;
}
}
for (MethodNode method : node.getMethods()) {
if (typesSet.contains(method.getReturnType().getName())) {
return true;
}
}
return false;
}
/**
* Determine if a {@link ClassNode} subclasses any of the specified types N.B. the
* type names are not normally fully qualified.
* @param node the class to examine
* @param types the types that may have been sub-classed
* @return {@code true} if the class subclasses any of the specified types, otherwise
* {@code false}
*/
public static boolean subclasses(ClassNode node, String... types) {
for (String type : types) {
if (node.getSuperClass().getName().equals(type)) {
return true;
}
}
return false;
}
public static boolean hasAtLeastOneInterface(ClassNode classNode, String... types) {
Set<String> typesSet = new HashSet<>(Arrays.asList(types));
for (ClassNode inter : classNode.getInterfaces()) {
if (typesSet.contains(inter.getName())) {
return true;
}
}
return false;
}
/**
* Extract a top-level {@code name} closure from inside this block if there is one,
* optionally removing it from the block at the same time.
* @param block a block statement (class definition)
* @param name the name to look for
* @param remove whether the extracted closure should be removed
* @return a beans Closure if one can be found, null otherwise
*/
public static ClosureExpression getClosure(BlockStatement block, String name, boolean remove) {
for (ExpressionStatement statement : getExpressionStatements(block)) {
Expression expression = statement.getExpression();
if (expression instanceof MethodCallExpression methodCallExpression) {
ClosureExpression closure = getClosure(name, methodCallExpression);
if (closure != null) {
if (remove) {
block.getStatements().remove(statement);
}
return closure;
}
}
}
return null;
}
private static List<ExpressionStatement> getExpressionStatements(BlockStatement block) {
List<ExpressionStatement> statements = new ArrayList<>();
for (Statement statement : block.getStatements()) {
if (statement instanceof ExpressionStatement expressionStatement) {
statements.add(expressionStatement);
}
}
return statements;
}
private static ClosureExpression getClosure(String name, MethodCallExpression expression) {
Expression method = expression.getMethod();
if (method instanceof ConstantExpression constantExpression && name.equals(constantExpression.getValue())) {
return (ClosureExpression) ((ArgumentListExpression) expression.getArguments()).getExpression(0);
}
return null;
}
public static ClosureExpression getClosure(BlockStatement block, String name) {
return getClosure(block, name, false);
}
}

@ -1,95 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
/**
* Strategy that can be used to apply some auto-configuration during the
* {@link CompilePhase#CONVERSION} Groovy compile phase.
*
* @author Phillip Webb
* @since 1.0.0
*/
public abstract class CompilerAutoConfiguration {
/**
* Strategy method used to determine when compiler auto-configuration should be
* applied. Defaults to always.
* @param classNode the class node
* @return {@code true} if the compiler should be auto-configured using this class. If
* this method returns {@code false} no other strategy methods will be called.
*/
public boolean matches(ClassNode classNode) {
return true;
}
/**
* Apply any dependency customizations. This method will only be called if
* {@link #matches} returns {@code true}.
* @param dependencies dependency customizer
* @throws CompilationFailedException if the dependencies cannot be applied
*/
public void applyDependencies(DependencyCustomizer dependencies) throws CompilationFailedException {
}
/**
* Apply any import customizations. This method will only be called if
* {@link #matches} returns {@code true}.
* @param imports import customizer
* @throws CompilationFailedException if the imports cannot be applied
*/
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
}
/**
* Apply any customizations to the main class. This method will only be called if
* {@link #matches} returns {@code true}. This method is useful when a groovy file
* defines more than one class but customization only applies to the first class.
* @param loader the class loader being used during compilation
* @param configuration the compiler configuration
* @param generatorContext the current context
* @param source the source unit
* @param classNode the main class
* @throws CompilationFailedException if the customizations cannot be applied
*/
public void applyToMainClass(GroovyClassLoader loader, GroovyCompilerConfiguration configuration,
GeneratorContext generatorContext, SourceUnit source, ClassNode classNode)
throws CompilationFailedException {
}
/**
* Apply any additional configuration.
* @param loader the class loader being used during compilation
* @param configuration the compiler configuration
* @param generatorContext the current context
* @param source the source unit
* @param classNode the class
* @throws CompilationFailedException if the configuration cannot be applied
*/
public void apply(GroovyClassLoader loader, GroovyCompilerConfiguration configuration,
GeneratorContext generatorContext, SourceUnit source, ClassNode classNode)
throws CompilationFailedException {
}
}

@ -1,83 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
import org.springframework.core.annotation.Order;
/**
* {@link ASTTransformation} to apply
* {@link CompilerAutoConfiguration#applyDependencies(DependencyCustomizer) dependency
* auto-configuration}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @since 1.0.0
*/
@Order(DependencyAutoConfigurationTransformation.ORDER)
public class DependencyAutoConfigurationTransformation implements ASTTransformation {
/**
* The order of the transformation.
*/
public static final int ORDER = DependencyManagementBomTransformation.ORDER + 100;
private final GroovyClassLoader loader;
private final DependencyResolutionContext dependencyResolutionContext;
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
public DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
DependencyResolutionContext dependencyResolutionContext,
Iterable<CompilerAutoConfiguration> compilerAutoConfigurations) {
this.loader = loader;
this.dependencyResolutionContext = dependencyResolutionContext;
this.compilerAutoConfigurations = compilerAutoConfigurations;
}
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
for (ASTNode astNode : nodes) {
if (astNode instanceof ModuleNode moduleNode) {
visitModule(moduleNode);
}
}
}
private void visitModule(ModuleNode module) {
DependencyCustomizer dependencies = new DependencyCustomizer(this.loader, module,
this.dependencyResolutionContext);
for (ClassNode classNode : module.getClasses()) {
for (CompilerAutoConfiguration autoConfiguration : this.compilerAutoConfigurations) {
if (autoConfiguration.matches(classNode)) {
autoConfiguration.applyDependencies(dependencies);
}
}
}
}
}

@ -1,262 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import groovy.lang.Grab;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
/**
* Customizer that allows dependencies to be added during compilation. Adding a dependency
* results in a {@link Grab @Grab} annotation being added to the primary {@link ClassNode
* class} is the {@link ModuleNode module} that's being customized.
* <p>
* This class provides a fluent API for conditionally adding dependencies. For example:
* {@code dependencies.ifMissing("com.corp.SomeClass").add(module)}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.0.0
*/
public class DependencyCustomizer {
private final GroovyClassLoader loader;
private final ClassNode classNode;
private final DependencyResolutionContext dependencyResolutionContext;
/**
* Create a new {@link DependencyCustomizer} instance.
* @param loader the current classloader
* @param moduleNode the current module
* @param dependencyResolutionContext the context for dependency resolution
*/
public DependencyCustomizer(GroovyClassLoader loader, ModuleNode moduleNode,
DependencyResolutionContext dependencyResolutionContext) {
this.loader = loader;
this.classNode = moduleNode.getClasses().get(0);
this.dependencyResolutionContext = dependencyResolutionContext;
}
/**
* Create a new nested {@link DependencyCustomizer}.
* @param parent the parent customizer
*/
protected DependencyCustomizer(DependencyCustomizer parent) {
this.loader = parent.loader;
this.classNode = parent.classNode;
this.dependencyResolutionContext = parent.dependencyResolutionContext;
}
public String getVersion(String artifactId) {
return getVersion(artifactId, "");
}
public String getVersion(String artifactId, String defaultVersion) {
String version = this.dependencyResolutionContext.getArtifactCoordinatesResolver().getVersion(artifactId);
if (version == null) {
version = defaultVersion;
}
return version;
}
/**
* Create a nested {@link DependencyCustomizer} that only applies if any of the
* specified class names are not on the class path.
* @param classNames the class names to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAnyMissingClasses(String... classNames) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String className : classNames) {
try {
Class.forName(className, false, DependencyCustomizer.this.loader);
}
catch (Exception ex) {
return true;
}
}
return false;
}
};
}
/**
* Create a nested {@link DependencyCustomizer} that only applies if all the specified
* class names are not on the class path.
* @param classNames the class names to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAllMissingClasses(String... classNames) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String className : classNames) {
try {
Class.forName(className, false, DependencyCustomizer.this.loader);
return false;
}
catch (Exception ex) {
// swallow exception and continue
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Create a nested {@link DependencyCustomizer} that only applies if the specified
* paths are on the class path.
* @param paths the paths to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAllResourcesPresent(String... paths) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String path : paths) {
try {
if (DependencyCustomizer.this.loader.getResource(path) == null) {
return false;
}
}
catch (Exception ex) {
// swallow exception and continue
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Create a nested {@link DependencyCustomizer} that only applies at least one of the
* specified paths is on the class path.
* @param paths the paths to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAnyResourcesPresent(String... paths) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String path : paths) {
try {
return DependencyCustomizer.this.loader.getResource(path) != null;
}
catch (Exception ex) {
// swallow exception and continue
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Add dependencies and all of their dependencies. The group ID and version of the
* dependencies are resolved from the modules using the customizer's
* {@link ArtifactCoordinatesResolver}.
* @param modules the module IDs
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String... modules) {
for (String module : modules) {
add(module, null, null, true);
}
return this;
}
/**
* Add a single dependency and, optionally, all of its dependencies. The group ID and
* version of the dependency are resolved from the module using the customizer's
* {@link ArtifactCoordinatesResolver}.
* @param module the module ID
* @param transitive {@code true} if the transitive dependencies should also be added,
* otherwise {@code false}
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String module, boolean transitive) {
return add(module, null, null, transitive);
}
/**
* Add a single dependency with the specified classifier and type and, optionally, all
* of its dependencies. The group ID and version of the dependency are resolved from
* the module by using the customizer's {@link ArtifactCoordinatesResolver}.
* @param module the module ID
* @param classifier the classifier, may be {@code null}
* @param type the type, may be {@code null}
* @param transitive {@code true} if the transitive dependencies should also be added,
* otherwise {@code false}
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String module, String classifier, String type, boolean transitive) {
if (canAdd()) {
ArtifactCoordinatesResolver artifactCoordinatesResolver = this.dependencyResolutionContext
.getArtifactCoordinatesResolver();
this.classNode.addAnnotation(createGrabAnnotation(artifactCoordinatesResolver.getGroupId(module),
artifactCoordinatesResolver.getArtifactId(module), artifactCoordinatesResolver.getVersion(module),
classifier, type, transitive));
}
return this;
}
private AnnotationNode createGrabAnnotation(String group, String module, String version, String classifier,
String type, boolean transitive) {
AnnotationNode annotationNode = new AnnotationNode(new ClassNode(Grab.class));
annotationNode.addMember("group", new ConstantExpression(group));
annotationNode.addMember("module", new ConstantExpression(module));
annotationNode.addMember("version", new ConstantExpression(version));
if (classifier != null) {
annotationNode.addMember("classifier", new ConstantExpression(classifier));
}
if (type != null) {
annotationNode.addMember("type", new ConstantExpression(type));
}
annotationNode.addMember("transitive", new ConstantExpression(transitive));
annotationNode.addMember("initClass", new ConstantExpression(false));
return annotationNode;
}
/**
* Strategy called to test if dependencies can be added. Subclasses override as
* required. Returns {@code true} by default.
* @return {@code true} if dependencies can be added, otherwise {@code false}
*/
protected boolean canAdd() {
return true;
}
/**
* Returns the {@link DependencyResolutionContext}.
* @return the dependency resolution context
*/
public DependencyResolutionContext getDependencyResolutionContext() {
return this.dependencyResolutionContext;
}
}

@ -1,241 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import groovy.grape.Grape;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.apache.maven.model.Repository;
import org.apache.maven.model.building.DefaultModelBuilder;
import org.apache.maven.model.building.DefaultModelBuilderFactory;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.resolution.InvalidRepositoryException;
import org.apache.maven.model.resolution.ModelResolver;
import org.apache.maven.model.resolution.UnresolvableModelException;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.transform.ASTTransformation;
import org.springframework.boot.cli.compiler.dependencies.MavenModelDependencyManagement;
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
import org.springframework.boot.groovy.DependencyManagementBom;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* {@link ASTTransformation} for processing
* {@link DependencyManagementBom @DependencyManagementBom} annotations.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
@Order(DependencyManagementBomTransformation.ORDER)
@SuppressWarnings("deprecation")
public class DependencyManagementBomTransformation extends AnnotatedNodeASTTransformation {
/**
* The order of the transformation.
*/
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 100;
private static final Set<String> DEPENDENCY_MANAGEMENT_BOM_ANNOTATION_NAMES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(DependencyManagementBom.class.getName(),
DependencyManagementBom.class.getSimpleName())));
private final DependencyResolutionContext resolutionContext;
public DependencyManagementBomTransformation(DependencyResolutionContext resolutionContext) {
super(DEPENDENCY_MANAGEMENT_BOM_ANNOTATION_NAMES, true);
this.resolutionContext = resolutionContext;
}
@Override
protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) {
if (!annotationNodes.isEmpty()) {
if (annotationNodes.size() > 1) {
for (AnnotationNode annotationNode : annotationNodes) {
handleDuplicateDependencyManagementBomAnnotation(annotationNode);
}
}
else {
processDependencyManagementBomAnnotation(annotationNodes.get(0));
}
}
}
private void processDependencyManagementBomAnnotation(AnnotationNode annotationNode) {
Expression valueExpression = annotationNode.getMember("value");
List<Map<String, String>> bomDependencies = createDependencyMaps(valueExpression);
updateDependencyResolutionContext(bomDependencies);
}
private List<Map<String, String>> createDependencyMaps(Expression valueExpression) {
Map<String, String> dependency = null;
List<ConstantExpression> constantExpressions = getConstantExpressions(valueExpression);
List<Map<String, String>> dependencies = new ArrayList<>(constantExpressions.size());
for (ConstantExpression expression : constantExpressions) {
Object value = expression.getValue();
if (value instanceof String string) {
String[] components = string.split(":");
if (components.length == 3) {
dependency = new HashMap<>();
dependency.put("group", components[0]);
dependency.put("module", components[1]);
dependency.put("version", components[2]);
dependency.put("type", "pom");
dependencies.add(dependency);
}
else {
handleMalformedDependency(expression);
}
}
}
return dependencies;
}
private List<ConstantExpression> getConstantExpressions(Expression valueExpression) {
if (valueExpression instanceof ListExpression listExpression) {
return getConstantExpressions(listExpression);
}
if (valueExpression instanceof ConstantExpression constantExpression
&& constantExpression.getValue() instanceof String) {
return Arrays.asList(constantExpression);
}
reportError("@DependencyManagementBom requires an inline constant that is a string or a string array",
valueExpression);
return Collections.emptyList();
}
private List<ConstantExpression> getConstantExpressions(ListExpression valueExpression) {
List<ConstantExpression> expressions = new ArrayList<>();
for (Expression expression : valueExpression.getExpressions()) {
if (expression instanceof ConstantExpression constantExpression
&& constantExpression.getValue() instanceof String) {
expressions.add(constantExpression);
}
else {
reportError("Each entry in the array must be an inline string constant", expression);
}
}
return expressions;
}
private void handleMalformedDependency(Expression expression) {
Message message = createSyntaxErrorMessage(
String.format("The string must be of the form \"group:module:version\"%n"), expression);
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
}
private void updateDependencyResolutionContext(List<Map<String, String>> bomDependencies) {
URI[] uris = Grape.getInstance().resolve(null, bomDependencies.toArray(new Map<?, ?>[0]));
DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance();
for (URI uri : uris) {
try {
DefaultModelBuildingRequest request = new DefaultModelBuildingRequest();
request.setModelResolver(new GrapeModelResolver());
request.setModelSource(new org.apache.maven.model.building.UrlModelSource(uri.toURL()));
request.setSystemProperties(System.getProperties());
Model model = modelBuilder.build(request).getEffectiveModel();
this.resolutionContext.addDependencyManagement(new MavenModelDependencyManagement(model));
}
catch (Exception ex) {
throw new IllegalStateException("Failed to build model for '" + uri + "'. Is it a valid Maven bom?",
ex);
}
}
}
private void handleDuplicateDependencyManagementBomAnnotation(AnnotationNode annotationNode) {
Message message = createSyntaxErrorMessage(
"Duplicate @DependencyManagementBom annotation. It must be declared at most once.", annotationNode);
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
}
private void reportError(String message, ASTNode node) {
getSourceUnit().getErrorCollector().addErrorAndContinue(createSyntaxErrorMessage(message, node));
}
private Message createSyntaxErrorMessage(String message, ASTNode node) {
return new SyntaxErrorMessage(new SyntaxException(message, node.getLineNumber(), node.getColumnNumber(),
node.getLastLineNumber(), node.getLastColumnNumber()), getSourceUnit());
}
private static class GrapeModelResolver implements ModelResolver {
@Override
public org.apache.maven.model.building.ModelSource resolveModel(Parent parent)
throws UnresolvableModelException {
return resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
}
@Override
public org.apache.maven.model.building.ModelSource resolveModel(Dependency dependency)
throws UnresolvableModelException {
return resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
}
@Override
public org.apache.maven.model.building.ModelSource resolveModel(String groupId, String artifactId,
String version) throws UnresolvableModelException {
Map<String, String> dependency = new HashMap<>();
dependency.put("group", groupId);
dependency.put("module", artifactId);
dependency.put("version", version);
dependency.put("type", "pom");
try {
return new org.apache.maven.model.building.UrlModelSource(
Grape.getInstance().resolve(null, dependency)[0].toURL());
}
catch (MalformedURLException ex) {
throw new UnresolvableModelException(ex.getMessage(), groupId, artifactId, version);
}
}
@Override
public void addRepository(Repository repository) throws InvalidRepositoryException {
}
@Override
public void addRepository(Repository repository, boolean replace) throws InvalidRepositoryException {
}
@Override
public ModelResolver newCopy() {
return this;
}
}
}

@ -1,245 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
/**
* Extension of the {@link GroovyClassLoader} with support for obtaining '.class' files as
* resources.
*
* @author Phillip Webb
* @author Dave Syer
* @since 1.0.0
*/
public class ExtendedGroovyClassLoader extends GroovyClassLoader {
private static final String SHARED_PACKAGE = "org.springframework.boot.groovy";
private static final URL[] NO_URLS = new URL[] {};
private final Map<String, byte[]> classResources = new HashMap<>();
private final GroovyCompilerScope scope;
private final CompilerConfiguration configuration;
public ExtendedGroovyClassLoader(GroovyCompilerScope scope) {
this(scope, createParentClassLoader(scope), new CompilerConfiguration());
}
private static ClassLoader createParentClassLoader(GroovyCompilerScope scope) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (scope == GroovyCompilerScope.DEFAULT) {
classLoader = new DefaultScopeParentClassLoader(classLoader);
}
return classLoader;
}
private ExtendedGroovyClassLoader(GroovyCompilerScope scope, ClassLoader parent,
CompilerConfiguration configuration) {
super(parent, configuration);
this.configuration = configuration;
this.scope = scope;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
}
catch (ClassNotFoundException ex) {
if (this.scope == GroovyCompilerScope.DEFAULT && name.startsWith(SHARED_PACKAGE)) {
Class<?> sharedClass = findSharedClass(name);
if (sharedClass != null) {
return sharedClass;
}
}
throw ex;
}
}
private Class<?> findSharedClass(String name) {
try {
String path = name.replace('.', '/').concat(".class");
try (InputStream inputStream = getParent().getResourceAsStream(path)) {
if (inputStream != null) {
return defineClass(name, FileCopyUtils.copyToByteArray(inputStream));
}
}
return null;
}
catch (Exception ex) {
return null;
}
}
@Override
public InputStream getResourceAsStream(String name) {
InputStream resourceStream = super.getResourceAsStream(name);
if (resourceStream == null) {
byte[] bytes = this.classResources.get(name);
resourceStream = (bytes != null) ? new ByteArrayInputStream(bytes) : null;
}
return resourceStream;
}
@Override
public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
return new ExtendedClassCollector(getInnerLoader(), unit, su);
}
private InnerLoader getInnerLoader() {
return new InnerLoader(ExtendedGroovyClassLoader.this) {
// Don't return URLs from the inner loader so that Tomcat only
// searches the parent. Fixes 'TLD skipped' issues
@Override
public URL[] getURLs() {
return NO_URLS;
}
};
}
public CompilerConfiguration getConfiguration() {
return this.configuration;
}
/**
* Inner collector class used to track as classes are added.
*/
protected class ExtendedClassCollector extends ClassCollector {
protected ExtendedClassCollector(InnerLoader loader, CompilationUnit unit, SourceUnit su) {
super(loader, unit, su);
}
@Override
protected Class<?> createClass(byte[] code, ClassNode classNode) {
Class<?> createdClass = super.createClass(code, classNode);
ExtendedGroovyClassLoader.this.classResources.put(classNode.getName().replace('.', '/') + ".class", code);
return createdClass;
}
}
/**
* ClassLoader used for a parent that filters so that only classes from groovy-all.jar
* are exposed.
*/
private static class DefaultScopeParentClassLoader extends ClassLoader {
private static final String[] GROOVY_JARS_PREFIXES = { "groovy", "antlr", "asm" };
private final URLClassLoader groovyOnlyClassLoader;
DefaultScopeParentClassLoader(ClassLoader parent) {
super(parent);
this.groovyOnlyClassLoader = new URLClassLoader(getGroovyJars(parent),
getClass().getClassLoader().getParent());
}
private URL[] getGroovyJars(ClassLoader parent) {
Set<URL> urls = new HashSet<>();
findGroovyJarsDirectly(parent, urls);
if (urls.isEmpty()) {
findGroovyJarsFromClassPath(urls);
}
Assert.state(!urls.isEmpty(), "Unable to find groovy JAR");
return new ArrayList<>(urls).toArray(new URL[0]);
}
private void findGroovyJarsDirectly(ClassLoader classLoader, Set<URL> urls) {
while (classLoader != null) {
if (classLoader instanceof URLClassLoader urlClassLoader) {
for (URL url : urlClassLoader.getURLs()) {
if (isGroovyJar(url.toString())) {
urls.add(url);
}
}
}
classLoader = classLoader.getParent();
}
}
private void findGroovyJarsFromClassPath(Set<URL> urls) {
String classpath = System.getProperty("java.class.path");
String[] entries = classpath.split(System.getProperty("path.separator"));
for (String entry : entries) {
if (isGroovyJar(entry)) {
File file = new File(entry);
if (file.canRead()) {
try {
urls.add(file.toURI().toURL());
}
catch (MalformedURLException ex) {
// Swallow and continue
}
}
}
}
}
private boolean isGroovyJar(String entry) {
entry = StringUtils.cleanPath(entry);
for (String jarPrefix : GROOVY_JARS_PREFIXES) {
if (entry.contains("/" + jarPrefix + "-")) {
return true;
}
}
return false;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
return this.groovyOnlyClassLoader.getResources(name);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (!name.startsWith("java.")) {
Class.forName(name, false, this.groovyOnlyClassLoader);
}
return super.loadClass(name, resolve);
}
}
}

@ -1,131 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import org.springframework.boot.groovy.DependencyManagementBom;
import org.springframework.core.Ordered;
/**
* A base class that lets plugin authors easily add additional BOMs to all apps. All the
* dependencies in the BOM (and its transitives) will be added to the dependency
* management lookup, so an app can use just the artifact id (e.g. "spring-jdbc") in a
* {@code @Grab}. To install, implement the missing methods and list the class in
* {@code META-INF/services/org.springframework.boot.cli.compiler.SpringBootAstTransformation}
* . The {@link #getOrder()} value needs to be before
* {@link DependencyManagementBomTransformation#ORDER}.
*
* @author Dave Syer
* @since 1.3.0
*/
@GroovyASTTransformation(phase = CompilePhase.CONVERSION)
public abstract class GenericBomAstTransformation implements SpringBootAstTransformation, Ordered {
private static final ClassNode BOM = ClassHelper.make(DependencyManagementBom.class);
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
for (ASTNode astNode : nodes) {
if (astNode instanceof ModuleNode moduleNode) {
visitModule(moduleNode, getBomModule());
}
}
}
/**
* The bom to be added to dependency management in compact form:
* {@code "<groupId>:<artifactId>:<version>"} (like in a {@code @Grab}).
* @return the maven co-ordinates of the BOM to add
*/
protected abstract String getBomModule();
private void visitModule(ModuleNode node, String module) {
addDependencyManagementBom(node, module);
}
private void addDependencyManagementBom(ModuleNode node, String module) {
AnnotatedNode annotated = getAnnotatedNode(node);
if (annotated != null) {
AnnotationNode bom = getAnnotation(annotated);
List<Expression> expressions = new ArrayList<>(getConstantExpressions(bom.getMember("value")));
expressions.add(new ConstantExpression(module));
bom.setMember("value", new ListExpression(expressions));
}
}
private AnnotationNode getAnnotation(AnnotatedNode annotated) {
List<AnnotationNode> annotations = annotated.getAnnotations(BOM);
if (!annotations.isEmpty()) {
return annotations.get(0);
}
AnnotationNode annotation = new AnnotationNode(BOM);
annotated.addAnnotation(annotation);
return annotation;
}
private AnnotatedNode getAnnotatedNode(ModuleNode node) {
PackageNode packageNode = node.getPackage();
if (packageNode != null && !packageNode.getAnnotations(BOM).isEmpty()) {
return packageNode;
}
if (!node.getClasses().isEmpty()) {
return node.getClasses().get(0);
}
return packageNode;
}
private List<ConstantExpression> getConstantExpressions(Expression valueExpression) {
if (valueExpression instanceof ListExpression listExpression) {
return getConstantExpressions(listExpression);
}
if (valueExpression instanceof ConstantExpression constantExpression
&& constantExpression.getValue() instanceof String) {
return Arrays.asList(constantExpression);
}
return Collections.emptyList();
}
private List<ConstantExpression> getConstantExpressions(ListExpression valueExpression) {
List<ConstantExpression> expressions = new ArrayList<>();
for (Expression expression : valueExpression.getExpressions()) {
if (expression instanceof ConstantExpression constantExpression
&& constantExpression.getValue() instanceof String) {
expressions.add(constantExpression);
}
}
return expressions;
}
}

@ -1,118 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.springframework.core.annotation.Order;
/**
* {@link ASTTransformation} to resolve beans declarations inside application source
* files. Users only need to define a <code>beans{}</code> DSL element, and this
* transformation will remove it and make it accessible to the Spring application via an
* interface.
*
* @author Dave Syer
* @since 1.0.0
*/
@Order(GroovyBeansTransformation.ORDER)
public class GroovyBeansTransformation implements ASTTransformation {
/**
* The order of the transformation.
*/
public static final int ORDER = DependencyManagementBomTransformation.ORDER + 200;
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
for (ASTNode node : nodes) {
if (node instanceof ModuleNode module) {
for (ClassNode classNode : new ArrayList<>(module.getClasses())) {
if (classNode.isScript()) {
classNode.visitContents(new ClassVisitor(source, classNode));
}
}
}
}
}
private class ClassVisitor extends ClassCodeVisitorSupport {
private static final String SOURCE_INTERFACE = "org.springframework.boot.BeanDefinitionLoader.GroovyBeanDefinitionSource";
private static final String BEANS = "beans";
private final SourceUnit source;
private final ClassNode classNode;
private boolean xformed = false;
ClassVisitor(SourceUnit source, ClassNode classNode) {
this.source = source;
this.classNode = classNode;
}
@Override
protected SourceUnit getSourceUnit() {
return this.source;
}
@Override
public void visitBlockStatement(BlockStatement block) {
if (block.isEmpty() || this.xformed) {
return;
}
ClosureExpression closure = beans(block);
if (closure != null) {
// Add a marker interface to the current script
this.classNode.addInterface(ClassHelper.make(SOURCE_INTERFACE));
// Implement the interface by adding a public read-only property with the
// same name as the method in the interface (getBeans). Make it return the
// closure.
this.classNode.addProperty(new PropertyNode(BEANS, Modifier.PUBLIC | Modifier.FINAL,
ClassHelper.CLOSURE_TYPE.getPlainNodeReference(), this.classNode, closure, null, null));
// Only do this once per class
this.xformed = true;
}
}
/**
* Extract a top-level <code>beans{}</code> closure from inside this block if
* there is one. Removes it from the block at the same time.
* @param block a block statement (class definition)
* @return a beans Closure if one can be found, null otherwise
*/
private ClosureExpression beans(BlockStatement block) {
return AstUtils.getClosure(block, BEANS, true);
}
}
}

@ -1,318 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import groovy.grape.GrapeEngine;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyClassLoader.ClassCollector;
import groovy.lang.GroovyCodeSource;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.ASTTransformationVisitor;
import org.springframework.boot.cli.compiler.dependencies.SpringBootDependenciesDependencyManagement;
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller;
import org.springframework.boot.cli.compiler.grape.MavenResolverGrapeEngineFactory;
import org.springframework.boot.cli.util.ResourceUtils;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.ClassUtils;
/**
* Compiler for Groovy sources. Primarily a simple Facade for
* {@link GroovyClassLoader#parseClass(GroovyCodeSource)} with the following additional
* features:
* <ul>
* <li>{@link CompilerAutoConfiguration} strategies will be read from
* {@code META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration}
* (per the standard java {@link ServiceLoader} contract) and applied during compilation
* </li>
*
* <li>Multiple classes can be returned if the Groovy source defines more than one Class
* </li>
*
* <li>Generated class files can also be loaded using
* {@link ClassLoader#getResource(String)}</li>
* </ul>
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @since 1.0.0
*/
public class GroovyCompiler {
private final GroovyCompilerConfiguration configuration;
private final ExtendedGroovyClassLoader loader;
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
private final List<ASTTransformation> transformations;
/**
* Create a new {@link GroovyCompiler} instance.
* @param configuration the compiler configuration
*/
public GroovyCompiler(GroovyCompilerConfiguration configuration) {
this.configuration = configuration;
this.loader = createLoader(configuration);
DependencyResolutionContext resolutionContext = new DependencyResolutionContext();
resolutionContext.addDependencyManagement(new SpringBootDependenciesDependencyManagement());
GrapeEngine grapeEngine = MavenResolverGrapeEngineFactory.create(this.loader,
configuration.getRepositoryConfiguration(), resolutionContext, configuration.isQuiet());
GrapeEngineInstaller.install(grapeEngine);
this.loader.getConfiguration().addCompilationCustomizers(new CompilerAutoConfigureCustomizer());
if (configuration.isAutoconfigure()) {
this.compilerAutoConfigurations = ServiceLoader.load(CompilerAutoConfiguration.class);
}
else {
this.compilerAutoConfigurations = Collections.emptySet();
}
this.transformations = new ArrayList<>();
this.transformations.add(new DependencyManagementBomTransformation(resolutionContext));
this.transformations.add(new DependencyAutoConfigurationTransformation(this.loader, resolutionContext,
this.compilerAutoConfigurations));
this.transformations.add(new GroovyBeansTransformation());
if (this.configuration.isGuessDependencies()) {
this.transformations.add(new ResolveDependencyCoordinatesTransformation(resolutionContext));
}
for (ASTTransformation transformation : ServiceLoader.load(SpringBootAstTransformation.class)) {
this.transformations.add(transformation);
}
this.transformations.sort(AnnotationAwareOrderComparator.INSTANCE);
}
/**
* Return a mutable list of the {@link ASTTransformation}s to be applied during
* {@link #compile(String...)}.
* @return the AST transformations to apply
*/
public List<ASTTransformation> getAstTransformations() {
return this.transformations;
}
public ExtendedGroovyClassLoader getLoader() {
return this.loader;
}
private ExtendedGroovyClassLoader createLoader(GroovyCompilerConfiguration configuration) {
ExtendedGroovyClassLoader loader = new ExtendedGroovyClassLoader(configuration.getScope());
for (URL url : getExistingUrls()) {
loader.addURL(url);
}
for (String classpath : configuration.getClasspath()) {
loader.addClasspath(classpath);
}
return loader;
}
private URL[] getExistingUrls() {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
if (tccl instanceof ExtendedGroovyClassLoader groovyClassLoader) {
return groovyClassLoader.getURLs();
}
else {
return new URL[0];
}
}
public void addCompilationCustomizers(CompilationCustomizer... customizers) {
this.loader.getConfiguration().addCompilationCustomizers(customizers);
}
/**
* Compile the specified Groovy sources, applying any
* {@link CompilerAutoConfiguration}s. All classes defined in the sources will be
* returned from this method.
* @param sources the sources to compile
* @return compiled classes
* @throws CompilationFailedException in case of compilation failures
* @throws IOException in case of I/O errors
* @throws CompilationFailedException in case of compilation errors
*/
public Class<?>[] compile(String... sources) throws CompilationFailedException, IOException {
this.loader.clearCache();
List<Class<?>> classes = new ArrayList<>();
CompilerConfiguration configuration = this.loader.getConfiguration();
CompilationUnit compilationUnit = new CompilationUnit(configuration, null, this.loader);
ClassCollector collector = this.loader.createCollector(compilationUnit, null);
compilationUnit.setClassgenCallback(collector);
for (String source : sources) {
List<String> paths = ResourceUtils.getUrls(source, this.loader);
for (String path : paths) {
compilationUnit.addSource(new URL(path));
}
}
addAstTransformations(compilationUnit);
compilationUnit.compile(Phases.CLASS_GENERATION);
for (Object loadedClass : collector.getLoadedClasses()) {
classes.add((Class<?>) loadedClass);
}
ClassNode mainClassNode = MainClass.get(compilationUnit);
Class<?> mainClass = null;
for (Class<?> loadedClass : classes) {
if (mainClassNode.getName().equals(loadedClass.getName())) {
mainClass = loadedClass;
}
}
if (mainClass != null) {
classes.remove(mainClass);
classes.add(0, mainClass);
}
return ClassUtils.toClassArray(classes);
}
@SuppressWarnings("rawtypes")
private void addAstTransformations(CompilationUnit compilationUnit) {
Deque[] phaseOperations = getPhaseOperations(compilationUnit);
processConversionOperations((LinkedList) phaseOperations[Phases.CONVERSION]);
}
@SuppressWarnings("rawtypes")
private Deque[] getPhaseOperations(CompilationUnit compilationUnit) {
try {
Field field = CompilationUnit.class.getDeclaredField("phaseOperations");
field.setAccessible(true);
return (Deque[]) field.get(compilationUnit);
}
catch (Exception ex) {
throw new IllegalStateException("Phase operations not available from compilation unit");
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void processConversionOperations(LinkedList conversionOperations) {
int index = getIndexOfASTTransformationVisitor(conversionOperations);
conversionOperations.add(index, new CompilationUnit.ISourceUnitOperation() {
@Override
public void call(SourceUnit source) throws CompilationFailedException {
ASTNode[] nodes = new ASTNode[] { source.getAST() };
for (ASTTransformation transformation : GroovyCompiler.this.transformations) {
transformation.visit(nodes, source);
}
}
});
}
private int getIndexOfASTTransformationVisitor(List<?> conversionOperations) {
for (int index = 0; index < conversionOperations.size(); index++) {
if (conversionOperations.get(index).getClass().getName()
.startsWith(ASTTransformationVisitor.class.getName())) {
return index;
}
}
return conversionOperations.size();
}
/**
* {@link CompilationCustomizer} to call {@link CompilerAutoConfiguration}s.
*/
private class CompilerAutoConfigureCustomizer extends CompilationCustomizer {
CompilerAutoConfigureCustomizer() {
super(CompilePhase.CONVERSION);
}
@Override
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode)
throws CompilationFailedException {
ImportCustomizer importCustomizer = new SmartImportCustomizer(source);
List<ClassNode> classNodes = source.getAST().getClasses();
ClassNode mainClassNode = MainClass.get(classNodes);
// Additional auto configuration
for (CompilerAutoConfiguration autoConfiguration : GroovyCompiler.this.compilerAutoConfigurations) {
if (classNodes.stream().anyMatch(autoConfiguration::matches)) {
if (GroovyCompiler.this.configuration.isGuessImports()) {
autoConfiguration.applyImports(importCustomizer);
importCustomizer.call(source, context, classNode);
}
if (classNode.equals(mainClassNode)) {
autoConfiguration.applyToMainClass(GroovyCompiler.this.loader,
GroovyCompiler.this.configuration, context, source, classNode);
}
autoConfiguration.apply(GroovyCompiler.this.loader, GroovyCompiler.this.configuration, context,
source, classNode);
}
}
importCustomizer.call(source, context, classNode);
}
}
private static class MainClass {
static ClassNode get(CompilationUnit source) {
return get(source.getAST().getClasses());
}
static ClassNode get(List<ClassNode> classes) {
for (ClassNode node : classes) {
if (AstUtils.hasAtLeastOneAnnotation(node, "Enable*AutoConfiguration")) {
return null; // No need to enhance this
}
if (AstUtils.hasAtLeastOneAnnotation(node, "*Controller", "Configuration", "Component", "*Service",
"Repository", "Enable*")) {
return node;
}
}
return classes.isEmpty() ? null : classes.get(0);
}
}
}

@ -1,81 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.util.List;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
/**
* Configuration for the {@link GroovyCompiler}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.0.0
*/
public interface GroovyCompilerConfiguration {
/**
* Constant to be used when there is no {@link #getClasspath() classpath}.
*/
String[] DEFAULT_CLASSPATH = { "." };
/**
* Returns the scope in which the compiler operates.
* @return the scope of the compiler
*/
GroovyCompilerScope getScope();
/**
* Returns if import declarations should be guessed.
* @return {@code true} if imports should be guessed, otherwise {@code false}
*/
boolean isGuessImports();
/**
* Returns if jar dependencies should be guessed.
* @return {@code true} if dependencies should be guessed, otherwise {@code false}
*/
boolean isGuessDependencies();
/**
* Returns true if auto-configuration transformations should be applied.
* @return {@code true} if auto-configuration transformations should be applied,
* otherwise {@code false}
*/
boolean isAutoconfigure();
/**
* Returns the classpath for local resources.
* @return a path for local resources
*/
String[] getClasspath();
/**
* Returns the configuration for the repositories that will be used by the compiler to
* resolve dependencies.
* @return the repository configurations
*/
List<RepositoryConfiguration> getRepositoryConfiguration();
/**
* Returns if running in quiet mode.
* @return {@code true} if running in quiet mode
*/
boolean isQuiet();
}

@ -1,38 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
/**
* The scope in which a groovy compiler operates.
*
* @author Phillip Webb
* @since 1.0.0
*/
public enum GroovyCompilerScope {
/**
* Default scope, exposes groovy.jar (loaded from the parent) and the shared cli
* package (loaded via groovy classloader).
*/
DEFAULT,
/**
* Extension scope, allows full access to internal CLI classes.
*/
EXTENSION
}

@ -1,129 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Repository;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
import org.springframework.boot.cli.compiler.maven.MavenSettings;
import org.springframework.boot.cli.compiler.maven.MavenSettingsReader;
import org.springframework.util.StringUtils;
/**
* Factory used to create {@link RepositoryConfiguration}s.
*
* @author Andy Wilkinson
* @author Dave Syer
* @since 1.0.0
*/
public final class RepositoryConfigurationFactory {
private static final RepositoryConfiguration MAVEN_CENTRAL = new RepositoryConfiguration("central",
URI.create("https://repo.maven.apache.org/maven2/"), false);
private static final RepositoryConfiguration SPRING_MILESTONE = new RepositoryConfiguration("spring-milestone",
URI.create("https://repo.spring.io/milestone"), false);
private static final RepositoryConfiguration SPRING_SNAPSHOT = new RepositoryConfiguration("spring-snapshot",
URI.create("https://repo.spring.io/snapshot"), true);
private RepositoryConfigurationFactory() {
}
/**
* Create a new default repository configuration.
* @return the newly-created default repository configuration
*/
public static List<RepositoryConfiguration> createDefaultRepositoryConfiguration() {
MavenSettings mavenSettings = new MavenSettingsReader().readSettings();
List<RepositoryConfiguration> repositoryConfiguration = new ArrayList<>();
repositoryConfiguration.add(MAVEN_CENTRAL);
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
repositoryConfiguration.add(SPRING_MILESTONE);
repositoryConfiguration.add(SPRING_SNAPSHOT);
}
addDefaultCacheAsRepository(mavenSettings.getLocalRepository(), repositoryConfiguration);
addActiveProfileRepositories(mavenSettings.getActiveProfiles(), repositoryConfiguration);
return repositoryConfiguration;
}
private static void addDefaultCacheAsRepository(String localRepository,
List<RepositoryConfiguration> repositoryConfiguration) {
RepositoryConfiguration repository = new RepositoryConfiguration("local",
getLocalRepositoryDirectory(localRepository).toURI(), true);
if (!repositoryConfiguration.contains(repository)) {
repositoryConfiguration.add(0, repository);
}
}
private static void addActiveProfileRepositories(List<Profile> activeProfiles,
List<RepositoryConfiguration> configurations) {
for (Profile activeProfile : activeProfiles) {
Interpolator interpolator = new RegexBasedInterpolator();
interpolator.addValueSource(new PropertiesBasedValueSource(activeProfile.getProperties()));
for (Repository repository : activeProfile.getRepositories()) {
configurations.add(getRepositoryConfiguration(interpolator, repository));
}
}
}
private static RepositoryConfiguration getRepositoryConfiguration(Interpolator interpolator,
Repository repository) {
String name = interpolate(interpolator, repository.getId());
String url = interpolate(interpolator, repository.getUrl());
boolean snapshotsEnabled = false;
if (repository.getSnapshots() != null) {
snapshotsEnabled = repository.getSnapshots().isEnabled();
}
return new RepositoryConfiguration(name, URI.create(url), snapshotsEnabled);
}
private static String interpolate(Interpolator interpolator, String value) {
try {
return interpolator.interpolate(value);
}
catch (InterpolationException ex) {
return value;
}
}
private static File getLocalRepositoryDirectory(String localRepository) {
if (StringUtils.hasText(localRepository)) {
return new File(localRepository);
}
return new File(getM2HomeDirectory(), "repository");
}
private static File getM2HomeDirectory() {
String mavenRoot = System.getProperty("maven.home");
if (StringUtils.hasLength(mavenRoot)) {
return new File(mavenRoot);
}
return new File(System.getProperty("user.home"), ".m2");
}
}

@ -1,109 +0,0 @@
/*
* Copyright 2012-2022 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import groovy.lang.Grab;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.transform.ASTTransformation;
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
import org.springframework.core.annotation.Order;
/**
* {@link ASTTransformation} to resolve {@link Grab @Grab} artifact coordinates.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.0.0
*/
@Order(ResolveDependencyCoordinatesTransformation.ORDER)
public class ResolveDependencyCoordinatesTransformation extends AnnotatedNodeASTTransformation {
/**
* The order of the transformation.
*/
public static final int ORDER = DependencyManagementBomTransformation.ORDER + 300;
private static final Set<String> GRAB_ANNOTATION_NAMES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(Grab.class.getName(), Grab.class.getSimpleName())));
private final DependencyResolutionContext resolutionContext;
public ResolveDependencyCoordinatesTransformation(DependencyResolutionContext resolutionContext) {
super(GRAB_ANNOTATION_NAMES, false);
this.resolutionContext = resolutionContext;
}
@Override
protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) {
for (AnnotationNode annotationNode : annotationNodes) {
transformGrabAnnotation(annotationNode);
}
}
private void transformGrabAnnotation(AnnotationNode grabAnnotation) {
grabAnnotation.setMember("initClass", new ConstantExpression(false));
String value = getValue(grabAnnotation);
if (value != null && !isConvenienceForm(value)) {
applyGroupAndVersion(grabAnnotation, value);
}
}
private String getValue(AnnotationNode annotation) {
Expression expression = annotation.getMember("value");
if (expression instanceof ConstantExpression constantExpression) {
Object value = constantExpression.getValue();
return (value instanceof String string) ? string : null;
}
return null;
}
private boolean isConvenienceForm(String value) {
return value.contains(":") || value.contains("#");
}
private void applyGroupAndVersion(AnnotationNode annotation, String module) {
if (module != null) {
setMember(annotation, "module", module);
}
else {
Expression expression = annotation.getMembers().get("module");
module = (String) ((ConstantExpression) expression).getValue();
}
if (annotation.getMember("group") == null) {
setMember(annotation, "group", this.resolutionContext.getArtifactCoordinatesResolver().getGroupId(module));
}
if (annotation.getMember("version") == null) {
setMember(annotation, "version",
this.resolutionContext.getArtifactCoordinatesResolver().getVersion(module));
}
}
private void setMember(AnnotationNode annotation, String name, String value) {
ConstantExpression expression = new ConstantExpression(value);
annotation.setMember(name, expression);
}
}

@ -1,55 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
/**
* Smart extension of {@link ImportCustomizer} that will only add a specific import if a
* class with the same name is not already explicitly imported.
*
* @author Dave Syer
*/
class SmartImportCustomizer extends ImportCustomizer {
private SourceUnit source;
SmartImportCustomizer(SourceUnit source) {
this.source = source;
}
@Override
public ImportCustomizer addImport(String alias, String className) {
if (this.source.getAST().getImport(ClassHelper.make(className).getNameWithoutPackage()) == null) {
super.addImport(alias, className);
}
return this;
}
@Override
public ImportCustomizer addImports(String... imports) {
for (String alias : imports) {
if (this.source.getAST().getImport(ClassHelper.make(alias).getNameWithoutPackage()) == null) {
super.addImports(alias);
}
}
return this;
}
}

@ -1,31 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import org.codehaus.groovy.transform.ASTTransformation;
/**
* Marker interface for AST transformations that should be installed automatically from
* {@code META-INF/services}.
*
* @author Dave Syer
* @since 1.0.0
*/
@FunctionalInterface
public interface SpringBootAstTransformation extends ASTTransformation {
}

@ -1,51 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for the caching infrastructure.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
public class CachingCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableCaching");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) throws CompilationFailedException {
dependencies.add("spring-context-support");
}
@Override
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
imports.addStarImports("org.springframework.cache", "org.springframework.cache.annotation",
"org.springframework.cache.concurrent");
}
}

@ -1,53 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
import org.springframework.boot.groovy.EnableGroovyTemplates;
import org.springframework.boot.groovy.GroovyTemplate;
/**
* {@link CompilerAutoConfiguration} for Groovy Templates (outside MVC).
*
* @author Dave Syer
* @since 1.1.0
*/
public class GroovyTemplatesCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableGroovyTemplates");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("groovy.text.TemplateEngine").add("groovy-templates");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("groovy.text");
imports.addImports(EnableGroovyTemplates.class.getCanonicalName());
imports.addStaticImport(GroovyTemplate.class.getName(), "template");
}
}

@ -1,51 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring JDBC.
*
* @author Dave Syer
* @since 1.0.0
*/
public class JdbcCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneFieldOrMethod(classNode, "JdbcTemplate", "NamedParameterJdbcTemplate",
"DataSource");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.jdbc.core.JdbcTemplate").add("spring-boot-starter-jdbc");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("org.springframework.jdbc.core", "org.springframework.jdbc.core.namedparam");
imports.addImports("javax.sql.DataSource");
}
}

@ -1,54 +0,0 @@
/*
* Copyright 2012-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring JMS.
*
* @author Greg Turnquist
* @author Stephane Nicoll
* @since 1.0.0
*/
public class JmsCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableJms")
|| AstUtils.hasAtLeastOneAnnotation(classNode, "EnableJmsMessaging");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) throws CompilationFailedException {
dependencies.add("spring-jms", "jakarta.jms-api");
}
@Override
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
imports.addStarImports("jakarta.jms", "org.springframework.jms.annotation", "org.springframework.jms.config",
"org.springframework.jms.core", "org.springframework.jms.listener",
"org.springframework.jms.listener.adapter");
}
}

@ -1,56 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring Rabbit.
*
* @author Greg Turnquist
* @author Stephane Nicoll
* @since 1.0.0
*/
public class RabbitCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableRabbit")
|| AstUtils.hasAtLeastOneAnnotation(classNode, "EnableRabbitMessaging");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) throws CompilationFailedException {
dependencies.add("spring-rabbit");
}
@Override
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
imports.addStarImports("org.springframework.amqp.rabbit.annotation", "org.springframework.amqp.rabbit.core",
"org.springframework.amqp.rabbit.config", "org.springframework.amqp.rabbit.connection",
"org.springframework.amqp.rabbit.listener", "org.springframework.amqp.rabbit.listener.adapter",
"org.springframework.amqp.core");
}
}

@ -1,63 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring Batch.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.0.0
*/
public class SpringBatchCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableBatchProcessing");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.batch.core.Job").add("spring-boot-starter-batch");
dependencies.ifAnyMissingClasses("org.springframework.jdbc.core.JdbcTemplate").add("spring-jdbc");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("org.springframework.batch.repeat.RepeatStatus",
"org.springframework.batch.core.scope.context.ChunkContext",
"org.springframework.batch.core.step.tasklet.Tasklet",
"org.springframework.batch.core.configuration.annotation.StepScope",
"org.springframework.batch.core.configuration.annotation.JobBuilderFactory",
"org.springframework.batch.core.configuration.annotation.StepBuilderFactory",
"org.springframework.batch.core.configuration.annotation.EnableBatchProcessing",
"org.springframework.batch.core.Step", "org.springframework.batch.core.StepExecution",
"org.springframework.batch.core.StepContribution", "org.springframework.batch.core.Job",
"org.springframework.batch.core.JobExecution", "org.springframework.batch.core.JobParameter",
"org.springframework.batch.core.JobParameters", "org.springframework.batch.core.launch.JobLauncher",
"org.springframework.batch.core.converter.JobParametersConverter",
"org.springframework.batch.core.converter.DefaultJobParametersConverter");
}
}

@ -1,94 +0,0 @@
/*
* Copyright 2012-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
/**
* {@link CompilerAutoConfiguration} for Spring.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.0.0
*/
public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.boot.SpringApplication").add("spring-boot-starter");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("jakarta.annotation.PostConstruct", "jakarta.annotation.PreDestroy",
"groovy.util.logging.Log", "org.springframework.stereotype.Controller",
"org.springframework.stereotype.Service", "org.springframework.stereotype.Component",
"org.springframework.beans.factory.annotation.Autowired",
"org.springframework.beans.factory.annotation.Value", "org.springframework.context.annotation.Import",
"org.springframework.context.annotation.ImportResource",
"org.springframework.context.annotation.Profile", "org.springframework.context.annotation.Scope",
"org.springframework.context.annotation.Configuration",
"org.springframework.context.annotation.ComponentScan", "org.springframework.context.annotation.Bean",
"org.springframework.context.ApplicationContext", "org.springframework.context.MessageSource",
"org.springframework.core.annotation.Order", "org.springframework.core.io.ResourceLoader",
"org.springframework.boot.ApplicationRunner", "org.springframework.boot.ApplicationArguments",
"org.springframework.boot.CommandLineRunner",
"org.springframework.boot.context.properties.ConfigurationProperties",
"org.springframework.boot.context.properties.EnableConfigurationProperties",
"org.springframework.boot.autoconfigure.EnableAutoConfiguration",
"org.springframework.boot.autoconfigure.SpringBootApplication",
"org.springframework.boot.context.properties.ConfigurationProperties",
"org.springframework.boot.context.properties.EnableConfigurationProperties");
imports.addStarImports("org.springframework.stereotype", "org.springframework.scheduling.annotation");
}
@Override
public void applyToMainClass(GroovyClassLoader loader, GroovyCompilerConfiguration configuration,
GeneratorContext generatorContext, SourceUnit source, ClassNode classNode)
throws CompilationFailedException {
addEnableAutoConfigurationAnnotation(classNode);
}
private void addEnableAutoConfigurationAnnotation(ClassNode classNode) {
if (!hasEnableAutoConfigureAnnotation(classNode)) {
AnnotationNode annotationNode = new AnnotationNode(ClassHelper.make("EnableAutoConfiguration"));
classNode.addAnnotation(annotationNode);
}
}
private boolean hasEnableAutoConfigureAnnotation(ClassNode classNode) {
for (AnnotationNode node : classNode.getAnnotations()) {
String name = node.getClassNode().getNameWithoutPackage();
if ("EnableAutoConfiguration".equals(name) || "SpringBootApplication".equals(name)) {
return true;
}
}
return false;
}
}

@ -1,61 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring Integration.
*
* @author Dave Syer
* @author Artem Bilan
* @since 1.0.0
*/
public class SpringIntegrationCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableIntegration")
|| AstUtils.hasAtLeastOneAnnotation(classNode, "MessageEndpoint");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.integration.config.EnableIntegration")
.add("spring-boot-starter-integration");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("org.springframework.messaging.Message", "org.springframework.messaging.MessageChannel",
"org.springframework.messaging.PollableChannel", "org.springframework.messaging.SubscribableChannel",
"org.springframework.messaging.MessageHeaders",
"org.springframework.integration.support.MessageBuilder",
"org.springframework.integration.channel.DirectChannel",
"org.springframework.integration.channel.QueueChannel",
"org.springframework.integration.channel.ExecutorChannel",
"org.springframework.integration.core.MessagingTemplate",
"org.springframework.integration.config.EnableIntegration");
imports.addStarImports("org.springframework.integration.annotation");
}
}

@ -1,57 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
import org.springframework.boot.groovy.GroovyTemplate;
/**
* {@link CompilerAutoConfiguration} for Spring MVC.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.0.0
*/
public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "Controller", "RestController", "EnableWebMvc");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.web.servlet.mvc.Controller")
.add("spring-boot-starter-web");
dependencies.ifAnyMissingClasses("groovy.text.TemplateEngine").add("groovy-templates");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("org.springframework.web.bind.annotation",
"org.springframework.web.servlet.config.annotation", "org.springframework.web.servlet",
"org.springframework.http", "org.springframework.web.servlet.handler", "org.springframework.http",
"org.springframework.ui", "groovy.text");
imports.addStaticImport(GroovyTemplate.class.getName(), "template");
}
}

@ -1,50 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring Retry.
*
* @author Dave Syer
* @since 1.3.0
*/
public class SpringRetryCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableRetry", "Retryable", "Recover");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.retry.annotation.EnableRetry").add("spring-retry",
"spring-boot-starter-aop");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("org.springframework.retry.annotation");
}
}

@ -1,57 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring Security.
*
* @author Dave Syer
* @since 1.0.0
*/
public class SpringSecurityCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableWebSecurity", "EnableGlobalMethodSecurity");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses(
"org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity")
.add("spring-boot-starter-security");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("org.springframework.security.core.Authentication",
"org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity",
"org.springframework.security.core.authority.AuthorityUtils")
.addStarImports("org.springframework.security.config.annotation.web.configuration",
"org.springframework.security.authentication",
"org.springframework.security.config.annotation.web",
"org.springframework.security.config.annotation.web.builders");
}
}

@ -1,73 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
/**
* {@link CompilerAutoConfiguration} for Spring Test.
*
* @author Dave Syer
* @since 1.1.0
*/
public class SpringTestCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "SpringBootTest");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.http.HttpHeaders").add("spring-boot-starter-web");
}
@Override
public void apply(GroovyClassLoader loader, GroovyCompilerConfiguration configuration,
GeneratorContext generatorContext, SourceUnit source, ClassNode classNode)
throws CompilationFailedException {
if (!AstUtils.hasAtLeastOneAnnotation(classNode, "RunWith")) {
AnnotationNode runWith = new AnnotationNode(ClassHelper.make("RunWith"));
runWith.addMember("value", new ClassExpression(ClassHelper.make("SpringRunner")));
classNode.addAnnotation(runWith);
}
}
@Override
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
imports.addStarImports("org.junit.runner", "org.springframework.boot.test",
"org.springframework.boot.test.context", "org.springframework.boot.test.web.client",
"org.springframework.http", "org.springframework.test.context.junit4",
"org.springframework.test.annotation")
.addImports("org.springframework.boot.test.context.SpringBootTest.WebEnvironment",
"org.springframework.boot.test.web.client.TestRestTemplate");
}
}

@ -1,54 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring Websocket.
*
* @author Dave Syer
* @since 1.0.0
*/
public class SpringWebsocketCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableWebSocket", "EnableWebSocketMessageBroker");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.web.socket.config.annotation.EnableWebSocket")
.add("spring-boot-starter-websocket").add("spring-messaging");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("org.springframework.messaging.handler.annotation",
"org.springframework.messaging.simp.config", "org.springframework.web.socket.handler",
"org.springframework.web.socket.sockjs.transport.handler",
"org.springframework.web.socket.config.annotation")
.addImports("org.springframework.web.socket.WebSocketHandler");
}
}

@ -1,53 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.AstUtils;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring MVC.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.0.0
*/
public class TransactionManagementCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableTransactionManagement");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.transaction.annotation.Transactional").add("spring-tx",
"spring-boot-starter-aop");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("org.springframework.transaction.annotation", "org.springframework.transaction.support");
imports.addImports("org.springframework.transaction.PlatformTransactionManager",
"org.springframework.transaction.support.AbstractPlatformTransactionManager");
}
}

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.
*/
/**
* Classes for auto-configuring the Groovy compiler.
*/
package org.springframework.boot.cli.compiler.autoconfigure;

@ -1,56 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.dependencies;
/**
* A resolver for artifacts' Maven coordinates, allowing group id, artifact id, or version
* to be obtained from a module identifier. A module identifier may be in the form
* {@code groupId:artifactId:version}, in which case coordinate resolution simply extracts
* the relevant piece from the identifier. Alternatively the identifier may be in the form
* {@code artifactId}, in which case coordinate resolution uses implementation-specific
* metadata to resolve the groupId and version.
*
* @author Andy Wilkinson
* @since 1.0.0
*/
public interface ArtifactCoordinatesResolver {
/**
* Gets the group id of the artifact identified by the given {@code module}. Returns
* {@code null} if the artifact is unknown to the resolver.
* @param module the id of the module
* @return the group id of the module
*/
String getGroupId(String module);
/**
* Gets the artifact id of the artifact identified by the given {@code module}.
* Returns {@code null} if the artifact is unknown to the resolver.
* @param module the id of the module
* @return the artifact id of the module
*/
String getArtifactId(String module);
/**
* Gets the version of the artifact identified by the given {@code module}. Returns
* {@code null} if the artifact is unknown to the resolver.
* @param module the id of the module
* @return the version of the module
*/
String getVersion(String module);
}

@ -1,70 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.dependencies;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* {@link DependencyManagement} that delegates to one or more {@link DependencyManagement}
* instances.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
public class CompositeDependencyManagement implements DependencyManagement {
private final List<DependencyManagement> delegates;
private final List<Dependency> dependencies = new ArrayList<>();
public CompositeDependencyManagement(DependencyManagement... delegates) {
this.delegates = Arrays.asList(delegates);
for (DependencyManagement delegate : delegates) {
this.dependencies.addAll(delegate.getDependencies());
}
}
@Override
public List<Dependency> getDependencies() {
return this.dependencies;
}
@Override
public String getSpringBootVersion() {
for (DependencyManagement delegate : this.delegates) {
String version = delegate.getSpringBootVersion();
if (version != null) {
return version;
}
}
return null;
}
@Override
public Dependency find(String artifactId) {
for (DependencyManagement delegate : this.delegates) {
Dependency found = delegate.find(artifactId);
if (found != null) {
return found;
}
}
return null;
}
}

@ -1,198 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.dependencies;
import java.util.Collections;
import java.util.List;
import org.springframework.util.Assert;
/**
* A single dependency.
*
* @author Phillip Webb
* @since 1.3.0
*/
public final class Dependency {
private final String groupId;
private final String artifactId;
private final String version;
private final List<Exclusion> exclusions;
/**
* Create a new {@link Dependency} instance.
* @param groupId the group ID
* @param artifactId the artifact ID
* @param version the version
*/
public Dependency(String groupId, String artifactId, String version) {
this(groupId, artifactId, version, Collections.emptyList());
}
/**
* Create a new {@link Dependency} instance.
* @param groupId the group ID
* @param artifactId the artifact ID
* @param version the version
* @param exclusions the exclusions
*/
public Dependency(String groupId, String artifactId, String version, List<Exclusion> exclusions) {
Assert.notNull(groupId, "GroupId must not be null");
Assert.notNull(artifactId, "ArtifactId must not be null");
Assert.notNull(version, "Version must not be null");
Assert.notNull(exclusions, "Exclusions must not be null");
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.exclusions = Collections.unmodifiableList(exclusions);
}
/**
* Return the dependency group id.
* @return the group ID
*/
public String getGroupId() {
return this.groupId;
}
/**
* Return the dependency artifact id.
* @return the artifact ID
*/
public String getArtifactId() {
return this.artifactId;
}
/**
* Return the dependency version.
* @return the version
*/
public String getVersion() {
return this.version;
}
/**
* Return the dependency exclusions.
* @return the exclusions
*/
public List<Exclusion> getExclusions() {
return this.exclusions;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() == obj.getClass()) {
Dependency other = (Dependency) obj;
boolean result = true;
result = result && this.groupId.equals(other.groupId);
result = result && this.artifactId.equals(other.artifactId);
result = result && this.version.equals(other.version);
result = result && this.exclusions.equals(other.exclusions);
return result;
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.groupId.hashCode();
result = prime * result + this.artifactId.hashCode();
result = prime * result + this.version.hashCode();
result = prime * result + this.exclusions.hashCode();
return result;
}
@Override
public String toString() {
return this.groupId + ":" + this.artifactId + ":" + this.version;
}
/**
* A dependency exclusion.
*/
public static final class Exclusion {
private final String groupId;
private final String artifactId;
Exclusion(String groupId, String artifactId) {
Assert.notNull(groupId, "GroupId must not be null");
Assert.notNull(artifactId, "ArtifactId must not be null");
this.groupId = groupId;
this.artifactId = artifactId;
}
/**
* Return the exclusion artifact ID.
* @return the exclusion artifact ID
*/
public String getArtifactId() {
return this.artifactId;
}
/**
* Return the exclusion group ID.
* @return the exclusion group ID
*/
public String getGroupId() {
return this.groupId;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() == obj.getClass()) {
Exclusion other = (Exclusion) obj;
boolean result = true;
result = result && this.groupId.equals(other.groupId);
result = result && this.artifactId.equals(other.artifactId);
return result;
}
return false;
}
@Override
public int hashCode() {
return this.groupId.hashCode() * 31 + this.artifactId.hashCode();
}
@Override
public String toString() {
return this.groupId + ":" + this.artifactId;
}
}
}

@ -1,48 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.dependencies;
import java.util.List;
/**
* An encapsulation of dependency management information.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
public interface DependencyManagement {
/**
* Returns the managed dependencies.
* @return the managed dependencies
*/
List<Dependency> getDependencies();
/**
* Returns the managed version of Spring Boot. May be {@code null}.
* @return the Spring Boot version, or {@code null}
*/
String getSpringBootVersion();
/**
* Finds the managed dependency with the given {@code artifactId}.
* @param artifactId the artifact ID of the dependency to find
* @return the dependency, or {@code null}
*/
Dependency find(String artifactId);
}

@ -1,73 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.dependencies;
import org.springframework.util.StringUtils;
/**
* {@link ArtifactCoordinatesResolver} backed by
* {@link SpringBootDependenciesDependencyManagement}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.0.0
*/
public class DependencyManagementArtifactCoordinatesResolver implements ArtifactCoordinatesResolver {
private final DependencyManagement dependencyManagement;
public DependencyManagementArtifactCoordinatesResolver() {
this(new SpringBootDependenciesDependencyManagement());
}
public DependencyManagementArtifactCoordinatesResolver(DependencyManagement dependencyManagement) {
this.dependencyManagement = dependencyManagement;
}
@Override
public String getGroupId(String artifactId) {
Dependency dependency = find(artifactId);
return (dependency != null) ? dependency.getGroupId() : null;
}
@Override
public String getArtifactId(String id) {
Dependency dependency = find(id);
return (dependency != null) ? dependency.getArtifactId() : null;
}
private Dependency find(String id) {
if (StringUtils.countOccurrencesOf(id, ":") == 2) {
String[] tokens = id.split(":");
return new Dependency(tokens[0], tokens[1], tokens[2]);
}
if (id != null) {
if (id.startsWith("spring-boot")) {
return new Dependency("org.springframework.boot", id, this.dependencyManagement.getSpringBootVersion());
}
return this.dependencyManagement.find(id);
}
return null;
}
@Override
public String getVersion(String module) {
Dependency dependency = find(module);
return (dependency != null) ? dependency.getVersion() : null;
}
}

@ -1,76 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.dependencies;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.model.Model;
import org.springframework.boot.cli.compiler.dependencies.Dependency.Exclusion;
/**
* {@link DependencyManagement} derived from a Maven {@link Model}.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
public class MavenModelDependencyManagement implements DependencyManagement {
private final List<Dependency> dependencies;
private final Map<String, Dependency> byArtifactId = new LinkedHashMap<>();
public MavenModelDependencyManagement(Model model) {
this.dependencies = extractDependenciesFromModel(model);
for (Dependency dependency : this.dependencies) {
this.byArtifactId.put(dependency.getArtifactId(), dependency);
}
}
private static List<Dependency> extractDependenciesFromModel(Model model) {
List<Dependency> dependencies = new ArrayList<>();
for (org.apache.maven.model.Dependency mavenDependency : model.getDependencyManagement().getDependencies()) {
List<Exclusion> exclusions = new ArrayList<>();
for (org.apache.maven.model.Exclusion mavenExclusion : mavenDependency.getExclusions()) {
exclusions.add(new Exclusion(mavenExclusion.getGroupId(), mavenExclusion.getArtifactId()));
}
Dependency dependency = new Dependency(mavenDependency.getGroupId(), mavenDependency.getArtifactId(),
mavenDependency.getVersion(), exclusions);
dependencies.add(dependency);
}
return dependencies;
}
@Override
public List<Dependency> getDependencies() {
return this.dependencies;
}
@Override
public String getSpringBootVersion() {
return find("spring-boot").getVersion();
}
@Override
public Dependency find(String artifactId) {
return this.byArtifactId.get(artifactId);
}
}

@ -1,53 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.dependencies;
import java.io.IOException;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.DefaultModelProcessor;
import org.apache.maven.model.io.DefaultModelReader;
import org.apache.maven.model.locator.DefaultModelLocator;
/**
* {@link DependencyManagement} derived from the effective pom of
* {@code spring-boot-dependencies}.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
public class SpringBootDependenciesDependencyManagement extends MavenModelDependencyManagement {
public SpringBootDependenciesDependencyManagement() {
super(readModel());
}
private static Model readModel() {
DefaultModelProcessor modelProcessor = new DefaultModelProcessor();
modelProcessor.setModelLocator(new DefaultModelLocator());
modelProcessor.setModelReader(new DefaultModelReader());
try {
return modelProcessor.read(SpringBootDependenciesDependencyManagement.class
.getResourceAsStream("spring-boot-dependencies-effective-bom.xml"), null);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to build model from effective pom", ex);
}
}
}

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.
*/
/**
* Classes for dependencies used during compilation.
*/
package org.springframework.boot.cli.compiler.dependencies;

@ -1,50 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.grape;
import java.util.List;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.ProxySelector;
import org.eclipse.aether.repository.RemoteRepository;
/**
* Composite {@link ProxySelector}.
*
* @author Dave Syer
* @since 1.1.0
*/
public class CompositeProxySelector implements ProxySelector {
private final List<ProxySelector> selectors;
public CompositeProxySelector(List<ProxySelector> selectors) {
this.selectors = selectors;
}
@Override
public Proxy getProxy(RemoteRepository repository) {
for (ProxySelector selector : this.selectors) {
Proxy proxy = selector.getProxy(repository);
if (proxy != null) {
return proxy;
}
}
return null;
}
}

@ -1,71 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.grape;
import java.io.File;
import java.util.Arrays;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.ProxySelector;
import org.eclipse.aether.util.repository.JreProxySelector;
import org.springframework.util.StringUtils;
/**
* A {@link RepositorySystemSessionAutoConfiguration} that, in the absence of any
* configuration, applies sensible defaults.
*
* @author Andy Wilkinson
* @since 1.0.0
*/
public class DefaultRepositorySystemSessionAutoConfiguration implements RepositorySystemSessionAutoConfiguration {
@Override
public void apply(DefaultRepositorySystemSession session, RepositorySystem repositorySystem) {
if (session.getLocalRepositoryManager() == null) {
LocalRepository localRepository = new LocalRepository(getM2RepoDirectory());
LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(session,
localRepository);
session.setLocalRepositoryManager(localRepositoryManager);
}
ProxySelector existing = session.getProxySelector();
if (!(existing instanceof CompositeProxySelector)) {
JreProxySelector fallback = new JreProxySelector();
ProxySelector selector = (existing != null) ? new CompositeProxySelector(Arrays.asList(existing, fallback))
: fallback;
session.setProxySelector(selector);
}
}
private File getM2RepoDirectory() {
return new File(getDefaultM2HomeDirectory(), "repository");
}
private File getDefaultM2HomeDirectory() {
String mavenRoot = System.getProperty("maven.home");
if (StringUtils.hasLength(mavenRoot)) {
return new File(mavenRoot);
}
return new File(System.getProperty("user.home"), ".m2");
}
}

@ -1,107 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.grape;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
import org.springframework.boot.cli.compiler.dependencies.CompositeDependencyManagement;
import org.springframework.boot.cli.compiler.dependencies.DependencyManagement;
import org.springframework.boot.cli.compiler.dependencies.DependencyManagementArtifactCoordinatesResolver;
/**
* Context used when resolving dependencies.
*
* @author Andy Wilkinson
* @since 1.1.0
*/
public class DependencyResolutionContext {
private final Map<String, Dependency> managedDependencyByGroupAndArtifact = new HashMap<>();
private final List<Dependency> managedDependencies = new ArrayList<>();
private DependencyManagement dependencyManagement = null;
private ArtifactCoordinatesResolver artifactCoordinatesResolver;
private String getIdentifier(Dependency dependency) {
return getIdentifier(dependency.getArtifact().getGroupId(), dependency.getArtifact().getArtifactId());
}
private String getIdentifier(String groupId, String artifactId) {
return groupId + ":" + artifactId;
}
public ArtifactCoordinatesResolver getArtifactCoordinatesResolver() {
return this.artifactCoordinatesResolver;
}
public String getManagedVersion(String groupId, String artifactId) {
Dependency dependency = getManagedDependency(groupId, artifactId);
if (dependency == null) {
dependency = this.managedDependencyByGroupAndArtifact.get(getIdentifier(groupId, artifactId));
}
return (dependency != null) ? dependency.getArtifact().getVersion() : null;
}
public List<Dependency> getManagedDependencies() {
return Collections.unmodifiableList(this.managedDependencies);
}
private Dependency getManagedDependency(String group, String artifact) {
return this.managedDependencyByGroupAndArtifact.get(getIdentifier(group, artifact));
}
public void addManagedDependencies(List<Dependency> dependencies) {
this.managedDependencies.addAll(dependencies);
for (Dependency dependency : dependencies) {
this.managedDependencyByGroupAndArtifact.put(getIdentifier(dependency), dependency);
}
}
public void addDependencyManagement(DependencyManagement dependencyManagement) {
for (org.springframework.boot.cli.compiler.dependencies.Dependency dependency : dependencyManagement
.getDependencies()) {
List<Exclusion> aetherExclusions = new ArrayList<>();
for (org.springframework.boot.cli.compiler.dependencies.Dependency.Exclusion exclusion : dependency
.getExclusions()) {
aetherExclusions.add(new Exclusion(exclusion.getGroupId(), exclusion.getArtifactId(), "*", "*"));
}
Dependency aetherDependency = new Dependency(new DefaultArtifact(dependency.getGroupId(),
dependency.getArtifactId(), "jar", dependency.getVersion()), JavaScopes.COMPILE, false,
aetherExclusions);
this.managedDependencies.add(0, aetherDependency);
this.managedDependencyByGroupAndArtifact.put(getIdentifier(aetherDependency), aetherDependency);
}
this.dependencyManagement = (this.dependencyManagement != null)
? new CompositeDependencyManagement(dependencyManagement, this.dependencyManagement)
: dependencyManagement;
this.artifactCoordinatesResolver = new DependencyManagementArtifactCoordinatesResolver(
this.dependencyManagement);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save