From b19f6bb238cd25b416ecb1c6e1c1993372c335f1 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 4 Nov 2013 22:35:23 -0800 Subject: [PATCH] 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. --- pom.xml | 1 - spring-boot-cli-grape/pom.xml | 176 --------- .../boot/cli/compiler/AetherGrapeEngine.java | 355 ------------------ spring-boot-cli/pom.xml | 185 ++++++--- spring-boot-cli/samples/template.groovy | 2 +- .../src/main/assembly/bin-package.xml | 2 +- .../main/assembly/jar-with-dependencies.xml | 26 ++ .../src/main/assembly/repackage-jar.xml | 25 -- .../src/main/groovy/testers/junit.groovy | 71 ---- .../src/main/groovy/testers/spock.groovy | 47 --- .../src/main/groovy/testers/tester.groovy | 42 --- .../boot/cli/command/CleanCommand.java | 30 +- .../cli/command/DefaultCommandFactory.java | 5 +- .../boot/cli/command/OptionHandler.java | 8 +- .../cli/command/OptionParsingCommand.java | 3 +- .../boot/cli/command/RunCommand.java | 13 +- .../boot/cli/command/ScriptCommand.java | 15 +- .../command/ScriptCompilationCustomizer.java | 2 +- .../boot/cli/command/TestCommand.java | 229 ++++------- .../boot/cli/command/tester/Failure.java | 52 --- .../boot/cli/command/tester/TestResults.java | 87 ----- .../cli/compiler/DependencyCustomizer.java | 26 +- .../compiler/ExtendedGroovyClassLoader.java | 147 +++++++- .../boot/cli/compiler/GroovyCompiler.java | 52 +-- .../compiler/GroovyCompilerConfiguration.java | 14 +- .../cli/compiler/GroovyCompilerScope.java | 37 ++ ...PropertiesArtifactCoordinatesResolver.java | 40 +- .../JUnitCompilerAutoConfiguration.java | 2 +- .../JmsCompilerAutoConfiguration.java | 16 +- .../SpringBatchCompilerAutoConfiguration.java | 2 +- .../SpringBootCompilerAutoConfiguration.java | 1 - ...gIntegrationCompilerAutoConfiguration.java | 22 +- ...SpringMobileCompilerAutoConfiguration.java | 16 +- .../SpringMvcCompilerAutoConfiguration.java | 20 +- ...onManagementCompilerAutoConfiguration.java | 7 +- .../cli/compiler/grape/AetherGrapeEngine.java | 249 ++++++++++++ .../DependencyResolutionFailedException.java | 3 +- .../{ => grape}/GrapeEngineInstaller.java | 20 +- .../cli/compiler/grape/ProgressReporter.java | 91 +++++ ...ndencyAutoConfigurationTransformation.java | 9 +- ...veDependencyCoordinatesTransformation.java | 7 +- .../cli/runner/SpringApplicationRunner.java | 2 +- .../boot/cli/testrunner/TestRunner.java | 149 ++++++++ .../testrunner/TestRunnerConfiguration.java | 28 ++ .../boot/cli/util/FileUtils.java | 95 ----- .../boot/cli/util/IoUtils.java | 95 ----- .../boot/groovy/DelegateTestRunner.java | 37 ++ .../boot/groovy/EnableDeviceResolver.java | 30 ++ .../groovy/EnableIntegrationPatterns.java | 30 ++ .../boot/groovy/EnableJmsMessaging.java | 30 ++ .../template => groovy}/GroovyTemplate.java | 28 +- .../boot/groovy/package-info.java | 23 ++ .../boot/cli/ClassLoaderIntegrationTests.java | 42 +++ .../springframework/boot/cli/CliTester.java | 24 +- .../boot/cli/SampleIntegrationTests.java | 44 ++- .../boot/cli/TestCommandIntegrationTests.java | 88 ++--- .../springframework/boot/cli/TestTest.java | 41 ++ .../boot/cli/command/ScriptCommandTests.java | 129 ------- .../ExtendedGroovyClassLoaderTests.java | 68 ++++ .../grape}/AetherGrapeEngineTests.java | 8 +- .../resources/classloader-test-app.groovy | 13 + spring-boot-full-build/pom.xml | 1 - 62 files changed, 1481 insertions(+), 1681 deletions(-) delete mode 100644 spring-boot-cli-grape/pom.xml delete mode 100644 spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler/AetherGrapeEngine.java create mode 100644 spring-boot-cli/src/main/assembly/jar-with-dependencies.xml delete mode 100644 spring-boot-cli/src/main/assembly/repackage-jar.xml delete mode 100644 spring-boot-cli/src/main/groovy/testers/junit.groovy delete mode 100644 spring-boot-cli/src/main/groovy/testers/spock.groovy delete mode 100644 spring-boot-cli/src/main/groovy/testers/tester.groovy delete mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java delete mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerScope.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java rename {spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler => spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape}/DependencyResolutionFailedException.java (94%) rename spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/{ => grape}/GrapeEngineInstaller.java (69%) create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ProgressReporter.java rename spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/{ => transformation}/DependencyAutoConfigurationTransformation.java (83%) rename spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/{ => transformation}/ResolveDependencyCoordinatesTransformation.java (94%) create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunnerConfiguration.java delete mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/util/FileUtils.java delete mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/util/IoUtils.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableDeviceResolver.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableIntegrationPatterns.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableJmsMessaging.java rename spring-boot-cli/src/main/java/org/springframework/boot/{cli/template => groovy}/GroovyTemplate.java (73%) create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/groovy/package-info.java create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/ClassLoaderIntegrationTests.java create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/TestTest.java delete mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoaderTests.java rename {spring-boot-cli-grape/src/test/java/org/springframework/boot/cli/compiler => spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape}/AetherGrapeEngineTests.java (94%) create mode 100644 spring-boot-cli/src/test/resources/classloader-test-app.groovy diff --git a/pom.xml b/pom.xml index d0d1d5faaf3..5be17f66d8e 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ spring-boot-actuator spring-boot-starters spring-boot-cli - spring-boot-cli-grape spring-boot-integration-tests diff --git a/spring-boot-cli-grape/pom.xml b/spring-boot-cli-grape/pom.xml deleted file mode 100644 index f251d1553b9..00000000000 --- a/spring-boot-cli-grape/pom.xml +++ /dev/null @@ -1,176 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-parent - 0.5.0.BUILD-SNAPSHOT - ../spring-boot-parent - - spring-boot-cli-grape - jar - - ${basedir}/.. - org.springframework.boot.cli.SpringCli - - - - - org.codehaus.groovy - groovy - provided - - - - commons-logging - commons-logging - 1.1.3 - - - org.apache.maven - maven-aether-provider - - - org.eclipse.sisu.plexus - org.eclipse.sisu - - - - - org.eclipse.aether - aether-api - - - org.eclipse.aether - aether-connector-basic - - - org.eclipse.aether - aether-impl - - - org.eclipse.aether - aether-spi - - - org.eclipse.aether - aether-transport-file - - - org.eclipse.aether - aether-transport-http - - - jcl-over-slf4j - org.slf4j - - - - - org.eclipse.aether - aether-util - - - - junit - junit - test - - - - - - maven-surefire-plugin - - - org.springframework:spring-core - org.springframework:spring-beans - org.springframework:spring-aop - org.springframework:spring-tx - org.springframework:spring-expression - org.springframework:spring-context - org.springframework:spring-test - org.springframework.retry:spring-retry - org.springframework.integration:spring-integration-core - org.springframework.integration:spring-integration-dsl-groovy-core - - - - - maven-shade-plugin - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - package - - shade - - - - - - ${start-class} - - - false - - - - - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.apache.maven.plugins - maven-antrun-plugin - [1.7,) - - run - - - - - - - - - - - - - - - - objectstyle - ObjectStyle.org Repository - http://objectstyle.org/maven2/ - - false - - - - diff --git a/spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler/AetherGrapeEngine.java b/spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler/AetherGrapeEngine.java deleted file mode 100644 index d608fd35148..00000000000 --- a/spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler/AetherGrapeEngine.java +++ /dev/null @@ -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 Aether, 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 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 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 repositories = new ArrayList(); - 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 dependencies = createDependencies(dependencyMaps); - - try { - List 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 createDependencies(Map... dependencyMaps) { - List dependencies = new ArrayList(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 resolve(List 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 files = new ArrayList(); - 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 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>> 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 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(""); - } - } - } -} diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml index 7ed9629a058..e742bf66861 100644 --- a/spring-boot-cli/pom.xml +++ b/spring-boot-cli/pom.xml @@ -43,19 +43,60 @@ org.codehaus.groovy - groovy + groovy-all - - org.codehaus.groovy - groovy-templates - true + org.springframework + spring-core + + + org.apache.maven + maven-aether-provider + + + org.eclipse.sisu.plexus + org.eclipse.sisu + + + + + org.eclipse.aether + aether-api + + + org.eclipse.aether + aether-connector-basic + + + org.eclipse.aether + aether-impl + + + org.eclipse.aether + aether-spi + + + org.eclipse.aether + aether-transport-file + + + org.eclipse.aether + aether-transport-http + + + jcl-over-slf4j + org.slf4j + + + + + org.eclipse.aether + aether-util - ${project.groupId} - spring-boot-cli-grape - ${project.version} + junit + junit provided @@ -76,11 +117,6 @@ - - junit - junit - test - org.javassist javassist @@ -111,74 +147,80 @@ ${project.build.directory}/generated-resources - - org.springframework:spring-core - org.springframework:spring-beans - org.springframework:spring-aop - org.springframework:spring-tx - org.springframework:spring-expression - org.springframework:spring-context - org.springframework:spring-test - org.springframework.retry:spring-retry - org.springframework.integration:spring-integration-core - org.springframework.integration:spring-integration-dsl-groovy-core - + - maven-shade-plugin - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - + org.apache.maven.plugins + maven-dependency-plugin - package + unpack + prepare-package - shade + unpack - - - - - - false + + + ${project.groupId} + spring-boot-loader + ${project.version} + jar + + + ${project.build.directory}/assembly + + + + copy + prepare-package + + copy-dependencies + + + ${project.build.directory}/assembly/lib + compile maven-assembly-plugin - false - - - src/main/assembly/repackage-jar.xml - src/main/assembly/bin-package.xml - - - - ${start-class} - - - - make-distribution + jar-with-dependencies package single + + + src/main/assembly/jar-with-dependencies.xml + + + + true + org.springframework.boot.loader.JarLauncher + + + ${start-class} + + + + + + bin-package + package + + single + + + + src/main/assembly/bin-package.xml + + @@ -238,6 +280,27 @@ + + + + org.apache.maven.plugins + + + maven-dependency-plugin + + + [2.8,) + + + + copy-dependencies + + + + + + + diff --git a/spring-boot-cli/samples/template.groovy b/spring-boot-cli/samples/template.groovy index 0bfc873e327..1a4ce642137 100644 --- a/spring-boot-cli/samples/template.groovy +++ b/spring-boot-cli/samples/template.groovy @@ -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 { diff --git a/spring-boot-cli/src/main/assembly/bin-package.xml b/spring-boot-cli/src/main/assembly/bin-package.xml index 9d36cb33bc8..95608c48790 100644 --- a/spring-boot-cli/src/main/assembly/bin-package.xml +++ b/spring-boot-cli/src/main/assembly/bin-package.xml @@ -25,7 +25,7 @@ - ${project.build.directory}/${project.artifactId}-${project.version}-repackaged.jar + ${project.build.directory}/${project.artifactId}-${project.version}-full.jar /lib ${project.build.finalName}.jar diff --git a/spring-boot-cli/src/main/assembly/jar-with-dependencies.xml b/spring-boot-cli/src/main/assembly/jar-with-dependencies.xml new file mode 100644 index 00000000000..e58cf8cf218 --- /dev/null +++ b/spring-boot-cli/src/main/assembly/jar-with-dependencies.xml @@ -0,0 +1,26 @@ + + + full + + jar + + false + + + + + ${project.groupId}:${project.artifactId} + + true + + + + + ${project.build.directory}/assembly + / + + + diff --git a/spring-boot-cli/src/main/assembly/repackage-jar.xml b/spring-boot-cli/src/main/assembly/repackage-jar.xml deleted file mode 100644 index 4a809ff3ab2..00000000000 --- a/spring-boot-cli/src/main/assembly/repackage-jar.xml +++ /dev/null @@ -1,25 +0,0 @@ - - repackaged - - jar - - false - - - - org.springframework.boot:spring-boot-cli:jar:* - - true - 755 - - - - org.springframework.boot:spring-boot-cli-grape:jar:* - - internal - 755 - provided - true - - - diff --git a/spring-boot-cli/src/main/groovy/testers/junit.groovy b/spring-boot-cli/src/main/groovy/testers/junit.groovy deleted file mode 100644 index 6523cb2c85e..00000000000 --- a/spring-boot-cli/src/main/groovy/testers/junit.groovy +++ /dev/null @@ -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> findTestableClasses(List> compiled) { - // Look for @Test methods - Set> testable = new LinkedHashSet>() - 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> testable) { - return JUnitTester.runEmbeddedTests(testable) - } - - static TestResults runEmbeddedTests(List> testable) { - Result results = JUnitCore.runClasses(testable.toArray(new Class[0])) - - TestResults testResults = new TestResults() - testResults.setRunCount(results.getRunCount()) - - List failures = new ArrayList() - for (org.junit.runner.notification.Failure failure : results.getFailures()) { - failures.add(new Failure(failure.exception.toString(), failure.trace)) - } - - testResults.setFailures(failures) - - return testResults - } - -} diff --git a/spring-boot-cli/src/main/groovy/testers/spock.groovy b/spring-boot-cli/src/main/groovy/testers/spock.groovy deleted file mode 100644 index afd34b10425..00000000000 --- a/spring-boot-cli/src/main/groovy/testers/spock.groovy +++ /dev/null @@ -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> findTestableClasses(List> compiled) { - // Look for classes that implement spock.lang.Specification - Set> testable = new LinkedHashSet>() - for (Class clazz : compiled) { - if (Specification.class.isAssignableFrom(clazz)) { - testable.add(clazz) - } - } - return testable - } - - @Override - protected TestResults test(List> testable) { - return JUnitTester.runEmbeddedTests(testable) - } - -} diff --git a/spring-boot-cli/src/main/groovy/testers/tester.groovy b/spring-boot-cli/src/main/groovy/testers/tester.groovy deleted file mode 100644 index 40dcb257a8e..00000000000 --- a/spring-boot-cli/src/main/groovy/testers/tester.groovy +++ /dev/null @@ -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> compiled) throws FileNotFoundException { - Set> testable = findTestableClasses(compiled) - - if (testable.size() == 0) { - return TestResults.NONE - } - - return test(new ArrayList>(testable)) - } - - protected abstract Set> findTestableClasses(List> compiled) - - protected abstract TestResults test(List> testable) - -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CleanCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CleanCommand.java index bad6a5a8eaf..814cd63fa7d 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CleanCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CleanCommand.java @@ -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 recursiveList(File file) { + List files = new ArrayList(); + if (file.isDirectory()) { + for (File inDir : file.listFiles()) { + files.addAll(recursiveList(inDir)); + } + } + files.add(file); + return files; + } + } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java index 8ac55ffc9b2..08d7dc1b6d4 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java @@ -30,8 +30,9 @@ import org.springframework.boot.cli.CommandFactory; */ public class DefaultCommandFactory implements CommandFactory { - private static final List DEFAULT_COMMANDS = Arrays. asList( - new VersionCommand(), new RunCommand(), new CleanCommand(), new TestCommand()); + private static final List DEFAULT_COMMANDS = Arrays + . asList(new VersionCommand(), new RunCommand(), new CleanCommand(), + new TestCommand()); @Override public Collection getCommands() { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java index f5b545058c9..1f4726ea466 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java @@ -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 { } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java index bfd9e0c7280..d42dee09f43 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java @@ -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 { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java index f5653dd6071..5cc3357836e 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java @@ -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; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCommand.java index 8a015527770..d487dc0b5bb 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCommand.java @@ -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; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCompilationCustomizer.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCompilationCustomizer.java index 62abaf62825..0761679d007 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCompilationCustomizer.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ScriptCompilationCustomizer.java @@ -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; /** diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java index e348729de9e..761b694b717 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java @@ -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] "; - } - - 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 noGuessImportsOption; + + private OptionSpec noGuessDependenciesOption; + + private OptionSpec 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 testerFiles = compileAndCollectTesterFiles(sources); - - // Compile - Pass 2 - add appropriate testers - List files = new ArrayList(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> compiled = new ArrayList>(); - List> testers = new ArrayList>(); - 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 compileAndCollectTesterFiles(Object[] sources) - throws CompilationFailedException, IOException { - Set testerUnits = new LinkedHashSet(); - List testerFiles = new ArrayList(); - 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 testerFiles, Set 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); - } - } - } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java deleted file mode 100644 index b827c2fc7fb..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java +++ /dev/null @@ -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; - } -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java deleted file mode 100644 index 53835427497..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java +++ /dev/null @@ -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 getFailures() { - return new ArrayList(); - } - - @Override - public boolean wasSuccessful() { - return true; - } - - }; - private int runCount = 0; - private List failures = new ArrayList(); - - 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 getFailures() { - return this.failures; - } - - public void setFailures(List failures) { - this.failures = failures; - } - -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/DependencyCustomizer.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/DependencyCustomizer.java index e2b43d2884c..eaef51e44c3 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/DependencyCustomizer.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/DependencyCustomizer.java @@ -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; } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoader.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoader.java index b0435de5ebd..58f82278798 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoader.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoader.java @@ -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 classResources = new HashMap(); + private static final String SHARED_PACKAGE = "org.springframework.boot.groovy"; - private CompilerConfiguration configuration; + private final Map classResources = new HashMap(); - 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); + } + } + } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java index e55ffd6aa0f..8c299637f4c 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java @@ -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 compilerAutoConfigurations; private final List 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(); - 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 grapeEngineClass = (Class) AETHER_CLASS_LOADER - .loadClass("org.springframework.boot.cli.compiler.AetherGrapeEngine"); - Constructor 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); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java index 0858c1b833a..dea85bc990f 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java @@ -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(); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerScope.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerScope.java new file mode 100644 index 00000000000..6693313295f --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerScope.java @@ -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 + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/PropertiesArtifactCoordinatesResolver.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/PropertiesArtifactCoordinatesResolver.java index ac6223bf7d1..709d6e54d32 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/PropertiesArtifactCoordinatesResolver.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/PropertiesArtifactCoordinatesResolver.java @@ -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 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; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java index 4659c5884bf..c3034691399 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java @@ -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 diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java index 7ce7ea6ac22..73539d94085 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java @@ -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 { - - } - } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java index 098f69b549c..98c5f81ae2b 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java @@ -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"); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java index d23c9b25336..c7298a13d1a 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java @@ -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); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringIntegrationCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringIntegrationCompilerAutoConfiguration.java index 23c84bc2399..442f1be9f53 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringIntegrationCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringIntegrationCompilerAutoConfiguration.java @@ -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()); } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMobileCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMobileCompilerAutoConfiguration.java index c96233117a5..734891d5c92 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMobileCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMobileCompilerAutoConfiguration.java @@ -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 { - - } - } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java index 56afcd471c0..5baaafe6588 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java @@ -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"); } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/TransactionManagementCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/TransactionManagementCompilerAutoConfiguration.java index ae037fe1db4..9d922bef993 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/TransactionManagementCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/TransactionManagementCompilerAutoConfiguration.java @@ -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 diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java new file mode 100644 index 00000000000..212b8b5d275 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java @@ -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 Aether, the dependency resolution system used by + * Maven. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +@SuppressWarnings("rawtypes") +public class AetherGrapeEngine implements GrapeEngine { + + private static final Collection 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 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 getRemoteRepositories() { + List repositories = new ArrayList(); + 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 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 dependencies = createDependencies(dependencyMaps); + List 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 createDependencies(Map... dependencyMaps) { + List dependencies = new ArrayList(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 resolve(List 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 getFiles(DependencyResult dependencyResult) { + List files = new ArrayList(); + for (ArtifactResult result : dependencyResult.getArtifactResults()) { + files.add(result.getArtifact().getFile()); + } + return files; + } + + @Override + public Map>> 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 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"); + } +} diff --git a/spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler/DependencyResolutionFailedException.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionFailedException.java similarity index 94% rename from spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler/DependencyResolutionFailedException.java rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionFailedException.java index e8a60d4b5b4..1a37f8b4d18 100644 --- a/spring-boot-cli-grape/src/main/java/org/springframework/boot/cli/compiler/DependencyResolutionFailedException.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionFailedException.java @@ -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 { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GrapeEngineInstaller.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeEngineInstaller.java similarity index 69% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GrapeEngineInstaller.java rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeEngineInstaller.java index f49da5ce275..b76c85d03cf 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GrapeEngineInstaller.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeEngineInstaller.java @@ -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); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ProgressReporter.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ProgressReporter.java new file mode 100644 index 00000000000..90cea8a2729 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ProgressReporter.java @@ -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(""); + } + } +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/DependencyAutoConfigurationTransformation.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/transformation/DependencyAutoConfigurationTransformation.java similarity index 83% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/DependencyAutoConfigurationTransformation.java rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/transformation/DependencyAutoConfigurationTransformation.java index 8d62ac5a3de..ed316aa616e 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/DependencyAutoConfigurationTransformation.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/transformation/DependencyAutoConfigurationTransformation.java @@ -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 compilerAutoConfigurations; - DependencyAutoConfigurationTransformation(GroovyClassLoader loader, + public DependencyAutoConfigurationTransformation(GroovyClassLoader loader, ArtifactCoordinatesResolver coordinatesResolver, Iterable compilerAutoConfigurations) { this.loader = loader; diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/ResolveDependencyCoordinatesTransformation.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/transformation/ResolveDependencyCoordinatesTransformation.java similarity index 94% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/ResolveDependencyCoordinatesTransformation.java rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/transformation/ResolveDependencyCoordinatesTransformation.java index d3f936b070c..5b9e9c7d604 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/ResolveDependencyCoordinatesTransformation.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/transformation/ResolveDependencyCoordinatesTransformation.java @@ -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 GRAB_ANNOTATION_NAMES = Collections .unmodifiableSet(new HashSet(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; } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java index 238a4919f9c..12fa1befa9b 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java @@ -37,7 +37,7 @@ public class SpringApplicationRunner { private static int runnerCounter = 0; - private SpringApplicationRunnerConfiguration configuration; + private final SpringApplicationRunnerConfiguration configuration; private final File[] files; diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java new file mode 100644 index 00000000000..260255e115f --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java @@ -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> testClasses = new ArrayList>(); + 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(); + } + } + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunnerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunnerConfiguration.java new file mode 100644 index 00000000000..c3389115fab --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunnerConfiguration.java @@ -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 { + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/FileUtils.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/FileUtils.java deleted file mode 100644 index 2589e33eaef..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/FileUtils.java +++ /dev/null @@ -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 recursiveList(File file) { - List files = new ArrayList(); - 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); - } - } -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/IoUtils.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/IoUtils.java deleted file mode 100644 index 3882ba88fe9..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/IoUtils.java +++ /dev/null @@ -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}. - *

