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