diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java index da8434b962..9356c4cb4e 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * 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. @@ -16,6 +16,9 @@ package org.springframework.boot.devtools.restart; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; @@ -23,13 +26,18 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import org.springframework.boot.devtools.settings.DevToolsSettings; +import org.springframework.util.StringUtils; /** - * A filtered collections of URLs which can be change after the application has started. + * A filtered collection of URLs which can change after the application has started. * * @author Phillip Webb + * @author Andy Wilkinson */ final class ChangeableUrls implements Iterable { @@ -74,7 +82,59 @@ final class ChangeableUrls implements Iterable { } public static ChangeableUrls fromUrlClassLoader(URLClassLoader classLoader) { - return fromUrls(classLoader.getURLs()); + List urls = new ArrayList(); + for (URL url : classLoader.getURLs()) { + urls.add(url); + urls.addAll(getUrlsFromClassPathOfJarManifestIfPossible(url)); + } + return fromUrls(urls); + } + + private static List getUrlsFromClassPathOfJarManifestIfPossible(URL url) { + JarFile jarFile = getJarFileIfPossible(url); + if (jarFile != null) { + try { + return getUrlsFromClassPathAttribute(jarFile.getManifest()); + } + catch (IOException ex) { + throw new IllegalStateException( + "Failed to read Class-Path attribute from manifest of jar " + + url); + } + } + return Collections.emptyList(); + } + + private static JarFile getJarFileIfPossible(URL url) { + try { + File file = new File(url.toURI()); + if (file.isFile()) { + return new JarFile(file); + } + } + catch (Exception ex) { + // Assume it's not a jar and continue + } + return null; + } + + private static List getUrlsFromClassPathAttribute(Manifest manifest) { + List urls = new ArrayList(); + String classPathAttribute = manifest.getMainAttributes() + .getValue(Attributes.Name.CLASS_PATH); + if (StringUtils.hasText(classPathAttribute)) { + for (String entry : StringUtils.delimitedListToStringArray(classPathAttribute, + " ")) { + try { + urls.add(new URL(entry)); + } + catch (MalformedURLException ex) { + throw new IllegalStateException( + "Class-Path attribute contains malformed URL", ex); + } + } + } + return urls; } public static ChangeableUrls fromUrls(Collection urls) { diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java index efcde69446..723e558866 100644 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * 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. @@ -17,13 +17,21 @@ package org.springframework.boot.devtools.restart; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; +import java.net.URLClassLoader; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.springframework.util.StringUtils; + +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; @@ -31,6 +39,7 @@ import static org.junit.Assert.assertThat; * Tests for {@link ChangeableUrls}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class ChangeableUrlsTests { @@ -64,6 +73,16 @@ public class ChangeableUrlsTests { assertThat(urls.size(), equalTo(0)); } + @Test + public void urlsFromJarClassPathAreConsidered() throws Exception { + URL projectCore = makeUrl("project-core"); + URL projectWeb = makeUrl("project-web"); + ChangeableUrls urls = ChangeableUrls.fromUrlClassLoader(new URLClassLoader( + new URL[] { makeJarFileWithUrlsInManifestClassPath(projectCore, + projectWeb) })); + assertThat(urls.toList(), contains(projectCore, projectWeb)); + } + private URL makeUrl(String name) throws IOException { File file = this.temporaryFolder.newFolder(); file = new File(file, name); @@ -73,4 +92,15 @@ public class ChangeableUrlsTests { return file.toURI().toURL(); } + private URL makeJarFileWithUrlsInManifestClassPath(URL... urls) throws Exception { + File classpathJar = this.temporaryFolder.newFile("classpath.jar"); + Manifest manifest = new Manifest(); + manifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), + "1.0"); + manifest.getMainAttributes().putValue(Attributes.Name.CLASS_PATH.toString(), + StringUtils.arrayToDelimitedString(urls, " ")); + new JarOutputStream(new FileOutputStream(classpathJar), manifest).close(); + return classpathJar.toURI().toURL(); + } + }