Add Restart auto-configuration
Add auto-configuration for application Restarts. Restarts are enabled by default (when not running from a fat jar) and will be triggered when any classpath folder changes. The ClassPathRestartStrategy additional customization of when a full restart is required. By default a PatternClassPathRestartStrategy with patterns loaded from DeveloperToolsProperties. Closes gh-3084pull/3077/merge
parent
a5c56ca482
commit
3d8db7cddb
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.autoconfigure;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration properties for developer tools.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.3.0
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties(prefix = "spring.developertools")
|
||||||
|
public class DeveloperToolsProperties {
|
||||||
|
|
||||||
|
private static final String DEFAULT_RESTART_EXCLUDES = "META-INF/resources/**,resource/**,static/**,public/**,templates/**";
|
||||||
|
|
||||||
|
private Restart restart = new Restart();
|
||||||
|
|
||||||
|
public Restart getRestart() {
|
||||||
|
return this.restart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restart properties
|
||||||
|
*/
|
||||||
|
public static class Restart {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable automatic restart.
|
||||||
|
*/
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patterns that should be excluding for triggering a full restart.
|
||||||
|
*/
|
||||||
|
private String exclude = DEFAULT_RESTART_EXCLUDES;
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExclude() {
|
||||||
|
return this.exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExclude(String exclude) {
|
||||||
|
this.exclude = exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFiles;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ApplicationEvent} containing details of a classpath change.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.3.0
|
||||||
|
* @see ClassPathFileChangeListener
|
||||||
|
*/
|
||||||
|
public class ClassPathChangedEvent extends ApplicationEvent {
|
||||||
|
|
||||||
|
private final Set<ChangedFiles> changeSet;
|
||||||
|
|
||||||
|
private final boolean restartRequired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ClassPathChangedEvent}.
|
||||||
|
* @param source the source of the event
|
||||||
|
* @param changeSet the changed files
|
||||||
|
* @param restartRequired if a restart is required due to the change
|
||||||
|
*/
|
||||||
|
public ClassPathChangedEvent(Object source, Set<ChangedFiles> changeSet,
|
||||||
|
boolean restartRequired) {
|
||||||
|
super(source);
|
||||||
|
Assert.notNull(changeSet, "ChangeSet must not be null");
|
||||||
|
this.changeSet = changeSet;
|
||||||
|
this.restartRequired = restartRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return details of the files that changed.
|
||||||
|
* @return the changed files
|
||||||
|
*/
|
||||||
|
public Set<ChangedFiles> getChangeSet() {
|
||||||
|
return this.changeSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if an application restart is required due to the change.
|
||||||
|
* @return if an application restart is required
|
||||||
|
*/
|
||||||
|
public boolean isRestartRequired() {
|
||||||
|
return this.restartRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFiles;
|
||||||
|
import org.springframework.boot.developertools.filewatch.FileChangeListener;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link FileChangeListener} to publish {@link ClassPathChangedEvent
|
||||||
|
* ClassPathChangedEvents}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.3.0
|
||||||
|
* @see ClassPathFileSystemWatcher
|
||||||
|
*/
|
||||||
|
public class ClassPathFileChangeListener implements FileChangeListener {
|
||||||
|
|
||||||
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
|
|
||||||
|
private final ClassPathRestartStrategy restartStrategy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ClassPathFileChangeListener} instance.
|
||||||
|
* @param eventPublisher the event publisher used send events
|
||||||
|
* @param restartStrategy the restart strategy to use
|
||||||
|
*/
|
||||||
|
public ClassPathFileChangeListener(ApplicationEventPublisher eventPublisher,
|
||||||
|
ClassPathRestartStrategy restartStrategy) {
|
||||||
|
Assert.notNull(eventPublisher, "EventPublisher must not be null");
|
||||||
|
Assert.notNull(restartStrategy, "RestartStrategy must not be null");
|
||||||
|
this.eventPublisher = eventPublisher;
|
||||||
|
this.restartStrategy = restartStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(Set<ChangedFiles> changeSet) {
|
||||||
|
boolean restart = isRestartRequired(changeSet);
|
||||||
|
ApplicationEvent event = new ClassPathChangedEvent(this, changeSet, restart);
|
||||||
|
this.eventPublisher.publishEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRestartRequired(Set<ChangedFiles> changeSet) {
|
||||||
|
for (ChangedFiles changedFiles : changeSet) {
|
||||||
|
for (ChangedFile changedFile : changedFiles) {
|
||||||
|
if (this.restartStrategy.isRestartRequired(changedFile)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.boot.developertools.filewatch.FileSystemWatcher;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ResourceUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates a {@link FileSystemWatcher} to watch the local classpath folders for
|
||||||
|
* changes.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.3.0
|
||||||
|
* @see ClassPathFileChangeListener
|
||||||
|
*/
|
||||||
|
public class ClassPathFileSystemWatcher implements InitializingBean, DisposableBean,
|
||||||
|
ApplicationContextAware {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(ClassPathFileSystemWatcher.class);
|
||||||
|
|
||||||
|
private final FileSystemWatcher fileSystemWatcher;
|
||||||
|
|
||||||
|
private ClassPathRestartStrategy restartStrategy;
|
||||||
|
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ClassPathFileSystemWatcher} instance.
|
||||||
|
* @param urls the classpath URLs to watch
|
||||||
|
*/
|
||||||
|
public ClassPathFileSystemWatcher(URL[] urls) {
|
||||||
|
this(new FileSystemWatcher(), null, urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ClassPathFileSystemWatcher} instance.
|
||||||
|
* @param restartStrategy the classpath restart strategy
|
||||||
|
* @param urls the URLs to watch
|
||||||
|
*/
|
||||||
|
public ClassPathFileSystemWatcher(ClassPathRestartStrategy restartStrategy, URL[] urls) {
|
||||||
|
this(new FileSystemWatcher(), restartStrategy, urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ClassPathFileSystemWatcher} instance.
|
||||||
|
* @param fileSystemWatcher the underlying {@link FileSystemWatcher} used to monitor
|
||||||
|
* the local file system
|
||||||
|
* @param restartStrategy the classpath restart strategy
|
||||||
|
* @param urls the URLs to watch
|
||||||
|
*/
|
||||||
|
protected ClassPathFileSystemWatcher(FileSystemWatcher fileSystemWatcher,
|
||||||
|
ClassPathRestartStrategy restartStrategy, URL[] urls) {
|
||||||
|
Assert.notNull(fileSystemWatcher, "FileSystemWatcher must not be null");
|
||||||
|
Assert.notNull(urls, "Urls must not be null");
|
||||||
|
this.fileSystemWatcher = new FileSystemWatcher();
|
||||||
|
this.restartStrategy = restartStrategy;
|
||||||
|
addUrls(urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addUrls(URL[] urls) {
|
||||||
|
for (URL url : urls) {
|
||||||
|
addUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addUrl(URL url) {
|
||||||
|
if (url.getProtocol().equals("file") && url.getPath().endsWith("/")) {
|
||||||
|
try {
|
||||||
|
this.fileSystemWatcher.addSourceFolder(ResourceUtils.getFile(url));
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
logger.warn("Unable to watch classpath URL " + url);
|
||||||
|
logger.trace("Unable to watch classpath URL " + url, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext)
|
||||||
|
throws BeansException {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
if (this.restartStrategy != null) {
|
||||||
|
this.fileSystemWatcher.addListener(new ClassPathFileChangeListener(
|
||||||
|
this.applicationContext, this.restartStrategy));
|
||||||
|
}
|
||||||
|
this.fileSystemWatcher.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() throws Exception {
|
||||||
|
this.fileSystemWatcher.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy interface used to determine when a changed classpath file should trigger a
|
||||||
|
* full application restart. For example, static web resources might not require a full
|
||||||
|
* restart where as class files would.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.3.0
|
||||||
|
* @see PatternClassPathRestartStrategy
|
||||||
|
*/
|
||||||
|
public interface ClassPathRestartStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if a full restart is required.
|
||||||
|
* @param file the changed file
|
||||||
|
* @return {@code true} if a full restart is required
|
||||||
|
*/
|
||||||
|
boolean isRestartRequired(ChangedFile file);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ant style pattern based {@link ClassPathRestartStrategy}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.3.0
|
||||||
|
* @see ClassPathRestartStrategy
|
||||||
|
*/
|
||||||
|
public class PatternClassPathRestartStrategy implements ClassPathRestartStrategy {
|
||||||
|
|
||||||
|
private final AntPathMatcher matcher = new AntPathMatcher();
|
||||||
|
|
||||||
|
private final String[] excludePatterns;
|
||||||
|
|
||||||
|
public PatternClassPathRestartStrategy(String excludePatterns) {
|
||||||
|
this.excludePatterns = StringUtils
|
||||||
|
.commaDelimitedListToStringArray(excludePatterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRestartRequired(ChangedFile file) {
|
||||||
|
for (String pattern : this.excludePatterns) {
|
||||||
|
if (this.matcher.match(pattern, file.getRelativeName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support for classpath monitoring
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.developertools.classpath;
|
||||||
|
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFiles;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.sameInstance;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ClassPathChangedEvent}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class ClassPathChangedEventTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
private Object source = new Object();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void changeSetMustNotBeNull() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("ChangeSet must not be null");
|
||||||
|
new ClassPathChangedEvent(this.source, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getChangeSet() throws Exception {
|
||||||
|
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
|
||||||
|
ClassPathChangedEvent event = new ClassPathChangedEvent(this.source, changeSet,
|
||||||
|
false);
|
||||||
|
assertThat(event.getChangeSet(), sameInstance(changeSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRestartRequired() throws Exception {
|
||||||
|
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
|
||||||
|
ClassPathChangedEvent event;
|
||||||
|
event = new ClassPathChangedEvent(this.source, changeSet, false);
|
||||||
|
assertThat(event.isRestartRequired(), equalTo(false));
|
||||||
|
event = new ClassPathChangedEvent(this.source, changeSet, true);
|
||||||
|
assertThat(event.isRestartRequired(), equalTo(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFiles;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ClassPathFileChangeListener}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class ClassPathFileChangeListenerTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<ApplicationEvent> eventCaptor;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void eventPublisherMustNotBeNull() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("EventPublisher must not be null");
|
||||||
|
new ClassPathFileChangeListener(null, mock(ClassPathRestartStrategy.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void restartStrategyMustNotBeNull() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("RestartStrategy must not be null");
|
||||||
|
new ClassPathFileChangeListener(mock(ApplicationEventPublisher.class), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendsEventWithoutRestart() throws Exception {
|
||||||
|
testSendsEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendsEventWithRestart() throws Exception {
|
||||||
|
testSendsEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testSendsEvent(boolean restart) {
|
||||||
|
ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class);
|
||||||
|
ClassPathRestartStrategy restartStrategy = mock(ClassPathRestartStrategy.class);
|
||||||
|
ClassPathFileChangeListener listener = new ClassPathFileChangeListener(
|
||||||
|
eventPublisher, restartStrategy);
|
||||||
|
File folder = new File("s1");
|
||||||
|
File file = new File("f1");
|
||||||
|
ChangedFile file1 = new ChangedFile(folder, file, ChangedFile.Type.ADD);
|
||||||
|
ChangedFile file2 = new ChangedFile(folder, file, ChangedFile.Type.ADD);
|
||||||
|
Set<ChangedFile> files = new LinkedHashSet<ChangedFile>();
|
||||||
|
files.add(file1);
|
||||||
|
files.add(file2);
|
||||||
|
ChangedFiles changedFiles = new ChangedFiles(new File("source"), files);
|
||||||
|
Set<ChangedFiles> changeSet = Collections.singleton(changedFiles);
|
||||||
|
if (restart) {
|
||||||
|
given(restartStrategy.isRestartRequired(file2)).willReturn(true);
|
||||||
|
}
|
||||||
|
listener.onChange(changeSet);
|
||||||
|
verify(eventPublisher).publishEvent(this.eventCaptor.capture());
|
||||||
|
ClassPathChangedEvent actualEvent = (ClassPathChangedEvent) this.eventCaptor
|
||||||
|
.getValue();
|
||||||
|
assertThat(actualEvent.getChangeSet(), equalTo(changeSet));
|
||||||
|
assertThat(actualEvent.isRestartRequired(), equalTo(restart));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||||
|
import org.springframework.boot.developertools.filewatch.FileSystemWatcher;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.env.MapPropertySource;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ClassPathFileSystemWatcher}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class ClassPathFileSystemWatcherTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temp = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void urlsMustNotBeNull() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("Urls must not be null");
|
||||||
|
URL[] urls = null;
|
||||||
|
new ClassPathFileSystemWatcher(urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void configuredWithRestartStrategy() throws Exception {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
Map<String, Object> properties = new HashMap<String, Object>();
|
||||||
|
File folder = this.temp.newFolder();
|
||||||
|
List<URL> urls = new ArrayList<URL>();
|
||||||
|
urls.add(new URL("http://spring.io"));
|
||||||
|
urls.add(folder.toURI().toURL());
|
||||||
|
properties.put("urls", urls);
|
||||||
|
MapPropertySource propertySource = new MapPropertySource("test", properties);
|
||||||
|
context.getEnvironment().getPropertySources().addLast(propertySource);
|
||||||
|
context.register(Config.class);
|
||||||
|
context.refresh();
|
||||||
|
Thread.sleep(100);
|
||||||
|
File classFile = new File(folder, "Example.class");
|
||||||
|
FileCopyUtils.copy("file".getBytes(), classFile);
|
||||||
|
Thread.sleep(1100);
|
||||||
|
List<ClassPathChangedEvent> events = context.getBean(Listener.class).getEvents();
|
||||||
|
assertThat(events.size(), equalTo(1));
|
||||||
|
assertThat(events.get(0).getChangeSet().iterator().next().getFiles().iterator()
|
||||||
|
.next().getFile(), equalTo(classFile));
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class Config {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public Environment environemnt;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ClassPathFileSystemWatcher watcher() {
|
||||||
|
FileSystemWatcher watcher = new FileSystemWatcher(false, 100, 10);
|
||||||
|
URL[] urls = this.environemnt.getProperty("urls", URL[].class);
|
||||||
|
return new ClassPathFileSystemWatcher(watcher, restartStrategy(), urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ClassPathRestartStrategy restartStrategy() {
|
||||||
|
return new ClassPathRestartStrategy() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRestartRequired(ChangedFile file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Listener listener() {
|
||||||
|
return new Listener();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Listener implements ApplicationListener<ClassPathChangedEvent> {
|
||||||
|
|
||||||
|
private List<ClassPathChangedEvent> events = new ArrayList<ClassPathChangedEvent>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ClassPathChangedEvent event) {
|
||||||
|
this.events.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClassPathChangedEvent> getEvents() {
|
||||||
|
return this.events;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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.developertools.classpath;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||||
|
import org.springframework.boot.developertools.filewatch.ChangedFile.Type;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PatternClassPathRestartStrategy}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class PatternClassPathRestartStrategyTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nullPattern() throws Exception {
|
||||||
|
ClassPathRestartStrategy strategy = createStrategy(null);
|
||||||
|
assertRestartRequired(strategy, "a/b.txt", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyPattern() throws Exception {
|
||||||
|
ClassPathRestartStrategy strategy = createStrategy("");
|
||||||
|
assertRestartRequired(strategy, "a/b.txt", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singlePattern() throws Exception {
|
||||||
|
ClassPathRestartStrategy strategy = createStrategy("static/**");
|
||||||
|
assertRestartRequired(strategy, "static/file.txt", false);
|
||||||
|
assertRestartRequired(strategy, "static/folder/file.txt", false);
|
||||||
|
assertRestartRequired(strategy, "public/file.txt", true);
|
||||||
|
assertRestartRequired(strategy, "public/folder/file.txt", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void multiplePatterns() throws Exception {
|
||||||
|
ClassPathRestartStrategy strategy = createStrategy("static/**,public/**");
|
||||||
|
assertRestartRequired(strategy, "static/file.txt", false);
|
||||||
|
assertRestartRequired(strategy, "static/folder/file.txt", false);
|
||||||
|
assertRestartRequired(strategy, "public/file.txt", false);
|
||||||
|
assertRestartRequired(strategy, "public/folder/file.txt", false);
|
||||||
|
assertRestartRequired(strategy, "src/file.txt", true);
|
||||||
|
assertRestartRequired(strategy, "src/folder/file.txt", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassPathRestartStrategy createStrategy(String pattern) {
|
||||||
|
return new PatternClassPathRestartStrategy(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertRestartRequired(ClassPathRestartStrategy strategy,
|
||||||
|
String relativeName, boolean expected) {
|
||||||
|
assertThat(strategy.isRestartRequired(mockFile(relativeName)), equalTo(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangedFile mockFile(String relativeName) {
|
||||||
|
return new ChangedFile(new File("."), new File("./" + relativeName), Type.ADD);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue