Change package names zero->boot

* actuator -> boot-ops
* cli -> boot-cli
* launcher -> boot-load
* autoconfig -> boot-config
* bootstrap -> boot-strap
* starters -> boot-up

[#54095231] [bs-253] Refactor Zero->Boot
This commit is contained in:
Dave Syer
2013-07-26 11:50:02 +01:00
parent b2873fbc2d
commit 2098e23fca
609 changed files with 1686 additions and 1626 deletions

View File

@@ -0,0 +1,58 @@
/*
* 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;
/**
* A single command that can be run from the CLI.
*
* @author Phillip Webb
* @author Dave Syer
* @see #run(String...)
*/
public interface Command {
/**
* Returns the name of the command.
*/
String getName();
/**
* Returns a description of the command.
*/
String getDescription();
/**
* Returns usage help for the command. This should be a simple one-line string
* describing basic usage. e.g. '[options] <file>'. Do not include the name of
* the command in this string.
*/
String getUsageHelp();
/**
* Gets full help text for the command, e.g. a longer description and one line per
* option.
*/
String getHelp();
/**
* Run the command.
* @param args command arguments (this will not include the command itself)
* @throws Exception
*/
void run(String... args) throws Exception;
}

View File

@@ -0,0 +1,36 @@
/*
* 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 java.util.Collection;
import java.util.ServiceLoader;
/**
* Factory used to create CLI {@link Command}s. Intended for use with a Java
* {@link ServiceLoader}.
*
* @author Dave Syer
*/
public interface CommandFactory {
/**
* Returns the CLI {@link Command}s.
* @return The commands
*/
Collection<Command> getCommands();
}

View File

@@ -0,0 +1,38 @@
/*
* 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;
/**
* Simple logger used by the CLI.
*
* @author Phillip Webb
*/
public abstract class Log {
public static void info(String message) {
System.out.println(message);
}
public static void error(String message) {
System.err.println(message);
}
public static void error(Exception ex) {
ex.printStackTrace(System.err);
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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;
/**
* Exception thrown when an unknown command is specified.
*
* @author Phillip Webb
*/
class NoSuchCommandException extends SpringCliException {
private static final long serialVersionUID = 1L;
public NoSuchCommandException(String name) {
super(String.format("%1$s: '%2$s' is not a valid command. See '%1$s help'.",
SpringCli.CLI_APP, name));
}
}

View File

@@ -0,0 +1,253 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
/**
* Spring Command Line Interface. This is the main entry-point for the Spring command line
* application. This class will parse input arguments and delegate to a suitable
* {@link Command} implementation based on the first argument.
*
* <p>
* The '-d' and '--debug' switches are handled by this class, however, most argument
* parsing is left to the {@link Command} implementation.
*
* @author Phillip Webb
* @see #main(String...)
* @see SpringCliException
* @see Command
*/
public class SpringCli {
public static final String CLI_APP = "spring";
private static final Set<SpringCliException.Option> NO_EXCEPTION_OPTIONS = EnumSet
.noneOf(SpringCliException.Option.class);
private List<Command> commands;
/**
* Create a new {@link SpringCli} implementation with the default set of commands.
*/
public SpringCli() {
setCommands(ServiceLoader.load(CommandFactory.class, getClass().getClassLoader()));
}
private void setCommands(Iterable<CommandFactory> iterable) {
this.commands = new ArrayList<Command>();
for (CommandFactory factory : iterable) {
for (Command command : factory.getCommands()) {
this.commands.add(command);
}
}
this.commands.add(0, new HelpCommand());
}
/**
* Set the command available to the CLI. Primarily used to support testing. NOTE: The
* 'help' command will be automatically provided in addition to this list.
* @param commands the commands to add
*/
public void setCommands(List<? extends Command> commands) {
this.commands = new ArrayList<Command>(commands);
this.commands.add(0, new HelpCommand());
}
/**
* Run the CLI and handle and errors.
* @param args the input arguments
* @return a return status code (non boot is used to indicate an error)
*/
public int runAndHandleErrors(String... args) {
String[] argsWithoutDebugFlags = removeDebugFlags(args);
boolean debug = argsWithoutDebugFlags.length != args.length;
try {
run(argsWithoutDebugFlags);
return 0;
}
catch (NoArgumentsException ex) {
showUsage();
return 1;
}
catch (Exception ex) {
return handleError(debug, ex);
}
}
private int handleError(boolean debug, Exception ex) {
Set<SpringCliException.Option> options = NO_EXCEPTION_OPTIONS;
if (ex instanceof SpringCliException) {
options = ((SpringCliException) ex).getOptions();
}
if (!(ex instanceof NoHelpCommandArgumentsException)) {
errorMessage(ex.getMessage());
}
if (options.contains(SpringCliException.Option.SHOW_USAGE)) {
showUsage();
}
if (debug || options.contains(SpringCliException.Option.STACK_TRACE)) {
printStackTrace(ex);
}
return 1;
}
/**
* Parse the arguments and run a suitable command.
* @param args the arguments
* @throws Exception
*/
protected void run(String... args) throws Exception {
if (args.length == 0) {
throw new NoArgumentsException();
}
String commandName = args[0];
String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
find(commandName).run(commandArguments);
}
private Command find(String name) {
for (Command candidate : this.commands) {
if (candidate.getName().equals(name)) {
return candidate;
}
}
throw new NoSuchCommandException(name);
}
protected void showUsage() {
Log.info("usage: " + CLI_APP + " ");
Log.info("");
Log.info(" <command> [<args>]");
Log.info("");
Log.info("Available commands are:");
for (Command command : this.commands) {
String usageHelp = command.getUsageHelp();
String description = command.getDescription();
Log.info(String.format("\n %1$s %2$-15s\n %3$s", command.getName(),
(usageHelp == null ? "" : usageHelp), (description == null ? ""
: description)));
}
Log.info("");
Log.info("See '" + CLI_APP
+ " help <command>' for more information on a specific command.");
}
protected void errorMessage(String message) {
Log.error(message == null ? "Unexpected error" : message);
}
protected void printStackTrace(Exception ex) {
Log.error("");
Log.error(ex);
Log.error("");
}
private String[] removeDebugFlags(String[] args) {
List<String> rtn = new ArrayList<String>(args.length);
for (String arg : args) {
if (!("-d".equals(arg) || "--debug".equals(arg))) {
rtn.add(arg);
}
}
return rtn.toArray(new String[rtn.size()]);
}
/**
* Internal {@link Command} used for 'help' requests.
*/
private class HelpCommand implements Command {
@Override
public void run(String... args) throws Exception {
if (args.length == 0) {
throw new NoHelpCommandArgumentsException();
}
String commandName = args[0];
for (Command command : SpringCli.this.commands) {
if (command.getName().equals(commandName)) {
Log.info(CLI_APP + " " + command.getName() + " - "
+ command.getDescription());
Log.info("");
if (command.getUsageHelp() != null) {
Log.info("usage: " + CLI_APP + " " + command.getName() + " "
+ command.getUsageHelp());
Log.info("");
}
if (command.getHelp() != null) {
Log.info(command.getHelp());
}
return;
}
}
throw new NoSuchCommandException(commandName);
}
@Override
public String getName() {
return "help";
}
@Override
public String getDescription() {
return "Get help on commands";
}
@Override
public String getUsageHelp() {
return "command";
}
@Override
public String getHelp() {
return null;
}
}
static class NoHelpCommandArgumentsException extends SpringCliException {
private static final long serialVersionUID = 1L;
public NoHelpCommandArgumentsException() {
super(Option.SHOW_USAGE);
}
}
static class NoArgumentsException extends SpringCliException {
private static final long serialVersionUID = 1L;
}
/**
* The main CLI entry-point.
* @param args CLI arguments
*/
public static void main(String... args) {
int exitCode = new SpringCli().runAndHandleErrors(args);
if (exitCode != 0) {
System.exit(exitCode);
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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 java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
/**
* Runtime exception wrapper that defines additional {@link Option}s that are understood
* by the {@link SpringCli}.
*
* @author Phillip Webb
*/
public class SpringCliException extends RuntimeException {
private static final long serialVersionUID = 0L;
private final EnumSet<Option> options;
/**
* Create a new {@link SpringCliException} with the specified options.
* @param options the exception options
*/
public SpringCliException(Option... options) {
this.options = asEnumSet(options);
}
/**
* Create a new {@link SpringCliException} with the specified options.
* @param message the exception message to display to the user
* @param options the exception options
*/
public SpringCliException(String message, Option... options) {
super(message);
this.options = asEnumSet(options);
}
/**
* Create a new {@link SpringCliException} with the specified options.
* @param message the exception message to display to the user
* @param cause the underlying cause
* @param options the exception options
*/
public SpringCliException(String message, Throwable cause, Option... options) {
super(message, cause);
this.options = asEnumSet(options);
}
private EnumSet<Option> asEnumSet(Option[] options) {
if (options == null || options.length == 0) {
return EnumSet.noneOf(Option.class);
}
return EnumSet.copyOf(Arrays.asList(options));
}
/**
* Returns options a set of options that are understood by the {@link SpringCli}.
*/
public Set<Option> getOptions() {
return Collections.unmodifiableSet(this.options);
}
/**
* Specific options understood by the {@link SpringCli}.
*/
public static enum Option {
/**
* Print basic CLI usage information.
*/
SHOW_USAGE,
/**
* Print the stack-trace of the exception.
*/
STACK_TRACE
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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 org.springframework.boot.cli.Command;
/**
* Abstract {@link Command} implementation.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class AbstractCommand implements Command {
private String name;
private String description;
/**
* Create a new {@link AbstractCommand} instance.
* @param name the name of the command
* @param description the command description
*/
public AbstractCommand(String name, String description) {
this.name = name;
this.description = description;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getDescription() {
return this.description;
}
@Override
public String getUsageHelp() {
return null;
}
@Override
public String getHelp() {
return null;
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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 java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.apache.ivy.util.FileUtil;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.Log;
/**
* {@link Command} to 'clean' up grapes, removing cached dependencies and forcing a
* download on the next attempt to resolve.
*
* @author Dave Syer
*/
public class CleanCommand extends OptionParsingCommand {
public CleanCommand() {
super("clean", "Clean up groovy grapes "
+ "(useful if snapshots are needed and you need an update)",
new CleanOptionHandler());
}
@Override
public String getUsageHelp() {
return "[options] <dependencies>";
}
private static class CleanOptionHandler extends OptionHandler {
private OptionSpec<Void> allOption;
private OptionSpec<Void> ivyOption;
private OptionSpec<Void> mvnOption;
@Override
protected void options() {
this.allOption = option("all", "Clean all files (not just snapshots)");
this.ivyOption = option("ivy", "Clean just ivy (grapes) cache. "
+ "Default is on unless --maven is used.");
this.mvnOption = option("maven", "Clean just maven cache. Default is off.");
}
@Override
protected void run(OptionSet options) throws Exception {
if (!options.has(this.ivyOption)) {
clean(options, getGrapesHome(), Layout.IVY);
}
if (options.has(this.mvnOption)) {
if (options.has(this.ivyOption)) {
clean(options, getGrapesHome(), Layout.IVY);
}
clean(options, getMavenHome(), Layout.MAVEN);
}
}
private void clean(OptionSet options, File root, Layout layout) {
if (root == null || !root.exists()) {
return;
}
ArrayList<Object> specs = new ArrayList<Object>(options.nonOptionArguments());
if (!specs.contains("org.springframework.boot") && layout == Layout.IVY) {
specs.add(0, "org.springframework.boot");
}
for (Object spec : specs) {
if (spec instanceof String) {
clean(options, root, layout, (String) spec);
}
}
}
private void clean(OptionSet options, File root, Layout layout, String spec) {
String group = spec;
String module = null;
if (spec.contains(":")) {
group = spec.substring(0, spec.indexOf(':'));
module = spec.substring(spec.indexOf(':') + 1);
}
File file = getModulePath(root, group, module, layout);
if (!file.exists()) {
return;
}
if (options.has(this.allOption) || group.equals("org.springframework.boot")) {
delete(file);
return;
}
for (Object obj : FileUtil.listAll(file, Collections.emptyList())) {
File candidate = (File) obj;
if (candidate.getName().contains("SNAPSHOT")) {
delete(candidate);
}
}
}
private void delete(File file) {
Log.info("Deleting: " + file);
FileUtil.forceDelete(file);
}
private File getModulePath(File root, String group, String module, Layout layout) {
File parent = root;
if (layout == Layout.IVY) {
parent = new File(parent, group);
}
else {
for (String path : group.split("\\.")) {
parent = new File(parent, path);
}
}
if (module == null) {
return parent;
}
return new File(parent, module);
}
private File getGrapesHome() {
String dir = System.getenv("GROOVY_HOME");
String userdir = System.getProperty("user.home");
File home;
if (dir == null || !new File(dir).exists()) {
dir = userdir;
home = new File(dir, ".groovy");
}
else {
home = new File(dir);
}
if (dir == null || !new File(dir).exists()) {
return null;
}
return new File(home, "grapes");
}
private File getMavenHome() {
String dir = System.getProperty("user.home");
if (dir == null || !new File(dir).exists()) {
return null;
}
File home = new File(dir);
return new File(new File(home, ".m2"), "repository");
}
private static enum Layout {
IVY, MAVEN;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 joptsimple.OptionSet;
import org.springframework.boot.cli.Command;
import static java.util.Arrays.asList;
/**
* {@link Command} to 'create' a new spring groovy script.
*
* @author Phillip Webb
*/
public class CreateCommand extends OptionParsingCommand {
public CreateCommand() {
super("create", "Create an new spring groovy script", new CreateOptionHandler());
}
@Override
public String getUsageHelp() {
return "[options] <file>";
}
private static class CreateOptionHandler extends OptionHandler {
@Override
protected void options() {
option(asList("overwite", "f"), "Overwrite any existing file");
option("type", "Create a specific application type").withOptionalArg()
.ofType(String.class).describedAs("web, batch, integration");
}
@Override
protected void run(OptionSet options) {
throw new IllegalStateException("Not implemented"); // FIXME
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.command;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.CommandFactory;
/**
* Default implementation of {@link CommandFactory}.
*
* @author Dave Syer
*/
public class DefaultCommandFactory implements CommandFactory {
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
new VersionCommand(), new RunCommand(), new CleanCommand());
@Override
public Collection<Command> getCommands() {
return DEFAULT_COMMANDS;
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.Closure;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpecBuilder;
/**
* A handler that parses and handles options.
*
* @author Dave Syer
* @see OptionParsingCommand
*/
public class OptionHandler {
private OptionParser parser;
private Closure<Void> closure;
public OptionSpecBuilder option(String name, String description) {
return getParser().accepts(name, description);
}
public OptionSpecBuilder option(Collection<String> aliases, String description) {
return getParser().acceptsAll(aliases, description);
}
public OptionParser getParser() {
if (this.parser == null) {
this.parser = new OptionParser();
options();
}
return this.parser;
}
protected void options() {
if (this.closure != null) {
this.closure.call();
}
}
public void setOptions(Closure<Void> closure) {
this.closure = closure;
}
public final void run(String... args) throws Exception {
OptionSet options = getParser().parse(args);
run(options);
}
protected void run(OptionSet options) throws Exception {
}
public String getHelp() {
OutputStream out = new ByteArrayOutputStream();
try {
getParser().printHelpOn(out);
}
catch (IOException ex) {
return "Help not available";
}
return out.toString();
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 org.springframework.boot.cli.Command;
/**
* Base class for a {@link Command} that pare options using an {@link OptionHandler}.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class OptionParsingCommand extends AbstractCommand {
private OptionHandler handler;
public OptionParsingCommand(String name, String description, OptionHandler handler) {
super(name, description);
this.handler = handler;
}
@Override
public String getHelp() {
return this.handler.getHelp();
}
@Override
public final void run(String... args) throws Exception {
this.handler.run(args);
}
protected OptionHandler getHandler() {
return this.handler;
}
}

View File

@@ -0,0 +1,195 @@
/*
* 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 java.awt.Desktop;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.runner.SpringApplicationRunner;
import org.springframework.boot.cli.runner.SpringApplicationRunnerConfiguration;
import static java.util.Arrays.asList;
/**
* {@link Command} to 'run' a groovy script or scripts.
*
* @author Phillip Webb
* @author Dave Syer
* @see SpringApplicationRunner
*/
public class RunCommand extends OptionParsingCommand {
public RunCommand() {
super("run", "Run a spring groovy script", new RunOptionHandler());
}
@Override
public String getUsageHelp() {
return "[options] <files> [--] [args]";
}
public void stop() {
if (this.getHandler() != null) {
((RunOptionHandler) this.getHandler()).runner.stop();
}
}
private static class RunOptionHandler extends OptionHandler {
private OptionSpec<Void> watchOption;
private OptionSpec<Void> editOption;
private OptionSpec<Void> noGuessImportsOption;
private OptionSpec<Void> noGuessDependenciesOption;
private OptionSpec<Void> verboseOption;
private OptionSpec<Void> quietOption;
private OptionSpec<Void> localOption;
private OptionSpec<String> classpathOption;
private SpringApplicationRunner runner;
@Override
protected void options() {
this.watchOption = option("watch", "Watch the specified file for changes");
this.localOption = option("local",
"Accumulate the dependencies in a local folder (./grapes)");
this.editOption = option(asList("edit", "e"),
"Open the file with the default system editor");
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.verboseOption = option(asList("verbose", "v"), "Verbose logging");
this.quietOption = option(asList("quiet", "q"), "Quiet logging");
this.classpathOption = option(asList("classpath", "cp"),
"Additional classpath entries").withRequiredArg();
}
@Override
protected void run(OptionSet options) throws Exception {
List<?> nonOptionArguments = options.nonOptionArguments();
File[] files = getFileArguments(nonOptionArguments);
List<?> args = nonOptionArguments.subList(files.length,
nonOptionArguments.size());
if (options.has(this.editOption)) {
Desktop.getDesktop().edit(files[0]);
}
SpringApplicationRunnerConfiguration configuration = new SpringApplicationRunnerConfigurationAdapter(
options);
if (configuration.isLocal() && System.getProperty("grape.root") == null) {
System.setProperty("grape.root", ".");
}
this.runner = new SpringApplicationRunner(configuration, files,
args.toArray(new String[args.size()]));
this.runner.compileAndRun();
}
private File[] getFileArguments(List<?> nonOptionArguments) {
List<File> files = new ArrayList<File>();
for (Object option : nonOptionArguments) {
if (option instanceof String) {
String filename = (String) option;
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);
}
}
}
}
if (files.size() == 0) {
throw new RuntimeException("Please specify a file to run");
}
return files.toArray(new File[files.size()]);
}
/**
* Simple adapter class to present the {@link OptionSet} as a
* {@link SpringApplicationRunnerConfiguration}.
*/
private class SpringApplicationRunnerConfigurationAdapter implements
SpringApplicationRunnerConfiguration {
private OptionSet options;
public SpringApplicationRunnerConfigurationAdapter(OptionSet options) {
this.options = options;
}
@Override
public boolean isWatchForFileChanges() {
return this.options.has(RunOptionHandler.this.watchOption);
}
@Override
public boolean isGuessImports() {
return !this.options.has(RunOptionHandler.this.noGuessImportsOption);
}
@Override
public boolean isGuessDependencies() {
return !this.options.has(RunOptionHandler.this.noGuessDependenciesOption);
}
@Override
public boolean isLocal() {
return this.options.has(RunOptionHandler.this.localOption);
}
@Override
public Level getLogLevel() {
if (this.options.has(RunOptionHandler.this.verboseOption)) {
return Level.FINEST;
}
if (this.options.has(RunOptionHandler.this.quietOption)) {
return Level.OFF;
}
return Level.INFO;
}
@Override
public String getClasspath() {
if (this.options.has(RunOptionHandler.this.classpathOption)) {
return this.options.valueOf(RunOptionHandler.this.classpathOption);
}
return "";
}
}
}
}

View File

@@ -0,0 +1,250 @@
/*
* 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.Closure;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import groovy.lang.Script;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import joptsimple.OptionParser;
import org.apache.ivy.util.FileUtil;
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;
/**
* {@link Command} to run a Groovy script.
*
* @author Dave Syer
*/
public class ScriptCommand implements Command {
private static final String[] DEFAULT_PATHS = new String[] { "${SPRING_HOME}/ext",
"${SPRING_HOME}/bin" };
private String[] paths = DEFAULT_PATHS;
private Class<?> mainClass;
private Object main;
private String name;
public ScriptCommand(String script) {
this.name = script;
}
@Override
public String getName() {
if (getMain() instanceof Command) {
return ((Command) getMain()).getName();
}
return this.name;
}
@Override
public String getDescription() {
if (getMain() instanceof Command) {
return ((Command) getMain()).getDescription();
}
return this.name;
}
@Override
public String getHelp() {
if (getMain() instanceof OptionHandler) {
return ((OptionHandler) getMain()).getHelp();
}
if (getMain() instanceof Command) {
return ((Command) getMain()).getHelp();
}
return null;
}
@Override
public void run(String... args) throws Exception {
run(getMain(), args);
}
private void run(Object main, String[] args) throws Exception {
if (main instanceof Command) {
((Command) main).run(args);
}
else if (main instanceof OptionHandler) {
((OptionHandler) getMain()).run(args);
}
else if (main instanceof Closure) {
((Closure<?>) main).call((Object[]) args);
}
else if (main instanceof Runnable) {
((Runnable) main).run();
}
else if (main instanceof Script) {
Script script = (Script) this.main;
script.setProperty("args", args);
if (this.main instanceof GroovyObjectSupport) {
GroovyObjectSupport object = (GroovyObjectSupport) this.main;
if (object.getMetaClass().hasProperty(object, "parser") != null) {
OptionParser parser = (OptionParser) object.getProperty("parser");
if (parser != null) {
script.setProperty("options", parser.parse(args));
}
}
}
Object result = script.run();
run(result, args);
}
}
/**
* Paths to search for script files.
*
* @param paths the paths to set
*/
public void setPaths(String[] paths) {
this.paths = (paths == null ? null : paths.clone());
}
@Override
public String getUsageHelp() {
if (getMain() instanceof Command) {
return ((Command) getMain()).getDescription();
}
return "[options] <args>";
}
protected Object getMain() {
if (this.main == null) {
try {
this.main = getMainClass().newInstance();
}
catch (Exception ex) {
throw new IllegalStateException("Cannot create main class: " + this.name,
ex);
}
if (this.main instanceof OptionHandler) {
((OptionHandler) this.main).options();
}
else if (this.main instanceof GroovyObjectSupport) {
GroovyObjectSupport object = (GroovyObjectSupport) this.main;
MetaClass metaClass = object.getMetaClass();
MetaMethod options = metaClass.getMetaMethod("options", null);
if (options != null) {
options.doMethodInvoke(this.main, null);
}
}
}
return this.main;
}
private void compile() {
GroovyCompiler compiler = new GroovyCompiler(new ScriptConfiguration());
compiler.addCompilationCustomizers(new ScriptCompilationCustomizer());
File source = locateSource(this.name);
Class<?>[] classes;
try {
classes = compiler.compile(source);
}
catch (CompilationFailedException ex) {
throw new IllegalStateException("Could not compile script", ex);
}
catch (IOException ex) {
throw new IllegalStateException("Could not compile script", ex);
}
this.mainClass = classes[0];
}
private Class<?> getMainClass() {
if (this.mainClass == null) {
compile();
}
return this.mainClass;
}
private File locateSource(String name) {
String resource = name;
if (!name.endsWith(".groovy")) {
resource = "commands/" + name + ".groovy";
}
URL url = getClass().getClassLoader().getResource(resource);
if (url != null) {
return locateSourceFromUrl(name, url);
}
String home = System.getProperty("SPRING_HOME", System.getenv("SPRING_HOME"));
if (home == null) {
home = ".";
}
for (String path : this.paths) {
String subbed = path.replace("${SPRING_HOME}", home);
File file = new File(subbed, resource);
if (file.exists()) {
return file;
}
}
throw new IllegalStateException("No script found for : " + name);
}
private File locateSourceFromUrl(String name, URL url) {
if (url.toString().startsWith("file:")) {
return new File(url.toString().substring("file:".length()));
}
// probably in JAR file
try {
File file = File.createTempFile(name, ".groovy");
file.deleteOnExit();
FileUtil.copy(url, file, null);
return file;
}
catch (IOException ex) {
throw new IllegalStateException("Could not create temp file for source: "
+ name);
}
}
private static class ScriptConfiguration implements GroovyCompilerConfiguration {
@Override
public boolean isGuessImports() {
return true;
}
@Override
public boolean isGuessDependencies() {
return true;
}
@Override
public String getClasspath() {
return "";
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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.Mixin;
import java.util.ArrayList;
import java.util.List;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpecBuilder;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
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.boot.cli.Command;
/**
* Customizer for the compilation of CLI commands.
*
* @author Dave Syer
*/
public class ScriptCompilationCustomizer extends CompilationCustomizer {
public ScriptCompilationCustomizer() {
super(CompilePhase.CONVERSION);
}
@Override
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode)
throws CompilationFailedException {
addOptionHandlerMixin(classNode);
overrideOptionsMethod(source, classNode);
addImports(source, context, classNode);
}
/**
* Add imports to the class node to make writing simple commands easier. No need to
* import {@link OptionParser}, {@link OptionSet}, {@link Command} or
* {@link OptionHandler}.
*
* @param source the source node
* @param context the current context
* @param classNode the class node to manipulate
*/
private void addImports(SourceUnit source, GeneratorContext context,
ClassNode classNode) {
ImportCustomizer importCustomizer = new ImportCustomizer();
importCustomizer.addImports("joptsimple.OptionParser", "joptsimple.OptionSet",
OptionParsingCommand.class.getCanonicalName(),
Command.class.getCanonicalName(), OptionHandler.class.getCanonicalName());
importCustomizer.call(source, context, classNode);
}
/**
* If the script defines a block in this form:
*
* <pre>
* options {
* option "foo", "My Foo option"
* option "bar", "Bar has a value" withOptionalArg() ofType Integer
* }
* </pre>
*
* Then the block is taken and used to override the {@link OptionHandler#options()}
* method. In the example "option" is a call to
* {@link OptionHandler#option(String, String)}, and hence returns an
* {@link OptionSpecBuilder}. Makes a nice readable DSL for adding options.
*
* @param source the source node
* @param classNode the class node to manipulate
*/
private void overrideOptionsMethod(SourceUnit source, ClassNode classNode) {
BlockStatement block = source.getAST().getStatementBlock();
List<Statement> statements = block.getStatements();
for (Statement statement : new ArrayList<Statement>(statements)) {
if (statement instanceof ExpressionStatement) {
ExpressionStatement expr = (ExpressionStatement) statement;
Expression expression = expr.getExpression();
if (expression instanceof MethodCallExpression) {
MethodCallExpression method = (MethodCallExpression) expression;
if (method.getMethod().getText().equals("options")) {
expression = method.getArguments();
if (expression instanceof ArgumentListExpression) {
ArgumentListExpression arguments = (ArgumentListExpression) expression;
expression = arguments.getExpression(0);
if (expression instanceof ClosureExpression) {
ClosureExpression closure = (ClosureExpression) expression;
classNode.addMethod(new MethodNode("options",
Opcodes.ACC_PROTECTED, ClassHelper.VOID_TYPE,
new Parameter[0], new ClassNode[0], closure
.getCode()));
statements.remove(statement);
}
}
}
}
}
}
}
/**
* Add {@link OptionHandler} as a mixin to the class node if it doesn't already
* declare it as a super class.
*
* @param classNode the class node to manipulate
*/
private void addOptionHandlerMixin(ClassNode classNode) {
// If we are not an OptionHandler then add that class as a mixin
if (!classNode.isDerivedFrom(ClassHelper.make(OptionHandler.class))
&& !classNode.isDerivedFrom(ClassHelper.make("OptionHandler"))) {
AnnotationNode mixin = new AnnotationNode(ClassHelper.make(Mixin.class));
mixin.addMember("value",
new ClassExpression(ClassHelper.make(OptionHandler.class)));
classNode.addAnnotation(mixin);
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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 org.springframework.boot.cli.Command;
/**
* {@link Command} to display the 'version' number.
*
* @author Phillip Webb
*/
public class VersionCommand extends AbstractCommand {
public VersionCommand() {
super("--version", "Show the version");
}
@Override
public void run(String... args) {
// FIXME: add version introspection
throw new IllegalStateException("Not implemented");
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
/**
* General purpose AST utilities.
*
* @author Phillip Webb
*/
public abstract class AstUtils {
/**
* Determine if an {@link AnnotatedNode} has one or more of the specified annotations.
*/
public static boolean hasAtLeastOneAnnotation(AnnotatedNode node,
String... annotations) {
for (AnnotationNode annotationNode : node.getAnnotations()) {
for (String annotation : annotations) {
if (annotation.equals(annotationNode.getClassNode().getName())) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
/**
* Strategy that can be used to apply some auto-configuration during the
* {@link CompilePhase#CONVERSION} Groovy compile phase.
*
* @author Phillip Webb
*/
public abstract class CompilerAutoConfiguration {
/**
* Strategy method used to determine when compiler auto-configuration should be
* applied. Defaults to always.
* @param classNode the class node
* @return {@code true} if the compiler should be auto configured using this class. If
* this method returns {@code false} no other strategy methods will be called.
*/
public boolean matches(ClassNode classNode) {
return true;
}
/**
* Apply any dependency customizations. This method will only be called if
* {@link #matches} returns {@code true}.
* @param dependencies dependency customizer
* @throws CompilationFailedException
*/
public void applyDependencies(DependencyCustomizer dependencies)
throws CompilationFailedException {
}
/**
* Apply any import customizations. This method will only be called if
* {@link #matches} returns {@code true}.
* @param imports import customizer
* @throws CompilationFailedException
*/
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
}
/**
* Apply any customizations to the main class. This method will only be called if
* {@link #matches} returns {@code true}. This method is useful when a groovy file
* defines more than one class but customization only applies to the first class.
*/
public void applyToMainClass(GroovyClassLoader loader,
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
}
/**
* Apply any additional configuration.
*/
public void apply(GroovyClassLoader loader,
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
}
}

View File

@@ -0,0 +1,276 @@
/*
* 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.Grape;
import groovy.lang.Grapes;
import groovy.lang.GroovyClassLoader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Customizer that allows dependencies to be added during compilation. Delegates to Groovy
* {@link Grapes} to actually resolve dependencies. This class provides a fluent API for
* conditionally adding dependencies. For example:
* {@code dependencies.ifMissing("com.corp.SomeClass").add(group, module, version)}.
*
* @author Phillip Webb
*/
public class DependencyCustomizer {
private final GroovyClassLoader loader;
private final List<Map<String, Object>> dependencies;
private Properties properties;
/**
* Create a new {@link DependencyCustomizer} instance. The {@link #call()} method must
* be used to actually resolve dependencies.
* @param loader
*/
public DependencyCustomizer(GroovyClassLoader loader) {
this.loader = loader;
this.dependencies = new ArrayList<Map<String, Object>>();
}
/**
* Create a new nested {@link DependencyCustomizer}.
* @param parent
*/
protected DependencyCustomizer(DependencyCustomizer parent) {
this.loader = parent.loader;
this.dependencies = parent.dependencies;
}
public String getProperty(String key) {
return getProperty(key, "");
}
public String getProperty(String key, String defaultValue) {
if (this.properties == null) {
this.properties = new Properties();
try {
for (URL url : Collections.list(this.loader
.getResources("META-INF/springcli.properties"))) {
this.properties.load(url.openStream());
}
}
catch (Exception e) {
// swallow and continue
}
}
return this.properties.getProperty(key, defaultValue);
}
/**
* Create a nested {@link DependencyCustomizer} that only applies if any of the
* specified class names are not on the class path.
* @param classNames the class names to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAnyMissingClasses(final String... classNames) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String classname : classNames) {
try {
DependencyCustomizer.this.loader.loadClass(classname);
}
catch (Exception ex) {
return true;
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Create a nested {@link DependencyCustomizer} that only applies if all of the
* specified class names are not on the class path.
* @param classNames the class names to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAllMissingClasses(final String... classNames) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String classname : classNames) {
try {
DependencyCustomizer.this.loader.loadClass(classname);
return false;
}
catch (Exception ex) {
// swallow exception and continue
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Create a nested {@link DependencyCustomizer} that only applies if the specified
* paths are on the class path.
* @param paths the paths to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAllResourcesPresent(final String... paths) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String path : paths) {
try {
if (DependencyCustomizer.this.loader.getResource(path) == null) {
return false;
}
return true;
}
catch (Exception ex) {
// swallow exception and continue
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Create a nested {@link DependencyCustomizer} that only applies at least one of the
* specified paths is on the class path.
* @param paths the paths to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifAnyResourcesPresent(final String... paths) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String path : paths) {
try {
if (DependencyCustomizer.this.loader.getResource(path) != null) {
return true;
}
return false;
}
catch (Exception ex) {
// swallow exception and continue
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Create a nested {@link DependencyCustomizer} that only applies the specified one
* was not yet added.
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifNotAdded(final String group, final String module) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
if (DependencyCustomizer.this.contains(group, module)) {
return false;
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* @param group the group ID
* @param module the module ID
* @return true if this module is already in the dependencies
*/
protected boolean contains(String group, String module) {
for (Map<String, Object> dependency : this.dependencies) {
if (group.equals(dependency.get("group"))
&& module.equals(dependency.get("module"))) {
return true;
}
}
return false;
}
/**
* Add a single dependencies.
* @param group the group ID
* @param module the module ID
* @param version the version
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String group, String module, String version) {
return this.add(group, module, version, true);
}
/**
* Add a single dependencies.
* @param group the group ID
* @param module the module ID
* @param version the version
* @return this {@link DependencyCustomizer} for continued use
*/
@SuppressWarnings("unchecked")
public DependencyCustomizer add(String group, String module, String version,
boolean transitive) {
if (canAdd()) {
Map<String, Object> dependency = new HashMap<String, Object>();
dependency.put("group", group);
dependency.put("module", module);
dependency.put("version", version);
dependency.put("transitive", transitive);
return add(dependency);
}
return this;
}
/**
* Add a dependencies.
* @param dependencies a map of the dependencies to add.
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(Map<String, Object>... dependencies) {
this.dependencies.addAll(Arrays.asList(dependencies));
return this;
}
/**
* Strategy called to test if dependencies can be added. Subclasses override as
* required.
*/
protected boolean canAdd() {
return true;
}
/**
* Apply the dependencies.
*/
void call() {
HashMap<String, Object> args = new HashMap<String, Object>();
args.put("classLoader", this.loader);
Grape.grab(args, this.dependencies.toArray(new Map[this.dependencies.size()]));
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.lang.GroovyClassLoader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
/**
* Extension of the {@link GroovyClassLoader} with support for obtaining '.class' files as
* resources.
*
* @author Phillip Webb
* @author Dave Syer
*/
class ExtendedGroovyClassLoader extends GroovyClassLoader {
private Map<String, byte[]> classResources = new HashMap<String, byte[]>();
private CompilerConfiguration configuration;
public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
super(loader, config);
this.configuration = config;
}
@Override
public InputStream getResourceAsStream(String name) {
InputStream resourceStream = super.getResourceAsStream(name);
if (resourceStream == null) {
byte[] bytes = this.classResources.get(name);
resourceStream = bytes == null ? null : new ByteArrayInputStream(bytes);
}
return resourceStream;
}
public CompilerConfiguration getConfiguration() {
return this.configuration;
}
@Override
public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
InnerLoader loader = AccessController
.doPrivileged(new PrivilegedAction<InnerLoader>() {
@Override
public InnerLoader run() {
return new InnerLoader(ExtendedGroovyClassLoader.this);
}
});
return new ExtendedClassCollector(loader, unit, su);
}
/**
* Inner collector class used to track as classes are added.
*/
protected class ExtendedClassCollector extends ClassCollector {
protected ExtendedClassCollector(InnerLoader loader, CompilationUnit unit,
SourceUnit su) {
super(loader, unit, su);
}
@Override
protected Class<?> createClass(byte[] code, ClassNode classNode) {
Class<?> createdClass = super.createClass(code, classNode);
ExtendedGroovyClassLoader.this.classResources.put(classNode.getName()
.replace(".", "/") + ".class", code);
return createdClass;
}
}
}

View File

@@ -0,0 +1,216 @@
/*
* 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.Grape;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyClassLoader.ClassCollector;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
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;
/**
* Compiler for Groovy source files. Primarily a simple Facade for
* {@link GroovyClassLoader#parseClass(File)} with the following additional features:
* <ul>
* <li>{@link CompilerAutoConfiguration} strategies will be read from
* <code>META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration</code>
* (per the standard java {@link ServiceLoader} contract) and applied during compilation</li>
*
* <li>Multiple classes can be returned if the Groovy source defines more than one Class</li>
*
* <li>Generated class files can also be loaded using
* {@link ClassLoader#getResource(String)}</li>
* <ul>
*
* @author Phillip Webb
* @author Dave Syer
*/
public class GroovyCompiler {
private GroovyCompilerConfiguration configuration;
private ExtendedGroovyClassLoader loader;
/**
* Create a new {@link GroovyCompiler} instance.
* @param configuration the compiler configuration
*/
public GroovyCompiler(final GroovyCompilerConfiguration configuration) {
this.configuration = configuration;
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
this.loader = new ExtendedGroovyClassLoader(getClass().getClassLoader(),
compilerConfiguration);
if (configuration.getClasspath().length() > 0) {
this.loader.addClasspath(configuration.getClasspath());
}
// FIXME: allow the extra resolvers to be switched on (off by default)
addExtraResolvers();
compilerConfiguration
.addCompilationCustomizers(new CompilerAutoConfigureCustomizer());
}
public void addCompilationCustomizers(CompilationCustomizer... customizers) {
this.loader.getConfiguration().addCompilationCustomizers(customizers);
}
public Object[] sources(File... files) throws CompilationFailedException, IOException {
List<File> compilables = new ArrayList<File>();
List<Object> others = new ArrayList<Object>();
for (File file : files) {
if (file.getName().endsWith(".groovy") || file.getName().endsWith(".java")) {
compilables.add(file);
}
else {
others.add(file);
}
}
Class<?>[] compiled = compile(compilables.toArray(new File[compilables.size()]));
others.addAll(0, Arrays.asList(compiled));
return others.toArray(new Object[others.size()]);
}
/**
* Compile the specified Groovy source files, applying any
* {@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 {
this.loader.clearCache();
List<Class<?>> classes = new ArrayList<Class<?>>();
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);
}
ClassNode mainClassNode = (ClassNode) compilationUnit.getAST().getClasses()
.get(0);
Class<?> mainClass = null;
for (Class<?> loadedClass : classes) {
if (mainClassNode.getName().equals(loadedClass.getName())) {
mainClass = loadedClass;
}
}
if (mainClass != null) {
classes.remove(mainClass);
classes.add(0, mainClass);
}
return classes.toArray(new Class<?>[classes.size()]);
}
/**
* {@link CompilationCustomizer} to call {@link CompilerAutoConfiguration}s.
*/
private class CompilerAutoConfigureCustomizer extends CompilationCustomizer {
public CompilerAutoConfigureCustomizer() {
super(CompilePhase.CONVERSION);
}
@Override
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode)
throws CompilationFailedException {
ImportCustomizer importCustomizer = new ImportCustomizer();
ServiceLoader<CompilerAutoConfiguration> customizers = ServiceLoader.load(
CompilerAutoConfiguration.class,
GroovyCompiler.class.getClassLoader());
// Early sweep to get dependencies
DependencyCustomizer dependencyCustomizer = new DependencyCustomizer(
GroovyCompiler.this.loader);
for (CompilerAutoConfiguration autoConfiguration : customizers) {
if (autoConfiguration.matches(classNode)) {
if (GroovyCompiler.this.configuration.isGuessDependencies()) {
autoConfiguration.applyDependencies(dependencyCustomizer);
}
}
}
dependencyCustomizer.call();
// Additional auto configuration
for (CompilerAutoConfiguration autoConfiguration : customizers) {
if (autoConfiguration.matches(classNode)) {
if (GroovyCompiler.this.configuration.isGuessImports()) {
autoConfiguration.applyImports(importCustomizer);
importCustomizer.call(source, context, classNode);
}
if (source.getAST().getClasses().size() > 0
&& classNode.equals(source.getAST().getClasses().get(0))) {
autoConfiguration.applyToMainClass(GroovyCompiler.this.loader,
GroovyCompiler.this.configuration, context, source,
classNode);
}
autoConfiguration
.apply(GroovyCompiler.this.loader,
GroovyCompiler.this.configuration, context, source,
classNode);
}
}
importCustomizer.call(source, context, classNode);
}
}
private void addExtraResolvers() {
Map<String, Object> resolver = new HashMap<String, Object>();
resolver.put("name", "spring-milestone");
resolver.put("root", "http://repo.springsource.org/milestone");
Grape.addResolver(resolver);
resolver.put("name", "spring-snapshot");
resolver.put("root", "http://repo.springsource.org/snapshot");
Grape.addResolver(resolver);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli.compiler;
/**
* Configuration for the {@link GroovyCompiler}.
*
* @author Phillip Webb
*/
public interface GroovyCompilerConfiguration {
/**
* Returns if import declarations should be guessed.
*/
boolean isGuessImports();
/**
* Returns if jar dependencies should be guessed.
*/
boolean isGuessDependencies();
/**
* @return a path for local resources (colon separated)
*/
String getClasspath();
}

View File

@@ -0,0 +1,67 @@
/*
* 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.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;
/**
* {@link CompilerAutoConfiguration} for the Recator.
*
* @author Dave Syer
*/
public class ReactorCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableReactor");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies
.ifAnyMissingClasses("org.reactor.Reactor")
.add("org.projectreactor", "reactor-spring",
dependencies.getProperty("reactor.version"), false)
.add("org.projectreactor", "reactor-core",
dependencies.getProperty("reactor.version"));
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("reactor.core.Reactor", "reactor.event.Event",
"reactor.spring.context.annotation.On",
"reactor.spring.context.annotation.Reply",
EnableReactor.class.getCanonicalName());
}
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public static @interface EnableReactor {
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.autoconfigure;
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;
/**
* {@link CompilerAutoConfiguration} for Spring Batch.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class SpringBatchCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableBatchProcessing");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses("org.springframework.batch.core.Job").add(
"org.springframework.batch", "spring-batch-core",
dependencies.getProperty("spring.batch.version", "2.2.0.RELEASE"));
dependencies.ifAnyMissingClasses("org.springframework.jdbc.core.JdbcTemplate")
.add("org.springframework", "spring-jdbc",
dependencies.getProperty("spring.version"));
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports(
"org.springframework.batch.repeat.RepeatStatus",
"org.springframework.batch.core.scope.context.ChunkContext",
"org.springframework.batch.core.step.tasklet.Tasklet",
"org.springframework.batch.core.configuration.annotation.StepScope",
"org.springframework.batch.core.configuration.annotation.JobBuilderFactory",
"org.springframework.batch.core.configuration.annotation.StepBuilderFactory",
"org.springframework.batch.core.configuration.annotation.EnableBatchProcessing",
"org.springframework.batch.core.Step",
"org.springframework.batch.core.StepExecution",
"org.springframework.batch.core.StepContribution",
"org.springframework.batch.core.Job",
"org.springframework.batch.core.JobExecution",
"org.springframework.batch.core.JobParameter",
"org.springframework.batch.core.JobParameters",
"org.springframework.batch.core.launch.JobLauncher",
"org.springframework.batch.core.converter.JobParametersConverter",
"org.springframework.batch.core.converter.DefaultJobParametersConverter");
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.autoconfigure;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
import org.springframework.boot.cli.compiler.DependencyCustomizer;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
/**
* {@link CompilerAutoConfiguration} for Spring.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifAnyMissingClasses(
"org.springframework.boot.strap.SpringApplication").add(
"org.springframework.boot", "spring-boot-up",
dependencies.getProperty("spring.boot.version"));
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("javax.sql.DataSource", "javax.annotation.PostConstruct",
"javax.annotation.PreDestroy", "groovy.util.logging.Log",
"org.springframework.stereotype.Controller",
"org.springframework.stereotype.Service",
"org.springframework.stereotype.Component",
"org.springframework.beans.factory.annotation.Autowired",
"org.springframework.beans.factory.annotation.Value",
"org.springframework.context.annotation.Import",
"org.springframework.context.annotation.ImportResource",
"org.springframework.context.annotation.Profile",
"org.springframework.context.annotation.Scope",
"org.springframework.context.annotation.Configuration",
"org.springframework.context.annotation.ComponentScan",
"org.springframework.context.annotation.Bean",
"org.springframework.context.ApplicationContext",
"org.springframework.context.MessageSource",
"org.springframework.core.annotation.Order",
"org.springframework.core.io.ResourceLoader",
"org.springframework.boot.strap.CommandLineRunner",
"org.springframework.boot.config.EnableAutoConfiguration");
imports.addStarImports("org.springframework.stereotype");
}
@Override
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);
}
private void addEnableAutoConfigurationAnnotation(SourceUnit source,
ClassNode classNode) {
if (!hasEnableAutoConfigureAnnotation(classNode)) {
try {
Class<?> annotationClass = source.getClassLoader().loadClass(
"org.springframework.boot.config.EnableAutoConfiguration");
AnnotationNode annotationNode = new AnnotationNode(new ClassNode(
annotationClass));
classNode.addAnnotation(annotationNode);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
}
private boolean hasEnableAutoConfigureAnnotation(ClassNode classNode) {
for (AnnotationNode node : classNode.getAnnotations()) {
if ("EnableAutoConfiguration".equals(node.getClassNode()
.getNameWithoutPackage())) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.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;
/**
* {@link CompilerAutoConfiguration} for Spring Integration.
*
* @author Dave Syer
*/
public class SpringIntegrationCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
// Slightly weird detection algorithm because there is no @Enable annotation for
// Integration
return AstUtils.hasAtLeastOneAnnotation(classNode, "MessageEndpoint",
"EnableIntegrationPatterns");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies
.ifAnyMissingClasses("org.springframework.integration.Message")
.add("org.springframework.integration", "spring-integration-core",
dependencies.getProperty("spring.integration.version"))
.add("org.springframework.integration",
"spring-integration-dsl-groovy-core",
dependencies.getProperty("spring.integration.dsl.version",
"1.0.0.M1"));
dependencies.ifAnyMissingClasses("groovy.util.XmlParser").add(
"org.codehaus.groovy", "groovy-xml",
dependencies.getProperty("groovy.version"));
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("org.springframework.integration.Message",
"org.springframework.integration.support.MessageBuilder",
"org.springframework.integration.MessageChannel",
"org.springframework.integration.MessageHeaders",
"org.springframework.integration.annotation.MessageEndpoint",
"org.springframework.integration.annotation.Header",
"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 {
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.autoconfigure;
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;
/**
* {@link CompilerAutoConfiguration} for Spring MVC.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies
.ifAnyMissingClasses("org.springframework.web.servlet.mvc.Controller")
.add("org.springframework", "spring-webmvc",
dependencies.getProperty("spring.version"));
dependencies.ifAnyMissingClasses("org.eclipse.jetty.server.Server").add(
"org.eclipse.jetty", "jetty-webapp",
dependencies.getProperty("jetty.version"));
dependencies.add("org.codehaus.groovy", "groovy-templates",
dependencies.getProperty("groovy.version"));
// FIXME restore Tomcat when we can get reload to work
// dependencies.ifMissingClasses("org.apache.catalina.startup.Tomcat")
// .add("org.apache.tomcat.embed", "tomcat-embed-core",
// dependencies.getProperty("tomcat.version"))
// .add("org.apache.tomcat.embed", "tomcat-embed-logging-juli",
// dependencies.getProperty("tomcat.version"));
}
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "Controller", "EnableWebMvc");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("org.springframework.web.bind.annotation",
"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");
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.autoconfigure;
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;
/**
* {@link CompilerAutoConfiguration} for Spring Security.
*
* @author Dave Syer
*/
public class SpringSecurityCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableWebSecurity");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies
.ifAnyMissingClasses(
"org.springframework.security.config.annotation.web.configuration.EnableWebSecurity")
.add("org.springframework.security", "spring-security-config",
dependencies.getProperty("spring.security.version"))
.add("org.springframework.security", "spring-security-web",
dependencies.getProperty("spring.security.version"), false);
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("org.springframework.security.core.Authentication",
"org.springframework.security.core.authority.AuthorityUtils")
.addStarImports(
"org.springframework.security.config.annotation.web.configuration",
"org.springframework.security.authentication",
"org.springframework.security.config.annotation.web",
"org.springframework.security.config.annotation.web.builders");
}
}

View File

@@ -0,0 +1,221 @@
/*
* 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.runner;
import java.io.File;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.springframework.boot.cli.compiler.GroovyCompiler;
/**
* Compiles Groovy code running the resulting classes using a {@code SpringApplication}.
* Takes care of threading and class-loading issues and can optionally monitor files for
* changes.
*
* @author Phillip Webb
* @author Dave Syer
*/
public class SpringApplicationRunner {
private static int watcherCounter = 0;
private static int runnerCounter = 0;
// FIXME logging
private SpringApplicationRunnerConfiguration configuration;
private final File[] files;
private final String[] args;
private final GroovyCompiler compiler;
private RunThread runThread;
private FileWatchThread fileWatchThread;
/**
* Create a new {@link SpringApplicationRunner} instance.
* @param configuration the configuration
* @param files the files to compile/watch
* @param args input arguments
*/
public SpringApplicationRunner(
final SpringApplicationRunnerConfiguration configuration, File[] files,
String... args) {
this.configuration = configuration;
this.files = files.clone();
this.args = args.clone();
this.compiler = new GroovyCompiler(configuration);
if (configuration.getLogLevel().intValue() <= Level.FINE.intValue()) {
System.setProperty("groovy.grape.report.downloads", "true");
}
}
/**
* Compile and run the application. This method is synchronized as it can be called by
* file monitoring threads.
* @throws Exception on error
*/
public synchronized void compileAndRun() throws Exception {
try {
stop();
// Compile
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
this.runThread = new RunThread(sources);
this.runThread.start();
this.runThread.join();
// Start monitoring for changes
if (this.fileWatchThread == null
&& this.configuration.isWatchForFileChanges()) {
this.fileWatchThread = new FileWatchThread();
this.fileWatchThread.start();
}
}
catch (Exception ex) {
if (this.fileWatchThread == null) {
throw ex;
}
else {
ex.printStackTrace();
}
}
}
/**
* Thread used to launch the Spring Application with the correct context classloader.
*/
private class RunThread extends Thread {
private final Object[] sources;
private Object applicationContext;
/**
* Create a new {@link RunThread} instance.
* @param sources the sources to launch
*/
public RunThread(Object... sources) {
super("runner-" + (runnerCounter++));
this.sources = sources;
if (sources.length != 0 && sources[0] instanceof Class) {
setContextClassLoader(((Class<?>) sources[0]).getClassLoader());
}
setDaemon(true);
}
@Override
public void run() {
try {
// User reflection to load and call Spring
Class<?> application = getContextClassLoader().loadClass(
"org.springframework.boot.strap.SpringApplication");
Method method = application.getMethod("run", Object[].class,
String[].class);
this.applicationContext = method.invoke(null, this.sources,
SpringApplicationRunner.this.args);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Shutdown the thread, closing any previously opened application context.
*/
public synchronized void shutdown() {
if (this.applicationContext != null) {
try {
Method method = this.applicationContext.getClass().getMethod("close");
method.invoke(this.applicationContext);
}
catch (NoSuchMethodException ex) {
// Not an application context that we can close
}
catch (Exception ex) {
ex.printStackTrace();
}
finally {
this.applicationContext = null;
}
}
}
}
/**
* Thread to watch for file changes and trigger recompile/reload.
*/
private class FileWatchThread extends Thread {
private long previous;
public FileWatchThread() {
super("filewatcher-" + (watcherCounter++));
this.previous = 0;
for (File file : SpringApplicationRunner.this.files) {
long current = file.lastModified();
if (current > this.previous) {
this.previous = current;
}
}
setDaemon(false);
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
for (File file : SpringApplicationRunner.this.files) {
long current = file.lastModified();
if (this.previous < current) {
this.previous = current;
compileAndRun();
}
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (Exception ex) {
// Swallow, will be reported by compileAndRun
}
}
}
}
public void stop() {
if (this.runThread != null) {
this.runThread.shutdown();
this.runThread = null;
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.runner;
import java.util.logging.Level;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
/**
* Configuration for the {@link SpringApplicationRunner}.
*
* @author Phillip Webb
*/
public interface SpringApplicationRunnerConfiguration extends GroovyCompilerConfiguration {
/**
* Returns {@code true} if the source file should be monitored for changes and
* automatically recompiled.
*/
boolean isWatchForFileChanges();
/**
* Returns the logging level to use.
*/
Level getLogLevel();
/**
* Returns {@code true} if the dependencies should be cached locally
*/
boolean isLocal();
}

View File

@@ -0,0 +1,63 @@
/*
* 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.template;
import groovy.text.GStringTemplateEngine;
import groovy.text.Template;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import org.codehaus.groovy.control.CompilationFailedException;
/**
* @author Dave Syer
*/
public abstract class GroovyTemplate {
// FIXME is this used?
public static String template(String name) throws IOException,
CompilationFailedException, ClassNotFoundException {
return template(name, Collections.<String, Object> emptyMap());
}
public static String template(String name, Map<String, ?> model) throws IOException,
CompilationFailedException, ClassNotFoundException {
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);
}
else {
if (resource != null) {
template = engine.createTemplate(resource);
}
else {
template = engine.createTemplate(name);
}
}
return template.make(model).toString();
}
}