- * Note: 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 - } - } - } - - } -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java new file mode 100644 index 00000000000..09eb0c32658 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java @@ -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); + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableDeviceResolver.java b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableDeviceResolver.java new file mode 100644 index 00000000000..e8c3075843b --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableDeviceResolver.java @@ -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 { + +} \ No newline at end of file diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableIntegrationPatterns.java b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableIntegrationPatterns.java new file mode 100644 index 00000000000..ff40972ecf0 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableIntegrationPatterns.java @@ -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 { + +} \ No newline at end of file diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableJmsMessaging.java b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableJmsMessaging.java new file mode 100644 index 00000000000..0a40997b819 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/EnableJmsMessaging.java @@ -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 { + +} \ No newline at end of file diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/template/GroovyTemplate.java b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/GroovyTemplate.java similarity index 73% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/template/GroovyTemplate.java rename to spring-boot-cli/src/main/java/org/springframework/boot/groovy/GroovyTemplate.java index a253497922f..b9d7fd3bf23 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/template/GroovyTemplate.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/GroovyTemplate.java @@ -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 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); } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/groovy/package-info.java b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/package-info.java new file mode 100644 index 00000000000..c0cfd3ca8cd --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/package-info.java @@ -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; + diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/ClassLoaderIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/ClassLoaderIntegrationTests.java new file mode 100644 index 00000000000..37c53341fca --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/ClassLoaderIntegrationTests.java @@ -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")); + } +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java index 5b9a0a53215..66559f468fc 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java @@ -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 commands = new ArrayList(); + private List commands = new ArrayList(); 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 future = Executors.newSingleThreadExecutor().submit( + new Callable() { + @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"); diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java index 34efacf4328..0d3f173131a 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java @@ -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); + } + } + } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java index 06dfee13b7c..41bb9414208 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java @@ -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")); } - } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestTest.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestTest.java new file mode 100644 index 00000000000..267b67a30f0 --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestTest.java @@ -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); + + } +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java deleted file mode 100644 index ed121654f8c..00000000000 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java +++ /dev/null @@ -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); - } - -} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoaderTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoaderTests.java new file mode 100644 index 00000000000..76e97af497f --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/ExtendedGroovyClassLoaderTests.java @@ -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"); + } + +} diff --git a/spring-boot-cli-grape/src/test/java/org/springframework/boot/cli/compiler/AetherGrapeEngineTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java similarity index 94% rename from spring-boot-cli-grape/src/test/java/org/springframework/boot/cli/compiler/AetherGrapeEngineTests.java rename to spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java index a4654ad37fa..0403afda200 100644 --- a/spring-boot-cli-grape/src/test/java/org/springframework/boot/cli/compiler/AetherGrapeEngineTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java @@ -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 args = new HashMap(); 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 { diff --git a/spring-boot-cli/src/test/resources/classloader-test-app.groovy b/spring-boot-cli/src/test/resources/classloader-test-app.groovy new file mode 100644 index 00000000000..96e002a9539 --- /dev/null +++ b/spring-boot-cli/src/test/resources/classloader-test-app.groovy @@ -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); + } + +} + diff --git a/spring-boot-full-build/pom.xml b/spring-boot-full-build/pom.xml index 984e4e58923..e40acbb0a98 100644 --- a/spring-boot-full-build/pom.xml +++ b/spring-boot-full-build/pom.xml @@ -16,7 +16,6 @@ ../spring-boot-actuator ../spring-boot-starters ../spring-boot-cli - ../spring-boot-cli-grape ../spring-boot-samples ../spring-boot-integration-tests ../spring-boot-javadoc