Isolate class loading for launched CLI apps

Rework classloading for launched applications so that CLI classes and
dependencies are not visible. This change allows many of the previous
hacks and workarounds to be removed.

With the exception of the 'org.springframework.boot.groovy' package
and 'groovy-all' all user required depndencies are now pulled in
via @Grab annotations.

The updated classloading algorithm has enabled the following changes:

- AetherGrapeEngine is now back in the cli project and the
  spring-boot-cli-grape project has been removed. The AetherGrapeEngine
  has also been simplified.

- The TestCommand now launches a TestRunner (similar in design to the
  SpringApplicationRunner) and report test failures directly using
  the junit TextListener. Adding custom 'testers' source to the users
  project is no longer required. The previous 'double compile' for
  tests has also been removed.

- Utility classes have been removed in favor of using versions from
  spring-core.

- The CLI jar is now packaged using the 'boot-loader' rather than using
  the maven shade plugin.

This commit also applied minor polish refactoring to a number of
classes.
This commit is contained in:
Phillip Webb 2013-11-04 22:35:23 -08:00
parent 58300deee4
commit b19f6bb238
62 changed files with 1481 additions and 1681 deletions

View File

@ -37,7 +37,6 @@
<module>spring-boot-actuator</module>
<module>spring-boot-starters</module>
<module>spring-boot-cli</module>
<module>spring-boot-cli-grape</module>
<module>spring-boot-integration-tests</module>
</modules>
</profile>

View File

@ -1,176 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>0.5.0.BUILD-SNAPSHOT</version>
<relativePath>../spring-boot-parent</relativePath>
</parent>
<artifactId>spring-boot-cli-grape</artifactId>
<packaging>jar</packaging>
<properties>
<main.basedir>${basedir}/..</main.basedir>
<start-class>org.springframework.boot.cli.SpringCli</start-class>
</properties>
<dependencies>
<!-- Provided -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<scope>provided</scope>
</dependency>
<!-- Compile -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-aether-provider</artifactId>
<exclusions>
<exclusion>
<artifactId>org.eclipse.sisu.plexus</artifactId>
<groupId>org.eclipse.sisu</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-connector-basic</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-impl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-spi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-file</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-http</artifactId>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-util</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-core</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-beans</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-aop</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-tx</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-expression</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-context</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-test</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework.retry:spring-retry</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework.integration:spring-integration-core</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework.integration:spring-integration-dsl-groovy-core</classpathDependencyExcludes>
</classpathDependencyExcludes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${start-class}</mainClass>
</transformer>
</transformers>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<versionRange>[1.7,)</versionRange>
<goals>
<goal>run</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute />
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<pluginRepositories>
<pluginRepository>
<id>objectstyle</id>
<name>ObjectStyle.org Repository</name>
<url>http://objectstyle.org/maven2/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -1,355 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import groovy.grape.GrapeEngine;
import groovy.lang.GroovyClassLoader;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.AbstractRepositoryListener;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.impl.ArtifactDescriptorReader;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.transfer.TransferEvent;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.filter.DependencyFilterUtils;
/**
* A {@link GrapeEngine} implementation that uses <a
* href="http://eclipse.org/aether">Aether</a>, the dependency resolution system used by
* Maven.
*
* @author Andy Wilkinson
*/
@SuppressWarnings("rawtypes")
public class AetherGrapeEngine implements GrapeEngine {
private static final String DEPENDENCY_MODULE = "module";
private static final String DEPENDENCY_GROUP = "group";
private static final String DEPENDENCY_VERSION = "version";
private static final Collection<Exclusion> WILDCARD_EXCLUSION = Arrays
.asList(new Exclusion("*", "*", "*", "*"));
private final Artifact parentArtifact;
private final ProgressReporter progressReporter = new ProgressReporter();
private final ArtifactDescriptorReader artifactDescriptorReader;
private final GroovyClassLoader defaultClassLoader;
private final RepositorySystemSession repositorySystemSession;
private final RepositorySystem repositorySystem;
private final List<RemoteRepository> repositories;
public AetherGrapeEngine(GroovyClassLoader classLoader, String parentGroupId,
String parentArtifactId, String parentVersion) {
this.defaultClassLoader = classLoader;
this.parentArtifact = new DefaultArtifact(parentGroupId, parentArtifactId, "pom",
parentVersion);
DefaultServiceLocator mavenServiceLocator = MavenRepositorySystemUtils
.newServiceLocator();
mavenServiceLocator.addService(RepositorySystem.class,
DefaultRepositorySystem.class);
mavenServiceLocator.addService(RepositoryConnectorFactory.class,
BasicRepositoryConnectorFactory.class);
mavenServiceLocator.addService(TransporterFactory.class,
HttpTransporterFactory.class);
mavenServiceLocator.addService(TransporterFactory.class,
FileTransporterFactory.class);
this.repositorySystem = mavenServiceLocator.getService(RepositorySystem.class);
DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils
.newSession();
repositorySystemSession.setTransferListener(new AbstractTransferListener() {
@Override
public void transferStarted(TransferEvent event)
throws TransferCancelledException {
AetherGrapeEngine.this.progressReporter.reportProgress();
}
@Override
public void transferProgressed(TransferEvent event)
throws TransferCancelledException {
AetherGrapeEngine.this.progressReporter.reportProgress();
}
});
repositorySystemSession.setRepositoryListener(new AbstractRepositoryListener() {
@Override
public void artifactResolved(RepositoryEvent event) {
AetherGrapeEngine.this.progressReporter.reportProgress();
}
});
String grapeRootProperty = System.getProperty("grape.root");
File root;
if (grapeRootProperty != null && grapeRootProperty.trim().length() > 0) {
root = new File(grapeRootProperty);
}
else {
root = new File(System.getProperty("user.home"), ".m2");
}
LocalRepository localRepo = new LocalRepository(new File(root, "repository"));
repositorySystemSession.setLocalRepositoryManager(this.repositorySystem
.newLocalRepositoryManager(repositorySystemSession, localRepo));
this.repositorySystemSession = repositorySystemSession;
List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
repositories.add(new RemoteRepository.Builder("central", "default",
"http://repo1.maven.org/maven2/").build());
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
repositories.add(new RemoteRepository.Builder("spring-snapshot", "default",
"http://repo.spring.io/snapshot").build());
repositories.add(new RemoteRepository.Builder("spring-milestone", "default",
"http://repo.spring.io/milestone").build());
}
this.repositories = repositories;
this.artifactDescriptorReader = mavenServiceLocator
.getService(ArtifactDescriptorReader.class);
}
@Override
public Object grab(Map args) {
return grab(args, args);
}
@Override
public Object grab(Map args, Map... dependencyMaps) {
List<Dependency> dependencies = createDependencies(dependencyMaps);
try {
List<File> files = resolve(dependencies);
GroovyClassLoader classLoader = (GroovyClassLoader) args.get("classLoader");
if (classLoader == null) {
classLoader = this.defaultClassLoader;
}
for (File file : files) {
classLoader.addURL(file.toURI().toURL());
}
}
catch (ArtifactResolutionException ex) {
throw new DependencyResolutionFailedException(ex);
}
catch (MalformedURLException ex) {
throw new DependencyResolutionFailedException(ex);
}
return null;
}
private List<Dependency> createDependencies(Map<?, ?>... dependencyMaps) {
List<Dependency> dependencies = new ArrayList<Dependency>(dependencyMaps.length);
for (Map<?, ?> dependencyMap : dependencyMaps) {
dependencies.add(createDependency(dependencyMap));
}
return dependencies;
}
private boolean isTransitive(Map<?, ?> dependencyMap) {
Boolean transitive = (Boolean) dependencyMap.get("transitive");
if (transitive == null) {
transitive = true;
}
return transitive;
}
private Dependency createDependency(Map<?, ?> dependencyMap) {
Artifact artifact = createArtifact(dependencyMap);
Dependency dependency;
if (!isTransitive(dependencyMap)) {
dependency = new Dependency(artifact, JavaScopes.COMPILE, null,
WILDCARD_EXCLUSION);
}
else {
dependency = new Dependency(artifact, JavaScopes.COMPILE);
}
return dependency;
}
private Artifact createArtifact(Map<?, ?> dependencyMap) {
String group = (String) dependencyMap.get(DEPENDENCY_GROUP);
String module = (String) dependencyMap.get(DEPENDENCY_MODULE);
String version = (String) dependencyMap.get(DEPENDENCY_VERSION);
return new DefaultArtifact(group, module, "jar", version);
}
private List<File> resolve(List<Dependency> dependencies)
throws ArtifactResolutionException {
CollectRequest collectRequest = new CollectRequest((Dependency) null,
dependencies, this.repositories);
collectRequest.setManagedDependencies(getManagedDependencies());
try {
DependencyResult dependencyResult = this.repositorySystem
.resolveDependencies(
this.repositorySystemSession,
new DependencyRequest(collectRequest, DependencyFilterUtils
.classpathFilter(JavaScopes.COMPILE)));
List<File> files = new ArrayList<File>();
for (ArtifactResult result : dependencyResult.getArtifactResults()) {
files.add(result.getArtifact().getFile());
}
return files;
}
catch (Exception ex) {
throw new DependencyResolutionFailedException(ex);
}
finally {
this.progressReporter.finished();
}
}
private List<Dependency> getManagedDependencies() {
ArtifactDescriptorRequest parentRequest = new ArtifactDescriptorRequest();
parentRequest.setArtifact(this.parentArtifact);
try {
ArtifactDescriptorResult artifactDescriptorResult = this.artifactDescriptorReader
.readArtifactDescriptor(this.repositorySystemSession, parentRequest);
return artifactDescriptorResult.getManagedDependencies();
}
catch (ArtifactDescriptorException ex) {
throw new DependencyResolutionFailedException(ex);
}
}
@Override
public Map<String, Map<String, List<String>>> enumerateGrapes() {
throw new UnsupportedOperationException("Grape enumeration is not supported");
}
@Override
public URI[] resolve(Map args, Map... dependencies) {
throw new UnsupportedOperationException("Resolving to URIs is not supported");
}
@Override
public URI[] resolve(Map args, List depsInfo, Map... dependencies) {
throw new UnsupportedOperationException("Resolving to URIs is not supported");
}
@Override
public Map[] listDependencies(ClassLoader classLoader) {
throw new UnsupportedOperationException("Listing dependencies is not supported");
}
@Override
public void addResolver(Map<String, Object> args) {
throw new UnsupportedOperationException("Adding a resolver is not supported");
}
@Override
public Object grab(String endorsedModule) {
throw new UnsupportedOperationException(
"Grabbing an endorsed module is not supported");
}
private static final class ProgressReporter {
private static final long INITIAL_DELAY = TimeUnit.SECONDS.toMillis(3);
private static final long PROGRESS_DELAY = TimeUnit.SECONDS.toMillis(1);
private long startTime = System.currentTimeMillis();
private long lastProgressTime = System.currentTimeMillis();
private boolean started;
private boolean finished;
void reportProgress() {
if (!this.finished
&& System.currentTimeMillis() - this.startTime > INITIAL_DELAY) {
if (!this.started) {
this.started = true;
System.out.print("Resolving dependencies..");
this.lastProgressTime = System.currentTimeMillis();
}
else if (System.currentTimeMillis() - this.lastProgressTime > PROGRESS_DELAY) {
System.out.print(".");
this.lastProgressTime = System.currentTimeMillis();
}
}
}
void finished() {
if (this.started && !this.finished) {
this.finished = true;
System.out.println("");
}
}
}
}

