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 bfd8a81a43d..da8434b9629 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 @@ -23,7 +23,8 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.regex.Pattern; + +import org.springframework.boot.devtools.settings.DevToolsSettings; /** * A filtered collections of URLs which can be change after the application has started. @@ -32,48 +33,24 @@ import java.util.regex.Pattern; */ final class ChangeableUrls implements Iterable { - private static final String[] SKIPPED_PROJECTS = { "spring-boot", - "spring-boot-devtools", "spring-boot-autoconfigure", "spring-boot-actuator", - "spring-boot-starter" }; - - private static final Pattern STARTER_PATTERN = Pattern - .compile("\\/spring-boot-starter-[\\w-]+\\/"); - private final List urls; private ChangeableUrls(URL... urls) { + DevToolsSettings settings = DevToolsSettings.get(); List reloadableUrls = new ArrayList(urls.length); for (URL url : urls) { - if (isReloadable(url)) { + if ((settings.isRestartInclude(url) || isFolderUrl(url.toString())) + && !settings.isRestartExclude(url)) { reloadableUrls.add(url); } } this.urls = Collections.unmodifiableList(reloadableUrls); } - private boolean isReloadable(URL url) { - String urlString = url.toString(); - return isFolderUrl(urlString) && !isSkipped(urlString); - } - private boolean isFolderUrl(String urlString) { return urlString.startsWith("file:") && urlString.endsWith("/"); } - private boolean isSkipped(String urlString) { - // Skip certain spring-boot projects to allow them to be imported in the same IDE - for (String skipped : SKIPPED_PROJECTS) { - if (urlString.contains("/" + skipped + "/target/classes/")) { - return true; - } - } - // Skip all starter projects - if (STARTER_PATTERN.matcher(urlString).find()) { - return true; - } - return false; - } - @Override public Iterator iterator() { return this.urls.iterator(); diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/settings/DevToolsSettings.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/settings/DevToolsSettings.java new file mode 100644 index 00000000000..525ab6cd600 --- /dev/null +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/settings/DevToolsSettings.java @@ -0,0 +1,116 @@ +/* + * 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.devtools.settings; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.springframework.core.io.UrlResource; +import org.springframework.core.io.support.PropertiesLoaderUtils; + +/** + * DevTools settings loaded from {@literal /META-INF/spring-devtools.properties} files. + * + * @author Phillip Webb + * @since 1.3.0 + */ +public class DevToolsSettings { + + /** + * The location to look for settings properties. Can be present in multiple JAR files. + */ + public static final String SETTINGS_RESOURCE_LOCATION = "META-INF/spring-devtools.properties"; + + private static DevToolsSettings settings; + + private final List restartIncludePatterns = new ArrayList(); + + private final List restartExcludePatterns = new ArrayList(); + + DevToolsSettings() { + } + + void add(Map properties) { + Map includes = getPatterns(properties, "restart.include."); + this.restartIncludePatterns.addAll(includes.values()); + Map excludes = getPatterns(properties, "restart.exclude."); + this.restartExcludePatterns.addAll(excludes.values()); + } + + private Map getPatterns(Map properties, String prefix) { + Map patterns = new LinkedHashMap(); + for (Map.Entry entry : properties.entrySet()) { + String name = String.valueOf(entry.getKey()); + if (name.startsWith(prefix)) { + Pattern pattern = Pattern.compile((String) entry.getValue()); + patterns.put(name, pattern); + } + } + return patterns; + } + + public boolean isRestartInclude(URL url) { + return isMatch(url.toString(), this.restartIncludePatterns); + } + + public boolean isRestartExclude(URL url) { + return isMatch(url.toString(), this.restartExcludePatterns); + } + + private boolean isMatch(String url, List patterns) { + for (Pattern pattern : patterns) { + if (pattern.matcher(url).find()) { + return true; + } + } + return false; + } + + public static DevToolsSettings get() { + if (settings == null) { + settings = load(); + } + return settings; + } + + static DevToolsSettings load() { + return load(SETTINGS_RESOURCE_LOCATION); + } + + static DevToolsSettings load(String location) { + try { + DevToolsSettings settings = new DevToolsSettings(); + Enumeration urls = Thread.currentThread().getContextClassLoader() + .getResources(location); + while (urls.hasMoreElements()) { + settings.add(PropertiesLoaderUtils + .loadProperties(new UrlResource(urls.nextElement()))); + } + return settings; + } + catch (Exception ex) { + throw new IllegalStateException("Unable to load devtools settings from " + + "location [" + location + "]", ex); + } + } + +} diff --git a/spring-boot-devtools/src/main/resources/META-INF/spring-devtools.properties b/spring-boot-devtools/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 00000000000..9810d8d63e5 --- /dev/null +++ b/spring-boot-devtools/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1,6 @@ +restart.exclude.spring-boot=/spring-boot/target/classes/ +restart.exclude.spring-boot-devtools=/spring-boot-devtools/target/classes/ +restart.exclude.spring-boot-autoconfigure=/spring-boot-autoconfigure/target/classes/ +restart.exclude.spring-boot-actuator=/spring-boot-actuator/target/classes/ +restart.exclude.spring-boot-starter=/spring-boot-starter/target/classes/ +restart.exclude.spring-boot-starters=/spring-boot-starter-[\\w-]+/ diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/settings/DevToolsSettingsTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/settings/DevToolsSettingsTests.java new file mode 100644 index 00000000000..80f017f5095 --- /dev/null +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/settings/DevToolsSettingsTests.java @@ -0,0 +1,81 @@ +/* + * 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.devtools.settings; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link DevToolsSettings}. + * + * @author Phillip Webb + */ +public class DevToolsSettingsTests { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private static final String ROOT = DevToolsSettingsTests.class.getPackage().getName() + .replace(".", "/") + "/"; + + @Test + public void includePatterns() throws Exception { + DevToolsSettings settings = DevToolsSettings + .load(ROOT + "spring-devtools-include.properties"); + assertThat(settings.isRestartInclude(new URL("file://test/a")), equalTo(true)); + assertThat(settings.isRestartInclude(new URL("file://test/b")), equalTo(true)); + assertThat(settings.isRestartInclude(new URL("file://test/c")), equalTo(false)); + } + + @Test + public void excludePatterns() throws Exception { + DevToolsSettings settings = DevToolsSettings + .load(ROOT + "spring-devtools-exclude.properties"); + assertThat(settings.isRestartExclude(new URL("file://test/a")), equalTo(true)); + assertThat(settings.isRestartExclude(new URL("file://test/b")), equalTo(true)); + assertThat(settings.isRestartExclude(new URL("file://test/c")), equalTo(false)); + } + + @Test + public void defaultIncludePatterns() throws Exception { + DevToolsSettings settings = DevToolsSettings.get(); + assertTrue(settings.isRestartExclude(makeUrl("spring-boot"))); + assertTrue(settings.isRestartExclude(makeUrl("spring-boot-autoconfigure"))); + assertTrue(settings.isRestartExclude(makeUrl("spring-boot-actuator"))); + assertTrue(settings.isRestartExclude(makeUrl("spring-boot-starter"))); + assertTrue(settings.isRestartExclude(makeUrl("spring-boot-starter-some-thing"))); + } + + private URL makeUrl(String name) throws IOException { + File file = this.temporaryFolder.newFolder(); + file = new File(file, name); + file = new File(file, "target"); + file = new File(file, "classes"); + file.mkdirs(); + return file.toURI().toURL(); + } + +} diff --git a/spring-boot-devtools/src/test/resources/org/springframework/boot/devtools/settings/spring-devtools-exclude.properties b/spring-boot-devtools/src/test/resources/org/springframework/boot/devtools/settings/spring-devtools-exclude.properties new file mode 100644 index 00000000000..ce101459d53 --- /dev/null +++ b/spring-boot-devtools/src/test/resources/org/springframework/boot/devtools/settings/spring-devtools-exclude.properties @@ -0,0 +1,2 @@ +restart.exclude.a=a.* +restart.exclude.b=b.* diff --git a/spring-boot-devtools/src/test/resources/org/springframework/boot/devtools/settings/spring-devtools-include.properties b/spring-boot-devtools/src/test/resources/org/springframework/boot/devtools/settings/spring-devtools-include.properties new file mode 100644 index 00000000000..6dd8b278f27 --- /dev/null +++ b/spring-boot-devtools/src/test/resources/org/springframework/boot/devtools/settings/spring-devtools-include.properties @@ -0,0 +1,2 @@ +restart.include.a=a.* +restart.include.b=b.*