diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java index aa680c43eb1..1d3c7035c25 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java @@ -25,6 +25,7 @@ import org.springframework.boot.cli.command.CommandFactory; import org.springframework.boot.cli.command.core.VersionCommand; import org.springframework.boot.cli.command.grab.GrabCommand; import org.springframework.boot.cli.command.install.InstallCommand; +import org.springframework.boot.cli.command.install.UninstallCommand; import org.springframework.boot.cli.command.jar.JarCommand; import org.springframework.boot.cli.command.run.RunCommand; import org.springframework.boot.cli.command.test.TestCommand; @@ -38,7 +39,7 @@ public class DefaultCommandFactory implements CommandFactory { private static final List DEFAULT_COMMANDS = Arrays. asList( new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(), - new JarCommand(), InstallCommand.install(), InstallCommand.uninstall()); + new JarCommand(), new InstallCommand(), new UninstallCommand()); @Override public Collection getCommands() { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/DependencyResolver.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/DependencyResolver.java new file mode 100644 index 00000000000..fc28f0e2b37 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/DependencyResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2014 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.install; + +import java.io.File; +import java.util.List; + +/** + * @author Andy Wilkinson + * @since 1.2.0 + */ +interface DependencyResolver { + + /** + * Resolves the given {@code artifactIdentifiers}, typically in the form + * "group:artifact:version", and their dependencies. + * + * @param artifactIdentifiers The artifacts to resolve + * @return The {@code File}s for the resolved artifacts + * @throws Exception if dependency resolution fails + */ + List resolve(List artifactIdentifiers) throws Exception; + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolver.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolver.java new file mode 100644 index 00000000000..7ce76c96d20 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolver.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2014 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.install; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.codehaus.groovy.control.CompilationFailedException; +import org.springframework.boot.cli.compiler.GroovyCompiler; +import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; + +/** + * A {@code DependencyResolver} implemented using Groovy's {@code @Grab} + * + * @author Dave Syer + * @author Andy Wilkinson + * @since 1.2.0 + */ +class GroovyGrabDependencyResolver implements DependencyResolver { + + private final GroovyCompilerConfiguration configuration; + + public GroovyGrabDependencyResolver(GroovyCompilerConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public List resolve(List artifactIdentifiers) + throws CompilationFailedException, IOException { + GroovyCompiler groovyCompiler = new GroovyCompiler(this.configuration); + List artifactFiles = new ArrayList(); + if (!artifactIdentifiers.isEmpty()) { + List initialUrls = getClassPathUrls(groovyCompiler); + groovyCompiler.compile(createSources(artifactIdentifiers)); + List artifactUrls = getClassPathUrls(groovyCompiler); + artifactUrls.removeAll(initialUrls); + + for (URL artifactUrl : artifactUrls) { + artifactFiles.add(toFile(artifactUrl)); + } + + } + return artifactFiles; + } + + private List getClassPathUrls(GroovyCompiler compiler) { + return new ArrayList(Arrays.asList(compiler.getLoader().getURLs())); + } + + private String createSources(List artifactIdentifiers) throws IOException { + File file = File.createTempFile("SpringCLIDependency", ".groovy"); + file.deleteOnExit(); + OutputStreamWriter stream = new OutputStreamWriter(new FileOutputStream(file), + "UTF-8"); + try { + for (String artifactIdentifier : artifactIdentifiers) { + stream.write("@Grab('" + artifactIdentifier + "')"); + } + // Dummy class to force compiler to do grab + stream.write("class Installer {}"); + } + finally { + stream.close(); + } + // Windows paths get tricky unless you work with URI + return file.getAbsoluteFile().toURI().toString(); + } + + private File toFile(URL url) { + try { + return new File(url.toURI()); + } + catch (URISyntaxException ex) { + return new File(url.getPath()); + } + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/InstallCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/InstallCommand.java index 12c5c8e12be..8ecada309f3 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/InstallCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/InstallCommand.java @@ -15,242 +15,59 @@ */ package org.springframework.boot.cli.command.install; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import joptsimple.OptionSet; -import joptsimple.OptionSpec; import org.springframework.boot.cli.command.Command; import org.springframework.boot.cli.command.OptionParsingCommand; import org.springframework.boot.cli.command.options.CompilerOptionHandler; -import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration; import org.springframework.boot.cli.command.status.ExitStatus; -import org.springframework.boot.cli.compiler.GroovyCompiler; -import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; -import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory; -import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; import org.springframework.boot.cli.util.Log; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.SystemPropertyUtils; /** * {@link Command} to install additional dependencies into the CLI. * * @author Dave Syer * @author Andy Wilkinson + * @since 1.2.0 */ public class InstallCommand extends OptionParsingCommand { - private final String usageHelp; - - public static Command install() { - return new InstallCommand("install", "Install dependencies to lib directory", - "[options] ", new InstallFileProcessorFactory()); - } - - public static Command uninstall() { - return new InstallCommand("uninstall", - "Uninstall dependencies from lib directory", "[options] ", - new UninstallFileProcessorFactory()); - } - - private InstallCommand(String name, String description, String usageHelp, - FileProcessorFactory visitor) { - super(name, description, new InstallOptionHandler(visitor)); - this.usageHelp = usageHelp; + public InstallCommand() { + super("install", "Install dependencies to the lib directory", + new InstallOptionHandler()); } @Override public String getUsageHelp() { - return this.usageHelp; + return "[options] "; } private static final class InstallOptionHandler extends CompilerOptionHandler { - private FileProcessorFactory factory; - private OptionSpec allOption; - - public InstallOptionHandler(FileProcessorFactory factory) { - this.factory = factory; - } - - @Override - protected void doOptions() { - if (this.factory.getClass().getName().contains("UninstallFile")) { - this.allOption = option("all", "Uninstall all"); - } - } - @Override protected ExitStatus run(OptionSet options) throws Exception { @SuppressWarnings("unchecked") List args = (List) options.nonOptionArguments(); - if (options.has(this.allOption)) { - if (!args.isEmpty()) { - throw new IllegalArgumentException( - "No non-optional arguments permitted with --all"); - } - uninstallAllJars(); - return ExitStatus.OK; - } if (args.isEmpty()) { throw new IllegalArgumentException( - "Non-optional arguments required. Specify dependencies via group:artifact:version"); + "Please specify at least one dependency, in the form group:artifact:version, to install"); } - List repositoryConfiguration = RepositoryConfigurationFactory - .createDefaultRepositoryConfiguration(); - - GroovyCompilerConfiguration configuration = new OptionSetGroovyCompilerConfiguration( - options, this, repositoryConfiguration) { - @Override - public boolean isAutoconfigure() { - return false; - } - }; - - GroovyCompiler groovyCompiler = new GroovyCompiler(configuration); try { - if (!args.isEmpty()) { - List initialUrls = getClassPathUrls(groovyCompiler); - groovyCompiler.compile(createSources(args)); - List urlsToProcessor = getClassPathUrls(groovyCompiler); - urlsToProcessor.removeAll(initialUrls); - - processJars(urlsToProcessor); - } + new Installer(options, this).install(args); } - catch (Exception ex) { - String message = ex.getMessage(); - Log.error(message != null ? message : ex.getClass().toString()); + catch (Exception e) { + String message = e.getMessage(); + Log.error(message != null ? message : e.getClass().toString()); } return ExitStatus.OK; } - private List getClassPathUrls(GroovyCompiler compiler) { - return new ArrayList(Arrays.asList(compiler.getLoader().getURLs())); - } - - private void processJars(List urlsToProcess) throws IOException { - File lib = getDefaultLibDirectory(); - - FileProcessor processor = this.factory.processor(lib); - - for (URL url : urlsToProcess) { - File file = toFile(url); - Log.info("Processing: " + file); - if (file.getName().endsWith(".jar")) { - processor.processFile(file); - } - } - } - - private File toFile(URL url) { - try { - return new File(url.toURI()); - } - catch (URISyntaxException ex) { - return new File(url.getPath()); - } - } - - private void uninstallAllJars() throws IOException { - File lib = getDefaultLibDirectory(); - File[] filesInLib = lib.listFiles(); - if (filesInLib != null) { - FileProcessor processor = new DeleteNotTheCliProcessor(); - for (File file : filesInLib) { - processor.processFile(file); - } - } - } - - private String createSources(List args) throws IOException { - File file = File.createTempFile("SpringInstallCommand", ".groovy"); - file.deleteOnExit(); - OutputStreamWriter stream = new OutputStreamWriter( - new FileOutputStream(file), "UTF-8"); - try { - for (String arg : args) { - stream.write("@Grab('" + arg + "')"); - } - // Dummy class to force compiler to do grab - stream.write("class Installer {}"); - } - finally { - stream.close(); - } - // Windows paths get tricky unless you work with URI - return file.getAbsoluteFile().toURI().toString(); - } - - private static File getDefaultLibDirectory() { - String home = SystemPropertyUtils - .resolvePlaceholders("${spring.home:${SPRING_HOME:.}}"); - final File lib = new File(home, "lib"); - return lib; - } - - } - - private interface FileProcessorFactory { - FileProcessor processor(File lib); - } - - private interface FileProcessor { - void processFile(File file) throws IOException; - } - - private static class DeleteNotTheCliProcessor implements FileProcessor { - @Override - public void processFile(File file) throws IOException { - if (!file.getName().startsWith("spring-boot-cli")) { - file.delete(); - } - } - - } - - private static class InstallFileProcessorFactory implements FileProcessorFactory { - - @Override - public FileProcessor processor(final File lib) { - Log.info("Installing into: " + lib); - lib.mkdirs(); - return new FileProcessor() { - @Override - public void processFile(File file) throws IOException { - FileCopyUtils.copy(file, new File(lib, file.getName())); - } - }; - } - - } - - private static class UninstallFileProcessorFactory implements FileProcessorFactory { - - @Override - public FileProcessor processor(final File lib) { - Log.info("Uninstalling from: " + lib); - return new FileProcessor() { - @Override - public void processFile(File file) throws IOException { - new File(lib, file.getName()).delete(); - } - }; - } - } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/Installer.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/Installer.java new file mode 100644 index 00000000000..7a534644362 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/Installer.java @@ -0,0 +1,172 @@ +/* + * Copyright 2012-2014 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.install; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Properties; + +import joptsimple.OptionSet; + +import org.springframework.boot.cli.command.options.CompilerOptionHandler; +import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration; +import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; +import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory; +import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; +import org.springframework.boot.cli.util.Log; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.SystemPropertyUtils; + +/** + * @author Andy Wilkinson + * @since 1.2.0 + */ +class Installer { + + private final DependencyResolver dependencyResolver; + + private final Properties installCounts; + + Installer(OptionSet options, CompilerOptionHandler compilerOptionHandler) + throws IOException { + this(new GroovyGrabDependencyResolver(createCompilerConfiguration(options, + compilerOptionHandler))); + } + + Installer(DependencyResolver resolver) throws IOException { + this.dependencyResolver = resolver; + this.installCounts = loadInstallCounts(); + } + + private static GroovyCompilerConfiguration createCompilerConfiguration( + OptionSet options, CompilerOptionHandler compilerOptionHandler) { + List repositoryConfiguration = RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration(); + + return new OptionSetGroovyCompilerConfiguration(options, compilerOptionHandler, + repositoryConfiguration) { + @Override + public boolean isAutoconfigure() { + return false; + } + }; + } + + private static Properties loadInstallCounts() throws IOException { + + Properties properties = new Properties(); + + File installed = getInstalled(); + if (installed.exists()) { + FileReader reader = new FileReader(installed); + properties.load(reader); + reader.close(); + } + + return properties; + } + + private void saveInstallCounts() throws IOException { + FileWriter writer = new FileWriter(getInstalled()); + try { + this.installCounts.store(writer, null); + } + finally { + writer.close(); + } + } + + public void install(List artifactIdentifiers) throws Exception { + File libDirectory = getDefaultLibDirectory(); + libDirectory.mkdirs(); + + Log.info("Installing into: " + libDirectory); + + List artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers); + for (File artifactFile : artifactFiles) { + int installCount = getInstallCount(artifactFile); + if (installCount == 0) { + FileCopyUtils.copy(artifactFile, + new File(libDirectory, artifactFile.getName())); + } + setInstallCount(artifactFile, installCount + 1); + } + + saveInstallCounts(); + } + + private int getInstallCount(File file) { + String countString = this.installCounts.getProperty(file.getName()); + if (countString == null) { + return 0; + } + return Integer.valueOf(countString); + } + + private void setInstallCount(File file, int count) { + if (count == 0) { + this.installCounts.remove(file.getName()); + } + else { + this.installCounts.setProperty(file.getName(), Integer.toString(count)); + } + } + + public void uninstall(List artifactIdentifiers) throws Exception { + File libDirectory = getDefaultLibDirectory(); + + Log.info("Uninstalling from: " + libDirectory); + + List artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers); + for (File artifactFile : artifactFiles) { + int installCount = getInstallCount(artifactFile); + if (installCount <= 1) { + new File(libDirectory, artifactFile.getName()).delete(); + } + setInstallCount(artifactFile, installCount - 1); + } + + saveInstallCounts(); + } + + public void uninstallAll() throws Exception { + File libDirectory = getDefaultLibDirectory(); + + Log.info("Uninstalling from: " + libDirectory); + + for (String name : this.installCounts.stringPropertyNames()) { + new File(libDirectory, name).delete(); + } + + this.installCounts.clear(); + saveInstallCounts(); + } + + private static File getDefaultLibDirectory() { + String home = SystemPropertyUtils + .resolvePlaceholders("${spring.home:${SPRING_HOME:.}}"); + final File lib = new File(home, "lib"); + return lib; + } + + private static File getInstalled() { + return new File(getDefaultLibDirectory(), ".installed"); + } +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/UninstallCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/UninstallCommand.java new file mode 100644 index 00000000000..bf136d986f3 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/install/UninstallCommand.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2014 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.install; + +import java.util.List; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; + +import org.springframework.boot.cli.command.Command; +import org.springframework.boot.cli.command.OptionParsingCommand; +import org.springframework.boot.cli.command.options.CompilerOptionHandler; +import org.springframework.boot.cli.command.status.ExitStatus; +import org.springframework.boot.cli.util.Log; + +/** + * {@link Command} to uninstall dependencies from the CLI's lib directory + * + * @author Dave Syer + * @author Andy Wilkinson + * @since 1.2.0 + */ +public class UninstallCommand extends OptionParsingCommand { + + public UninstallCommand() { + super("uninstall", "Uninstall dependencies from the lib directory", + new UninstallOptionHandler()); + } + + @Override + public String getUsageHelp() { + return "[options] "; + } + + private static class UninstallOptionHandler extends CompilerOptionHandler { + + private OptionSpec allOption; + + @Override + protected void doOptions() { + this.allOption = option("all", "Uninstall all"); + } + + @Override + protected ExitStatus run(OptionSet options) throws Exception { + + @SuppressWarnings("unchecked") + List args = (List) options.nonOptionArguments(); + + try { + if (options.has(this.allOption)) { + if (!args.isEmpty()) { + throw new IllegalArgumentException( + "Please use --all without specifying any dependencies"); + } + + new Installer(options, this).uninstallAll(); + } + if (args.isEmpty()) { + throw new IllegalArgumentException( + "Please specify at least one dependency, in the form group:artifact:version, to uninstall"); + } + + new Installer(options, this).uninstall(args); + } + catch (Exception e) { + String message = e.getMessage(); + Log.error(message != null ? message : e.getClass().toString()); + } + + return ExitStatus.OK; + + } + + } + +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java new file mode 100644 index 00000000000..f8d936ce8b1 --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012-2014 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.install; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; +import org.springframework.boot.cli.compiler.GroovyCompilerScope; +import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory; +import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; + +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link GroovyGrabDependencyResolver}. + * + * @author Andy Wilkinson + */ +public class GroovyGrabDependencyResolverTests { + + private DependencyResolver resolver; + + @Before + public void setupResolver() { + GroovyCompilerConfiguration configuration = new GroovyCompilerConfiguration() { + + @Override + public boolean isGuessImports() { + return true; + } + + @Override + public boolean isGuessDependencies() { + return true; + } + + @Override + public boolean isAutoconfigure() { + return false; + } + + @Override + public GroovyCompilerScope getScope() { + return GroovyCompilerScope.DEFAULT; + } + + @Override + public List getRepositoryConfiguration() { + return RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration(); + } + + @Override + public String[] getClasspath() { + return new String[] { "." }; + } + }; + this.resolver = new GroovyGrabDependencyResolver(configuration); + } + + @Test + public void resolveArtifactWithNoDependencies() throws Exception { + List resolved = this.resolver.resolve(Arrays + .asList("commons-logging:commons-logging:1.1.3")); + assertThat(resolved, hasSize(1)); + assertThat(getNames(resolved), hasItems("commons-logging-1.1.3.jar")); + } + + @Test + public void resolveArtifactWithDependencies() throws Exception { + List resolved = this.resolver.resolve(Arrays + .asList("org.springframework:spring-core:4.1.1.RELEASE")); + assertThat(resolved, hasSize(2)); + assertThat(getNames(resolved), + hasItems("commons-logging-1.1.3.jar", "spring-core-4.1.1.RELEASE.jar")); + } + + @SuppressWarnings("unchecked") + @Test + public void resolveShorthandArtifactWithDependencies() throws Exception { + List resolved = this.resolver.resolve(Arrays.asList("spring-core")); + assertThat(resolved, hasSize(2)); + assertThat(getNames(resolved), + hasItems(startsWith("commons-logging-"), startsWith("spring-core-"))); + } + + @Test + public void resolveMultipleArtifacts() throws Exception { + List resolved = this.resolver.resolve(Arrays.asList("junit:junit:4.11", + "commons-logging:commons-logging:1.1.3")); + assertThat(resolved, hasSize(3)); + assertThat( + getNames(resolved), + hasItems("junit-4.11.jar", "commons-logging-1.1.3.jar", + "hamcrest-core-1.3.jar")); + } + + public Set getNames(Collection files) { + Set names = new HashSet(files.size()); + for (File file : files) { + names.add(file.getName()); + } + return names; + } + +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/InstallerTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/InstallerTests.java new file mode 100644 index 00000000000..2130e7d0744 --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/InstallerTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2012-2014 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.install; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.util.FileSystemUtils; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link Installer} + * + * @author Andy Wilkinson + */ +public class InstallerTests { + + public DependencyResolver resolver = mock(DependencyResolver.class); + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private Installer installer; + + @Before + public void setUp() throws IOException { + System.setProperty("spring.home", "target"); + FileSystemUtils.deleteRecursively(new File("target/lib")); + + this.installer = new Installer(this.resolver); + } + + @After + public void cleanUp() { + System.clearProperty("spring.home"); + } + + @Test + public void installNewDependency() throws Exception { + File foo = createTemporaryFile("foo.jar"); + + when(this.resolver.resolve(Arrays.asList("foo"))).thenReturn(Arrays.asList(foo)); + this.installer.install(Arrays.asList("foo")); + + assertThat(getNamesOfFilesInLib(), containsInAnyOrder("foo.jar", ".installed")); + } + + @Test + public void installAndUninstall() throws Exception { + File foo = createTemporaryFile("foo.jar"); + + when(this.resolver.resolve(Arrays.asList("foo"))).thenReturn(Arrays.asList(foo)); + + this.installer.install(Arrays.asList("foo")); + this.installer.uninstall(Arrays.asList("foo")); + + assertThat(getNamesOfFilesInLib(), contains(".installed")); + } + + @Test + public void installAndUninstallWithCommonDependencies() throws Exception { + File alpha = createTemporaryFile("alpha.jar"); + File bravo = createTemporaryFile("bravo.jar"); + File charlie = createTemporaryFile("charlie.jar"); + + when(this.resolver.resolve(Arrays.asList("bravo"))).thenReturn( + Arrays.asList(bravo, alpha)); + + when(this.resolver.resolve(Arrays.asList("charlie"))).thenReturn( + Arrays.asList(charlie, alpha)); + + this.installer.install(Arrays.asList("bravo")); + + assertThat(getNamesOfFilesInLib(), + containsInAnyOrder("alpha.jar", "bravo.jar", ".installed")); + + this.installer.install(Arrays.asList("charlie")); + + assertThat(getNamesOfFilesInLib(), + containsInAnyOrder("alpha.jar", "bravo.jar", "charlie.jar", ".installed")); + + this.installer.uninstall(Arrays.asList("bravo")); + + assertThat(getNamesOfFilesInLib(), + containsInAnyOrder("alpha.jar", "charlie.jar", ".installed")); + + this.installer.uninstall(Arrays.asList("charlie")); + + assertThat(getNamesOfFilesInLib(), containsInAnyOrder(".installed")); + } + + @Test + public void uninstallAll() throws Exception { + File alpha = createTemporaryFile("alpha.jar"); + File bravo = createTemporaryFile("bravo.jar"); + File charlie = createTemporaryFile("charlie.jar"); + + when(this.resolver.resolve(Arrays.asList("bravo"))).thenReturn( + Arrays.asList(bravo, alpha)); + + when(this.resolver.resolve(Arrays.asList("charlie"))).thenReturn( + Arrays.asList(charlie, alpha)); + + this.installer.install(Arrays.asList("bravo")); + this.installer.install(Arrays.asList("charlie")); + + assertThat(getNamesOfFilesInLib(), + containsInAnyOrder("alpha.jar", "bravo.jar", "charlie.jar", ".installed")); + + this.installer.uninstallAll(); + + assertThat(getNamesOfFilesInLib(), containsInAnyOrder(".installed")); + } + + private Set getNamesOfFilesInLib() { + Set names = new HashSet(); + for (File file : new File("target/lib").listFiles()) { + names.add(file.getName()); + } + return names; + } + + private File createTemporaryFile(String name) throws IOException { + File temporaryFile = this.tempFolder.newFile(name); + temporaryFile.deleteOnExit(); + return temporaryFile; + } +}