/** * Update the current running application with the specified {@link ClassLoaderFiles} * and trigger a reload. * @param files updated class loader files */ public void updateAndRestart(ClassLoaderFiles files) { Set<URL> urls = new LinkedHashSet<URL>(); Set<URL> classLoaderUrls = getClassLoaderUrls(); for (SourceFolder folder : files.getSourceFolders()) { for (Entry<String, ClassLoaderFile> entry : folder.getFilesEntrySet()) { for (URL url : classLoaderUrls) { if (updateFileSystem(url, entry.getKey(), entry.getValue())) { urls.add(url); } } } urls.addAll(getMatchingUrls(classLoaderUrls, folder.getName())); } updateTimeStamp(urls); restart(urls, files); }
private boolean updateFileSystem(URL url, String name, ClassLoaderFile classLoaderFile) { if (!isFolderUrl(url.toString())) { return false; } try { File folder = ResourceUtils.getFile(url); File file = new File(folder, name); if (file.exists() && file.canWrite()) { if (classLoaderFile.getKind() == Kind.DELETED) { return file.delete(); } FileCopyUtils.copy(classLoaderFile.getContents(), file); return true; } } catch (IOException ex) { // Ignore } return false; }
@Test public void updateAndRestart() throws Exception { URL url1 = new URL("file:/proj/module-a.jar!/"); URL url2 = new URL("file:/proj/module-b.jar!/"); URL url3 = new URL("file:/proj/module-c.jar!/"); URL url4 = new URL("file:/proj/module-d.jar!/"); URLClassLoader classLoaderA = new URLClassLoader(new URL[] { url1, url2 }); URLClassLoader classLoaderB = new URLClassLoader(new URL[] { url3, url4 }, classLoaderA); SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter(); MockRestartServer server = new MockRestartServer(filter, classLoaderB); ClassLoaderFiles files = new ClassLoaderFiles(); ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]); ClassLoaderFile fileB = new ClassLoaderFile(Kind.ADDED, new byte[0]); files.addFile("my/module-a", "ClassA.class", fileA); files.addFile("my/module-c", "ClassB.class", fileB); server.updateAndRestart(files); Set<URL> expectedUrls = new LinkedHashSet<URL>(Arrays.asList(url1, url3)); assertThat(server.restartUrls).isEqualTo(expectedUrls); assertThat(server.restartFiles).isEqualTo(files); }
@Test public void updateSetsJarLastModified() throws Exception { long startTime = System.currentTimeMillis(); File folder = this.temp.newFolder(); File jarFile = new File(folder, "module-a.jar"); new FileOutputStream(jarFile).close(); jarFile.setLastModified(0); URL url = jarFile.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[] { url }); SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter(); MockRestartServer server = new MockRestartServer(filter, classLoader); ClassLoaderFiles files = new ClassLoaderFiles(); ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]); files.addFile("my/module-a", "ClassA.class", fileA); server.updateAndRestart(files); assertThat(jarFile.lastModified()).isGreaterThan(startTime - 1000); }
@Test public void updateReplacesLocalFilesWhenPossible() throws Exception { // This is critical for Cloud Foundry support where the application is // run exploded and resources can be found from the servlet root (outside of the // classloader) File folder = this.temp.newFolder(); File classFile = new File(folder, "ClassA.class"); FileCopyUtils.copy("abc".getBytes(), classFile); URL url = folder.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[] { url }); SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter(); MockRestartServer server = new MockRestartServer(filter, classLoader); ClassLoaderFiles files = new ClassLoaderFiles(); ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, "def".getBytes()); files.addFile("my/module-a", "ClassA.class", fileA); server.updateAndRestart(files); assertThat(FileCopyUtils.copyToByteArray(classFile)).isEqualTo("def".getBytes()); }
@Test public void updateAndRestart() throws Exception { URL url1 = new URL("file:/proj/module-a.jar!/"); URL url2 = new URL("file:/proj/module-b.jar!/"); URL url3 = new URL("file:/proj/module-c.jar!/"); URL url4 = new URL("file:/proj/module-d.jar!/"); URLClassLoader classLoaderA = new URLClassLoader(new URL[] { url1, url2 }); URLClassLoader classLoaderB = new URLClassLoader(new URL[] { url3, url4 }, classLoaderA); SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter(); MockRestartServer server = new MockRestartServer(filter, classLoaderB); ClassLoaderFiles files = new ClassLoaderFiles(); ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]); ClassLoaderFile fileB = new ClassLoaderFile(Kind.ADDED, new byte[0]); files.addFile("my/module-a", "ClassA.class", fileA); files.addFile("my/module-c", "ClassB.class", fileB); server.updateAndRestart(files); Set<URL> expectedUrls = new LinkedHashSet<URL>(Arrays.asList(url1, url3)); assertThat(server.restartUrls, equalTo(expectedUrls)); assertThat(server.restartFiles, equalTo(files)); }
@Test public void updateSetsJarLastModified() throws Exception { long startTime = System.currentTimeMillis(); File folder = this.temp.newFolder(); File jarFile = new File(folder, "module-a.jar"); new FileOutputStream(jarFile).close(); jarFile.setLastModified(0); URL url = jarFile.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[] { url }); SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter(); MockRestartServer server = new MockRestartServer(filter, classLoader); ClassLoaderFiles files = new ClassLoaderFiles(); ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]); files.addFile("my/module-a", "ClassA.class", fileA); server.updateAndRestart(files); assertThat(jarFile.lastModified(), greaterThan(startTime - 1000)); }
@Test public void updateReplacesLocalFilesWhenPossible() throws Exception { // This is critical for Cloud Foundry support where the application is // run exploded and resources can be found from the servlet root (outside of the // classloader) File folder = this.temp.newFolder(); File classFile = new File(folder, "ClassA.class"); FileCopyUtils.copy("abc".getBytes(), classFile); URL url = folder.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[] { url }); SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter(); MockRestartServer server = new MockRestartServer(filter, classLoader); ClassLoaderFiles files = new ClassLoaderFiles(); ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, "def".getBytes()); files.addFile("my/module-a", "ClassA.class", fileA); server.updateAndRestart(files); assertThat(FileCopyUtils.copyToByteArray(classFile), equalTo("def".getBytes())); }
private ClassLoaderFile asClassLoaderFile(ChangedFile changedFile) throws IOException { ClassLoaderFile.Kind kind = TYPE_MAPPINGS.get(changedFile.getType()); byte[] bytes = (kind == Kind.DELETED ? null : FileCopyUtils.copyToByteArray(changedFile.getFile())); long lastModified = (kind == Kind.DELETED ? System.currentTimeMillis() : changedFile.getFile().lastModified()); return new ClassLoaderFile(kind, lastModified, bytes); }
@Test public void addClassLoaderFiles() throws Exception { ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles(); classLoaderFiles.addFile("f", new ClassLoaderFile(Kind.ADDED, "abc".getBytes())); Restarter restarter = Restarter.getInstance(); restarter.addClassLoaderFiles(classLoaderFiles); restarter.restart(); ClassLoader classLoader = ((TestableRestarter) restarter) .getRelaunchClassLoader(); assertThat(FileCopyUtils.copyToByteArray(classLoader.getResourceAsStream("f"))) .isEqualTo("abc".getBytes()); }
@Test public void sendClassLoaderFiles() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ClassLoaderFiles files = new ClassLoaderFiles(); files.addFile("name", new ClassLoaderFile(Kind.ADDED, new byte[0])); byte[] bytes = serialize(files); request.setContent(bytes); this.server.handle(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response)); verify(this.delegate).updateAndRestart(this.filesCaptor.capture()); assertThat(this.filesCaptor.getValue().getFile("name")).isNotNull(); assertThat(response.getStatus()).isEqualTo(200); }
private void verifyUploadRequest(File sourceFolder, MockClientHttpRequest request) throws IOException, ClassNotFoundException { ClassLoaderFiles classLoaderFiles = deserialize(request.getBodyAsBytes()); Collection<SourceFolder> sourceFolders = classLoaderFiles.getSourceFolders(); assertThat(sourceFolders.size()).isEqualTo(1); SourceFolder classSourceFolder = sourceFolders.iterator().next(); assertThat(classSourceFolder.getName()).isEqualTo(sourceFolder.getAbsolutePath()); Iterator<ClassLoaderFile> classFiles = classSourceFolder.getFiles().iterator(); assertClassFile(classFiles.next(), "File1", ClassLoaderFile.Kind.ADDED); assertClassFile(classFiles.next(), "File2", ClassLoaderFile.Kind.MODIFIED); assertClassFile(classFiles.next(), null, ClassLoaderFile.Kind.DELETED); assertThat(classFiles.hasNext()).isFalse(); }
@Test public void sendsClassLoaderFiles() throws Exception { File sourceFolder = this.temp.newFolder(); Set<ChangedFile> files = new LinkedHashSet<ChangedFile>(); File file1 = createFile(sourceFolder, "File1"); File file2 = createFile(sourceFolder, "File2"); File file3 = createFile(sourceFolder, "File3"); files.add(new ChangedFile(sourceFolder, file1, Type.ADD)); files.add(new ChangedFile(sourceFolder, file2, Type.MODIFY)); files.add(new ChangedFile(sourceFolder, file3, Type.DELETE)); Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>(); changeSet.add(new ChangedFiles(sourceFolder, files)); ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false); this.requestFactory.willRespond(HttpStatus.OK); this.uploader.onApplicationEvent(event); MockClientHttpRequest request = this.requestFactory.getExecutedRequests().get(0); ClassLoaderFiles classLoaderFiles = deserialize(request.getBodyAsBytes()); Collection<SourceFolder> sourceFolders = classLoaderFiles.getSourceFolders(); assertThat(sourceFolders.size()).isEqualTo(1); SourceFolder classSourceFolder = sourceFolders.iterator().next(); assertThat(classSourceFolder.getName()).isEqualTo(sourceFolder.getAbsolutePath()); Iterator<ClassLoaderFile> classFiles = classSourceFolder.getFiles().iterator(); assertClassFile(classFiles.next(), "File1", ClassLoaderFile.Kind.ADDED); assertClassFile(classFiles.next(), "File2", ClassLoaderFile.Kind.MODIFIED); assertClassFile(classFiles.next(), null, ClassLoaderFile.Kind.DELETED); assertThat(classFiles.hasNext()).isFalse(); }
@Test public void addClassLoaderFiles() throws Exception { ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles(); classLoaderFiles.addFile("f", new ClassLoaderFile(Kind.ADDED, "abc".getBytes())); Restarter restarter = Restarter.getInstance(); restarter.addClassLoaderFiles(classLoaderFiles); restarter.restart(); ClassLoader classLoader = ((TestableRestarter) restarter) .getRelaunchClassLoader(); assertThat(FileCopyUtils.copyToByteArray(classLoader.getResourceAsStream("f")), equalTo("abc".getBytes())); }
@Test public void sendClassLoaderFiles() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ClassLoaderFiles files = new ClassLoaderFiles(); files.addFile("name", new ClassLoaderFile(Kind.ADDED, new byte[0])); byte[] bytes = serialize(files); request.setContent(bytes); this.server.handle(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response)); verify(this.delegate).updateAndRestart(this.filesCaptor.capture()); assertThat(this.filesCaptor.getValue().getFile("name"), notNullValue()); assertThat(response.getStatus(), equalTo(200)); }
@Test public void sendsClassLoaderFiles() throws Exception { File sourceFolder = this.temp.newFolder(); Set<ChangedFile> files = new LinkedHashSet<ChangedFile>(); File file1 = createFile(sourceFolder, "File1"); File file2 = createFile(sourceFolder, "File2"); File file3 = createFile(sourceFolder, "File3"); files.add(new ChangedFile(sourceFolder, file1, Type.ADD)); files.add(new ChangedFile(sourceFolder, file2, Type.MODIFY)); files.add(new ChangedFile(sourceFolder, file3, Type.DELETE)); Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>(); changeSet.add(new ChangedFiles(sourceFolder, files)); ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false); this.requestFactory.willRespond(HttpStatus.OK); this.uploader.onApplicationEvent(event); MockClientHttpRequest request = this.requestFactory.getExecutedRequests().get(0); ClassLoaderFiles classLoaderFiles = deserialize(request.getBodyAsBytes()); Collection<SourceFolder> sourceFolders = classLoaderFiles.getSourceFolders(); assertThat(sourceFolders.size(), equalTo(1)); SourceFolder classSourceFolder = sourceFolders.iterator().next(); assertThat(classSourceFolder.getName(), equalTo(sourceFolder.getAbsolutePath())); Iterator<ClassLoaderFile> classFiles = classSourceFolder.getFiles().iterator(); assertClassFile(classFiles.next(), "File1", ClassLoaderFile.Kind.ADDED); assertClassFile(classFiles.next(), "File2", ClassLoaderFile.Kind.MODIFIED); assertClassFile(classFiles.next(), null, ClassLoaderFile.Kind.DELETED); assertThat(classFiles.hasNext(), equalTo(false)); }
private void assertClassFile(ClassLoaderFile file, String content, Kind kind) { assertThat(file.getContents()) .isEqualTo(content == null ? null : content.getBytes()); assertThat(file.getKind()).isEqualTo(kind); }
private void assertClassFile(ClassLoaderFile file, String content, Kind kind) { assertThat(file.getContents(), equalTo(content == null ? null : content.getBytes())); assertThat(file.getKind(), equalTo(kind)); }