Commit cd7b1b19 authored by Dave Syer's avatar Dave Syer

Add InitCommand to execute groovy script on startup

InitCommand runs on creation of SpringCli so it can search for additional
Commands in updated classpath. Also added as interactive command in Shell
session.
parent 03325019
...@@ -233,14 +233,11 @@ ...@@ -233,14 +233,11 @@
<archive> <archive>
<manifest> <manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries> <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<mainClass>org.springframework.boot.loader.PropertiesLauncher</mainClass> <mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
</manifest> </manifest>
<manifestEntries> <manifestEntries>
<Start-Class>${start-class}</Start-Class> <Start-Class>${start-class}</Start-Class>
</manifestEntries> </manifestEntries>
<manifestEntries>
<Class-Loader>groovy.lang.GroovyClassLoader</Class-Loader>
</manifestEntries>
</archive> </archive>
</configuration> </configuration>
</execution> </execution>
...@@ -348,57 +345,6 @@ ...@@ -348,57 +345,6 @@
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<versionRange>[1.7,)</versionRange>
<goals>
<goal>run</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute />
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-dependency-plugin
</artifactId>
<versionRange>
[2.8,)
</versionRange>
<goals>
<goal>
copy-dependencies
</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build> </build>
<pluginRepositories> <pluginRepositories>
<pluginRepository> <pluginRepository>
......
...@@ -22,10 +22,10 @@ import java.util.Collection; ...@@ -22,10 +22,10 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.ServiceLoader;
import java.util.Set; import java.util.Set;
import org.springframework.boot.cli.command.AbstractCommand; import org.springframework.boot.cli.command.AbstractCommand;
import org.springframework.boot.cli.command.InitCommand;
/** /**
* Spring Command Line Interface. This is the main entry-point for the Spring command line * Spring Command Line Interface. This is the main entry-point for the Spring command line
...@@ -48,7 +48,7 @@ public class SpringCli { ...@@ -48,7 +48,7 @@ public class SpringCli {
private static final Set<SpringCliException.Option> NO_EXCEPTION_OPTIONS = EnumSet private static final Set<SpringCliException.Option> NO_EXCEPTION_OPTIONS = EnumSet
.noneOf(SpringCliException.Option.class); .noneOf(SpringCliException.Option.class);
private List<Command> commands; private List<Command> commands = new ArrayList<Command>();
private String displayName = CLI_APP + " "; private String displayName = CLI_APP + " ";
...@@ -56,15 +56,11 @@ public class SpringCli { ...@@ -56,15 +56,11 @@ public class SpringCli {
* Create a new {@link SpringCli} implementation with the default set of commands. * Create a new {@link SpringCli} implementation with the default set of commands.
*/ */
public SpringCli() { public SpringCli() {
setCommands(ServiceLoader.load(CommandFactory.class, getClass().getClassLoader())); try {
} new InitCommand(this).run();
}
private void setCommands(Iterable<CommandFactory> iterable) { catch (Exception e) {
this.commands = new ArrayList<Command>(); throw new IllegalStateException("Cannot init with those args", e);
for (CommandFactory factory : iterable) {
for (Command command : factory.getCommands(this)) {
this.commands.add(command);
}
} }
this.commands.add(0, new HelpCommand()); this.commands.add(0, new HelpCommand());
this.commands.add(new HintCommand()); this.commands.add(new HintCommand());
......
...@@ -46,12 +46,20 @@ public class FileOptions { ...@@ -46,12 +46,20 @@ public class FileOptions {
} }
/** /**
* Create a new {@link FileOptions} instance. * Create a new {@link FileOptions} instance. If it is an error to pass options that
* specify non-existent files, but the default paths are allowed not to exist (the
* paths are tested before use). If default paths are provided and the option set
* contains no file arguments it is not an error even if none of the default paths
* exist).
*
* @param optionSet the source option set * @param optionSet the source option set
* @param classLoader an optional classloader used to try and load files that are not * @param classLoader an optional classloader used to try and load files that are not
* found directly. * found in the local filesystem
* @param defaultPaths the default paths to use if no files are provided in the option
* set
*/ */
public FileOptions(OptionSet optionSet, ClassLoader classLoader) { public FileOptions(OptionSet optionSet, ClassLoader classLoader,
String... defaultPaths) {
List<?> nonOptionArguments = optionSet.nonOptionArguments(); List<?> nonOptionArguments = optionSet.nonOptionArguments();
List<File> files = new ArrayList<File>(); List<File> files = new ArrayList<File>();
for (Object option : nonOptionArguments) { for (Object option : nonOptionArguments) {
...@@ -63,18 +71,26 @@ public class FileOptions { ...@@ -63,18 +71,26 @@ public class FileOptions {
if (filename.endsWith(".groovy") || filename.endsWith(".java")) { if (filename.endsWith(".groovy") || filename.endsWith(".java")) {
File file = getFile(filename, classLoader); File file = getFile(filename, classLoader);
if (file == null) { if (file == null) {
throw new RuntimeException("Can't find " + filename); throw new IllegalArgumentException("Can't find " + filename);
} }
files.add(file); files.add(file);
} }
} }
} }
this.args = Collections.unmodifiableList(nonOptionArguments.subList(files.size(),
nonOptionArguments.size()));
if (files.size() == 0) { if (files.size() == 0) {
throw new RuntimeException("Please specify a file to run"); if (defaultPaths.length == 0) {
throw new RuntimeException("Please specify at least one file to run");
}
for (String path : defaultPaths) {
File file = getFile(path, classLoader);
if (file != null && file.exists()) {
files.add(file);
}
}
} }
this.files = Collections.unmodifiableList(files); this.files = Collections.unmodifiableList(files);
this.args = Collections.unmodifiableList(nonOptionArguments.subList(files.size(),
nonOptionArguments.size()));
} }
private File getFile(String filename, ClassLoader classLoader) { private File getFile(String filename, ClassLoader classLoader) {
......
/*
* 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.GroovyClassLoader;
import groovy.lang.Script;
import java.io.File;
import java.util.List;
import java.util.ServiceLoader;
import joptsimple.OptionSet;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.CommandFactory;
import org.springframework.boot.cli.SpringCli;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
/**
* <p>
* Command to initialize the Spring CLI with commands from the classpath. If the current
* context class loader is a GroovyClassLoader then it can be enhanced by passing in
* compiler options (e.g. <code>--classpath=...</code>).
* </p>
* <p>
* If the current context class loader is not already GroovyClassLoader then one will be
* created and will replace the current context loader. In this case command arguments can
* include files to compile that have <code>@Grab</code> annotations to process. By
* default a script called "init.groovy" or "spring.groovy" is used if it exists in the
* current directory or the root of the classpath.
* </p>
*
* @author Dave Syer
*/
public class InitCommand extends OptionParsingCommand {
public static final String NAME = "init";
public InitCommand(SpringCli cli) {
super(NAME, "(Re)-initialize the Spring cli", new InitOptionHandler(cli));
}
private static class InitOptionHandler extends CompilerOptionHandler {
private SpringCli cli;
private GroovyCompiler compiler;
public InitOptionHandler(SpringCli cli) {
this.cli = cli;
}
@Override
protected void run(OptionSet options) throws Exception {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
boolean enhanced = false;
FileOptions fileOptions = new FileOptions(options, loader, "init.groovy",
"spring.groovy");
File[] files = fileOptions.getFilesArray();
if (!(loader instanceof GroovyClassLoader)) {
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
.createDefaultRepositoryConfiguration();
GroovyCompilerConfiguration configuration = new InitGroovyCompilerConfigurationAdapter(
options, this, repositoryConfiguration);
this.compiler = new GroovyCompiler(configuration);
loader = this.compiler.getLoader();
Thread.currentThread().setContextClassLoader(loader);
}
else {
String classpath = getClasspathOption().value(options);
if (classpath != null && classpath.length() > 0) {
((GroovyClassLoader) loader).addClasspath(classpath);
enhanced = true;
}
}
if (this.compiler != null && files.length > 0) {
Class<?>[] classes = this.compiler.compile(files);
for (Class<?> type : classes) {
if (Script.class.isAssignableFrom(type)) {
((Script) type.newInstance()).run();
}
}
enhanced = true;
}
if (this.cli.getCommands().isEmpty() || enhanced) {
for (CommandFactory factory : ServiceLoader.load(CommandFactory.class,
loader)) {
for (Command command : factory.getCommands(this.cli)) {
this.cli.register(command);
}
}
}
}
}
private static class InitGroovyCompilerConfigurationAdapter extends
GroovyCompilerConfigurationAdapter {
private InitGroovyCompilerConfigurationAdapter(OptionSet optionSet,
CompilerOptionHandler compilerOptionHandler,
List<RepositoryConfiguration> repositoryConfiguration) {
super(optionSet, compilerOptionHandler, repositoryConfiguration);
}
@Override
public GroovyCompilerScope getScope() {
return GroovyCompilerScope.EXTENSION;
}
}
}
...@@ -99,6 +99,7 @@ public class ShellCommand extends AbstractCommand { ...@@ -99,6 +99,7 @@ public class ShellCommand extends AbstractCommand {
PromptCommand prompt = new PromptCommand(this); PromptCommand prompt = new PromptCommand(this);
cli.register(prompt); cli.register(prompt);
cli.register(new InitCommand(cli));
} }
private ConsoleReader createConsoleReader() throws IOException { private ConsoleReader createConsoleReader() throws IOException {
......
...@@ -46,7 +46,7 @@ import org.springframework.util.FileCopyUtils; ...@@ -46,7 +46,7 @@ import org.springframework.util.FileCopyUtils;
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer * @author Dave Syer
*/ */
class ExtendedGroovyClassLoader extends GroovyClassLoader { public class ExtendedGroovyClassLoader extends GroovyClassLoader {
private static final String SHARED_PACKAGE = "org.springframework.boot.groovy"; private static final String SHARED_PACKAGE = "org.springframework.boot.groovy";
......
...@@ -48,7 +48,6 @@ import org.springframework.boot.cli.compiler.grape.AetherGrapeEngine; ...@@ -48,7 +48,6 @@ import org.springframework.boot.cli.compiler.grape.AetherGrapeEngine;
import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller; import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
import org.springframework.boot.cli.compiler.transformation.DependencyAutoConfigurationTransformation; import org.springframework.boot.cli.compiler.transformation.DependencyAutoConfigurationTransformation;
import org.springframework.boot.cli.compiler.transformation.GrabResolversAutoConfigurationTransformation;
import org.springframework.boot.cli.compiler.transformation.GroovyBeansTransformation; import org.springframework.boot.cli.compiler.transformation.GroovyBeansTransformation;
import org.springframework.boot.cli.compiler.transformation.ResolveDependencyCoordinatesTransformation; import org.springframework.boot.cli.compiler.transformation.ResolveDependencyCoordinatesTransformation;
...@@ -108,7 +107,7 @@ public class GroovyCompiler { ...@@ -108,7 +107,7 @@ public class GroovyCompiler {
} }
this.transformations = new ArrayList<ASTTransformation>(); this.transformations = new ArrayList<ASTTransformation>();
this.transformations.add(new GrabResolversAutoConfigurationTransformation()); // this.transformations.add(new GrabResolversAutoConfigurationTransformation());
this.transformations.add(new DependencyAutoConfigurationTransformation( this.transformations.add(new DependencyAutoConfigurationTransformation(
this.loader, this.coordinatesResolver, this.compilerAutoConfigurations)); this.loader, this.coordinatesResolver, this.compilerAutoConfigurations));
this.transformations.add(new GroovyBeansTransformation()); this.transformations.add(new GroovyBeansTransformation());
...@@ -118,6 +117,10 @@ public class GroovyCompiler { ...@@ -118,6 +117,10 @@ public class GroovyCompiler {
} }
} }
public ExtendedGroovyClassLoader getLoader() {
return this.loader;
}
private ExtendedGroovyClassLoader createLoader( private ExtendedGroovyClassLoader createLoader(
GroovyCompilerConfiguration configuration) { GroovyCompilerConfiguration configuration) {
ExtendedGroovyClassLoader loader = new ExtendedGroovyClassLoader( ExtendedGroovyClassLoader loader = new ExtendedGroovyClassLoader(
......
...@@ -24,7 +24,7 @@ package org.springframework.boot.cli.compiler; ...@@ -24,7 +24,7 @@ package org.springframework.boot.cli.compiler;
public enum GroovyCompilerScope { public enum GroovyCompilerScope {
/** /**
* Default scope, exposes groovy-all.jar (loaded from the parent) and the shared cli * Default scope, exposes groovy.jar (loaded from the parent) and the shared cli
* package (loaded via groovy classloader). * package (loaded via groovy classloader).
*/ */
DEFAULT, DEFAULT,
......
...@@ -50,8 +50,8 @@ public final class RepositoryConfigurationFactory { ...@@ -50,8 +50,8 @@ public final class RepositoryConfigurationFactory {
repositoryConfiguration.add(MAVEN_CENTRAL); repositoryConfiguration.add(MAVEN_CENTRAL);
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) { if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
repositoryConfiguration.add(SPRING_SNAPSHOT);
repositoryConfiguration.add(SPRING_MILESTONE); repositoryConfiguration.add(SPRING_MILESTONE);
repositoryConfiguration.add(SPRING_SNAPSHOT);
} }
addDefaultCacheAsRespository(repositoryConfiguration); addDefaultCacheAsRespository(repositoryConfiguration);
......
...@@ -263,6 +263,9 @@ public class AetherGrapeEngine implements GrapeEngine { ...@@ -263,6 +263,9 @@ public class AetherGrapeEngine implements GrapeEngine {
} }
protected void addRepository(RemoteRepository repository) { protected void addRepository(RemoteRepository repository) {
if (this.repositories.contains(repository)) {
return;
}
if (repository.getProxy() == null) { if (repository.getProxy() == null) {
RemoteRepository.Builder builder = new RemoteRepository.Builder(repository); RemoteRepository.Builder builder = new RemoteRepository.Builder(repository);
builder.setProxy(this.proxySelector.getProxy(repository)); builder.setProxy(this.proxySelector.getProxy(repository));
......
/*
* 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 cli.command;
import org.springframework.boot.cli.command.AbstractCommand;
/**
* @author Dave Syer
*/
public class CustomCommand extends AbstractCommand {
public CustomCommand() {
super("custom", "Custom command added in tests");
}
@Override
public void run(String... args) throws Exception {
System.err.println("Custom Command Hello");
}
}
/*
* 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 cli.command;
import java.util.Collection;
import java.util.Collections;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.CommandFactory;
import org.springframework.boot.cli.SpringCli;
/**
* @author Dave Syer
*/
public class CustomCommandFactory implements CommandFactory {
@Override
public Collection<Command> getCommands(SpringCli cli) {
return Collections.<Command> singleton(new CustomCommand());
}
}
/*
* 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.GroovyClassLoader;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.OutputCapture;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.SpringCli;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Dave Syer
*/
public class InitCommandTests {
@Rule
public OutputCapture output = new OutputCapture();
private SpringCli cli = mock(SpringCli.class);
private InitCommand command = new InitCommand(this.cli);
private int defaultCount = new DefaultCommandFactory().getCommands(this.cli).size();
private ClassLoader classLoader;
@Before
public void init() {
this.classLoader = Thread.currentThread().getContextClassLoader();
}
@After
public void close() {
Thread.currentThread().setContextClassLoader(this.classLoader);
}
@Test
public void explicitClasspath() throws Exception {
Thread.currentThread().setContextClassLoader(new GroovyClassLoader());
this.command.run("--cp=src/test/plugins/custom/custom/0.0.1/custom-0.0.1.jar");
verify(this.cli, times(this.defaultCount + 1)).register(any(Command.class));
}
@Test
public void initScript() throws Exception {
this.command.run("src/test/resources/grab.groovy");
verify(this.cli, times(this.defaultCount + 1)).register(any(Command.class));
assertTrue(this.output.toString().contains("Hello Grab"));
}
@Test(expected = IllegalArgumentException.class)
public void initNonExistentScript() throws Exception {
this.command.run("nonexistent.groovy");
}
// There is an init.groovy on the test classpath so this succeeds
@Test
public void initDefault() throws Exception {
this.command.run();
assertTrue(this.output.toString().contains("Hello World"));
}
}
@GrabResolver(name="test", root="file:./src/test/plugins")
@Grab("custom:custom:0.0.1")
@Controller
class Foo {}
println "Hello Grab"
\ No newline at end of file
println "Hello World"
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment