Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFileURLStreamHandler;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceDirectory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.io.AbstractResource;
Expand All @@ -58,6 +59,7 @@
* @author Andy Wilkinson
* @author Phillip Webb
* @author Stephane Nicoll
* @author DongHoon Lee
*/
final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternResolver {

Expand Down Expand Up @@ -146,22 +148,35 @@ private String trimLocationPattern(String pattern) {
}

private boolean isDeleted(Resource resource) {
for (Entry<String, ClassLoaderFile> entry : this.classLoaderFiles.getFileEntries()) {
try {
String name = entry.getKey();
ClassLoaderFile file = entry.getValue();
if (file.getKind() == Kind.DELETED && resource.exists()
&& resource.getURI().toString().endsWith(name)) {
String uri = null;
for (SourceDirectory sourceDirectory : this.classLoaderFiles.getSourceDirectories()) {
for (Entry<String, ClassLoaderFile> entry : sourceDirectory.getFilesEntrySet()) {
if (entry.getValue().getKind() != Kind.DELETED) {
continue;
}
if (uri == null) {
if (!resource.exists()) {
return false;
}
uri = getUri(resource);
}
if (uri.endsWith(entry.getKey())) {
return true;
}
}
catch (IOException ex) {
throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex);
}
}
return false;
}

private String getUri(Resource resource) {
try {
return resource.getURI().toString();
}
catch (IOException ex) {
throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex);
}
}

/**
* A {@link Resource} that represents a {@link ClassLoaderFile} that has been
* {@link Kind#DELETED deleted}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.File;
import java.io.IOException;
import java.net.URI;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -43,13 +44,15 @@
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;

/**
* Tests for {@link ClassLoaderFilesResourcePatternResolver}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author DongHoon Lee
*/
class ClassLoaderFilesResourcePatternResolverTests {

Expand Down Expand Up @@ -90,6 +93,41 @@ void getResourceWhenDeletedShouldReturnDeletedResource(@TempDir File directory)
assertThat(resource).isInstanceOf(DeletedClassLoaderFileResource.class);
}

@Test
void getResourceWhenDeletedInAnotherSourceDirectoryShouldReturnDeletedResource(@TempDir File directory)
throws Exception {
File file = createFile(directory, "name.class");
this.files.addFile("one", "other.class", new ClassLoaderFile(Kind.ADDED, new byte[0]));
this.files.addFile("two", "name.class", new ClassLoaderFile(Kind.DELETED, null));
Resource resource = this.resolver.getResource("file:" + file.getAbsolutePath());
assertThat(resource).isInstanceOf(DeletedClassLoaderFileResource.class);
}

@Test
void getResourceWhenManyFilesAreDeletedShouldCheckResourceOnlyOnce() throws Exception {
Resource resource = mock(Resource.class);
given(resource.exists()).willReturn(true);
given(resource.getURI()).willReturn(URI.create("file:/app/classes/three.class"));
this.resolver = createResolverResolving("foo:some-file.txt", resource);
this.files.addFile("one", "one.class", new ClassLoaderFile(Kind.DELETED, null));
this.files.addFile("one", "two.class", new ClassLoaderFile(Kind.DELETED, null));
this.files.addFile("two", "three.class", new ClassLoaderFile(Kind.DELETED, null));
assertThat(this.resolver.getResource("foo:some-file.txt")).isInstanceOf(DeletedClassLoaderFileResource.class);
then(resource).should().exists();
then(resource).should().getURI();
}

@Test
void getResourceWhenNoFileIsDeletedShouldNotCheckResource() throws Exception {
Resource resource = mock(Resource.class);
this.resolver = createResolverResolving("foo:some-file.txt", resource);
this.files.addFile("one", "one.class", new ClassLoaderFile(Kind.ADDED, new byte[0]));
this.files.addFile("two", "two.class", new ClassLoaderFile(Kind.MODIFIED, new byte[0]));
assertThat(this.resolver.getResource("foo:some-file.txt")).isSameAs(resource);
then(resource).should(never()).exists();
then(resource).should(never()).getURI();
}

@Test
void getResourcesShouldReturnResources(@TempDir File directory) throws Exception {
File file = createFile(directory, "name.class");
Expand Down Expand Up @@ -173,6 +211,12 @@ void customProtocolResolverRegisteredAfterCreationIsUsedInWebApplication() {
then(resolver).should().resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
}

private ClassLoaderFilesResourcePatternResolver createResolverResolving(String location, Resource resource) {
GenericApplicationContext context = new GenericApplicationContext();
context.addProtocolResolver(mockProtocolResolver(location, resource));
return new ClassLoaderFilesResourcePatternResolver(context, this.files);
}

private ProtocolResolver mockProtocolResolver(String path, Resource resource) {
ProtocolResolver resolver = mock(ProtocolResolver.class);
given(resolver.resolve(eq(path), any(ResourceLoader.class))).willReturn(resource);
Expand Down
Loading