View File

@ -43,19 +43,60 @@
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<artifactId>groovy-all</artifactId>
</dependency>
<!-- Optional -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-templates</artifactId>
<optional>true</optional>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-aether-provider</artifactId>
<exclusions>
<exclusion>
<artifactId>org.eclipse.sisu.plexus</artifactId>
<groupId>org.eclipse.sisu</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-connector-basic</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-impl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-spi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-file</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-http</artifactId>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-util</artifactId>
</dependency>
<!-- Provided -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-boot-cli-grape</artifactId>
<version>${project.version}</version>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>provided</scope>
</dependency>
<!-- Test -->
@ -76,11 +117,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
@ -111,74 +147,80 @@
<additionalClasspathElements>
<additionalClasspathElement>${project.build.directory}/generated-resources</additionalClasspathElement>
</additionalClasspathElements>
<classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-core</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-beans</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-aop</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-tx</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-expression</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-context</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework:spring-test</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework.retry:spring-retry</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework.integration:spring-integration-core</classpathDependencyExcludes>
<classpathDependencyExcludes>org.springframework.integration:spring-integration-dsl-groovy-core</classpathDependencyExcludes>
</classpathDependencyExcludes>
</configuration>
</plugin>
<!-- Build an executable JAR manually since we can't easily depend on
a maven plugin that is part of the reactor -->
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<id>unpack</id>
<phase>prepare-package</phase>
<goals>
<goal>shade</goal>
<goal>unpack</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
</transformer>
</transformers>
<createDependencyReducedPom>false</createDependencyReducedPom>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>${project.version}</version>
<type>jar</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/assembly</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/assembly/lib</outputDirectory>
<includeScope>compile</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<inherited>false</inherited>
<configuration>
<descriptors>
<descriptor>src/main/assembly/repackage-jar.xml</descriptor>
<descriptor>src/main/assembly/bin-package.xml</descriptor>
</descriptors>
<archive>
<manifest>
<mainClass>${start-class}</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-distribution</id>
<id>jar-with-dependencies</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/jar-with-dependencies.xml</descriptor>
</descriptors>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
</manifest>
<manifestEntries>
<Start-Class>${start-class}</Start-Class>
</manifestEntries>
</archive>
</configuration>
</execution>
<execution>
<id>bin-package</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/bin-package.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
@ -238,6 +280,27 @@
<execute />
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-dependency-plugin
</artifactId>
<versionRange>
[2.8,)
</versionRange>
<goals>
<goal>
copy-dependencies
</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>

View File

@ -1,6 +1,6 @@
package org.test
import static org.springframework.boot.cli.template.GroovyTemplate.template;
import static org.springframework.boot.groovy.GroovyTemplate.*;
@Component
class Example implements CommandLineRunner {

View File

@ -25,7 +25,7 @@
</fileSets>
<files>
<file>
<source>${project.build.directory}/${project.artifactId}-${project.version}-repackaged.jar</source>
<source>${project.build.directory}/${project.artifactId}-${project.version}-full.jar</source>
<outputDirectory>/lib</outputDirectory>
<destName>${project.build.finalName}.jar</destName>
</file>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>full</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact />
<includes>
<include>${project.groupId}:${project.artifactId}</include>
</includes>
<unpack>true</unpack>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.directory}/assembly</directory>
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View File

@ -1,25 +0,0 @@
<assembly>
<id>repackaged</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<includes>
<include>org.springframework.boot:spring-boot-cli:jar:*</include>
</includes>
<unpack>true</unpack>
<directoryMode>755</directoryMode>
</dependencySet>
<dependencySet>
<includes>
<include>org.springframework.boot:spring-boot-cli-grape:jar:*</include>
</includes>
<outputDirectory>internal</outputDirectory>
<directoryMode>755</directoryMode>
<scope>provided</scope>
<unpack>true</unpack>
</dependencySet>
</dependencySets>
</assembly>

View File

