diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml index 3e2dd26170ee..2ae02a51bc75 100644 --- a/spring-boot-cli/pom.xml +++ b/spring-boot-cli/pom.xml @@ -38,6 +38,10 @@ org.springframework.boot spring-boot-loader-tools + + org.springframework.boot + spring-boot-aether + jline jline diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java index 3b60f5d3d942..1b361270f4d7 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java @@ -16,22 +16,10 @@ package org.springframework.boot.cli.compiler; -import java.io.File; -import java.net.URI; import java.util.ArrayList; import java.util.List; -import org.apache.maven.settings.Profile; -import org.apache.maven.settings.Repository; -import org.codehaus.plexus.interpolation.InterpolationException; -import org.codehaus.plexus.interpolation.Interpolator; -import org.codehaus.plexus.interpolation.PropertiesBasedValueSource; -import org.codehaus.plexus.interpolation.RegexBasedInterpolator; - import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; -import org.springframework.boot.cli.compiler.maven.MavenSettings; -import org.springframework.boot.cli.compiler.maven.MavenSettingsReader; -import org.springframework.util.StringUtils; /** * Factory used to create {@link RepositoryConfiguration}s. @@ -41,15 +29,6 @@ */ public final class RepositoryConfigurationFactory { - private static final RepositoryConfiguration MAVEN_CENTRAL = new RepositoryConfiguration( - "central", URI.create("http://repo1.maven.org/maven2/"), false); - - private static final RepositoryConfiguration SPRING_MILESTONE = new RepositoryConfiguration( - "spring-milestone", URI.create("http://repo.spring.io/milestone"), false); - - private static final RepositoryConfiguration SPRING_SNAPSHOT = new RepositoryConfiguration( - "spring-snapshot", URI.create("http://repo.spring.io/snapshot"), true); - private RepositoryConfigurationFactory() { } @@ -58,74 +37,19 @@ private RepositoryConfigurationFactory() { * @return the newly-created default repository configuration */ public static List createDefaultRepositoryConfiguration() { - MavenSettings mavenSettings = new MavenSettingsReader().readSettings(); - List repositoryConfiguration = new ArrayList(); - repositoryConfiguration.add(MAVEN_CENTRAL); - if (!Boolean.getBoolean("disableSpringSnapshotRepos")) { - repositoryConfiguration.add(SPRING_MILESTONE); - repositoryConfiguration.add(SPRING_SNAPSHOT); - } - addDefaultCacheAsRepository(mavenSettings.getLocalRepository(), - repositoryConfiguration); - addActiveProfileRepositories(mavenSettings.getActiveProfiles(), - repositoryConfiguration); - return repositoryConfiguration; - } - - private static void addDefaultCacheAsRepository(String localRepository, - List repositoryConfiguration) { - RepositoryConfiguration repository = new RepositoryConfiguration("local", - getLocalRepositoryDirectory(localRepository).toURI(), true); - if (!repositoryConfiguration.contains(repository)) { - repositoryConfiguration.add(0, repository); - } - } - - private static void addActiveProfileRepositories(List activeProfiles, - List configurations) { - for (Profile activeProfile : activeProfiles) { - Interpolator interpolator = new RegexBasedInterpolator(); - interpolator.addValueSource( - new PropertiesBasedValueSource(activeProfile.getProperties())); - for (Repository repository : activeProfile.getRepositories()) { - configurations.add(getRepositoryConfiguration(interpolator, repository)); - } - } - } - - private static RepositoryConfiguration getRepositoryConfiguration( - Interpolator interpolator, Repository repository) { - String name = interpolate(interpolator, repository.getId()); - String url = interpolate(interpolator, repository.getUrl()); - boolean snapshotsEnabled = false; - if (repository.getSnapshots() != null) { - snapshotsEnabled = repository.getSnapshots().isEnabled(); - } - return new RepositoryConfiguration(name, URI.create(url), snapshotsEnabled); - } - - private static String interpolate(Interpolator interpolator, String value) { - try { - return interpolator.interpolate(value); - } - catch (InterpolationException ex) { - return value; - } - } - - private static File getLocalRepositoryDirectory(String localRepository) { - if (StringUtils.hasText(localRepository)) { - return new File(localRepository); - } - return new File(getM2HomeDirectory(), "repository"); + return convert(org.springframework.boot.aether.RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration()); } - private static File getM2HomeDirectory() { - String mavenRoot = System.getProperty("maven.home"); - if (StringUtils.hasLength(mavenRoot)) { - return new File(mavenRoot); + private static List convert( + List repositoryConfigurations) { + List list = new ArrayList(); + for (org.springframework.boot.aether.RepositoryConfiguration repositoryConfiguration : repositoryConfigurations) { + list.add(new RepositoryConfiguration(repositoryConfiguration.getName(), + repositoryConfiguration.getUri(), + repositoryConfiguration.getSnapshotsEnabled())); } - return new File(System.getProperty("user.home"), ".m2"); + return list; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java index 82e8e78fd14d..4eb412788deb 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java @@ -27,20 +27,16 @@ import groovy.grape.GrapeEngine; import groovy.lang.GroovyClassLoader; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystem; + import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.Exclusion; import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.resolution.ArtifactResolutionException; -import org.eclipse.aether.resolution.ArtifactResult; -import org.eclipse.aether.resolution.DependencyRequest; -import org.eclipse.aether.resolution.DependencyResult; import org.eclipse.aether.util.artifact.JavaScopes; -import org.eclipse.aether.util.filter.DependencyFilterUtils; + +import org.springframework.boot.aether.AetherEngine; +import org.springframework.boot.aether.DependencyResolutionFailedException; /** * A {@link GrapeEngine} implementation that uses @@ -63,40 +59,15 @@ public class AetherGrapeEngine implements GrapeEngine { private final DependencyResolutionContext resolutionContext; - private final ProgressReporter progressReporter; + private final AetherEngine engine; private final GroovyClassLoader classLoader; - private final DefaultRepositorySystemSession session; - - private final RepositorySystem repositorySystem; - - private final List repositories; - - public AetherGrapeEngine(GroovyClassLoader classLoader, - RepositorySystem repositorySystem, - DefaultRepositorySystemSession repositorySystemSession, - List remoteRepositories, + public AetherGrapeEngine(GroovyClassLoader classLoader, AetherEngine engine, DependencyResolutionContext resolutionContext) { this.classLoader = classLoader; - this.repositorySystem = repositorySystem; - this.session = repositorySystemSession; + this.engine = engine; this.resolutionContext = resolutionContext; - this.repositories = new ArrayList(); - List remotes = new ArrayList( - remoteRepositories); - Collections.reverse(remotes); // priority is reversed in addRepository - for (RemoteRepository repository : remotes) { - addRepository(repository); - } - this.progressReporter = getProgressReporter(this.session); - } - - private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session) { - if (Boolean.getBoolean("groovy.grape.report.downloads")) { - return new DetailedProgressReporter(session, System.out); - } - return new SummaryProgressReporter(session, System.out); } @Override @@ -115,9 +86,6 @@ public Object grab(Map args, Map... dependencyMaps) { classLoader.addURL(file.toURI().toURL()); } } - catch (ArtifactResolutionException ex) { - throw new DependencyResolutionFailedException(ex); - } catch (MalformedURLException ex) { throw new DependencyResolutionFailedException(ex); } @@ -196,23 +164,6 @@ private boolean isTransitive(Map dependencyMap) { return (transitive == null ? true : transitive); } - private List getDependencies(DependencyResult dependencyResult) { - List dependencies = new ArrayList(); - for (ArtifactResult artifactResult : dependencyResult.getArtifactResults()) { - dependencies.add( - new Dependency(artifactResult.getArtifact(), JavaScopes.COMPILE)); - } - return dependencies; - } - - private List getFiles(DependencyResult dependencyResult) { - List files = new ArrayList(); - for (ArtifactResult result : dependencyResult.getArtifactResults()) { - files.add(result.getArtifact().getFile()); - } - return files; - } - private GroovyClassLoader getClassLoader(Map args) { GroovyClassLoader classLoader = (GroovyClassLoader) args.get("classLoader"); return (classLoader == null ? this.classLoader : classLoader); @@ -229,41 +180,7 @@ public void addResolver(Map args) { } protected void addRepository(RemoteRepository repository) { - if (this.repositories.contains(repository)) { - return; - } - repository = getPossibleMirror(repository); - repository = applyProxy(repository); - repository = applyAuthentication(repository); - this.repositories.add(0, repository); - } - - private RemoteRepository getPossibleMirror(RemoteRepository remoteRepository) { - RemoteRepository mirror = this.session.getMirrorSelector() - .getMirror(remoteRepository); - if (mirror != null) { - return mirror; - } - return remoteRepository; - } - - private RemoteRepository applyProxy(RemoteRepository repository) { - if (repository.getProxy() == null) { - RemoteRepository.Builder builder = new RemoteRepository.Builder(repository); - builder.setProxy(this.session.getProxySelector().getProxy(repository)); - repository = builder.build(); - } - return repository; - } - - private RemoteRepository applyAuthentication(RemoteRepository repository) { - if (repository.getAuthentication() == null) { - RemoteRepository.Builder builder = new RemoteRepository.Builder(repository); - builder.setAuthentication(this.session.getAuthenticationSelector() - .getAuthentication(repository)); - repository = builder.build(); - } - return repository; + this.engine.addRepository(repository); } @Override @@ -280,54 +197,17 @@ public URI[] resolve(Map args, Map... dependencyMaps) { public URI[] resolve(Map args, List depsInfo, Map... dependencyMaps) { List exclusions = createExclusions(args); List dependencies = createDependencies(dependencyMaps, exclusions); - try { - List files = resolve(dependencies); - List uris = new ArrayList(files.size()); - for (File file : files) { - uris.add(file.toURI()); - } - return uris.toArray(new URI[uris.size()]); - } - catch (Exception ex) { - throw new DependencyResolutionFailedException(ex); + List files = resolve(dependencies); + List uris = new ArrayList(files.size()); + for (File file : files) { + uris.add(file.toURI()); } + return uris.toArray(new URI[uris.size()]); } private List resolve(List dependencies) - throws ArtifactResolutionException { - try { - CollectRequest collectRequest = getCollectRequest(dependencies); - DependencyRequest dependencyRequest = getDependencyRequest(collectRequest); - DependencyResult result = this.repositorySystem - .resolveDependencies(this.session, dependencyRequest); - addManagedDependencies(result); - return getFiles(result); - } - catch (Exception ex) { - throw new DependencyResolutionFailedException(ex); - } - finally { - this.progressReporter.finished(); - } - } - - private CollectRequest getCollectRequest(List dependencies) { - CollectRequest collectRequest = new CollectRequest((Dependency) null, - dependencies, new ArrayList(this.repositories)); - collectRequest - .setManagedDependencies(this.resolutionContext.getManagedDependencies()); - return collectRequest; - } - - private DependencyRequest getDependencyRequest(CollectRequest collectRequest) { - DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, - DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE, - JavaScopes.RUNTIME)); - return dependencyRequest; - } - - private void addManagedDependencies(DependencyResult result) { - this.resolutionContext.addManagedDependencies(getDependencies(result)); + throws DependencyResolutionFailedException { + return this.engine.resolve(dependencies); } @Override diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java index b691ca594605..2735cbd42f06 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java @@ -18,22 +18,10 @@ import java.util.ArrayList; import java.util.List; -import java.util.ServiceLoader; import groovy.lang.GroovyClassLoader; -import org.apache.maven.repository.internal.MavenRepositorySystemUtils; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; -import org.eclipse.aether.impl.DefaultServiceLocator; -import org.eclipse.aether.internal.impl.DefaultRepositorySystem; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.repository.RepositoryPolicy; -import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; -import org.eclipse.aether.spi.connector.transport.TransporterFactory; -import org.eclipse.aether.spi.locator.ServiceLocator; -import org.eclipse.aether.transport.file.FileTransporterFactory; -import org.eclipse.aether.transport.http.HttpTransporterFactory; + +import org.springframework.boot.aether.AetherEngine; /** * Utility class to create a pre-configured {@link AetherGrapeEngine}. @@ -44,55 +32,21 @@ public abstract class AetherGrapeEngineFactory { public static AetherGrapeEngine create(GroovyClassLoader classLoader, List repositoryConfigurations, - DependencyResolutionContext dependencyResolutionContext) { - - RepositorySystem repositorySystem = createServiceLocator() - .getService(RepositorySystem.class); - - DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils - .newSession(); - - ServiceLoader autoConfigurations = ServiceLoader - .load(RepositorySystemSessionAutoConfiguration.class); - - for (RepositorySystemSessionAutoConfiguration autoConfiguration : autoConfigurations) { - autoConfiguration.apply(repositorySystemSession, repositorySystem); - } - - new DefaultRepositorySystemSessionAutoConfiguration() - .apply(repositorySystemSession, repositorySystem); - - return new AetherGrapeEngine(classLoader, repositorySystem, - repositorySystemSession, createRepositories(repositoryConfigurations), - dependencyResolutionContext); - } - - private static ServiceLocator createServiceLocator() { - DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); - locator.addService(RepositorySystem.class, DefaultRepositorySystem.class); - locator.addService(RepositoryConnectorFactory.class, - BasicRepositoryConnectorFactory.class); - locator.addService(TransporterFactory.class, HttpTransporterFactory.class); - locator.addService(TransporterFactory.class, FileTransporterFactory.class); - return locator; + DependencyResolutionContext dependencyManagement) { + AetherEngine engine = AetherEngine.create(convert(repositoryConfigurations), + dependencyManagement); + return new AetherGrapeEngine(classLoader, engine, dependencyManagement); } - private static List createRepositories( + private static List convert( List repositoryConfigurations) { - List repositories = new ArrayList( - repositoryConfigurations.size()); + List list = new ArrayList(); for (RepositoryConfiguration repositoryConfiguration : repositoryConfigurations) { - RemoteRepository.Builder builder = new RemoteRepository.Builder( - repositoryConfiguration.getName(), "default", - repositoryConfiguration.getUri().toASCIIString()); - - if (!repositoryConfiguration.getSnapshotsEnabled()) { - builder.setSnapshotPolicy( - new RepositoryPolicy(false, RepositoryPolicy.UPDATE_POLICY_NEVER, - RepositoryPolicy.CHECKSUM_POLICY_IGNORE)); - } - repositories.add(builder.build()); + list.add(new org.springframework.boot.aether.RepositoryConfiguration( + repositoryConfiguration.getName(), repositoryConfiguration.getUri(), + repositoryConfiguration.getSnapshotsEnabled())); } - return repositories; + return list; } + } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionContext.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionContext.java index 4095830acdda..fac821f4dffd 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionContext.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionContext.java @@ -17,16 +17,14 @@ package org.springframework.boot.cli.compiler.grape; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.Exclusion; import org.eclipse.aether.util.artifact.JavaScopes; +import org.springframework.boot.aether.DependencyManagementContext; import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver; import org.springframework.boot.cli.compiler.dependencies.CompositeDependencyManagement; import org.springframework.boot.cli.compiler.dependencies.DependencyManagement; @@ -39,11 +37,7 @@ * @author Andy Wilkinson * @since 1.1.0 */ -public class DependencyResolutionContext { - - private final Map managedDependencyByGroupAndArtifact = new HashMap(); - - private final List managedDependencies = new ArrayList(); +public class DependencyResolutionContext extends DependencyManagementContext { private DependencyManagement dependencyManagement = null; @@ -53,46 +47,12 @@ public DependencyResolutionContext() { addDependencyManagement(new SpringBootDependenciesDependencyManagement()); } - private String getIdentifier(Dependency dependency) { - return getIdentifier(dependency.getArtifact().getGroupId(), - dependency.getArtifact().getArtifactId()); - } - - private String getIdentifier(String groupId, String artifactId) { - return groupId + ":" + artifactId; - } - public ArtifactCoordinatesResolver getArtifactCoordinatesResolver() { return this.artifactCoordinatesResolver; } - public String getManagedVersion(String groupId, String artifactId) { - Dependency dependency = getManagedDependency(groupId, artifactId); - if (dependency == null) { - dependency = this.managedDependencyByGroupAndArtifact - .get(getIdentifier(groupId, artifactId)); - } - return dependency != null ? dependency.getArtifact().getVersion() : null; - } - - public List getManagedDependencies() { - return Collections.unmodifiableList(this.managedDependencies); - } - - private Dependency getManagedDependency(String group, String artifact) { - return this.managedDependencyByGroupAndArtifact - .get(getIdentifier(group, artifact)); - } - - void addManagedDependencies(List dependencies) { - this.managedDependencies.addAll(dependencies); - for (Dependency dependency : dependencies) { - this.managedDependencyByGroupAndArtifact.put(getIdentifier(dependency), - dependency); - } - } - public void addDependencyManagement(DependencyManagement dependencyManagement) { + List dependencies = new ArrayList(); for (org.springframework.boot.cli.compiler.dependencies.Dependency dependency : dependencyManagement .getDependencies()) { List aetherExclusions = new ArrayList(); @@ -105,10 +65,9 @@ public void addDependencyManagement(DependencyManagement dependencyManagement) { new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), "jar", dependency.getVersion()), JavaScopes.COMPILE, false, aetherExclusions); - this.managedDependencies.add(0, aetherDependency); - this.managedDependencyByGroupAndArtifact.put(getIdentifier(aetherDependency), - aetherDependency); + dependencies.add(aetherDependency); } + addManagedDependencies(dependencies); this.dependencyManagement = this.dependencyManagement == null ? dependencyManagement : new CompositeDependencyManagement(dependencyManagement, diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionConfiguration.java similarity index 90% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionAutoConfiguration.java rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionConfiguration.java index 0c6949ba958e..d596bdf0052d 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionConfiguration.java @@ -23,6 +23,7 @@ import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; +import org.springframework.boot.aether.RepositorySystemSessionConfiguration; import org.springframework.util.StringUtils; /** @@ -32,8 +33,8 @@ * @author Andy Wilkinson * @since 1.2.5 */ -public class GrapeRootRepositorySystemSessionAutoConfiguration - implements RepositorySystemSessionAutoConfiguration { +public class GrapeRootRepositorySystemSessionConfiguration + implements RepositorySystemSessionConfiguration { @Override public void apply(DefaultRepositorySystemSession session, diff --git a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.aether.RepositorySystemSessionConfiguration b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.aether.RepositorySystemSessionConfiguration new file mode 100644 index 000000000000..d6bc07678145 --- /dev/null +++ b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.aether.RepositorySystemSessionConfiguration @@ -0,0 +1 @@ +org.springframework.boot.cli.compiler.grape.GrapeRootRepositorySystemSessionConfiguration \ No newline at end of file diff --git a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.grape.RepositorySystemSessionAutoConfiguration b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.grape.RepositorySystemSessionAutoConfiguration deleted file mode 100644 index cf394a7e183e..000000000000 --- a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.grape.RepositorySystemSessionAutoConfiguration +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.cli.compiler.grape.SettingsXmlRepositorySystemSessionAutoConfiguration -org.springframework.boot.cli.compiler.grape.GrapeRootRepositorySystemSessionAutoConfiguration \ No newline at end of file diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java index de40b02aa15f..77e15e7fb2b4 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java @@ -16,7 +16,6 @@ package org.springframework.boot.cli.compiler.grape; -import java.io.File; import java.net.URI; import java.net.URL; import java.util.ArrayList; @@ -27,12 +26,8 @@ import java.util.Map; import groovy.lang.GroovyClassLoader; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.repository.Authentication; -import org.eclipse.aether.repository.RemoteRepository; -import org.junit.Test; -import org.springframework.test.util.ReflectionTestUtils; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -66,59 +61,6 @@ public void dependencyResolution() { assertThat(this.groovyClassLoader.getURLs()).hasSize(5); } - @Test - public void proxySelector() { - doWithCustomUserHome(new Runnable() { - - @Override - public void run() { - AetherGrapeEngine grapeEngine = createGrapeEngine(); - - DefaultRepositorySystemSession session = (DefaultRepositorySystemSession) ReflectionTestUtils - .getField(grapeEngine, "session"); - - assertThat(session.getProxySelector() instanceof CompositeProxySelector) - .isTrue(); - } - - }); - } - - @Test - public void repositoryMirrors() { - doWithCustomUserHome(new Runnable() { - - @SuppressWarnings("unchecked") - @Override - public void run() { - AetherGrapeEngine grapeEngine = createGrapeEngine(); - - List repositories = (List) ReflectionTestUtils - .getField(grapeEngine, "repositories"); - assertThat(repositories).hasSize(1); - assertThat(repositories.get(0).getId()).isEqualTo("central-mirror"); - } - }); - } - - @Test - public void repositoryAuthentication() { - doWithCustomUserHome(new Runnable() { - - @SuppressWarnings("unchecked") - @Override - public void run() { - AetherGrapeEngine grapeEngine = createGrapeEngine(); - - List repositories = (List) ReflectionTestUtils - .getField(grapeEngine, "repositories"); - assertThat(repositories).hasSize(1); - Authentication authentication = repositories.get(0).getAuthentication(); - assertThat(authentication).isNotNull(); - } - }); - } - @Test public void dependencyResolutionWithExclusions() { Map args = new HashMap(); @@ -242,25 +184,4 @@ private Map createExclusion(String group, String module) { return exclusion; } - private void doWithCustomUserHome(Runnable action) { - doWithSystemProperty("user.home", - new File("src/test/resources").getAbsolutePath(), action); - } - - private void doWithSystemProperty(String key, String value, Runnable action) { - String previousValue = setOrClearSystemProperty(key, value); - try { - action.run(); - } - finally { - setOrClearSystemProperty(key, previousValue); - } - } - - private String setOrClearSystemProperty(String key, String value) { - if (value != null) { - return System.setProperty(key, value); - } - return System.clearProperty(key); - } } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionAutoConfigurationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionConfigurationTests.java similarity index 89% rename from spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionAutoConfigurationTests.java rename to spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionConfigurationTests.java index 6d0680cc3434..dd1befd0105a 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionAutoConfigurationTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/GrapeRootRepositorySystemSessionConfigurationTests.java @@ -39,11 +39,11 @@ import static org.mockito.Mockito.verify; /** - * Tests for {@link GrapeRootRepositorySystemSessionAutoConfiguration} + * Tests for {@link GrapeRootRepositorySystemSessionConfiguration} * * @author Andy Wilkinson */ -public class GrapeRootRepositorySystemSessionAutoConfigurationTests { +public class GrapeRootRepositorySystemSessionConfigurationTests { private DefaultRepositorySystemSession session = MavenRepositorySystemUtils .newSession(); @@ -69,12 +69,12 @@ public LocalRepositoryManager answer( .getArgumentAt(1, LocalRepository.class); return new SimpleLocalRepositoryManagerFactory() .newInstance( - GrapeRootRepositorySystemSessionAutoConfigurationTests.this.session, + GrapeRootRepositorySystemSessionConfigurationTests.this.session, localRepository); } }); - new GrapeRootRepositorySystemSessionAutoConfiguration().apply(this.session, + new GrapeRootRepositorySystemSessionConfiguration().apply(this.session, this.repositorySystem); verify(this.repositorySystem, times(0)) .newLocalRepositoryManager(eq(this.session), any(LocalRepository.class)); @@ -89,7 +89,7 @@ public void grapeRootConfiguresLocalRepositoryLocation() { System.setProperty("grape.root", "foo"); try { - new GrapeRootRepositorySystemSessionAutoConfiguration().apply(this.session, + new GrapeRootRepositorySystemSessionConfiguration().apply(this.session, this.repositorySystem); } finally { @@ -111,7 +111,7 @@ public LocalRepositoryManager answer(InvocationOnMock invocation) LocalRepository localRepository = invocation.getArgumentAt(1, LocalRepository.class); return new SimpleLocalRepositoryManagerFactory().newInstance( - GrapeRootRepositorySystemSessionAutoConfigurationTests.this.session, + GrapeRootRepositorySystemSessionConfigurationTests.this.session, localRepository); } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 5b16dc4088c2..f31a50830bee 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -247,6 +247,11 @@ spring-boot-devtools 1.5.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-aether + 1.5.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-loader @@ -257,6 +262,11 @@ spring-boot-loader-tools 1.5.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-thin-wrapper + 1.5.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter diff --git a/spring-boot-samples/spring-boot-sample-ant/pom.xml b/spring-boot-samples/spring-boot-sample-ant/pom.xml index c681c3e2328b..47e3e965002f 100644 --- a/spring-boot-samples/spring-boot-sample-ant/pom.xml +++ b/spring-boot-samples/spring-boot-sample-ant/pom.xml @@ -1,5 +1,4 @@ - - + 4.0.0 @@ -60,8 +59,8 @@ package - - + + @@ -101,4 +100,4 @@ - + \ No newline at end of file diff --git a/spring-boot-tools/pom.xml b/spring-boot-tools/pom.xml index cbf35f78954a..42941a1a2357 100644 --- a/spring-boot-tools/pom.xml +++ b/spring-boot-tools/pom.xml @@ -22,6 +22,8 @@ spring-boot-configuration-metadata spring-boot-configuration-processor + spring-boot-thin-wrapper + spring-boot-aether spring-boot-loader spring-boot-loader-tools spring-boot-maven-plugin diff --git a/spring-boot-tools/spring-boot-aether/pom.xml b/spring-boot-tools/spring-boot-aether/pom.xml new file mode 100644 index 000000000000..99651fa44f96 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-tools + 1.5.0.BUILD-SNAPSHOT + + spring-boot-aether + Spring Boot Aether + Spring Boot Aether + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/.. + + + + + org.springframework.boot + spring-boot-loader + + + org.springframework + spring-core + + + org.apache.maven + maven-aether-provider + + + org.eclipse.sisu.plexus + org.eclipse.sisu + + + + + org.apache.maven + maven-settings-builder + + + org.codehaus.plexus + plexus-component-api + + + * + * + + + + + org.eclipse.aether + aether-api + + + org.eclipse.aether + aether-connector-basic + + + org.eclipse.aether + aether-impl + + + org.eclipse.aether + aether-spi + + + org.eclipse.aether + aether-transport-file + + + org.eclipse.aether + aether-transport-http + + + jcl-over-slf4j + org.slf4j + + + + + org.eclipse.aether + aether-util + + + + org.springframework.boot + spring-boot-test + test + + + + + + org.apache.maven.plugins + maven-shade-plugin + + true + exec + true + true + + + org.springframework.boot.loader.thin.ThinJarLauncher + + + + + commons-logging:commons-logging:* + + org/apache/** + + + + + + + shade-runtime-dependencies + package + + shade + + + + + + + diff --git a/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/AetherEngine.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/AetherEngine.java new file mode 100644 index 000000000000..6b4e54529904 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/AetherEngine.java @@ -0,0 +1,285 @@ +/* + * Copyright 2012-2015 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.aether; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; + +import org.apache.maven.repository.internal.MavenRepositorySystemUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.impl.ArtifactDescriptorReader; +import org.eclipse.aether.impl.DefaultServiceLocator; +import org.eclipse.aether.internal.impl.DefaultRepositorySystem; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryPolicy; +import org.eclipse.aether.resolution.ArtifactDescriptorRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; +import org.eclipse.aether.spi.connector.transport.TransporterFactory; +import org.eclipse.aether.spi.locator.ServiceLocator; +import org.eclipse.aether.transport.file.FileTransporterFactory; +import org.eclipse.aether.transport.http.HttpTransporterFactory; +import org.eclipse.aether.util.artifact.JavaScopes; +import org.eclipse.aether.util.filter.DependencyFilterUtils; + +import org.springframework.util.StringUtils; + +/** + * A utility wrapper for Aether, the dependency + * resolution system used by Maven. + * + * @author Andy Wilkinson + * @author Phillip Webb + * @author Dave Syer + */ +public class AetherEngine { + + private static ServiceLocator serviceLocator; + + private final DependencyManagementContext dependencyManagement; + + private final ProgressReporter progressReporter; + + private final DefaultRepositorySystemSession session; + + private final RepositorySystem repositorySystem; + + private final List repositories; + + public static AetherEngine create( + List repositoryConfigurations, + DependencyManagementContext dependencyManagement) { + + RepositorySystem repositorySystem = getServiceLocator() + .getService(RepositorySystem.class); + + DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils + .newSession(); + + ServiceLoader autoConfigurations = ServiceLoader + .load(RepositorySystemSessionConfiguration.class); + + for (RepositorySystemSessionConfiguration autoConfiguration : autoConfigurations) { + autoConfiguration.apply(repositorySystemSession, repositorySystem); + } + + new DefaultRepositorySystemSessionAutoConfiguration() + .apply(repositorySystemSession, repositorySystem); + + return new AetherEngine(repositorySystem, repositorySystemSession, + createRepositories(repositoryConfigurations), + dependencyManagement); + } + + AetherEngine(RepositorySystem repositorySystem, + DefaultRepositorySystemSession repositorySystemSession, + List remoteRepositories, + DependencyManagementContext dependencyManagement) { + this.repositorySystem = repositorySystem; + this.session = repositorySystemSession; + this.dependencyManagement = dependencyManagement; + this.repositories = new ArrayList(); + List remotes = new ArrayList( + remoteRepositories); + Collections.reverse(remotes); // priority is reversed in addRepository + for (RemoteRepository repository : remotes) { + addRepository(repository); + } + this.progressReporter = getProgressReporter(this.session); + } + + private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session) { + if (Boolean.getBoolean("groovy.grape.report.downloads")) { + return new DetailedProgressReporter(session, System.out); + } + return new SummaryProgressReporter(session, System.out); + } + + private List getDependencies(DependencyResult dependencyResult) { + List dependencies = new ArrayList(); + for (ArtifactResult artifactResult : dependencyResult.getArtifactResults()) { + dependencies.add( + new Dependency(artifactResult.getArtifact(), JavaScopes.COMPILE)); + } + return dependencies; + } + + private List getFiles(DependencyResult dependencyResult) { + List files = new ArrayList(); + for (ArtifactResult result : dependencyResult.getArtifactResults()) { + files.add(result.getArtifact().getFile()); + } + return files; + } + + public void addRepository(RemoteRepository repository) { + if (this.repositories.contains(repository)) { + return; + } + repository = getPossibleMirror(repository); + repository = applyProxy(repository); + repository = applyAuthentication(repository); + this.repositories.add(0, repository); + } + + private RemoteRepository getPossibleMirror(RemoteRepository remoteRepository) { + RemoteRepository mirror = this.session.getMirrorSelector() + .getMirror(remoteRepository); + if (mirror != null) { + return mirror; + } + return remoteRepository; + } + + private RemoteRepository applyProxy(RemoteRepository repository) { + if (repository.getProxy() == null) { + RemoteRepository.Builder builder = new RemoteRepository.Builder(repository); + builder.setProxy(this.session.getProxySelector().getProxy(repository)); + repository = builder.build(); + } + return repository; + } + + private RemoteRepository applyAuthentication(RemoteRepository repository) { + if (repository.getAuthentication() == null) { + RemoteRepository.Builder builder = new RemoteRepository.Builder(repository); + builder.setAuthentication(this.session.getAuthenticationSelector() + .getAuthentication(repository)); + repository = builder.build(); + } + return repository; + } + + public List resolve(List dependencies) + throws DependencyResolutionFailedException { + try { + CollectRequest collectRequest = getCollectRequest(dependencies); + DependencyRequest dependencyRequest = getDependencyRequest(collectRequest); + DependencyResult result = this.repositorySystem + .resolveDependencies(this.session, dependencyRequest); + addManagedDependencies(result); + return getFiles(result); + } + catch (Exception ex) { + throw new DependencyResolutionFailedException(ex); + } + finally { + this.progressReporter.finished(); + } + } + + public void addDependencyManagementBoms(List boms) { + for (Dependency bom : boms) { + try { + ArtifactDescriptorReader resolver = AetherEngine.getServiceLocator() + .getService(ArtifactDescriptorReader.class); + ArtifactDescriptorRequest request = new ArtifactDescriptorRequest( + bom.getArtifact(), this.repositories, null); + ArtifactDescriptorResult descriptor = resolver + .readArtifactDescriptor(this.session, request); + List managedDependencies = descriptor + .getManagedDependencies(); + this.dependencyManagement.addManagedDependencies(managedDependencies); + } + catch (Exception ex) { + throw new IllegalStateException("Failed to build model for '" + bom + + "'. Is it a valid Maven bom?", ex); + } + } + } + + private CollectRequest getCollectRequest(List dependencies) { + List resolve = new ArrayList(); + for (Dependency dependency : dependencies) { + String version = dependency.getArtifact().getVersion(); + String group = dependency.getArtifact().getGroupId(); + String module = dependency.getArtifact().getArtifactId(); + if (!StringUtils.hasText(version)) { + version = this.dependencyManagement.getManagedVersion(group, module); + if (version == null) { + throw new IllegalStateException( + "Cannot resolve version for " + dependency); + } + resolve.add(dependency + .setArtifact(dependency.getArtifact().setVersion(version))); + } + else { + resolve.add(dependency); + } + } + CollectRequest collectRequest = new CollectRequest((Dependency) null, resolve, + new ArrayList(this.repositories)); + collectRequest + .setManagedDependencies(this.dependencyManagement.getManagedDependencies()); + return collectRequest; + } + + private DependencyRequest getDependencyRequest(CollectRequest collectRequest) { + DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, + DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE, + JavaScopes.RUNTIME)); + return dependencyRequest; + } + + private void addManagedDependencies(DependencyResult result) { + this.dependencyManagement.addManagedDependencies(getDependencies(result)); + } + + public static ServiceLocator getServiceLocator() { + if (AetherEngine.serviceLocator == null) { + DefaultServiceLocator locator = MavenRepositorySystemUtils + .newServiceLocator(); + locator.addService(RepositorySystem.class, DefaultRepositorySystem.class); + locator.addService(RepositoryConnectorFactory.class, + BasicRepositoryConnectorFactory.class); + locator.addService(TransporterFactory.class, HttpTransporterFactory.class); + locator.addService(TransporterFactory.class, FileTransporterFactory.class); + AetherEngine.serviceLocator = locator; + } + return AetherEngine.serviceLocator; + } + + private static List createRepositories( + List repositoryConfigurations) { + List repositories = new ArrayList( + repositoryConfigurations.size()); + for (RepositoryConfiguration repositoryConfiguration : repositoryConfigurations) { + RemoteRepository.Builder builder = new RemoteRepository.Builder( + repositoryConfiguration.getName(), "default", + repositoryConfiguration.getUri().toASCIIString()); + + if (!repositoryConfiguration.getSnapshotsEnabled()) { + builder.setSnapshotPolicy( + new RepositoryPolicy(false, RepositoryPolicy.UPDATE_POLICY_NEVER, + RepositoryPolicy.CHECKSUM_POLICY_IGNORE)); + } + repositories.add(builder.build()); + } + return repositories; + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/CompositeProxySelector.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/CompositeProxySelector.java similarity index 96% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/CompositeProxySelector.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/CompositeProxySelector.java index f34652c4fdc6..e4beb3afcc4b 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/CompositeProxySelector.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/CompositeProxySelector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; import java.util.ArrayList; import java.util.List; diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DefaultRepositorySystemSessionAutoConfiguration.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DefaultRepositorySystemSessionAutoConfiguration.java similarity index 92% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DefaultRepositorySystemSessionAutoConfiguration.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DefaultRepositorySystemSessionAutoConfiguration.java index d3fc4bf35028..d99c060c7e7a 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DefaultRepositorySystemSessionAutoConfiguration.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DefaultRepositorySystemSessionAutoConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; import java.io.File; import java.util.Arrays; @@ -29,13 +29,13 @@ import org.springframework.util.StringUtils; /** - * A {@link RepositorySystemSessionAutoConfiguration} that, in the absence of any + * A {@link RepositorySystemSessionConfiguration} that, in the absence of any * configuration, applies sensible defaults. * * @author Andy Wilkinson */ public class DefaultRepositorySystemSessionAutoConfiguration - implements RepositorySystemSessionAutoConfiguration { + implements RepositorySystemSessionConfiguration { @Override public void apply(DefaultRepositorySystemSession session, diff --git a/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DependencyManagementContext.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DependencyManagementContext.java new file mode 100644 index 000000000000..4b4e56392596 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DependencyManagementContext.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2015 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.aether; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.aether.graph.Dependency; + +/** + * Context used when resolving dependencies. + * + * @author Andy Wilkinson + * @since 1.1.0 + */ +public class DependencyManagementContext { + + private final Map managedDependencyByGroupAndArtifact = new HashMap(); + + private final List managedDependencies = new ArrayList(); + + private String getIdentifier(Dependency dependency) { + return getIdentifier(dependency.getArtifact().getGroupId(), + dependency.getArtifact().getArtifactId()); + } + + private String getIdentifier(String groupId, String artifactId) { + return groupId + ":" + artifactId; + } + + public String getManagedVersion(String groupId, String artifactId) { + Dependency dependency = getManagedDependency(groupId, artifactId); + if (dependency == null) { + dependency = this.managedDependencyByGroupAndArtifact + .get(getIdentifier(groupId, artifactId)); + } + return dependency != null ? dependency.getArtifact().getVersion() : null; + } + + public List getManagedDependencies() { + return Collections.unmodifiableList(this.managedDependencies); + } + + private Dependency getManagedDependency(String group, String artifact) { + return this.managedDependencyByGroupAndArtifact + .get(getIdentifier(group, artifact)); + } + + public void addManagedDependencies(List dependencies) { + this.managedDependencies.addAll(dependencies); + for (Dependency dependency : dependencies) { + this.managedDependencyByGroupAndArtifact.put(getIdentifier(dependency), + dependency); + } + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionFailedException.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DependencyResolutionFailedException.java similarity index 95% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionFailedException.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DependencyResolutionFailedException.java index 17a708cd4b7a..79c4030c9e3e 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DependencyResolutionFailedException.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DependencyResolutionFailedException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; /** * Thrown to indicate a failure during dependency resolution. diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DetailedProgressReporter.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DetailedProgressReporter.java similarity index 97% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DetailedProgressReporter.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DetailedProgressReporter.java index 1d6426bfc76b..e23e84d6ac9c 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/DetailedProgressReporter.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/DetailedProgressReporter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; import java.io.PrintStream; diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ProgressReporter.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/ProgressReporter.java similarity index 93% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ProgressReporter.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/ProgressReporter.java index e0283979c596..7e4f3d368c82 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ProgressReporter.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/ProgressReporter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; /** * Reports progress on a dependency resolution operation. diff --git a/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositoryConfiguration.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositoryConfiguration.java new file mode 100644 index 000000000000..5f30456343ce --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositoryConfiguration.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.aether; + +import java.net.URI; + +import org.springframework.util.ObjectUtils; + +/** + * The configuration of a repository. + * + * @author Andy Wilkinson + */ +public final class RepositoryConfiguration { + + private final String name; + + private final URI uri; + + private final boolean snapshotsEnabled; + + /** + * Creates a new {@code RepositoryConfiguration} instance. + * @param name The name of the repository + * @param uri The uri of the repository + * @param snapshotsEnabled {@code true} if the repository should enable access to + * snapshots, {@code false} otherwise + */ + public RepositoryConfiguration(String name, URI uri, boolean snapshotsEnabled) { + this.name = name; + this.uri = uri; + this.snapshotsEnabled = snapshotsEnabled; + } + + /** + * Return the name of the repository. + * @return the repository name + */ + public String getName() { + return this.name; + } + + @Override + public String toString() { + return "RepositoryConfiguration [name=" + this.name + ", uri=" + this.uri + + ", snapshotsEnabled=" + this.snapshotsEnabled + "]"; + } + + /** + * Return the URI of the repository. + * @return the repository URI + */ + public URI getUri() { + return this.uri; + } + + /** + * Return if the repository should enable access to snapshots. + * @return {@code true} if snapshot access is enabled + */ + public boolean getSnapshotsEnabled() { + return this.snapshotsEnabled; + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RepositoryConfiguration other = (RepositoryConfiguration) obj; + return ObjectUtils.nullSafeEquals(this.name, other.name); + } + +} diff --git a/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositoryConfigurationFactory.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositoryConfigurationFactory.java new file mode 100644 index 000000000000..82a9023f8bfc --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositoryConfigurationFactory.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012-2015 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.aether; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.settings.Profile; +import org.apache.maven.settings.Repository; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.Interpolator; +import org.codehaus.plexus.interpolation.PropertiesBasedValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; + +import org.springframework.boot.aether.maven.MavenSettings; +import org.springframework.boot.aether.maven.MavenSettingsReader; +import org.springframework.util.StringUtils; + +/** + * Factory used to create {@link RepositoryConfiguration}s. + * + * @author Andy Wilkinson + * @author Dave Syer + */ +public final class RepositoryConfigurationFactory { + + private static final RepositoryConfiguration MAVEN_CENTRAL = new RepositoryConfiguration( + "central", URI.create("http://repo1.maven.org/maven2/"), false); + + private static final RepositoryConfiguration SPRING_MILESTONE = new RepositoryConfiguration( + "spring-milestone", URI.create("http://repo.spring.io/milestone"), false); + + private static final RepositoryConfiguration SPRING_SNAPSHOT = new RepositoryConfiguration( + "spring-snapshot", URI.create("http://repo.spring.io/snapshot"), true); + + private RepositoryConfigurationFactory() { + } + + /** + * Create a new default repository configuration. + * @return the newly-created default repository configuration + */ + public static List createDefaultRepositoryConfiguration() { + MavenSettings mavenSettings = new MavenSettingsReader().readSettings(); + List repositoryConfiguration = new ArrayList(); + repositoryConfiguration.add(MAVEN_CENTRAL); + if (!Boolean.getBoolean("disableSpringSnapshotRepos")) { + repositoryConfiguration.add(SPRING_MILESTONE); + repositoryConfiguration.add(SPRING_SNAPSHOT); + } + addDefaultCacheAsRepository(mavenSettings.getLocalRepository(), + repositoryConfiguration); + addActiveProfileRepositories(mavenSettings.getActiveProfiles(), + repositoryConfiguration); + return repositoryConfiguration; + } + + private static void addDefaultCacheAsRepository(String localRepository, + List repositoryConfiguration) { + RepositoryConfiguration repository = new RepositoryConfiguration("local", + getLocalRepositoryDirectory(localRepository).toURI(), true); + if (!repositoryConfiguration.contains(repository)) { + repositoryConfiguration.add(0, repository); + } + } + + private static void addActiveProfileRepositories(List activeProfiles, + List configurations) { + for (Profile activeProfile : activeProfiles) { + Interpolator interpolator = new RegexBasedInterpolator(); + interpolator.addValueSource( + new PropertiesBasedValueSource(activeProfile.getProperties())); + for (Repository repository : activeProfile.getRepositories()) { + configurations.add(getRepositoryConfiguration(interpolator, repository)); + } + } + } + + private static RepositoryConfiguration getRepositoryConfiguration( + Interpolator interpolator, Repository repository) { + String name = interpolate(interpolator, repository.getId()); + String url = interpolate(interpolator, repository.getUrl()); + boolean snapshotsEnabled = false; + if (repository.getSnapshots() != null) { + snapshotsEnabled = repository.getSnapshots().isEnabled(); + } + return new RepositoryConfiguration(name, URI.create(url), snapshotsEnabled); + } + + private static String interpolate(Interpolator interpolator, String value) { + try { + return interpolator.interpolate(value); + } + catch (InterpolationException ex) { + return value; + } + } + + private static File getLocalRepositoryDirectory(String localRepository) { + if (StringUtils.hasText(localRepository)) { + return new File(localRepository); + } + return new File(getM2HomeDirectory(), "repository"); + } + + private static File getM2HomeDirectory() { + String mavenRoot = System.getProperty("maven.home"); + if (StringUtils.hasLength(mavenRoot)) { + return new File(mavenRoot); + } + return new File(System.getProperty("user.home"), ".m2"); + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/RepositorySystemSessionAutoConfiguration.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositorySystemSessionConfiguration.java similarity index 88% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/RepositorySystemSessionAutoConfiguration.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositorySystemSessionConfiguration.java index 44dc2c25c6ad..2ecc2f0c4c94 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/RepositorySystemSessionAutoConfiguration.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/RepositorySystemSessionConfiguration.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; /** * Strategy that can be used to apply some auto-configuration during the installation of - * an {@link AetherGrapeEngine}. + * an {@link AetherEngine}. * * @author Andy Wilkinson */ -public interface RepositorySystemSessionAutoConfiguration { +public interface RepositorySystemSessionConfiguration { /** * Apply the configuration. diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SummaryProgressReporter.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/SummaryProgressReporter.java similarity index 98% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SummaryProgressReporter.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/SummaryProgressReporter.java index 6ad5ca9d817f..c7a21c40f587 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SummaryProgressReporter.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/SummaryProgressReporter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; import java.io.PrintStream; import java.util.concurrent.TimeUnit; diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/maven/MavenSettings.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/MavenSettings.java similarity index 99% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/maven/MavenSettings.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/MavenSettings.java index f3a6bbc3acd0..259f59c61acc 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/maven/MavenSettings.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/MavenSettings.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.maven; +package org.springframework.boot.aether.maven; import java.io.BufferedReader; import java.io.File; diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/maven/MavenSettingsReader.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/MavenSettingsReader.java similarity index 97% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/maven/MavenSettingsReader.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/MavenSettingsReader.java index 97d0fa7196df..a98fa3f9c628 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/maven/MavenSettingsReader.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/MavenSettingsReader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.maven; +package org.springframework.boot.aether.maven; import java.io.File; import java.lang.reflect.Field; @@ -32,8 +32,6 @@ import org.sonatype.plexus.components.cipher.PlexusCipherException; import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher; -import org.springframework.boot.cli.util.Log; - /** * {@code MavenSettingsReader} reads settings from a user's Maven settings.xml file, * decrypting them if necessary using settings-security.xml. @@ -57,7 +55,7 @@ public MavenSettings readSettings() { Settings settings = loadSettings(); SettingsDecryptionResult decrypted = decryptSettings(settings); if (!decrypted.getProblems().isEmpty()) { - Log.error( + System.err.println( "Maven settings decryption failed. Some Maven repositories may be inaccessible"); // Continue - the encrypted credentials may not be used } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/SettingsXmlRepositorySystemSessionConfiguration.java similarity index 84% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java rename to spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/SettingsXmlRepositorySystemSessionConfiguration.java index b0438f716968..ec3902853c43 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/aether/maven/SettingsXmlRepositorySystemSessionConfiguration.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether.maven; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.repository.LocalRepository; -import org.springframework.boot.cli.compiler.maven.MavenSettings; -import org.springframework.boot.cli.compiler.maven.MavenSettingsReader; +import org.springframework.boot.aether.RepositorySystemSessionConfiguration; /** * Auto-configuration for a RepositorySystemSession that uses Maven's settings.xml to @@ -29,8 +28,8 @@ * * @author Andy Wilkinson */ -public class SettingsXmlRepositorySystemSessionAutoConfiguration - implements RepositorySystemSessionAutoConfiguration { +public class SettingsXmlRepositorySystemSessionConfiguration + implements RepositorySystemSessionConfiguration { @Override public void apply(DefaultRepositorySystemSession session, diff --git a/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/loader/thin/PomLoader.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/loader/thin/PomLoader.java new file mode 100644 index 000000000000..83a6a6b262b8 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/loader/thin/PomLoader.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2015 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.loader.thin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; +import org.apache.maven.model.building.DefaultModelProcessor; +import org.apache.maven.model.io.DefaultModelReader; +import org.apache.maven.model.locator.DefaultModelLocator; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.Dependency; + +import org.springframework.core.io.Resource; + +/** + * Utility class to help with reading and extracting dependencies from a physical pom. + * + * @author Dave Syer + * + */ +public class PomLoader { + + public List getDependencies(Resource pom) { + if (!pom.exists()) { + return Collections.emptyList(); + } + Model model = readModel(pom); + return convert(model.getDependencies()); + } + + public List getDependencyManagement(Resource pom) { + if (!pom.exists()) { + return Collections.emptyList(); + } + List list = new ArrayList(); + Model model = readModel(pom); + if (model.getParent() != null) { + list.add(new Dependency(getParentArtifact(model), "import")); + } + if (model.getDependencyManagement() != null) { + list.addAll(convert(model.getDependencyManagement().getDependencies())); + } + return list; + } + + private Artifact getParentArtifact(Model model) { + Parent parent = model.getParent(); + return new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "pom", + parent.getVersion()); + } + + private List convert( + List dependencies) { + List result = new ArrayList(); + for (org.apache.maven.model.Dependency dependency : dependencies) { + String scope = dependency.getScope(); + if (!"test".equals(scope) && !"provided".equals(scope)) { + result.add(new Dependency(artifact(dependency), dependency.getScope())); + } + } + return result; + } + + private Artifact artifact(org.apache.maven.model.Dependency dependency) { + return new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), + dependency.getClassifier(), dependency.getType(), + dependency.getVersion()); + } + + private static Model readModel(Resource resource) { + DefaultModelProcessor modelProcessor = new DefaultModelProcessor(); + modelProcessor.setModelLocator(new DefaultModelLocator()); + modelProcessor.setModelReader(new DefaultModelReader()); + + try { + return modelProcessor.read(resource.getInputStream(), null); + } + catch (IOException ex) { + throw new IllegalStateException("Failed to build model from effective pom", + ex); + } + } + +} diff --git a/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/loader/thin/ThinJarLauncher.java b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/loader/thin/ThinJarLauncher.java new file mode 100644 index 000000000000..4065dcf570f2 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/main/java/org/springframework/boot/loader/thin/ThinJarLauncher.java @@ -0,0 +1,294 @@ +/* + * Copyright 2012-2016 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.loader.thin; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.jar.JarFile; + +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.Dependency; + +import org.springframework.boot.aether.AetherEngine; +import org.springframework.boot.aether.DependencyManagementContext; +import org.springframework.boot.aether.RepositoryConfigurationFactory; +import org.springframework.boot.loader.ExecutableArchiveLauncher; +import org.springframework.boot.loader.LaunchedURLClassLoader; +import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.archive.Archive.Entry; +import org.springframework.boot.loader.archive.ExplodedArchive; +import org.springframework.boot.loader.archive.JarFileArchive; +import org.springframework.boot.loader.util.MainClassFinder; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.core.io.support.ResourcePatternUtils; + +/** + * Launcher that downloads the dependencies for the app before it starts. + * + * @author Dave Syer + */ +public class ThinJarLauncher extends ExecutableArchiveLauncher { + + private static final String DEFAULT_BOM = "org.springframework.boot:spring-boot-dependencies"; + + private static final String DEFAULT_VERSION = "1.5.0.BUILD-SNAPSHOT"; + + private PomLoader pomLoader = new PomLoader(); + + public static void main(String[] args) throws Exception { + new ThinJarLauncher().launch(args); + } + + public ThinJarLauncher() throws Exception { + super(computeArchive()); + } + + @Override + protected void launch(String[] args) throws Exception { + String root = System.getProperty("main.root"); + String debug = System.getProperty("debug"); + if (root != null) { + // There is a grape root that is used by the aether engine internally + System.setProperty("grape.root", root); + } + if (System.getProperty("main.dryrun") != null) { + getClassPathArchives(); + if (debug != null) { + System.out.println( + "Downloaded dependencies" + (root == null ? "" : " to " + root)); + } + return; + } + super.launch(args); + } + + protected ClassLoader createClassLoader(URL[] urls) throws Exception { + return new LaunchedURLClassLoader(urls, getClass().getClassLoader().getParent()); + } + + @Override + protected String getMainClass() throws Exception { + if (System.getProperty("main.class") != null) { + return System.getProperty("main.class"); + } + try { + return super.getMainClass(); + } + catch (IllegalStateException e) { + File root = new File(getArchive().getUrl().toURI()); + if (getArchive() instanceof ExplodedArchive) { + return MainClassFinder.findSingleMainClass(root); + } + else { + return MainClassFinder.findSingleMainClass(new JarFile(root), "/"); + } + } + } + + private static Archive computeArchive() throws Exception { + File file = new File(findArchive()); + if (file.isDirectory()) { + return new ExplodedArchive(file); + } + return new JarFileArchive(file); + } + + private static URI findArchive() throws Exception { + String path = System.getProperty("main.archive"); + URI archive = path == null ? null : new URI(path); + File dir = new File("target/classes"); + if (archive == null && dir.exists()) { + archive = dir.toURI(); + } + if (archive == null) { + dir = new File("build/classes"); + if (dir.exists()) { + archive = dir.toURI(); + } + } + if (archive == null) { + dir = new File("."); + archive = dir.toURI(); + } + return archive; + } + + @Override + protected List getClassPathArchives() throws Exception { + Collection dependencies = new LinkedHashSet(); + Collection boms = new LinkedHashSet(); + // TODO: Maybe use something that conserves order? + Properties libs = loadLibraryProperties(); + for (String key : libs.stringPropertyNames()) { + String lib = libs.getProperty(key); + if (key.startsWith("dependencies")) { + dependencies.add(dependency(lib)); + } + if (key.startsWith("boms")) { + boms.add(dependency(lib)); + } + } + + boms.addAll(getPomDependencyManagement()); + dependencies.addAll(getPomDependencies()); + + if (boms.isEmpty()) { + boms.add(dependency(getDefaultBom())); + } + + List archives = archives(resolve(new ArrayList(boms), + new ArrayList(dependencies))); + if (!archives.isEmpty()) { + archives.set(0, getArchive()); + } + else { + archives.add(getArchive()); + } + return archives; + } + + private String getDefaultBom() { + return DEFAULT_BOM + ":" + getVersion(); + } + + private String getVersion() { + Package pkg = ThinJarLauncher.class.getPackage(); + return (pkg != null ? pkg.getImplementationVersion() : DEFAULT_VERSION); + } + + private Properties loadLibraryProperties() throws IOException, MalformedURLException { + UrlResource resource = new UrlResource( + getArchive().getUrl() + "META-INF/lib.properties"); + Properties props = resource.exists() + ? PropertiesLoaderUtils.loadProperties(resource) : new Properties(); + FileSystemResource local = new FileSystemResource("lib.properties"); + if (local.exists()) { + PropertiesLoaderUtils.fillProperties(props, local); + } + return props; + } + + private List archives(List files) throws IOException { + List archives = new ArrayList(); + for (File file : files) { + archives.add(new JarFileArchive(file, file.toURI().toURL())); + } + return archives; + } + + private Dependency dependency(String coordinates) { + String[] parts = coordinates.split(":"); + if (parts.length < 2) { + throw new IllegalArgumentException( + "Co-ordinates should contain group:artifact[:extension][:classifier][:version]"); + } + String extension = "jar"; + String classifier; + String version; + String artifactId; + String groupId; + if (parts.length > 4) { + extension = parts[2]; + classifier = parts[3]; + version = parts[4]; + } + else if (parts.length > 3) { + if (parts[3].contains(".")) { + version = parts[3]; + classifier = parts[2]; + } + else { + extension = parts[2]; + classifier = parts[3]; + version = null; + } + + } + else if (parts.length > 2) { + if (parts[2].contains(".")) { + version = parts[2]; + classifier = null; + } + else { + classifier = parts[2]; + version = null; + } + } + else { + classifier = null; + version = null; + } + groupId = parts[0]; + artifactId = parts[1]; + return new Dependency( + new DefaultArtifact(groupId, artifactId, classifier, extension, version), + "compile"); + } + + private List resolve(List boms, List dependencies) + throws Exception { + AetherEngine engine = AetherEngine.create( + RepositoryConfigurationFactory.createDefaultRepositoryConfiguration(), + new DependencyManagementContext()); + engine.addDependencyManagementBoms(boms); + List files = engine.resolve(dependencies); + return files; + } + + private List getPomDependencies() throws Exception { + return this.pomLoader.getDependencies(getPom()); + } + + private List getPomDependencyManagement() throws Exception { + return this.pomLoader.getDependencyManagement(getPom()); + } + + private Resource getPom() throws Exception { + Resource pom = new UrlResource(getArchive().getUrl() + "pom.xml"); + if (!pom.exists()) { + for (Resource resource : ResourcePatternUtils + .getResourcePatternResolver(new DefaultResourceLoader()) + .getResources(getArchive().getUrl() + "META-INF/maven/**/pom.xml")) { + if (resource.exists()) { + return resource; + } + } + } + if (!pom.exists()) { + pom = new FileSystemResource("./pom.xml"); + } + return pom; + } + + @Override + protected boolean isNestedArchive(Entry entry) { + return false; + } + +} diff --git a/spring-boot-tools/spring-boot-aether/src/main/resources/META-INF/services/org.springframework.boot.aether.RepositorySystemSessionConfiguration b/spring-boot-tools/spring-boot-aether/src/main/resources/META-INF/services/org.springframework.boot.aether.RepositorySystemSessionConfiguration new file mode 100644 index 000000000000..17db239ede0b --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/main/resources/META-INF/services/org.springframework.boot.aether.RepositorySystemSessionConfiguration @@ -0,0 +1 @@ +org.springframework.boot.aether.maven.SettingsXmlRepositorySystemSessionConfiguration \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/AetherEngineTests.java b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/AetherEngineTests.java new file mode 100644 index 000000000000..81fced9ff6f6 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/AetherEngineTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2016 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.aether; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.repository.Authentication; +import org.eclipse.aether.repository.RemoteRepository; +import org.junit.Test; + +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AetherEngine}. + * + * @author Andy Wilkinson + */ +public class AetherEngineTests { + + private AetherEngine createEngine(RepositoryConfiguration... additionalRepositories) { + List repositoryConfigurations = new ArrayList(); + repositoryConfigurations.add(new RepositoryConfiguration("central", + URI.create("http://repo1.maven.org/maven2"), false)); + repositoryConfigurations.addAll(Arrays.asList(additionalRepositories)); + return AetherEngine.create(repositoryConfigurations, + new DependencyManagementContext()); + } + + @Test + public void proxySelector() { + doWithCustomUserHome(new Runnable() { + + @Override + public void run() { + AetherEngine grapeEngine = createEngine(); + + DefaultRepositorySystemSession session = (DefaultRepositorySystemSession) ReflectionTestUtils + .getField(grapeEngine, "session"); + + assertThat(session.getProxySelector() instanceof CompositeProxySelector) + .isTrue(); + } + + }); + } + + @Test + public void repositoryMirrors() { + doWithCustomUserHome(new Runnable() { + + @SuppressWarnings("unchecked") + @Override + public void run() { + AetherEngine grapeEngine = createEngine(); + + List repositories = (List) ReflectionTestUtils + .getField(grapeEngine, "repositories"); + assertThat(repositories).hasSize(1); + assertThat(repositories.get(0).getId()).isEqualTo("central-mirror"); + } + }); + } + + @Test + public void repositoryAuthentication() { + doWithCustomUserHome(new Runnable() { + + @SuppressWarnings("unchecked") + @Override + public void run() { + AetherEngine grapeEngine = createEngine(); + + List repositories = (List) ReflectionTestUtils + .getField(grapeEngine, "repositories"); + assertThat(repositories).hasSize(1); + Authentication authentication = repositories.get(0).getAuthentication(); + assertThat(authentication).isNotNull(); + } + }); + } + + private void doWithCustomUserHome(Runnable action) { + doWithSystemProperty("user.home", + new File("src/test/resources").getAbsolutePath(), action); + } + + private void doWithSystemProperty(String key, String value, Runnable action) { + String previousValue = setOrClearSystemProperty(key, value); + try { + action.run(); + } + finally { + setOrClearSystemProperty(key, previousValue); + } + } + + private String setOrClearSystemProperty(String key, String value) { + if (value != null) { + return System.setProperty(key, value); + } + return System.clearProperty(key); + } +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/DetailedProgressReporterTests.java b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/DetailedProgressReporterTests.java similarity index 98% rename from spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/DetailedProgressReporterTests.java rename to spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/DetailedProgressReporterTests.java index 9f9d3c0c25d1..b3d53e94c1e4 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/DetailedProgressReporterTests.java +++ b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/DetailedProgressReporterTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether; import java.io.ByteArrayOutputStream; import java.io.PrintStream; diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/maven/SettingsXmlRepositorySystemSessionConfigurationTests.java similarity index 90% rename from spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java rename to spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/maven/SettingsXmlRepositorySystemSessionConfigurationTests.java index 9d768e5154dd..8687256d7371 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java +++ b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/maven/SettingsXmlRepositorySystemSessionConfigurationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.cli.compiler.grape; +package org.springframework.boot.aether.maven; import java.io.File; @@ -38,7 +38,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import org.springframework.boot.cli.testutil.SystemProperties; +import org.springframework.boot.aether.testutil.SystemProperties; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -46,11 +46,11 @@ import static org.mockito.Matchers.eq; /** - * Tests for {@link SettingsXmlRepositorySystemSessionAutoConfiguration}. + * Tests for {@link SettingsXmlRepositorySystemSessionConfiguration}. * * @author Andy Wilkinson */ -public class SettingsXmlRepositorySystemSessionAutoConfigurationTests { +public class SettingsXmlRepositorySystemSessionConfigurationTests { @Rule public ExpectedException thrown = ExpectedException.none(); @@ -94,8 +94,8 @@ public LocalRepositoryManager answer( SystemProperties.doWithSystemProperties(new Runnable() { @Override public void run() { - new SettingsXmlRepositorySystemSessionAutoConfiguration().apply(session, - SettingsXmlRepositorySystemSessionAutoConfigurationTests.this.repositorySystem); + new SettingsXmlRepositorySystemSessionConfiguration().apply(session, + SettingsXmlRepositorySystemSessionConfigurationTests.this.repositorySystem); } }, "user.home:src/test/resources/maven-settings/property-interpolation", "foo:bar"); @@ -110,8 +110,8 @@ private void assertSessionCustomization(String userHome) { SystemProperties.doWithSystemProperties(new Runnable() { @Override public void run() { - new SettingsXmlRepositorySystemSessionAutoConfiguration().apply(session, - SettingsXmlRepositorySystemSessionAutoConfigurationTests.this.repositorySystem); + new SettingsXmlRepositorySystemSessionConfiguration().apply(session, + SettingsXmlRepositorySystemSessionConfigurationTests.this.repositorySystem); } }, "user.home:" + userHome); diff --git a/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/testutil/SystemProperties.java b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/testutil/SystemProperties.java new file mode 100644 index 000000000000..76ef1819c8cc --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/java/org/springframework/boot/aether/testutil/SystemProperties.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2016 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.aether.testutil; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Utilities for working with System properties in unit tests + * + * @author Andy Wilkinson + */ +public final class SystemProperties { + + private SystemProperties() { + } + + /** + * Performs the given {@code action} with the given system properties set. System + * properties are restored to their previous values once the action has run. + * + * @param action The action to perform + * @param systemPropertyPairs The system properties, each in the form + * {@code key:value} + */ + public static void doWithSystemProperties(Runnable action, + String... systemPropertyPairs) { + Map originalValues = new HashMap(); + for (String pair : systemPropertyPairs) { + String[] components = pair.split(":"); + String key = components[0]; + String value = components[1]; + originalValues.put(key, System.setProperty(key, value)); + } + try { + action.run(); + } + finally { + for (Entry entry : originalValues.entrySet()) { + if (entry.getValue() == null) { + System.clearProperty(entry.getKey()); + } + else { + System.setProperty(entry.getKey(), entry.getValue()); + } + } + } + } +} diff --git a/spring-boot-tools/spring-boot-aether/src/test/resources/.m2/settings.xml b/spring-boot-tools/spring-boot-aether/src/test/resources/.m2/settings.xml new file mode 100644 index 000000000000..86a68acfac13 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/resources/.m2/settings.xml @@ -0,0 +1,33 @@ + + + + + central-mirror + http://central-mirror.example.com/maven2 + central + + + + + + central-mirror + user + password + + + + + + true + http + proxy.example.com + 3128 + user + password + + + + \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/active-profile-repositories/.m2/settings.xml b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/active-profile-repositories/.m2/settings.xml new file mode 100644 index 000000000000..8cc312378d8e --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/active-profile-repositories/.m2/settings.xml @@ -0,0 +1,97 @@ + + + + + my-mirror + http://maven.example.com/mirror + my-server + + + + + + my-server + tester + secret + + + + + + my-proxy + true + http + proxy.example.com + 8080 + proxyuser + somepassword + + + + + + active-by-default + + true + + + + active-by-default + maven.example.com/activeByDefault + + + + + active-by-property + + + foo + bar + + + + + active-by-property + maven.example.com/activeByProperty + + + + + interpolation-profile + + + interpolate + true + + + + maven.example.com + ${repo.base}/content + + + + interpolate-releases + ${repo.content}/releases + + true + never + + + false + + + + interpolate-snapshots + ${repo.content}/snapshots + + false + + + true + + + + + + + diff --git a/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/basic/.m2/settings.xml b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/basic/.m2/settings.xml new file mode 100644 index 000000000000..5439ce8cfb85 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/basic/.m2/settings.xml @@ -0,0 +1,48 @@ + + + + + my-mirror + http://maven.example.com/mirror + my-server + + + + + + my-server + tester + secret + + + + + + my-proxy + true + http + proxy.example.com + 8080 + proxyuser + somepassword + + + + + + test-profile + + + ${user.home}/.m2/some_file + + + + + example-repository + http://repo.example.com + + + + + + diff --git a/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/encrypted/.m2/settings-security.xml b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/encrypted/.m2/settings-security.xml new file mode 100644 index 000000000000..7b6597c44e94 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/encrypted/.m2/settings-security.xml @@ -0,0 +1,3 @@ + + {oAyWuFO63U8HHgiplpqtgXih0/pwcRA0d+uA+Z7TBEk=} + \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/encrypted/.m2/settings.xml b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/encrypted/.m2/settings.xml new file mode 100644 index 000000000000..b8701c783a1f --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/encrypted/.m2/settings.xml @@ -0,0 +1,31 @@ + + + + + my-mirror + http://maven.example.com/mirror + my-server + + + + + + my-server + tester + {Ur5BpeQGlYUHhXsHahO/HbMBcPSFSUtN5gbWuFFPYGw=} + + + + + + my-proxy + true + http + proxy.example.com + 8080 + proxyuser + {3iRQQyaIUgQHwH8uzTvr9/52pZAjLOTWz/SlWDB7CM4=} + + + + \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/property-interpolation/.m2/settings.xml b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/property-interpolation/.m2/settings.xml new file mode 100644 index 000000000000..873642e6dd86 --- /dev/null +++ b/spring-boot-tools/spring-boot-aether/src/test/resources/maven-settings/property-interpolation/.m2/settings.xml @@ -0,0 +1,8 @@ + + + ${foo}/repository + + diff --git a/spring-boot-tools/spring-boot-antlib/pom.xml b/spring-boot-tools/spring-boot-antlib/pom.xml index 1b28cf68c5ac..e2674972a7c8 100644 --- a/spring-boot-tools/spring-boot-antlib/pom.xml +++ b/spring-boot-tools/spring-boot-antlib/pom.xml @@ -21,12 +21,17 @@ org.springframework.boot - spring-boot-loader - provided + spring-boot-loader-tools + compile + + + org.springframework + spring-core + compile org.springframework.boot - spring-boot-loader-tools + spring-boot-loader compile @@ -45,6 +50,7 @@ org.springframework.boot:spring-boot-loader-tools + org.springframework.boot:spring-boot-loader org.springframework:spring-core diff --git a/spring-boot-tools/spring-boot-antlib/src/main/java/org/springframework/boot/ant/FindMainClass.java b/spring-boot-tools/spring-boot-antlib/src/main/java/org/springframework/boot/ant/FindMainClass.java index 94f2fe0a8479..ef38afc6e820 100644 --- a/spring-boot-tools/spring-boot-antlib/src/main/java/org/springframework/boot/ant/FindMainClass.java +++ b/spring-boot-tools/spring-boot-antlib/src/main/java/org/springframework/boot/ant/FindMainClass.java @@ -24,7 +24,7 @@ import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; -import org.springframework.boot.loader.tools.MainClassFinder; +import org.springframework.boot.loader.util.MainClassFinder; import org.springframework.util.StringUtils; /** diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java index 987336250ccb..067c4483e08d 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java @@ -291,6 +291,8 @@ enum LayoutType { MODULE(new Layouts.Module()), + THIN(new Layouts.Thin()), + NONE(new Layouts.None()); Layout layout; diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/FindMainClassTask.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/FindMainClassTask.java index a2a99428ce57..320db7b74d7c 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/FindMainClassTask.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/FindMainClassTask.java @@ -29,7 +29,7 @@ import org.gradle.api.tasks.TaskAction; import org.springframework.boot.gradle.SpringBootPluginExtension; -import org.springframework.boot.loader.tools.MainClassFinder; +import org.springframework.boot.loader.util.MainClassFinder; /** * Task to find and set the 'mainClassName' convention when it's missing by searching the diff --git a/spring-boot-tools/spring-boot-loader-tools/pom.xml b/spring-boot-tools/spring-boot-loader-tools/pom.xml index b84220d5db94..986271a3fd7a 100644 --- a/spring-boot-tools/spring-boot-loader-tools/pom.xml +++ b/spring-boot-tools/spring-boot-loader-tools/pom.xml @@ -23,12 +23,19 @@ org.springframework spring-core - + + org.springframework.boot + spring-boot-thin-wrapper + org.springframework.boot spring-boot-loader - provided + + org.springframework.boot + spring-boot-aether + + ch.qos.logback logback-classic @@ -66,6 +73,12 @@ ${project.version} spring-boot-loader.jar + + org.springframework.boot + spring-boot-thin-wrapper + ${project.version} + spring-boot-thin-wrapper.jar + ${basedir}/target/generated-resources/loader/META-INF/loader false diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java index d5a05dd0946f..a2fba9476ba7 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java @@ -55,6 +55,8 @@ public class JarWriter { private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar"; + private static final String NESTED_WRAPPER_JAR = "META-INF/loader/spring-boot-thin-wrapper.jar"; + private static final int BUFFER_SIZE = 32 * 1024; private final JarOutputStream jarOutput; @@ -208,7 +210,18 @@ private long getNestedLibraryTime(File file) { * @throws IOException if the classes cannot be written */ public void writeLoaderClasses() throws IOException { - URL loaderJar = getClass().getClassLoader().getResource(NESTED_LOADER_JAR); + writeLoaderClasses(NESTED_LOADER_JAR); + } + + /** + * Write the required spring-boot-loader classes to the JAR. + * + * @param launcherClassName the class that is going to be the main class + * @throws IOException if the classes cannot be written + */ + public void writeLoaderClasses(String launcherClassName) throws IOException { + String loaderJarLocation = launcherClassName.contains("Thin") ? NESTED_WRAPPER_JAR : NESTED_LOADER_JAR; + URL loaderJar = getClass().getClassLoader().getResource(loaderJarLocation); JarInputStream inputStream = new JarInputStream( new BufferedInputStream(loaderJar.openStream())); JarEntry entry; diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java index 3cebdc7a05c5..56f1624e9ece 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java @@ -118,6 +118,33 @@ public boolean isExecutable() { } + /** + * Thin executable layout. + */ + public static class Thin implements Layout { + + @Override + public String getLauncherClassName() { + return "org.springframework.boot.loader.wrapper.ThinJarWrapper"; + } + + @Override + public boolean isExecutable() { + return true; + } + + @Override + public String getLibraryDestination(String libraryName, LibraryScope scope) { + return null; + } + + @Override + public String getClassesLocation() { + return ""; + } + + } + /** * Executable WAR layout. */ diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java index d9d09426368e..6c7e0afc1f32 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java @@ -46,7 +46,10 @@ * search. * * @author Phillip Webb + * + * @deprecated in favour of MainClassFinder in spring-boot-loader */ +@Deprecated public abstract class MainClassFinder { private static final String DOT_CLASS = ".class"; diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index c25129844bbb..6d15d135bcbb 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -29,6 +29,7 @@ import java.util.jar.Manifest; import org.springframework.boot.loader.tools.JarWriter.EntryTransformer; +import org.springframework.boot.loader.util.MainClassFinder; import org.springframework.lang.UsesJava8; /** @@ -217,7 +218,7 @@ public void library(Library library) throws IOException { } writeNestedLibraries(standardLibraries, seen, writer); if (this.layout.isExecutable()) { - writer.writeLoaderClasses(); + writer.writeLoaderClasses(this.layout.getLauncherClassName()); } } finally { diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java index 84e098c7bd15..ebb05ad2b4ba 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java @@ -26,9 +26,10 @@ import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.springframework.boot.loader.tools.MainClassFinder.ClassNameCallback; import org.springframework.boot.loader.tools.sample.ClassWithMainMethod; import org.springframework.boot.loader.tools.sample.ClassWithoutMainMethod; +import org.springframework.boot.loader.util.MainClassFinder; +import org.springframework.boot.loader.util.MainClassFinder.ClassNameCallback; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-boot-tools/spring-boot-loader/pom.xml b/spring-boot-tools/spring-boot-loader/pom.xml index eca0b8db15b5..3a406b860ed7 100644 --- a/spring-boot-tools/spring-boot-loader/pom.xml +++ b/spring-boot-tools/spring-boot-loader/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.springframework.boot @@ -25,16 +26,6 @@ spring-core true - - org.slf4j - jcl-over-slf4j - test - - - ch.qos.logback - logback-classic - test - org.springframework spring-webmvc diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml index ac5eaa5184d0..3c17f76c4751 100644 --- a/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml @@ -39,6 +39,7 @@ jar + /*/MANIFEST.MF,/*/manifest.mf ${project.build.directory}/assembly diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/MainClassFinder.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/MainClassFinder.java new file mode 100644 index 000000000000..37ee3dc85ca0 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/MainClassFinder.java @@ -0,0 +1,356 @@ +/* + * Copyright 2012-2015 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.loader.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.Type; + +/** + * Finds any class with a {@code public static main} method by performing a breadth first + * search. + * + * @author Phillip Webb + */ +public abstract class MainClassFinder { + + private static final String DOT_CLASS = ".class"; + + private static final Type STRING_ARRAY_TYPE = Type.getType(String[].class); + + private static final Type MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE, + STRING_ARRAY_TYPE); + + private static final String MAIN_METHOD_NAME = "main"; + + private static final FileFilter CLASS_FILE_FILTER = new FileFilter() { + @Override + public boolean accept(File file) { + return (file.isFile() && file.getName().endsWith(DOT_CLASS)); + } + }; + + private static final FileFilter PACKAGE_FOLDER_FILTER = new FileFilter() { + @Override + public boolean accept(File file) { + return file.isDirectory() && !file.getName().startsWith("."); + } + }; + + /** + * Find the main class from a given folder. + * @param rootFolder the root folder to search + * @return the main class or {@code null} + * @throws IOException if the folder cannot be read + */ + public static String findMainClass(File rootFolder) throws IOException { + return doWithMainClasses(rootFolder, new ClassNameCallback() { + @Override + public String doWith(String className) { + return className; + } + }); + } + + /** + * Find a single main class from a given folder. + * @param rootFolder the root folder to search + * @return the main class or {@code null} + * @throws IOException if the folder cannot be read + */ + public static String findSingleMainClass(File rootFolder) throws IOException { + MainClassesCallback callback = new MainClassesCallback(); + MainClassFinder.doWithMainClasses(rootFolder, callback); + return callback.getMainClass(); + } + + /** + * Perform the given callback operation on all main classes from the given root + * folder. + * @param the result type + * @param rootFolder the root folder + * @param callback the callback + * @return the first callback result or {@code null} + * @throws IOException in case of I/O errors + */ + public static T doWithMainClasses(File rootFolder, ClassNameCallback callback) + throws IOException { + if (!rootFolder.exists()) { + return null; // nothing to do + } + if (!rootFolder.isDirectory()) { + throw new IllegalArgumentException( + "Invalid root folder '" + rootFolder + "'"); + } + String prefix = rootFolder.getAbsolutePath() + "/"; + Deque stack = new ArrayDeque(); + stack.push(rootFolder); + while (!stack.isEmpty()) { + File file = stack.pop(); + if (file.isFile()) { + InputStream inputStream = new FileInputStream(file); + try { + if (isMainClass(inputStream)) { + String className = convertToClassName(file.getAbsolutePath(), + prefix); + T result = callback.doWith(className); + if (result != null) { + return result; + } + } + } + finally { + inputStream.close(); + } + } + if (file.isDirectory()) { + pushAllSorted(stack, file.listFiles(PACKAGE_FOLDER_FILTER)); + pushAllSorted(stack, file.listFiles(CLASS_FILE_FILTER)); + } + } + return null; + } + + private static void pushAllSorted(Deque stack, File[] files) { + Arrays.sort(files, new Comparator() { + @Override + public int compare(File o1, File o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + for (File file : files) { + stack.push(file); + } + } + + /** + * Find the main class in a given jar file. + * @param jarFile the jar file to search + * @param classesLocation the location within the jar containing classes + * @return the main class or {@code null} + * @throws IOException if the jar file cannot be read + */ + public static String findMainClass(JarFile jarFile, String classesLocation) + throws IOException { + return doWithMainClasses(jarFile, classesLocation, + new ClassNameCallback() { + @Override + public String doWith(String className) { + return className; + } + }); + } + + /** + * Find a single main class in a given jar file. + * @param jarFile the jar file to search + * @param classesLocation the location within the jar containing classes + * @return the main class or {@code null} + * @throws IOException if the jar file cannot be read + */ + public static String findSingleMainClass(JarFile jarFile, String classesLocation) + throws IOException { + MainClassesCallback callback = new MainClassesCallback(); + MainClassFinder.doWithMainClasses(jarFile, classesLocation, callback); + return callback.getMainClass(); + } + + /** + * Perform the given callback operation on all main classes from the given jar. + * @param the result type + * @param jarFile the jar file to search + * @param classesLocation the location within the jar containing classes + * @param callback the callback + * @return the first callback result or {@code null} + * @throws IOException in case of I/O errors + */ + public static T doWithMainClasses(JarFile jarFile, String classesLocation, + ClassNameCallback callback) throws IOException { + List classEntries = getClassEntries(jarFile, classesLocation); + Collections.sort(classEntries, new ClassEntryComparator()); + for (JarEntry entry : classEntries) { + InputStream inputStream = new BufferedInputStream( + jarFile.getInputStream(entry)); + try { + if (isMainClass(inputStream)) { + String className = convertToClassName(entry.getName(), + classesLocation); + T result = callback.doWith(className); + if (result != null) { + return result; + } + } + } + finally { + inputStream.close(); + } + } + return null; + } + + private static String convertToClassName(String name, String prefix) { + name = name.replace("/", "."); + name = name.replace('\\', '.'); + name = name.substring(0, name.length() - DOT_CLASS.length()); + if (prefix != null) { + name = name.substring(prefix.length()); + } + return name; + } + + private static List getClassEntries(JarFile source, + String classesLocation) { + classesLocation = (classesLocation != null ? classesLocation : ""); + Enumeration sourceEntries = source.entries(); + List classEntries = new ArrayList(); + while (sourceEntries.hasMoreElements()) { + JarEntry entry = sourceEntries.nextElement(); + if (entry.getName().startsWith(classesLocation) + && entry.getName().endsWith(DOT_CLASS)) { + classEntries.add(entry); + } + } + return classEntries; + } + + private static boolean isMainClass(InputStream inputStream) { + try { + ClassReader classReader = new ClassReader(inputStream); + MainMethodFinder mainMethodFinder = new MainMethodFinder(); + classReader.accept(mainMethodFinder, ClassReader.SKIP_CODE); + return mainMethodFinder.isFound(); + } + catch (IOException ex) { + return false; + } + } + + private static class ClassEntryComparator implements Comparator { + + @Override + public int compare(JarEntry o1, JarEntry o2) { + Integer d1 = getDepth(o1); + Integer d2 = getDepth(o2); + int depthCompare = d1.compareTo(d2); + if (depthCompare != 0) { + return depthCompare; + } + return o1.getName().compareTo(o2.getName()); + } + + private int getDepth(JarEntry entry) { + return entry.getName().split("/").length; + } + + } + + private static class MainMethodFinder extends ClassVisitor { + + private boolean found; + + MainMethodFinder() { + super(Opcodes.ASM4); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + if (isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC) + && MAIN_METHOD_NAME.equals(name) + && MAIN_METHOD_TYPE.getDescriptor().equals(desc)) { + this.found = true; + } + return null; + } + + private boolean isAccess(int access, int... requiredOpsCodes) { + for (int requiredOpsCode : requiredOpsCodes) { + if ((access & requiredOpsCode) == 0) { + return false; + } + } + return true; + } + + public boolean isFound() { + return this.found; + } + + } + + /** + * Callback interface used to receive class names. + * @param the result type + */ + public interface ClassNameCallback { + + /** + * Handle the specified class name. + * @param className the class name + * @return a non-null value if processing should end or {@code null} to continue + */ + T doWith(String className); + + } + + /** + * Find a single main class, throwing an {@link IllegalStateException} if multiple + * candidates exist. + */ + private static class MainClassesCallback implements ClassNameCallback { + + private final Set classNames = new LinkedHashSet(); + + @Override + public Object doWith(String className) { + this.classNames.add(className); + return null; + } + + public String getMainClass() { + if (this.classNames.size() > 1) { + throw new IllegalStateException( + "Unable to find a single main class from the following candidates " + + this.classNames); + } + return this.classNames.isEmpty() ? null : this.classNames.iterator().next(); + } + + } + +} diff --git a/spring-boot-tools/spring-boot-maven-plugin/pom.xml b/spring-boot-tools/spring-boot-maven-plugin/pom.xml index 2a0647b22646..b664c231d48d 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/pom.xml +++ b/spring-boot-tools/spring-boot-maven-plugin/pom.xml @@ -124,6 +124,14 @@ org.springframework.boot spring-boot-loader-tools + + org.springframework.boot + spring-boot-loader + + + org.springframework + spring-core + org.apache.maven maven-archiver diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java index b22b1b7e5703..7c484dbb86b9 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java @@ -38,7 +38,7 @@ import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts; import org.springframework.boot.loader.tools.FileUtils; -import org.springframework.boot.loader.tools.MainClassFinder; +import org.springframework.boot.loader.util.MainClassFinder; /** * Base class to run a spring application. diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java index 309eb72db37d..9b0635e89b3d 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java @@ -126,7 +126,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { /** * The type of archive (which corresponds to how the dependencies are laid out inside - * it). Possible values are JAR, WAR, ZIP, DIR, NONE. Defaults to a guess based on the + * it). Possible values are JAR, THIN, WAR, ZIP, DIR, NONE. Defaults to a guess based on the * archive type. * @since 1.0 */ @@ -197,7 +197,7 @@ private void repackage() throws MojoExecutionException { File source = this.project.getArtifact().getFile(); File target = getTargetFile(); Repackager repackager = getRepackager(source); - Set artifacts = filterDependencies(this.project.getArtifacts(), + Set artifacts = filterDependencies(getLibraries(), getFilters(getAdditionalFilters())); Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); @@ -211,6 +211,10 @@ private void repackage() throws MojoExecutionException { updateArtifact(source, target, repackager.getBackupFile()); } + private Set getLibraries() { + return this.project.getArtifacts(); + } + private File getTargetFile() { String classifier = (this.classifier == null ? "" : this.classifier.trim()); if (classifier.length() > 0 && !classifier.startsWith("-")) { @@ -342,7 +346,12 @@ public enum LayoutType { /** * No Layout. */ - NONE(new Layouts.None()); + NONE(new Layouts.None()), + + /** + * Thin Layout. + */ + THIN(new Layouts.Thin()); private final Layout layout; diff --git a/spring-boot-tools/spring-boot-thin-wrapper/pom.xml b/spring-boot-tools/spring-boot-thin-wrapper/pom.xml new file mode 100644 index 000000000000..6223f9f68f4d --- /dev/null +++ b/spring-boot-tools/spring-boot-thin-wrapper/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-tools + 1.5.0.BUILD-SNAPSHOT + + spring-boot-thin-wrapper + Spring Boot Thin Wrapper + Spring Boot Loader + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + + ${basedir}/.. + + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot.loader.wrapper.ThinJarWrapper + + + + + + + diff --git a/spring-boot-tools/spring-boot-thin-wrapper/src/main/java/org/springframework/boot/loader/wrapper/ThinJarWrapper.java b/spring-boot-tools/spring-boot-thin-wrapper/src/main/java/org/springframework/boot/loader/wrapper/ThinJarWrapper.java new file mode 100644 index 000000000000..a40ae32f4066 --- /dev/null +++ b/spring-boot-tools/spring-boot-thin-wrapper/src/main/java/org/springframework/boot/loader/wrapper/ThinJarWrapper.java @@ -0,0 +1,222 @@ +/* + * Copyright 2012-2015 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.loader.wrapper; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Very thin main class that downloads another library to be the real launcher. + * + * @author Dave Syer + * + */ +public class ThinJarWrapper { + + /** + * System property key for the main library where the launcher class is located. + */ + public static final String MAIN_LIBRARY = "main.library"; + + /** + * System property key used to store the local file system location of the main + * archive (the one that this class is found in). + */ + public static final String MAIN_ARCHIVE = "main.archive"; + + /** + * System property key for remote location of main archive (the one that this class is + * found in). + */ + public static final String MAIN_REPO = "main.repo"; + + /** + * System property key used to override the launcher main class if necessary. Defaults + * to ThinJarLauncher. + */ + public static final String MAIN_LAUNCHER = "main.launcher"; + + private static final String DEFAULT_LAUNCHER_CLASS = "org.springframework.boot.loader.thin.ThinJarLauncher"; + + private static final String DEFAULT_LIBRARY = "org.springframework.boot:spring-boot-aether:exec:1.5.0.BUILD-SNAPSHOT"; + + private Library library; + + public static void main(String[] args) throws Exception { + Class launcher = ThinJarWrapper.class; + System.setProperty(MAIN_ARCHIVE, launcher.getProtectionDomain().getCodeSource() + .getLocation().toURI().toString()); + new ThinJarWrapper().launch(args); + } + + public ThinJarWrapper() { + this.library = library(); + } + + private Library library() { + String coordinates = System.getProperty(MAIN_LIBRARY); + return new Library(coordinates == null ? DEFAULT_LIBRARY : coordinates); + } + + private void launch(String... args) throws Exception { + ClassLoader classLoader = getClassLoader(); + Class launcher = classLoader.loadClass(launcherClass()); + findMainMethod(launcher).invoke(null, new Object[] { args }); + } + + private String launcherClass() { + String launcher = System.getProperty(MAIN_LAUNCHER); + return launcher == null ? DEFAULT_LAUNCHER_CLASS : launcher; + } + + private Method findMainMethod(Class launcher) throws NoSuchMethodException { + return launcher.getMethod("main", String[].class); + } + + private ClassLoader getClassLoader() throws Exception { + URL[] urls = getUrls(); + URLClassLoader classLoader = new URLClassLoader(urls, + ThinJarWrapper.class.getClassLoader().getParent()); + Thread.currentThread().setContextClassLoader(classLoader); + return classLoader; + } + + private URL[] getUrls() throws Exception { + this.library.download(mavenLocal()); + return new URL[] { + new File(mavenLocal() + this.library.getPath()).toURI().toURL() }; + } + + private String mavenLocal() { + return home() + "/.m2/repository"; + } + + private String home() { + String home = System.getProperty("user.home"); + return home == null ? "." : home; + } + + /** + * Convenience class to hold the co-ordinates of the library to be downloaded. + * + */ + static class Library { + + private String coordinates; + private String groupId; + private String artifactId; + private String version; + private String classifier; + + Library(String coordinates) { + this.coordinates = coordinates; + String[] parts = coordinates.split(":"); + if (parts.length < 3) { + throw new IllegalArgumentException( + "Co-ordinates should contain group:artifact[:classifier]:version"); + } + if (parts.length > 3) { + this.classifier = parts[2]; + this.version = parts[3]; + } + else { + this.version = parts[2]; + } + this.groupId = parts[0]; + this.artifactId = parts[1]; + } + + public void download(String path) { + File target = new File(path + getPath()); + if (!target.exists()) { + String repo = repo(); + InputStream input = null; + OutputStream output = null; + try { + input = new URL(repo + getPath()).openStream(); + if (target.getParentFile().mkdirs()) { + output = new FileOutputStream(target); + byte[] bytes = new byte[4096]; + int count = input.read(bytes); + while (count > 0) { + output.write(bytes, 0, count); + count = input.read(bytes); + } + } + } + catch (Exception e) { + throw new IllegalStateException( + "Cannot download library for launcher " + coordinates, e); + } + finally { + if (input != null) { + try { + input.close(); + } + catch (Exception e) { + } + } + if (output != null) { + try { + output.close(); + } + catch (Exception e) { + } + } + } + } + } + + private static String repo() { + String repo = System.getProperty(MAIN_REPO); + return repo != null ? repo : "https://repo.spring.io/libs-snapshot"; + } + + public String getCoordinates() { + return this.coordinates; + } + + public String getGroupId() { + return this.groupId; + } + + public String getArtifactId() { + return this.artifactId; + } + + public String getVersion() { + return this.version; + } + + public String getClassifier() { + return this.classifier; + } + + public String getPath() { + return "/" + this.groupId.replace(".", "/") + "/" + this.artifactId + "/" + + this.version + "/" + this.artifactId + "-" + this.version + + (this.classifier != null ? "-" + this.classifier : "") + ".jar"; + } + + } + +}