diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java index 0a359b709c..f5bc8e4739 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java @@ -18,6 +18,7 @@ package org.springframework.bootstrap.cli; import java.awt.Desktop; import java.io.File; +import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -31,9 +32,11 @@ import org.springframework.bootstrap.cli.runner.BootstrapRunnerConfiguration; import static java.util.Arrays.asList; /** - * {@link Command} to 'run' a spring groovy script. + * {@link Command} to 'run' a groovy script or scripts. * * @author Phillip Webb + * @author Dave Syer + * * @see BootstrapRunner */ public class RunCommand extends OptionParsingCommand { @@ -58,7 +61,7 @@ public class RunCommand extends OptionParsingCommand { @Override public String getUsageHelp() { - return "[options] "; + return "[options] [--] [args]"; } public void stop() { @@ -86,30 +89,40 @@ public class RunCommand extends OptionParsingCommand { @Override protected void run(OptionSet options) throws Exception { List nonOptionArguments = options.nonOptionArguments(); - File file = getFileArgument(nonOptionArguments); - List args = nonOptionArguments.subList(1, nonOptionArguments.size()); + File[] files = getFileArguments(nonOptionArguments); + List args = nonOptionArguments.subList(files.length, + nonOptionArguments.size()); if (options.has(this.editOption)) { - Desktop.getDesktop().edit(file); + Desktop.getDesktop().edit(files[0]); } BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter( options); - this.runner = new BootstrapRunner(configuration, file, + this.runner = new BootstrapRunner(configuration, files, args.toArray(new String[args.size()])); this.runner.compileAndRun(); } - private File getFileArgument(List nonOptionArguments) { - if (nonOptionArguments.size() == 0) { - throw new RuntimeException("Please specify a file to run"); + private File[] getFileArguments(List nonOptionArguments) { + List files = new ArrayList(); + for (String filename : nonOptionArguments) { + if ("--".equals(filename)) { + break; + } + // TODO: add support for strict Java compilation + // TODO: add support for recursive search in directory + if (filename.endsWith(".groovy") || filename.endsWith(".java")) { + File file = new File(filename); + if (file.isFile() && file.canRead()) { + files.add(file); + } + } } - String filename = nonOptionArguments.get(0); - File file = new File(filename); - if (!file.isFile() || !file.canRead()) { - throw new RuntimeException("Unable to read '" + filename + "'"); + if (files.size() == 0) { + throw new RuntimeException("Please specify a file to run"); } - return file; + return files.toArray(new File[files.size()]); } /** diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/ExtendedGroovyClassLoader.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/ExtendedGroovyClassLoader.java index 823ae24b1e..9006c3afdd 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/ExtendedGroovyClassLoader.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/ExtendedGroovyClassLoader.java @@ -35,13 +35,16 @@ import org.codehaus.groovy.control.SourceUnit; * resources. * * @author Phillip Webb + * @author Dave Syer */ class ExtendedGroovyClassLoader extends GroovyClassLoader { private Map classResources = new HashMap(); + private CompilerConfiguration configuration; public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) { super(loader, config); + this.configuration = config; } @Override @@ -54,8 +57,12 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader { return resourceStream; } + public CompilerConfiguration getConfiguration() { + return this.configuration; + } + @Override - protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) { + public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) { InnerLoader loader = AccessController .doPrivileged(new PrivilegedAction() { @Override diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java index 24e1308668..2c5d30893f 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java @@ -17,6 +17,7 @@ package org.springframework.bootstrap.cli.compiler; import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyClassLoader.ClassCollector; import java.io.File; import java.io.IOException; @@ -26,8 +27,10 @@ import java.util.List; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.Phases; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.customizers.CompilationCustomizer; import org.codehaus.groovy.control.customizers.ImportCustomizer; @@ -49,6 +52,7 @@ import org.springframework.bootstrap.cli.compiler.autoconfigure.SpringMvcCompile *
    * * @author Phillip Webb + * @author Dave Syer */ public class GroovyCompiler { @@ -79,24 +83,37 @@ public class GroovyCompiler { /** * Compile the specified Groovy source files, applying any - * {@link CompilerAutoConfiguration}s. All classes defined in the file will be - * returned from this method with the first item being the primary class (defined at - * the top of the file). + * {@link CompilerAutoConfiguration}s. All classes defined in the files will be + * returned from this method. * @param file the file to compile * @return compiled classes * @throws CompilationFailedException * @throws IOException */ - public Class[] compile(File file) throws CompilationFailedException, IOException { + public Class[] compile(File... file) throws CompilationFailedException, + IOException { + this.loader.clearCache(); List> classes = new ArrayList>(); - Class mainClass = this.loader.parseClass(file); - for (Class loadedClass : this.loader.getLoadedClasses()) { - classes.add(loadedClass); + + CompilerConfiguration compilerConfiguration = this.loader.getConfiguration(); + + CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration, + null, this.loader); + SourceUnit sourceUnit = new SourceUnit(file[0], compilerConfiguration, + this.loader, compilationUnit.getErrorCollector()); + ClassCollector collector = this.loader.createCollector(compilationUnit, + sourceUnit); + compilationUnit.setClassgenCallback(collector); + + compilationUnit.addSources(file); + compilationUnit.compile(Phases.CLASS_GENERATION); + for (Object loadedClass : collector.getLoadedClasses()) { + classes.add((Class) loadedClass); } - classes.remove(mainClass); - classes.add(0, mainClass); + return classes.toArray(new Class[classes.size()]); + } /** diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java index cd987bd200..b5b86a1011 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java @@ -37,7 +37,7 @@ public class BootstrapRunner { private BootstrapRunnerConfiguration configuration; - private final File file; + private final File[] files; private final String[] args; @@ -50,13 +50,13 @@ public class BootstrapRunner { /** * Create a new {@link BootstrapRunner} instance. * @param configuration the configuration - * @param file the file to compile/watch + * @param files the files to compile/watch * @param args input arguments */ - public BootstrapRunner(final BootstrapRunnerConfiguration configuration, File file, - String... args) { + public BootstrapRunner(final BootstrapRunnerConfiguration configuration, + File[] files, String... args) { this.configuration = configuration; - this.file = file; + this.files = files; this.args = args; this.compiler = new GroovyCompiler(configuration); if (configuration.getLogLevel().intValue() <= Level.FINE.intValue()) { @@ -75,9 +75,9 @@ public class BootstrapRunner { stop(); // Compile - Class[] classes = this.compiler.compile(this.file); + Class[] classes = this.compiler.compile(this.files); if (classes.length == 0) { - throw new RuntimeException("No classes found in '" + this.file + "'"); + throw new RuntimeException("No classes found in '" + this.files + "'"); } // Run in new thread to ensure that the context classloader is setup @@ -164,7 +164,13 @@ public class BootstrapRunner { private long previous; public FileWatchThread() { - this.previous = BootstrapRunner.this.file.lastModified(); + this.previous = 0; + for (File file : BootstrapRunner.this.files) { + long current = file.lastModified(); + if (current > this.previous) { + this.previous = current; + } + } setDaemon(false); } @@ -173,10 +179,12 @@ public class BootstrapRunner { while (true) { try { Thread.sleep(TimeUnit.SECONDS.toMillis(1)); - long current = BootstrapRunner.this.file.lastModified(); - if (this.previous < current) { - this.previous = current; - compileAndRun(); + for (File file : BootstrapRunner.this.files) { + long current = file.lastModified(); + if (this.previous < current) { + this.previous = current; + compileAndRun(); + } } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); diff --git a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java index 004adcc654..edc15b4335 100644 --- a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java +++ b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java @@ -59,7 +59,7 @@ public class SampleIntegrationTests { return this.output.toString(); } - private void start(final String sample) throws Exception { + private void start(final String... sample) throws Exception { Future future = Executors.newSingleThreadExecutor().submit( new Callable() { @Override @@ -94,12 +94,23 @@ public class SampleIntegrationTests { @Test public void jobSample() throws Exception { - start("samples/job.groovy"); + start("samples/job.groovy", "foo=bar"); String output = getOutput(); assertTrue("Wrong output: " + output, output.contains("completed with the following parameters")); } + @Test + public void jobWebSample() throws Exception { + start("samples/job.groovy", "samples/web.groovy", "foo=bar"); + String output = getOutput(); + assertTrue("Wrong output: " + output, + output.contains("completed with the following parameters")); + String result = FileUtil.readEntirely(new URL("http://localhost:8080") + .openStream()); + assertEquals("World!", result); + } + @Test public void webSample() throws Exception { start("samples/web.groovy"); diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchDatabaseInitializer.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchDatabaseInitializer.java index a11f3202e4..58cf3cd2f8 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchDatabaseInitializer.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchDatabaseInitializer.java @@ -20,11 +20,18 @@ import javax.sql.DataSource; import org.springframework.batch.support.DatabaseType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ResourceLoader; import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.stereotype.Component; +/** + * Initialize the Spring Batch schema (ignoring errors, so should be idempotent). + * + * @author Dave Syer + * + */ @Component public class BatchDatabaseInitializer { @@ -34,6 +41,9 @@ public class BatchDatabaseInitializer { @Autowired private ResourceLoader resourceLoader; + @Value("${spring.batch.schema:classpath:org/springframework/batch/core/schema-@@platform@@.sql}") + private String schemaLocation = "classpath:org/springframework/batch/core/schema-@@platform@@.sql"; + @PostConstruct protected void initialize() throws Exception { String platform = DatabaseType.fromMetaData(this.dataSource).toString() @@ -42,12 +52,9 @@ public class BatchDatabaseInitializer { platform = "hsqldb"; } ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); - populator - .addScript(this.resourceLoader - .getResource("org/springframework/batch/core/schema-" + platform - + ".sql")); + populator.addScript(this.resourceLoader.getResource(this.schemaLocation.replace( + "@@platform@@", platform))); populator.setContinueOnError(true); DatabasePopulatorUtils.execute(populator, this.dataSource); } - } \ No newline at end of file diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/AutoConfigurationUtils.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/AutoConfigurationUtils.java index fe6ef76b83..92c18d90b9 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/AutoConfigurationUtils.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/AutoConfigurationUtils.java @@ -16,6 +16,7 @@ package org.springframework.bootstrap.context.annotation; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,8 +48,17 @@ public abstract class AutoConfigurationUtils { public static void storeBasePackages(ConfigurableListableBeanFactory beanFactory, List basePackages) { - beanFactory.registerSingleton(BASE_PACKAGES_BEAN, - Collections.unmodifiableList(basePackages)); + if (!beanFactory.containsBean(BASE_PACKAGES_BEAN)) { + beanFactory.registerSingleton(BASE_PACKAGES_BEAN, new ArrayList( + basePackages)); + } else { + List packages = getBasePackages(beanFactory); + for (String pkg : basePackages) { + if (packages.contains(pkg)) { + packages.add(pkg); + } + } + } } }