@ -1,71 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.junit.runner.JUnitCore
import org.junit.runner.Result
import org.springframework.boot.cli.command.tester.Failure
import org.springframework.boot.cli.command.tester.TestResults
import java.lang.annotation.Annotation
import java.lang.reflect.Method
/**
* Groovy script to run JUnit tests inside the {@link TestCommand}.
* Needs to be compiled along with the actual code to work properly.
*
* @author Greg Turnquist
*/
class JUnitTester extends AbstractTester {
@Override
protected Set<Class<?>> findTestableClasses(List<Class<?>> compiled) {
// Look for @Test methods
Set<Class<?>> testable = new LinkedHashSet<Class<?>>()
for (Class<?> clazz : compiled) {
for (Method method : clazz.getMethods()) {
for (Annotation annotation : method.getAnnotations()) {
if (annotation.toString().contains("Test")) {
testable.add(clazz)
}
}
}
}
return testable
}
@Override
protected TestResults test(List<Class<?>> testable) {
return JUnitTester.runEmbeddedTests(testable)
}
static TestResults runEmbeddedTests(List<Class<?>> testable) {
Result results = JUnitCore.runClasses(testable.toArray(new Class<?>[0]))
TestResults testResults = new TestResults()
testResults.setRunCount(results.getRunCount())
List<Failure> failures = new ArrayList<Failure>()
for (org.junit.runner.notification.Failure failure : results.getFailures()) {
failures.add(new Failure(failure.exception.toString(), failure.trace))
}
testResults.setFailures(failures)
return testResults
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.springframework.boot.cli.command.tester.TestResults
import spock.lang.Specification
/**
* Groovy script to run Spock tests inside the {@link TestCommand}.
* Needs to be compiled along with the actual code to work properly.
*
* NOTE: SpockTester depends on JUnitTester to actually run the tests
*
* @author Greg Turnquist
*/
class SpockTester extends AbstractTester {
@Override
protected Set<Class<?>> findTestableClasses(List<Class<?>> compiled) {
// Look for classes that implement spock.lang.Specification
Set<Class<?>> testable = new LinkedHashSet<Class<?>>()
for (Class<?> clazz : compiled) {
if (Specification.class.isAssignableFrom(clazz)) {
testable.add(clazz)
}
}
return testable
}
@Override
protected TestResults test(List<Class<?>> testable) {
return JUnitTester.runEmbeddedTests(testable)
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.springframework.boot.cli.command.tester.TestResults
/**
* Groovy script define abstract basis for automated testers for {@link TestCommand}.
* Needs to be compiled along with the actual code to work properly.
*
* @author Greg Turnquist
*/
public abstract class AbstractTester {
public TestResults findAndTest(List<Class<?>> compiled) throws FileNotFoundException {
Set<Class<?>> testable = findTestableClasses(compiled)
if (testable.size() == 0) {
return TestResults.NONE
}
return test(new ArrayList<Class<?>>(testable))
}
protected abstract Set<Class<?>> findTestableClasses(List<Class<?>> compiled)
protected abstract TestResults test(List<Class<?>> testable)
}

View File

@ -18,13 +18,13 @@ package org.springframework.boot.cli.command;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.Log;
import org.springframework.boot.cli.util.FileUtils;
/**
* {@link Command} to 'clean' up grapes, removing cached dependencies and forcing a
@ -107,7 +107,7 @@ public class CleanCommand extends OptionParsingCommand {
return;
}
for (Object obj : FileUtils.recursiveList(file)) {
for (Object obj : recursiveList(file)) {
File candidate = (File) obj;
if (candidate.getName().contains("SNAPSHOT")) {
delete(candidate);
@ -117,7 +117,7 @@ public class CleanCommand extends OptionParsingCommand {
private void delete(File file) {
Log.info("Deleting: " + file);
FileUtils.recursiveDelete(file);
recursiveDelete(file);
}
private File getModulePath(File root, String group, String module, Layout layout) {
@ -167,6 +167,30 @@ public class CleanCommand extends OptionParsingCommand {
IVY, MAVEN;
}
private void recursiveDelete(File file) {
if (file.exists()) {
if (file.isDirectory()) {
for (File inDir : file.listFiles()) {
recursiveDelete(inDir);
}
}
if (!file.delete()) {
throw new IllegalStateException("Failed to delete " + file);
}
}
}
private List<File> recursiveList(File file) {
List<File> files = new ArrayList<File>();
if (file.isDirectory()) {
for (File inDir : file.listFiles()) {
files.addAll(recursiveList(inDir));
}
}
files.add(file);
return files;
}
}
}

View File

@ -30,8 +30,9 @@ import org.springframework.boot.cli.CommandFactory;
*/
public class DefaultCommandFactory implements CommandFactory {
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
new VersionCommand(), new RunCommand(), new CleanCommand(), new TestCommand());
private static final List<Command> DEFAULT_COMMANDS = Arrays
.<Command> asList(new VersionCommand(), new RunCommand(), new CleanCommand(),
new TestCommand());
@Override
public Collection<Command> getCommands() {

View File

@ -28,10 +28,11 @@ import joptsimple.OptionSet;
import joptsimple.OptionSpecBuilder;
/**
* A handler that parses and handles options.
* Delegate used by {@link OptionParsingCommand} to parse options and run the command.
*
* @author Dave Syer
* @see OptionParsingCommand
* @see #run(OptionSet)
*/
public class OptionHandler {
@ -70,6 +71,11 @@ public class OptionHandler {
run(options);
}
/**
* Run the command using the specified parsed {@link OptionSet}.
* @param options the parsed option set
* @throws Exception
*/
protected void run(OptionSet options) throws Exception {
}

View File

@ -19,10 +19,11 @@ package org.springframework.boot.cli.command;
import org.springframework.boot.cli.Command;
/**
* Base class for a {@link Command} that pare options using an {@link OptionHandler}.
* Base class for a {@link Command} that parse options using an {@link OptionHandler}.
*
* @author Phillip Webb
* @author Dave Syer
* @see OptionHandler
*/
public abstract class OptionParsingCommand extends AbstractCommand {

View File

@ -23,6 +23,7 @@ import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
import org.springframework.boot.cli.runner.SpringApplicationRunner;
import org.springframework.boot.cli.runner.SpringApplicationRunnerConfiguration;
@ -120,6 +121,11 @@ public class RunCommand extends OptionParsingCommand {
this.options = options;
}
@Override
public GroovyCompilerScope getScope() {
return GroovyCompilerScope.DEFAULT;
}
@Override
public boolean isWatchForFileChanges() {
return this.options.has(RunOptionHandler.this.watchOption);
@ -152,11 +158,12 @@ public class RunCommand extends OptionParsingCommand {
}
@Override
public String getClasspath() {
public String[] getClasspath() {
if (this.options.has(RunOptionHandler.this.classpathOption)) {
return this.options.valueOf(RunOptionHandler.this.classpathOption);
return this.options.valueOf(RunOptionHandler.this.classpathOption)
.split(":");
}
return "";
return NO_CLASSPATH;
}
}

View File

@ -23,6 +23,7 @@ import groovy.lang.MetaMethod;
import groovy.lang.Script;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
@ -32,7 +33,8 @@ import org.codehaus.groovy.control.CompilationFailedException;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.util.FileUtils;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
import org.springframework.util.FileCopyUtils;
/**
* {@link Command} to run a Groovy script.
@ -219,7 +221,7 @@ public class ScriptCommand implements Command {
try {
File file = File.createTempFile(name, ".groovy");
file.deleteOnExit();
FileUtils.copy(url, file);
FileCopyUtils.copy(url.openStream(), new FileOutputStream(file));
return file;
}
catch (IOException ex) {
@ -230,6 +232,11 @@ public class ScriptCommand implements Command {
private static class ScriptConfiguration implements GroovyCompilerConfiguration {
@Override
public GroovyCompilerScope getScope() {
return GroovyCompilerScope.EXTENSION;
}
@Override
public boolean isGuessImports() {
return true;
@ -241,8 +248,8 @@ public class ScriptCommand implements Command {
}
@Override
public String getClasspath() {
return "";
public String[] getClasspath() {
return NO_CLASSPATH;
}
}

View File

@ -44,7 +44,7 @@ import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.objectweb.asm.Opcodes;
import org.springframework.asm.Opcodes;
import org.springframework.boot.cli.Command;
/**

View File

@ -16,196 +16,95 @@
package org.springframework.boot.cli.command;
import groovy.lang.GroovyObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.codehaus.groovy.control.CompilationFailedException;
import org.springframework.boot.cli.Log;
import org.springframework.boot.cli.command.tester.Failure;
import org.springframework.boot.cli.command.tester.TestResults;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.util.FileUtils;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
import org.springframework.boot.cli.testrunner.TestRunner;
import org.springframework.boot.cli.testrunner.TestRunnerConfiguration;
import static java.util.Arrays.asList;
/**
* Invokes testing for auto-compiled scripts
* {@link Command} to run a groovy test script or scripts.
*
* @author Greg Turnquist
* @author Phillip Webb
*/
public class TestCommand extends OptionParsingCommand {
public TestCommand() {
super("test", "Test a groovy script", new TestOptionHandler());
}
@Override
public String getUsageHelp() {
return "[options] <files>";
}
public TestResults getResults() {
return ((TestOptionHandler) this.getHandler()).results;
}
private static class TestGroovyCompilerConfiguration implements
GroovyCompilerConfiguration {
@Override
public boolean isGuessImports() {
return true;
}
@Override
public boolean isGuessDependencies() {
return true;
}
@Override
public String getClasspath() {
return "";
}
super("test", "Run a spring groovy script test", new TestOptionHandler());
}
private static class TestOptionHandler extends OptionHandler {
private TestResults results;
private OptionSpec<Void> noGuessImportsOption;
private OptionSpec<Void> noGuessDependenciesOption;
private OptionSpec<String> classpathOption;
private TestRunner runner;
@Override
protected 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.classpathOption = option(asList("classpath", "cp"),
"Additional classpath entries").withRequiredArg();
}
@Override
protected void run(OptionSet options) throws Exception {
TestGroovyCompilerConfiguration configuration = new TestGroovyCompilerConfiguration();
GroovyCompiler compiler = new GroovyCompiler(configuration);
FileOptions fileOptions = new FileOptions(options);
TestRunnerConfiguration configuration = new TestRunnerConfigurationAdapter(
options);
this.runner = new TestRunner(configuration, fileOptions.getFilesArray(),
fileOptions.getArgsArray());
this.runner.compileAndRunTests();
}
FileOptions fileOptions = new FileOptions(options, getClass()
.getClassLoader());
/**
* Simple adapter class to present the {@link OptionSet} as a
* {@link TestRunnerConfiguration}.
*/
private class TestRunnerConfigurationAdapter implements TestRunnerConfiguration {
/*
* Need to compile the code twice: The first time automatically pulls in
* autoconfigured libraries including test tools. Then the compiled code can
* be scanned to see what libraries were activated. Then it can be recompiled,
* with appropriate tester groovy scripts included in the same classloading
* context. Then the testers can be fetched and invoked through reflection
* against the composite AST.
*/
private OptionSet options;
// Compile - Pass 1 - compile source code to see what test libraries were
// pulled in
Object[] sources = compiler.sources(fileOptions.getFilesArray());
List<File> testerFiles = compileAndCollectTesterFiles(sources);
// Compile - Pass 2 - add appropriate testers
List<File> files = new ArrayList<File>(fileOptions.getFiles());
files.addAll(testerFiles);
sources = compiler.sources(files.toArray(new File[files.size()]));
if (sources.length == 0) {
throw new RuntimeException("No classes found in '" + files + "'");
public TestRunnerConfigurationAdapter(OptionSet options) {
this.options = options;
}
// Extract list of compiled classes
List<Class<?>> compiled = new ArrayList<Class<?>>();
List<Class<?>> testers = new ArrayList<Class<?>>();
for (Object source : sources) {
if (source instanceof Class) {
Class<?> sourceClass = (Class<?>) source;
if (sourceClass.getSuperclass().getName().equals("AbstractTester")) {
testers.add(sourceClass);
}
else {
compiled.add((Class<?>) source);
}
@Override
public GroovyCompilerScope getScope() {
return GroovyCompilerScope.DEFAULT;
}
@Override
public boolean isGuessImports() {
return !this.options.has(TestOptionHandler.this.noGuessImportsOption);
}
@Override
public boolean isGuessDependencies() {
return !this.options
.has(TestOptionHandler.this.noGuessDependenciesOption);
}
@Override
public String[] getClasspath() {
if (this.options.has(TestOptionHandler.this.classpathOption)) {
return this.options.valueOf(TestOptionHandler.this.classpathOption)
.split(":");
}
return NO_CLASSPATH;
}
this.results = new TestResults();
for (Class<?> tester : testers) {
GroovyObject obj = (GroovyObject) tester.newInstance();
this.results.add((TestResults) obj.invokeMethod("findAndTest", compiled));
}
printReport(this.results);
}
private List<File> compileAndCollectTesterFiles(Object[] sources)
throws CompilationFailedException, IOException {
Set<String> testerUnits = new LinkedHashSet<String>();
List<File> testerFiles = new ArrayList<File>();
addTesterOnClass(sources, "org.junit.Test", testerFiles, testerUnits, "junit");
addTesterOnClass(sources, "spock.lang.Specification", testerFiles,
testerUnits, "junit", "spock");
if (!testerFiles.isEmpty()) {
testerFiles.add(createTempTesterFile("tester"));
}
return testerFiles;
}
private void addTesterOnClass(Object[] sources, String className,
List<File> testerFiles, Set<String> testerUnits, String... testerNames) {
for (Object source : sources) {
if (source instanceof Class<?>) {
try {
((Class<?>) source).getClassLoader().loadClass(className);
for (String testerName : testerNames) {
if (testerUnits.add(testerName)) {
testerFiles.add(createTempTesterFile(testerName));
}
}
return;
}
catch (ClassNotFoundException ex) {
}
}
}
}
private File createTempTesterFile(String name) {
try {
File file = File.createTempFile(name, ".groovy");
file.deleteOnExit();
URL resource = getClass().getClassLoader().getResource(
"testers/" + name + ".groovy");
FileUtils.copy(resource, file);
return file;
}
catch (IOException ex) {
throw new IllegalStateException("Could not create temp file for source: "
+ name);
}
}
private void printReport(TestResults results) throws FileNotFoundException {
PrintWriter writer = new PrintWriter("results.txt");
String header = "Total: " + results.getRunCount() + ", Success: "
+ (results.getRunCount() - results.getFailureCount())
+ ", : Failures: " + results.getFailureCount() + "\n" + "Passed? "
+ results.wasSuccessful();
String trailer = "";
String trace = "";
for (Failure failure : results.getFailures()) {
trailer += "Failed: " + failure.getDescription().toString() + "\n";
trace += failure.getTrace() + "\n";
}
writer.println(header);
writer.println(trace);
writer.close();
Log.info(header);
Log.info(trailer);
}
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.command.tester;
/**
* Platform neutral way to capture a test failure
*
* NOTE: This is needed to avoid having to add JUnit jar file to the deployable artifacts
*
* @author Greg Turnquist
*/
public class Failure {
private String description;
private String trace;
public Failure(String description, String trace) {
this.description = description;
this.trace = trace;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTrace() {
return this.trace;
}
public void setTrace(String trace) {
this.trace = trace;
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.command.tester;
import java.util.ArrayList;
import java.util.List;
/**
* Platform neutral way to collect test results
*
* NOTE: This is needed to avoid having to add JUnit's jar file to the deployable
* artifacts
*
* @author Greg Turnquist
*/
public class TestResults {
public static final TestResults NONE = new TestResults() {
@Override
public int getRunCount() {
return 0;
}
@Override
public int getFailureCount() {
return 0;
}
@Override
public List<Failure> getFailures() {
return new ArrayList<Failure>();
}
@Override
public boolean wasSuccessful() {
return true;
}
};
private int runCount = 0;
private List<Failure> failures = new ArrayList<Failure>();
public void add(TestResults results) {
this.runCount += results.getRunCount();
this.failures.addAll(results.getFailures());
}
public boolean wasSuccessful() {
return this.failures.size() == 0;
}
public int getRunCount() {
return this.runCount;
}
public void setRunCount(int runCount) {
this.runCount = runCount;
}
public int getFailureCount() {
return this.failures.size();
}
public List<Failure> getFailures() {
return this.failures;
}
public void setFailures(List<Failure> failures) {
this.failures = failures;
}
}

View File

@ -177,15 +177,16 @@ public class DependencyCustomizer {
}
/**
* Add a single dependency and all of its dependencies. The group ID and version of
* the dependency are resolves using the customizer's
* {@link ArtifactCoordinatesResolver}.
* @param module The module ID
* Add dependencies and all of their dependencies. The group ID and version of the
* dependency are resolves using the customizer's {@link ArtifactCoordinatesResolver}.
* @param modules The module IDs
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String module) {
return this.add(this.coordinatesResolver.getGroupId(module), module,
this.coordinatesResolver.getVersion(module), true);
public DependencyCustomizer add(String... modules) {
for (String module : modules) {
add(module, true);
}
return this;
}
/**
@ -198,15 +199,10 @@ public class DependencyCustomizer {
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String module, boolean transitive) {
return this.add(this.coordinatesResolver.getGroupId(module), module,
this.coordinatesResolver.getVersion(module), transitive);
}
private DependencyCustomizer add(String group, String module, String version,
boolean transitive) {
if (canAdd()) {
this.classNode.addAnnotation(createGrabAnnotation(group, module, version,
transitive));
this.classNode.addAnnotation(createGrabAnnotation(
this.coordinatesResolver.getGroupId(module), module,
this.coordinatesResolver.getVersion(module), transitive));
}
return this;
}

View File

@ -19,7 +19,11 @@ package org.springframework.boot.cli.compiler;
import groovy.lang.GroovyClassLoader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
@ -29,6 +33,8 @@ 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;
/**
* Extension of the {@link GroovyClassLoader} with support for obtaining '.class' files as
@ -39,13 +45,67 @@ import org.codehaus.groovy.control.SourceUnit;
*/
class ExtendedGroovyClassLoader extends GroovyClassLoader {
private Map<String, byte[]> classResources = new HashMap<String, byte[]>();
private static final String SHARED_PACKAGE = "org.springframework.boot.groovy";
private CompilerConfiguration configuration;
private final Map<String, byte[]> classResources = new HashMap<String, byte[]>();
public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
super(loader, config);
this.configuration = config;
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");
InputStream inputStream = getParent().getResourceAsStream(path);
if (inputStream != null) {
try {
return defineClass(name, FileCopyUtils.copyToByteArray(inputStream));
}
finally {
inputStream.close();
}
}
return null;
}
catch (Exception ex) {
return null;
}
}
@Override
@ -58,10 +118,6 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
return resourceStream;
}
public CompilerConfiguration getConfiguration() {
return this.configuration;
}
@Override
public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
InnerLoader loader = AccessController
@ -74,6 +130,10 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
return new ExtendedClassCollector(loader, unit, su);
}
public CompilerConfiguration getConfiguration() {
return this.configuration;
}
/**
* Inner collector class used to track as classes are added.
*/
@ -93,4 +153,73 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
}
}
/**
* ClassLoader used for a parent that filters so that only classes from groovy-all.jar
* are exposed.
*/
private static class DefaultScopeParentClassLoader extends ClassLoader {
private final URLClassLoader groovyOnlyClassLoader;
public DefaultScopeParentClassLoader(ClassLoader parent) {
super(parent);
this.groovyOnlyClassLoader = new URLClassLoader(
new URL[] { getGroovyJar(parent) }, null);
}
private URL getGroovyJar(final ClassLoader parent) {
URL result = findGroovyJarDirectly(parent);
if (result == null) {
result = findGroovyJarFromClassPath(parent);
}
Assert.state(result != null, "Unable to find groovy JAR");
return result;
}
private URL findGroovyJarDirectly(ClassLoader classLoader) {
while (classLoader != null) {
if (classLoader instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) classLoader).getURLs();
for (URL url : urls) {
if (isGroovyJar(url.toString())) {
return url;
}
}
}
classLoader = classLoader.getParent();
}
return null;
}
private URL findGroovyJarFromClassPath(ClassLoader parent) {
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 {
return file.toURI().toURL();
}
catch (MalformedURLException ex) {
// Swallow and continue
}
}
}
}
return null;
}
private boolean isGroovyJar(String entry) {
return entry.contains("/groovy-all");
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
this.groovyOnlyClassLoader.loadClass(name);
return super.loadClass(name, resolve);
}
}
}

View File

@ -16,16 +16,12 @@
package org.springframework.boot.cli.compiler;
import groovy.grape.GrapeEngine;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyClassLoader.ClassCollector;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@ -45,6 +41,10 @@ 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.grape.AetherGrapeEngine;
import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller;
import org.springframework.boot.cli.compiler.transformation.DependencyAutoConfigurationTransformation;
import org.springframework.boot.cli.compiler.transformation.ResolveDependencyCoordinatesTransformation;
/**
* Compiler for Groovy source files. Primarily a simple Facade for
@ -67,15 +67,12 @@ import org.codehaus.groovy.transform.ASTTransformationVisitor;
*/
public class GroovyCompiler {
private static final ClassLoader AETHER_CLASS_LOADER = new URLClassLoader(
new URL[] { GroovyCompiler.class.getResource("/internal/") });
private final ArtifactCoordinatesResolver coordinatesResolver;
private final GroovyCompilerConfiguration configuration;
private final ExtendedGroovyClassLoader loader;
private final ArtifactCoordinatesResolver coordinatesResolver;
private final ServiceLoader<CompilerAutoConfiguration> compilerAutoConfigurations;
private final List<ASTTransformation> transformations;
@ -85,44 +82,35 @@ public class GroovyCompiler {
* @param configuration the compiler configuration
*/
public GroovyCompiler(final GroovyCompilerConfiguration configuration) {
this.configuration = configuration;
this.loader = new ExtendedGroovyClassLoader(getClass().getClassLoader(),
new CompilerConfiguration());
if (configuration.getClasspath().length() > 0) {
this.loader.addClasspath(configuration.getClasspath());
}
this.loader = createLoader(configuration);
this.coordinatesResolver = new PropertiesArtifactCoordinatesResolver(this.loader);
installGrapeEngine();
GrapeEngineInstaller.install(new AetherGrapeEngine(this.loader));
this.loader.getConfiguration().addCompilationCustomizers(
new CompilerAutoConfigureCustomizer());
this.compilerAutoConfigurations = ServiceLoader.load(
CompilerAutoConfiguration.class, GroovyCompiler.class.getClassLoader());
this.transformations = new ArrayList<ASTTransformation>();
this.transformations.add(new DependencyAutoConfigurationTransformation(this.loader,
this.coordinatesResolver, this.compilerAutoConfigurations));
this.transformations.add(new DependencyAutoConfigurationTransformation(
this.loader, this.coordinatesResolver, this.compilerAutoConfigurations));
if (this.configuration.isGuessDependencies()) {
this.transformations.add(new ResolveDependencyCoordinatesTransformation(
this.coordinatesResolver));
}
}
@SuppressWarnings("unchecked")
private void installGrapeEngine() {
try {
Class<GrapeEngine> grapeEngineClass = (Class<GrapeEngine>) AETHER_CLASS_LOADER
.loadClass("org.springframework.boot.cli.compiler.AetherGrapeEngine");
Constructor<GrapeEngine> constructor = grapeEngineClass.getConstructor(
GroovyClassLoader.class, String.class, String.class, String.class);
GrapeEngine grapeEngine = constructor.newInstance(this.loader,
"org.springframework.boot", "spring-boot-starter-parent",
this.coordinatesResolver.getVersion("spring-boot"));
new GrapeEngineInstaller(grapeEngine).install();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to install custom GrapeEngine", ex);
private ExtendedGroovyClassLoader createLoader(
GroovyCompilerConfiguration configuration) {
ExtendedGroovyClassLoader loader = new ExtendedGroovyClassLoader(
configuration.getScope());
for (String classpath : configuration.getClasspath()) {
loader.addClasspath(classpath);
}
return loader;
}
public void addCompilationCustomizers(CompilationCustomizer... customizers) {
@ -201,7 +189,7 @@ public class GroovyCompiler {
}
@SuppressWarnings("rawtypes")
private LinkedList[] getPhaseOperations(final CompilationUnit compilationUnit) {
private LinkedList[] getPhaseOperations(CompilationUnit compilationUnit) {
try {
Field field = CompilationUnit.class.getDeclaredField("phaseOperations");
field.setAccessible(true);

View File

@ -23,6 +23,16 @@ package org.springframework.boot.cli.compiler;
*/
public interface GroovyCompilerConfiguration {
/**
* Constant to be used when there is not {@link #getClasspath() classpath}.
*/
public static final String[] NO_CLASSPATH = {};
/**
* Returns the scope in which the compiler operates.
*/
GroovyCompilerScope getScope();
/**
* Returns if import declarations should be guessed.
*/
@ -34,8 +44,8 @@ public interface GroovyCompilerConfiguration {
boolean isGuessDependencies();
/**
* @return a path for local resources (colon separated)
* @return a path for local resources
*/
String getClasspath();
String[] getClasspath();
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
/**
* The scope in which a groovy compiler operates.
*
* @author Phillip Webb
*/
public enum GroovyCompilerScope {
/**
* Default scope, exposes groovy-all.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
}

View File

@ -16,27 +16,25 @@
package org.springframework.boot.cli.compiler;
import groovy.lang.GroovyClassLoader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Properties;
import org.springframework.util.Assert;
/**
* {@link ArtifactCoordinatesResolver} backed by a properties file.
*
* @author Andy Wilkinson
*/
final class PropertiesArtifactCoordinatesResolver implements ArtifactCoordinatesResolver {
public final class PropertiesArtifactCoordinatesResolver implements
ArtifactCoordinatesResolver {
private final GroovyClassLoader loader;
private final ClassLoader loader;
private Properties properties = null;
public PropertiesArtifactCoordinatesResolver(GroovyClassLoader loader) {
public PropertiesArtifactCoordinatesResolver(ClassLoader loader) {
this.loader = loader;
}
@ -52,34 +50,24 @@ final class PropertiesArtifactCoordinatesResolver implements ArtifactCoordinates
private String getProperty(String name) {
if (this.properties == null) {
loadProperties();
this.properties = loadProperties();
}
String property = this.properties.getProperty(name);
return property;
}
private void loadProperties() {
private Properties loadProperties() {
Properties properties = new Properties();
InputStream inputStream = this.loader
.getResourceAsStream("META-INF/springcli.properties");
Assert.state(inputStream != null, "Unable to load springcli properties");
try {
ArrayList<URL> urls = Collections.list(this.loader
.getResources("META-INF/springcli.properties"));
for (URL url : urls) {
InputStream inputStream = url.openStream();
try {
properties.load(inputStream);
}
catch (IOException ex) {
// Swallow and continue
}
finally {
inputStream.close();
}
}
properties.load(inputStream);
return properties;
}
catch (IOException ex) {
// Swallow and continue
throw new IllegalStateException("Unable to load springcli properties", ex);
}
this.properties = properties;
}
}

View File

@ -38,7 +38,7 @@ public class JUnitCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public void applyDependencies(DependencyCustomizer dependencies)
throws CompilationFailedException {
dependencies.add("junit").add("spring-test").add("hamcrest-library");
dependencies.add("junit").add("spring-test", "hamcrest-library");
}
@Override

View File

@ -16,18 +16,13 @@
package org.springframework.boot.cli.compiler.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
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;
import org.springframework.boot.groovy.EnableJmsMessaging;
/**
* {@link CompilerAutoConfiguration} for Spring JMS.
@ -46,7 +41,7 @@ public class JmsCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public void applyDependencies(DependencyCustomizer dependencies)
throws CompilationFailedException {
dependencies.add("spring-jms").add("geronimo-jms_1.1_spec");
dependencies.add("spring-jms", "geronimo-jms_1.1_spec");
}
@ -58,11 +53,4 @@ public class JmsCompilerAutoConfiguration extends CompilerAutoConfiguration {
EnableJmsMessaging.class.getCanonicalName());
}
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public static @interface EnableJmsMessaging {
}
}

View File

@ -38,7 +38,7 @@ public class SpringBatchCompilerAutoConfiguration extends CompilerAutoConfigurat
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.batch.core.Job").add(
"spring-batch-core");
"spring-boot-starter-batch");
dependencies.ifAnyMissingClasses("org.springframework.jdbc.core.JdbcTemplate")
.add("spring-jdbc");
}

View File

@ -71,7 +71,6 @@ public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfigurati
public void applyToMainClass(GroovyClassLoader loader,
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
// Could add switch for auto config, but it seems like it wouldn't get used much
addEnableAutoConfigurationAnnotation(source, classNode);
}

View File

@ -16,17 +16,12 @@
package org.springframework.boot.cli.compiler.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
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.EnableIntegrationPatterns;
/**
* {@link CompilerAutoConfiguration} for Spring Integration.
@ -45,8 +40,8 @@ public class SpringIntegrationCompilerAutoConfiguration extends CompilerAutoConf
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.integration.Message")
.add("spring-integration-core").add("spring-integration-dsl-groovy-core");
dependencies.ifAnyMissingClasses("org.springframework.integration.Message").add(
"spring-boot-starter-integration", "spring-integration-dsl-groovy-core");
dependencies.ifAnyMissingClasses("groovy.util.XmlParser").add("groovy-xml");
}
@ -61,15 +56,8 @@ public class SpringIntegrationCompilerAutoConfiguration extends CompilerAutoConf
"org.springframework.integration.annotation.Headers",
"org.springframework.integration.annotation.Payload",
"org.springframework.integration.annotation.Payloads",
EnableIntegrationPatterns.class.getCanonicalName(),
"org.springframework.integration.dsl.groovy.MessageFlow",
"org.springframework.integration.dsl.groovy.builder.IntegrationBuilder");
}
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public static @interface EnableIntegrationPatterns {
"org.springframework.integration.dsl.groovy.builder.IntegrationBuilder",
EnableIntegrationPatterns.class.getCanonicalName());
}
}

View File

@ -16,18 +16,13 @@
package org.springframework.boot.cli.compiler.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
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;
import org.springframework.boot.groovy.EnableDeviceResolver;
/**
* {@link CompilerAutoConfiguration} for Spring Mobile.
@ -45,7 +40,7 @@ public class SpringMobileCompilerAutoConfiguration extends CompilerAutoConfigura
@Override
public void applyDependencies(DependencyCustomizer dependencies)
throws CompilationFailedException {
dependencies.add("spring-mobile-device");
dependencies.add("spring-boot-starter-mobile");
}
@Override
@ -54,11 +49,4 @@ public class SpringMobileCompilerAutoConfiguration extends CompilerAutoConfigura
imports.addImports(EnableDeviceResolver.class.getCanonicalName());
}
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public static @interface EnableDeviceResolver {
}
}

View File

@ -16,17 +16,12 @@
package org.springframework.boot.cli.compiler.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
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.
@ -39,7 +34,7 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "Controller",
"RestController", "EnableWebMvc", "WebConfiguration");
"RestController", "EnableWebMvc");
}
@Override
@ -47,7 +42,6 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio
dependencies
.ifAnyMissingClasses("org.springframework.web.servlet.mvc.Controller")
.add("spring-boot-starter-web");
dependencies.add("groovy-templates");
}
@ -57,15 +51,7 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio
"org.springframework.web.servlet.config.annotation",
"org.springframework.web.servlet",
"org.springframework.web.servlet.handler", "org.springframework.http");
imports.addStaticImport("org.springframework.boot.cli.template.GroovyTemplate",
"template");
imports.addImports(WebConfiguration.class.getCanonicalName());
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
public static @interface WebConfiguration {
imports.addStaticImport(GroovyTemplate.class.getName(), "template");
}
}

View File

@ -38,10 +38,9 @@ public class TransactionManagementCompilerAutoConfiguration extends
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies
.ifAnyMissingClasses(
"org.springframework.transaction.annotation.Transactional")
.add("spring-tx").add("spring-boot-starter-aop");
dependencies.ifAnyMissingClasses(
"org.springframework.transaction.annotation.Transactional").add(
"spring-tx", "spring-boot-starter-aop");
}
@Override

View File

@ -0,0 +1,249 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.grape;
import groovy.grape.GrapeEngine;
import groovy.lang.GroovyClassLoader;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.filter.DependencyFilterUtils;
import org.springframework.util.StringUtils;
/**
* A {@link GrapeEngine} implementation that uses <a
* href="http://eclipse.org/aether">Aether</a>, the dependency resolution system used by
* Maven.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
@SuppressWarnings("rawtypes")
public class AetherGrapeEngine implements GrapeEngine {
private static final Collection<Exclusion> WILDCARD_EXCLUSION = Arrays
.asList(new Exclusion("*", "*", "*", "*"));
private final ProgressReporter progressReporter;
private final GroovyClassLoader classLoader;
private final RepositorySystemSession session;
private final RepositorySystem repositorySystem;
private final List<RemoteRepository> repositories;
public AetherGrapeEngine(GroovyClassLoader classLoader) {
this.classLoader = classLoader;
this.repositorySystem = createServiceLocator().getService(RepositorySystem.class);
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository localRepository = new LocalRepository(getM2RepoDirectory());
LocalRepositoryManager localRepositoryManager = this.repositorySystem
.newLocalRepositoryManager(session, localRepository);
session.setLocalRepositoryManager(localRepositoryManager);
this.session = session;
this.repositories = getRemoteRepositories();
this.progressReporter = new ProgressReporter(session);
}
private ServiceLocator createServiceLocator() {
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositorySystem.class, DefaultRepositorySystem.class);
locator.addService(RepositoryConnectorFactory.class,
BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
return locator;
}
private File getM2RepoDirectory() {
return new File(getM2HomeDirectory(), "repository");
}
private File getM2HomeDirectory() {
String grapeRoot = System.getProperty("grape.root");
if (StringUtils.hasLength(grapeRoot)) {
return new File(grapeRoot);
}
return new File(System.getProperty("user.home"), ".m2");
}
private List<RemoteRepository> getRemoteRepositories() {
List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
addRemoteRepository(repositories, "central", "http://repo1.maven.org/maven2/");
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
addRemoteRepository(repositories, "spring-snapshot",
"http://repo.spring.io/snapshot");
addRemoteRepository(repositories, "spring-milestone",
"http://repo.spring.io/milestone");
}
return repositories;
}
private void addRemoteRepository(List<RemoteRepository> repositories, String id,
String url) {
repositories.add(new RemoteRepository.Builder(id, "default", url).build());
}
@Override
public Object grab(Map args) {
return grab(args, args);
}
@Override
public Object grab(Map args, Map... dependencyMaps) {
try {
List<Dependency> dependencies = createDependencies(dependencyMaps);
List<File> files = resolve(dependencies);
GroovyClassLoader classLoader = getClassLoader(args);
for (File file : files) {
classLoader.addURL(file.toURI().toURL());
}
}
catch (ArtifactResolutionException ex) {
throw new DependencyResolutionFailedException(ex);
}
catch (MalformedURLException ex) {
throw new DependencyResolutionFailedException(ex);
}
return null;
}
private GroovyClassLoader getClassLoader(Map args) {
GroovyClassLoader classLoader = (GroovyClassLoader) args.get("classLoader");
return (classLoader == null ? this.classLoader : classLoader);
}
private List<Dependency> createDependencies(Map<?, ?>... dependencyMaps) {
List<Dependency> dependencies = new ArrayList<Dependency>(dependencyMaps.length);
for (Map<?, ?> dependencyMap : dependencyMaps) {
dependencies.add(createDependency(dependencyMap));
}
return dependencies;
}
private Dependency createDependency(Map<?, ?> dependencyMap) {
Artifact artifact = createArtifact(dependencyMap);
if (isTransitive(dependencyMap)) {
return new Dependency(artifact, JavaScopes.COMPILE);
}
return new Dependency(artifact, JavaScopes.COMPILE, null, WILDCARD_EXCLUSION);
}
private Artifact createArtifact(Map<?, ?> dependencyMap) {
String group = (String) dependencyMap.get("group");
String module = (String) dependencyMap.get("module");
String version = (String) dependencyMap.get("version");
return new DefaultArtifact(group, module, "jar", version);
}
private boolean isTransitive(Map<?, ?> dependencyMap) {
Boolean transitive = (Boolean) dependencyMap.get("transitive");
return (transitive == null ? true : transitive);
}
private List<File> resolve(List<Dependency> dependencies)
throws ArtifactResolutionException {
try {
CollectRequest collectRequest = new CollectRequest((Dependency) null,
dependencies, this.repositories);
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest,
DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE));
DependencyResult dependencyResult = this.repositorySystem
.resolveDependencies(this.session, dependencyRequest);
return getFiles(dependencyResult);
}
catch (Exception ex) {
throw new DependencyResolutionFailedException(ex);
}
finally {
this.progressReporter.finished();
}
}
private List<File> getFiles(DependencyResult dependencyResult) {
List<File> files = new ArrayList<File>();
for (ArtifactResult result : dependencyResult.getArtifactResults()) {
files.add(result.getArtifact().getFile());
}
return files;
}
@Override
public Map<String, Map<String, List<String>>> enumerateGrapes() {
throw new UnsupportedOperationException("Grape enumeration is not supported");
}
@Override
public URI[] resolve(Map args, Map... dependencies) {
throw new UnsupportedOperationException("Resolving to URIs is not supported");
}
@Override
public URI[] resolve(Map args, List depsInfo, Map... dependencies) {
throw new UnsupportedOperationException("Resolving to URIs is not supported");
}
@Override
public Map[] listDependencies(ClassLoader classLoader) {
throw new UnsupportedOperationException("Listing dependencies is not supported");
}
@Override
public void addResolver(Map<String, Object> args) {
throw new UnsupportedOperationException("Adding a resolver is not supported");
}
@Override
public Object grab(String endorsedModule) {
throw new UnsupportedOperationException(
"Grabbing an endorsed module is not supported");
}
}

View File

@ -14,10 +14,11 @@
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
package org.springframework.boot.cli.compiler.grape;
/**
* Thrown to indicate a failure during dependency resolution.
*
* @author Andy Wilkinson
*/
public class DependencyResolutionFailedException extends RuntimeException {

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
package org.springframework.boot.cli.compiler.grape;
import groovy.grape.Grape;
import groovy.grape.GrapeEngine;
@ -22,22 +22,18 @@ import groovy.grape.GrapeEngine;
import java.lang.reflect.Field;
/**
* Utility to install a specific {@link Grape} engine with Groovy.
*
* @author Andy Wilkinson
*/
public class GrapeEngineInstaller {
public abstract class GrapeEngineInstaller {
private final GrapeEngine grapeEngine;
public GrapeEngineInstaller(GrapeEngine grapeEngine) {
this.grapeEngine = grapeEngine;
}
public void install() {
public static void install(GrapeEngine engine) {
synchronized (Grape.class) {
try {
Field instanceField = Grape.class.getDeclaredField("instance");
instanceField.setAccessible(true);
instanceField.set(null, this.grapeEngine);
Field field = Grape.class.getDeclaredField("instance");
field.setAccessible(true);
field.set(null, engine);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to install GrapeEngine", ex);

View File

@ -0,0 +1,91 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler.grape;
import java.util.concurrent.TimeUnit;
import org.eclipse.aether.AbstractRepositoryListener;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.transfer.TransferEvent;
/**
* Provide console progress feedback for long running resolves.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
final class ProgressReporter {
private static final long INITIAL_DELAY = TimeUnit.SECONDS.toMillis(3);
private static final long PROGRESS_DELAY = TimeUnit.SECONDS.toMillis(1);
private long startTime = System.currentTimeMillis();
private long lastProgressTime = System.currentTimeMillis();
private boolean started;
private boolean finished;
public ProgressReporter(DefaultRepositorySystemSession session) {
session.setTransferListener(new AbstractTransferListener() {
@Override
public void transferStarted(TransferEvent event)
throws TransferCancelledException {
reportProgress();
}
@Override
public void transferProgressed(TransferEvent event)
throws TransferCancelledException {
reportProgress();
}
});
session.setRepositoryListener(new AbstractRepositoryListener() {
@Override
public void artifactResolved(RepositoryEvent event) {
reportProgress();
}
});
}
private void reportProgress() {
if (!this.finished && System.currentTimeMillis() - this.startTime > INITIAL_DELAY) {
if (!this.started) {
this.started = true;
System.out.print("Resolving dependencies..");
this.lastProgressTime = System.currentTimeMillis();
}
else if (System.currentTimeMillis() - this.lastProgressTime > PROGRESS_DELAY) {
System.out.print(".");
this.lastProgressTime = System.currentTimeMillis();
}
}
}
public void finished() {
if (this.started && !this.finished) {
this.finished = true;
System.out.println("");
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
package org.springframework.boot.cli.compiler.transformation;
import groovy.lang.GroovyClassLoader;
@ -23,6 +23,9 @@ 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.ArtifactCoordinatesResolver;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
/**
* {@link ASTTransformation} to apply
@ -33,7 +36,7 @@ import org.codehaus.groovy.transform.ASTTransformation;
* @author Dave Syer
* @author Andy Wilkinson
*/
class DependencyAutoConfigurationTransformation implements ASTTransformation {
public class DependencyAutoConfigurationTransformation implements ASTTransformation {
private final GroovyClassLoader loader;
@ -41,7 +44,7 @@ class DependencyAutoConfigurationTransformation implements ASTTransformation {
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
public DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
ArtifactCoordinatesResolver coordinatesResolver,
Iterable<CompilerAutoConfiguration> compilerAutoConfigurations) {
this.loader = loader;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
package org.springframework.boot.cli.compiler.transformation;
import groovy.lang.Grab;
@ -34,13 +34,14 @@ import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.springframework.boot.cli.compiler.ArtifactCoordinatesResolver;
/**
* {@link ASTTransformation} to resolve {@link Grab} artifact coordinates.
* @author Andy Wilkinson
* @author Phillip Webb
*/
class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
public class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
private static final Set<String> GRAB_ANNOTATION_NAMES = Collections
.unmodifiableSet(new HashSet<String>(Arrays.asList(Grab.class.getName(),
@ -48,7 +49,7 @@ class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
private final ArtifactCoordinatesResolver coordinatesResolver;
ResolveDependencyCoordinatesTransformation(
public ResolveDependencyCoordinatesTransformation(
ArtifactCoordinatesResolver coordinatesResolver) {
this.coordinatesResolver = coordinatesResolver;
}

View File

@ -37,7 +37,7 @@ public class SpringApplicationRunner {
private static int runnerCounter = 0;
private SpringApplicationRunnerConfiguration configuration;
private final SpringApplicationRunnerConfiguration configuration;
private final File[] files;

View File

@ -0,0 +1,149 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.testrunner;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.groovy.DelegateTestRunner;
/**
* Compile and run groovy based tests.
*
* @author Phillip Webb
*/
public class TestRunner {
private static final String DELEGATE_RUNNER = DelegateTestRunner.class.getName();
private static final String JUNIT_TEST_ANNOTATION = "org.junit.Test";
private final File[] files;
private final GroovyCompiler compiler;
/**
* Create a new {@link TestRunner} instance.
* @param configuration
* @param files
* @param args
*/
public TestRunner(TestRunnerConfiguration configuration, File[] files, String[] args) {
this.files = files.clone();
this.compiler = new GroovyCompiler(configuration);
}
public void compileAndRunTests() throws Exception {
Object[] sources = this.compiler.sources(this.files);
if (sources.length == 0) {
throw new RuntimeException("No classes found in '" + this.files + "'");
}
// Run in new thread to ensure that the context classloader is setup
RunThread runThread = new RunThread(sources);
runThread.start();
runThread.join();
}
/**
* Thread used to launch the Spring Application with the correct context classloader.
*/
private class RunThread extends Thread {
private final Class<?>[] testClasses;
private final Class<?> spockSpecificationClass;
/**
* Create a new {@link RunThread} instance.
* @param sources the sources to launch
*/
public RunThread(Object... sources) {
super("testrunner");
setDaemon(true);
if (sources.length != 0 && sources[0] instanceof Class) {
setContextClassLoader(((Class<?>) sources[0]).getClassLoader());
}
this.spockSpecificationClass = loadSpockSpecificationClass(getContextClassLoader());
this.testClasses = getTestClasses(sources);
}
private Class<?> loadSpockSpecificationClass(ClassLoader contextClassLoader) {
try {
return getContextClassLoader().loadClass("spock.lang.Specification");
}
catch (Exception ex) {
return null;
}
}
private Class<?>[] getTestClasses(Object[] sources) {
List<Class<?>> testClasses = new ArrayList<Class<?>>();
for (Object source : sources) {
if ((source instanceof Class) && isTestable((Class<?>) source)) {
testClasses.add((Class<?>) source);
}
}
return testClasses.toArray(new Class<?>[testClasses.size()]);
}
private boolean isTestable(Class<?> sourceClass) {
return (isJunitTest(sourceClass) || isSpockTest(sourceClass));
}
private boolean isJunitTest(Class<?> sourceClass) {
for (Method method : sourceClass.getMethods()) {
for (Annotation annotation : method.getAnnotations()) {
if (annotation.annotationType().getName()
.equals(JUNIT_TEST_ANNOTATION)) {
return true;
}
}
}
return false;
}
private boolean isSpockTest(Class<?> sourceClass) {
return (this.spockSpecificationClass != null && this.spockSpecificationClass
.isAssignableFrom(sourceClass));
}
@Override
public void run() {
try {
if (this.testClasses.length == 0) {
System.out.println("No tests found");
}
else {
Class<?> delegateClass = Thread.currentThread()
.getContextClassLoader()
.loadClass(DelegateTestRunner.class.getName());
Method runMethod = delegateClass.getMethod("run", Class[].class);
runMethod.invoke(null, new Object[] { this.testClasses });
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.testrunner;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
/**
* Configuration for {@link TestRunner}.
*
* @author Phillip Webb
*/
public interface TestRunnerConfiguration extends GroovyCompilerConfiguration {
}

View File

@ -1,95 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* File utility methods
*
* @author Andy Wilkinson
*/
public class FileUtils {
private FileUtils() {
}
/**
* Recursively deletes the given {@code file} and all files beneath it.
* @param file The root of the structure to delete
* @throw IllegalStateException if the delete fails
*/
public static void recursiveDelete(File file) {
if (file.exists()) {
if (file.isDirectory()) {
for (File inDir : file.listFiles()) {
recursiveDelete(inDir);
}
}
if (!file.delete()) {
throw new IllegalStateException("Failed to delete " + file);
}
}
}
/**
* Lists the given {@code file} and all the files beneath it.
* @param file The root of the structure to delete
* @return The list of files and directories
*/
public static List<File> recursiveList(File file) {
List<File> files = new ArrayList<File>();
if (file.isDirectory()) {
for (File inDir : file.listFiles()) {
files.addAll(recursiveList(inDir));
}
}
files.add(file);
return files;
}
/**
* Copies the data read from the given {@code source} {@link URL} to the given
* {@code target} {@link File}.
* @param source The source to copy from
* @param target The target to copy to
*/
public static void copy(URL source, File target) {
InputStream input = null;
OutputStream output = null;
try {
input = source.openStream();
output = new FileOutputStream(target);
IoUtils.copy(input, output);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to copy '" + source + "' to '"
+ target + "'", ex);
}
finally {
IoUtils.closeQuietly(input, output);
}
}
}

View File

@ -1,95 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.util;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
/**
* General IO utility methods
*
* @author Andy Wilkinson
*/
public class IoUtils {
private IoUtils() {
}
/**
* Reads the entire contents of the resource referenced by {@code uri} and returns
* them as a String.
* @param uri The resource to read
* @return The contents of the resource
*/
public static String readEntirely(String uri) {
try {
InputStream stream = URI.create(uri).toURL().openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
StringBuilder result = new StringBuilder();
while ((line = reader.readLine()) != null) {
result.append(line);
}
return result.toString();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
/**
* Copies the data read from {@code input} into {@code output}.
* <p>
* <strong>Note:</strong> it is the caller's responsibility to close the streams
* @param input The stream to read data from
* @param output The stream to write data to
* @throws IOException if the copy fails
*/
public static void copy(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[4096];
int read;
while ((read = input.read(buffer)) >= 0) {
output.write(buffer, 0, read);
}
}
/**
* Quietly closes the given {@link Closeable Closeables}. Any exceptions thrown by
* {@link Closeable#close() close()} are swallowed. Any {@code null}
* {@code Closeables} are ignored.
* @param closeables The {@link Closeable closeables} to close
*/
public static void closeQuietly(Closeable... closeables) {
for (Closeable closeable : closeables) {
if (closeable != null) {
try {
closeable.close();
}
catch (IOException ioe) {
// Closing quietly
}
}
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.groovy;
import org.junit.internal.TextListener;
import org.junit.runner.JUnitCore;
import org.springframework.boot.cli.testrunner.TestRunner;
/**
* Delegate test runner to launch tests in user application classpath.
*
* @author Phillip Webb
* @see TestRunner
*/
public class DelegateTestRunner {
public static void run(Class<?>[] testClasses) {
JUnitCore jUnitCore = new JUnitCore();
jUnitCore.addListener(new TextListener(System.out));
jUnitCore.run(testClasses);
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.groovy;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableDeviceResolver {
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.groovy;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableIntegrationPatterns {
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.groovy;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableJmsMessaging {
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cli.template;
package org.springframework.boot.groovy;
import groovy.text.GStringTemplateEngine;
import groovy.text.Template;
@ -39,23 +39,25 @@ public abstract class GroovyTemplate {
public static String template(String name, Map<String, ?> model) throws IOException,
CompilationFailedException, ClassNotFoundException {
return getTemplate(name).make(model).toString();
}
private static Template getTemplate(String name) throws CompilationFailedException,
ClassNotFoundException, IOException {
GStringTemplateEngine engine = new GStringTemplateEngine();
File file = new File("templates", name);
URL resource = GroovyTemplate.class.getClassLoader().getResource(
"templates/" + name);
Template template;
if (file.exists()) {
template = engine.createTemplate(file);
return engine.createTemplate(file);
}
else {
if (resource != null) {
template = engine.createTemplate(resource);
}
else {
template = engine.createTemplate(name);
}
ClassLoader classLoader = GroovyTemplate.class.getClassLoader();
URL resource = classLoader.getResource("templates/" + name);
if (resource != null) {
return engine.createTemplate(resource);
}
return template.make(model).toString();
return engine.createTemplate(name);
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Groovy util classes that are "shared" between the CLI and user applications. Classes is
* this package can be loaded from compiled user code. Not under the cli package in case
* we want to extract into a separate jar at a future date.
*/
package org.springframework.boot.groovy;

View File

@ -0,0 +1,42 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli;
import org.junit.Rule;
import org.junit.Test;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
/**
* Tests for CLI Classloader issues.
*
* @author Phillip Webb
*/
public class ClassLoaderIntegrationTests {
@Rule
public CliTester cli = new CliTester();
@Test
public void runWithIsolatedClassLoader() throws Exception {
// CLI classes or dependencies should not be exposed to the app
String output = this.cli.run("src/test/resources/classloader-test-app.groovy",
SpringCli.class.getName());
assertThat(output, containsString("HasClasses-false-true-false"));
}
}

View File

@ -27,8 +27,10 @@ import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.boot.OutputCapture;
import org.springframework.boot.cli.command.AbstractCommand;
import org.springframework.boot.cli.command.CleanCommand;
import org.springframework.boot.cli.command.RunCommand;
import org.springframework.boot.cli.command.TestCommand;
/**
* {@link TestRule} that can be used to invoke CLI commands.
@ -41,7 +43,7 @@ public class CliTester implements TestRule {
private long timeout = TimeUnit.MINUTES.toMillis(6);
private List<RunCommand> commands = new ArrayList<RunCommand>();
private List<AbstractCommand> commands = new ArrayList<AbstractCommand>();
public void setTimeout(long timeout) {
this.timeout = timeout;
@ -61,6 +63,20 @@ public class CliTester implements TestRule {
return getOutput();
}
public String test(final String... args) throws Exception {
Future<TestCommand> future = Executors.newSingleThreadExecutor().submit(
new Callable<TestCommand>() {
@Override
public TestCommand call() throws Exception {
TestCommand command = new TestCommand();
command.run(args);
return command;
}
});
this.commands.add(future.get(this.timeout, TimeUnit.MILLISECONDS));
return getOutput();
}
public String getOutput() {
return this.outputCapture.toString();
}
@ -87,9 +103,9 @@ public class CliTester implements TestRule {
this.base.evaluate();
}
finally {
for (RunCommand command : CliTester.this.commands) {
if (command != null) {
command.stop();
for (AbstractCommand command : CliTester.this.commands) {
if (command != null && command instanceof RunCommand) {
((RunCommand) command).stop();
}
}
System.clearProperty("disableSpringSnapshotRepos");

View File

@ -16,14 +16,17 @@
package org.springframework.boot.cli;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import org.codehaus.plexus.util.FileUtils;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.cli.util.FileUtils;
import org.springframework.boot.cli.util.IoUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@ -38,14 +41,14 @@ import static org.junit.Assert.assertTrue;
*/
public class SampleIntegrationTests {
@Rule
public CliTester cli = new CliTester();
@BeforeClass
public static void cleanGrapes() throws Exception {
GrapesCleaner.cleanIfNecessary();
}
@Rule
public CliTester cli = new CliTester();
@Test
public void appSample() throws Exception {
String output = this.cli.run("samples/app.groovy");
@ -61,7 +64,6 @@ public class SampleIntegrationTests {
@Test
public void jobSample() throws Exception {
String output = this.cli.run("samples/job.groovy", "foo=bar");
System.out.println(output);
assertTrue("Wrong output: " + output,
output.contains("completed with the following parameters"));
}
@ -83,30 +85,30 @@ public class SampleIntegrationTests {
"foo=bar");
assertTrue("Wrong output: " + output,
output.contains("completed with the following parameters"));
String result = IoUtils.readEntirely("http://localhost:8080");
String result = readEntirely("http://localhost:8080");
assertEquals("World!", result);
}
@Test
public void webSample() throws Exception {
this.cli.run("samples/web.groovy");
String result = IoUtils.readEntirely("http://localhost:8080");
String result = readEntirely("http://localhost:8080");
assertEquals("World!", result);
}
@Test
public void uiSample() throws Exception {
this.cli.run("samples/ui.groovy", "--classpath=.:src/test/resources");
String result = IoUtils.readEntirely("http://localhost:8080");
String result = readEntirely("http://localhost:8080");
assertTrue("Wrong output: " + result, result.contains("Hello World"));
result = IoUtils.readEntirely("http://localhost:8080/css/bootstrap.min.css");
result = readEntirely("http://localhost:8080/css/bootstrap.min.css");
assertTrue("Wrong output: " + result, result.contains("container"));
}
@Test
public void actuatorSample() throws Exception {
this.cli.run("samples/actuator.groovy");
String result = IoUtils.readEntirely("http://localhost:8080");
String result = readEntirely("http://localhost:8080");
assertEquals("{\"message\":\"Hello World!\"}", result);
}
@ -139,7 +141,7 @@ public class SampleIntegrationTests {
String output = this.cli.run("samples/jms.groovy");
assertTrue("Wrong output: " + output,
output.contains("Received Greetings from Spring Boot via ActiveMQ"));
FileUtils.recursiveDelete(new File("activemq-data")); // cleanup ActiveMQ cruft
FileUtils.deleteDirectory(new File("activemq-data"));// cleanup ActiveMQ cruft
}
@Test
@ -154,8 +156,24 @@ public class SampleIntegrationTests {
@Test
public void deviceSample() throws Exception {
this.cli.run("samples/device.groovy");
String result = IoUtils.readEntirely("http://localhost:8080");
String result = readEntirely("http://localhost:8080");
assertEquals("Hello Normal Device!", result);
}
private static String readEntirely(String uri) {
try {
InputStream stream = URI.create(uri).toURL().openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
StringBuilder result = new StringBuilder();
while ((line = reader.readLine()) != null) {
result.append(line);
}
return result.toString();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@ -19,22 +19,29 @@ package org.springframework.boot.cli;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.cli.command.CleanCommand;
import org.springframework.boot.cli.command.TestCommand;
import org.springframework.boot.cli.command.tester.TestResults;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
/**
* Integration tests to exercise the CLI's test command.
*
* @author Greg Turnquist
* @author Phillip Webb
*/
public class TestCommandIntegrationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule
public CliTester cli = new CliTester();
@BeforeClass
public static void cleanGrapes() throws Exception {
GrapesCleaner.cleanIfNecessary();
@ -53,84 +60,53 @@ public class TestCommandIntegrationTests {
@Test
public void noTests() throws Throwable {
TestCommand command = new TestCommand();
command.run("test-samples/book.groovy");
TestResults results = command.getResults();
assertEquals(0, results.getRunCount());
assertEquals(0, results.getFailureCount());
assertTrue(results.wasSuccessful());
String output = this.cli.test("test-samples/book.groovy");
assertThat(output, containsString("No tests found"));
}
@Test
public void empty() throws Exception {
TestCommand command = new TestCommand();
command.run("test-samples/empty.groovy");
TestResults results = command.getResults();
assertEquals(0, results.getRunCount());
assertEquals(0, results.getFailureCount());
assertTrue(results.wasSuccessful());
String output = this.cli.test("test-samples/empty.groovy");
assertThat(output, containsString("No tests found"));
}
@Test(expected = RuntimeException.class)
@Test
public void noFile() throws Exception {
try {
TestCommand command = new TestCommand();
command.run("test-samples/nothing.groovy");
}
catch (RuntimeException ex) {
assertEquals("Can't find test-samples/nothing.groovy", ex.getMessage());
throw ex;
}
TestCommand command = new TestCommand();
this.thrown.expect(RuntimeException.class);
this.thrown.expectMessage("Can't find test-samples/nothing.groovy");
command.run("test-samples/nothing.groovy");
}
@Test
public void appAndTestsInOneFile() throws Exception {
TestCommand command = new TestCommand();
command.run("test-samples/book_and_tests.groovy");
TestResults results = command.getResults();
assertEquals(1, results.getRunCount());
assertEquals(0, results.getFailureCount());
assertTrue(results.wasSuccessful());
String output = this.cli.test("test-samples/book_and_tests.groovy");
assertThat(output, containsString("OK (1 test)"));
}
@Test
public void appInOneFileTestsInAnotherFile() throws Exception {
TestCommand command = new TestCommand();
command.run("test-samples/book.groovy", "test-samples/test.groovy");
TestResults results = command.getResults();
assertEquals(1, results.getRunCount());
assertEquals(0, results.getFailureCount());
assertTrue(results.wasSuccessful());
String output = this.cli.test("test-samples/book.groovy",
"test-samples/test.groovy");
assertThat(output, containsString("OK (1 test)"));
}
@Test
public void spockTester() throws Exception {
TestCommand command = new TestCommand();
command.run("test-samples/spock.groovy");
TestResults results = command.getResults();
assertEquals(1, results.getRunCount());
assertEquals(0, results.getFailureCount());
assertTrue(results.wasSuccessful());
String output = this.cli.test("test-samples/spock.groovy");
assertThat(output, containsString("OK (1 test)"));
}
@Test
public void spockAndJunitTester() throws Exception {
TestCommand command = new TestCommand();
command.run("test-samples/spock.groovy", "test-samples/book_and_tests.groovy");
TestResults results = command.getResults();
assertEquals(2, results.getRunCount());
assertEquals(0, results.getFailureCount());
assertTrue(results.wasSuccessful());
String output = this.cli.test("test-samples/spock.groovy",
"test-samples/book_and_tests.groovy");
assertThat(output, containsString("OK (2 tests)"));
}
@Test
public void verifyFailures() throws Exception {
TestCommand command = new TestCommand();
command.run("test-samples/failures.groovy");
TestResults results = command.getResults();
assertEquals(5, results.getRunCount());
assertEquals(3, results.getFailureCount());
assertFalse(results.wasSuccessful());
String output = this.cli.test("test-samples/failures.groovy");
assertThat(output, containsString("Tests run: 5, Failures: 3"));
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli;
import org.junit.Test;
import org.junit.internal.TextListener;
import org.junit.runner.JUnitCore;
import static org.junit.Assert.fail;
/**
* @author pwebb
*/
public class TestTest {
@Test
public void testName() throws Exception {
fail("Arse");
}
public static void main(String[] args) {
JUnitCore core = new JUnitCore();
core.addListener(new TextListener(System.out));
core.run(TestTest.class);
}
}

View File

@ -1,129 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.command;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.Script;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.cli.GrapesCleaner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link ScriptCommand}.
*
* @author Dave Syer
*/
public class ScriptCommandTests {
public static boolean executed = false;
@BeforeClass
public static void cleanGrapes() throws Exception {
GrapesCleaner.cleanIfNecessary();
}
@Test(expected = IllegalStateException.class)
public void testMissing() throws Exception {
ScriptCommand command = new ScriptCommand("missing");
command.run("World");
}
@Test
public void testScript() throws Exception {
ScriptCommand command = new ScriptCommand("script");
command.run("World");
assertEquals("World",
((String[]) ((Script) command.getMain()).getProperty("args"))[0]);
}
@Test
public void testLocateFile() throws Exception {
ScriptCommand command = new ScriptCommand(
"src/test/resources/commands/script.groovy");
command.setPaths(new String[] { "." });
command.run("World");
assertEquals("World",
((String[]) ((Script) command.getMain()).getProperty("args"))[0]);
}
@Test
public void testRunnable() throws Exception {
ScriptCommand command = new ScriptCommand("runnable");
command.run("World");
assertTrue(executed);
}
@Test
public void testClosure() throws Exception {
ScriptCommand command = new ScriptCommand("closure");
command.run("World");
assertTrue(executed);
}
@Test
public void testCommand() throws Exception {
ScriptCommand command = new ScriptCommand("command");
assertEquals("My script command", command.getUsageHelp());
command.run("World");
assertTrue(executed);
}
@Test
public void testDuplicateClassName() throws Exception {
ScriptCommand command1 = new ScriptCommand("handler");
ScriptCommand command2 = new ScriptCommand("command");
assertNotSame(command1.getMain().getClass(), command2.getMain().getClass());
assertEquals(command1.getMain().getClass().getName(), command2.getMain()
.getClass().getName());
}
@Test
public void testOptions() throws Exception {
ScriptCommand command = new ScriptCommand("handler");
String out = ((OptionHandler) command.getMain()).getHelp();
assertTrue("Wrong output: " + out, out.contains("--foo"));
command.run("World", "--foo");
assertTrue(executed);
}
@Test
public void testMixin() throws Exception {
ScriptCommand command = new ScriptCommand("mixin");
GroovyObjectSupport object = (GroovyObjectSupport) command.getMain();
String out = (String) object.getProperty("help");
assertTrue("Wrong output: " + out, out.contains("--foo"));
command.run("World", "--foo");
assertTrue(executed);
}
@Test
public void testMixinWithBlock() throws Exception {
ScriptCommand command = new ScriptCommand("test");
GroovyObjectSupport object = (GroovyObjectSupport) command.getMain();
String out = (String) object.getProperty("help");
System.err.println(out);
assertTrue("Wrong output: " + out, out.contains("--foo"));
command.run("World", "--foo", "--bar=2");
assertTrue(executed);
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link ExtendedGroovyClassLoader}.
*
* @author Phillip Webb
*/
public class ExtendedGroovyClassLoaderTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private ClassLoader contextClassLoader;
private ExtendedGroovyClassLoader defaultScopeGroovyClassLoader;
@Before
public void setup() {
this.contextClassLoader = Thread.currentThread().getContextClassLoader();
this.defaultScopeGroovyClassLoader = new ExtendedGroovyClassLoader(
GroovyCompilerScope.DEFAULT);
}
@Test
public void loadsGroovyFromSameClassLoader() throws Exception {
Class<?> c1 = this.contextClassLoader.loadClass("groovy.lang.Script");
Class<?> c2 = this.defaultScopeGroovyClassLoader.loadClass("groovy.lang.Script");
assertThat(c1.getClassLoader(), sameInstance(c2.getClassLoader()));
}
@Test
public void filteresNonGroovy() throws Exception {
this.contextClassLoader.loadClass("org.springframework.util.StringUtils");
this.thrown.expect(ClassNotFoundException.class);
this.defaultScopeGroovyClassLoader
.loadClass("org.springframework.util.StringUtils");
}
@Test
public void loadsJavaTypes() throws Exception {
this.defaultScopeGroovyClassLoader.loadClass("java.lang.Boolean");
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
package org.springframework.boot.cli.compiler.grape;
import groovy.lang.GroovyClassLoader;
@ -26,6 +26,8 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link AetherGrapeEngine}.
*
* @author Andy Wilkinson
*/
public class AetherGrapeEngineTests {
@ -33,7 +35,7 @@ public class AetherGrapeEngineTests {
private final GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
private final AetherGrapeEngine grapeEngine = new AetherGrapeEngine(
this.groovyClassLoader, null, null, null);
this.groovyClassLoader);
@Test
public void dependencyResolution() {
@ -75,7 +77,7 @@ public class AetherGrapeEngineTests {
Map<String, Object> args = new HashMap<String, Object>();
System.setProperty("disableSpringSnapshotRepos", "true");
try {
new AetherGrapeEngine(this.groovyClassLoader, null, null, null).grab(args,
new AetherGrapeEngine(this.groovyClassLoader).grab(args,
createDependency("org.springframework", "spring-jdbc", "3.2.0.M1"));
}
finally {

View File

@ -0,0 +1,13 @@
import org.springframework.util.*
@Component
public class Test implements CommandLineRunner {
public void run(String... args) throws Exception {
println "HasClasses-" + ClassUtils.isPresent("missing", null) + "-" +
ClassUtils.isPresent("org.springframework.boot.SpringApplication", null) + "-" +
ClassUtils.isPresent(args[0], null);
}
}

View File

@ -16,7 +16,6 @@
<module>../spring-boot-actuator</module>
<module>../spring-boot-starters</module>
<module>../spring-boot-cli</module>
<module>../spring-boot-cli-grape</module>
<module>../spring-boot-samples</module>
<module>../spring-boot-integration-tests</module>
<module>../spring-boot-javadoc</module>