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; }
@Override public Enumeration<URL> getResources(String name) throws IOException { // Use the parent since we're shadowing resource and we don't want duplicates Enumeration<URL> resources = getParent().getResources(name); ClassLoaderFile file = this.updatedFiles.getFile(name); if (file != null) { // Assume that we're replacing just the first item if (resources.hasMoreElements()) { resources.nextElement(); } if (file.getKind() != Kind.DELETED) { return new CompoundEnumeration<URL>(createFileUrl(name, file), resources); } } return resources; }
@Override public URL findResource(final String name) { final ClassLoaderFile file = this.updatedFiles.getFile(name); if (file == null) { return super.findResource(name); } if (file.getKind() == Kind.DELETED) { return null; } return AccessController.doPrivileged(new PrivilegedAction<URL>() { @Override public URL run() { return createFileUrl(name, file); } }); }
@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); ClassLoaderFile file = this.updatedFiles.getFile(path); if (file != null && file.getKind() == Kind.DELETED) { throw new ClassNotFoundException(name); } Class<?> loadedClass = findLoadedClass(name); if (loadedClass == null) { try { loadedClass = findClass(name); } catch (ClassNotFoundException ex) { loadedClass = getParent().loadClass(name); } } if (resolve) { resolveClass(loadedClass); } return loadedClass; }
@Override protected Class<?> findClass(final String name) throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); final ClassLoaderFile file = this.updatedFiles.getFile(path); if (file == null) { return super.findClass(name); } if (file.getKind() == Kind.DELETED) { throw new ClassNotFoundException(name); } return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() { @Override public Class<?> run() { byte[] bytes = file.getContents(); return defineClass(name, bytes, 0, bytes.length); } }); }
@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 getSourceFolders() throws Exception { ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]); ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); ClassLoaderFile file3 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); ClassLoaderFile file4 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); this.files.addFile("a", "myfile1", file1); this.files.addFile("a", "myfile2", file2); this.files.addFile("b", "myfile3", file3); this.files.addFile("b", "myfile4", file4); Iterator<SourceFolder> sourceFolders = this.files.getSourceFolders().iterator(); SourceFolder sourceFolder1 = sourceFolders.next(); SourceFolder sourceFolder2 = sourceFolders.next(); assertThat(sourceFolders.hasNext()).isFalse(); assertThat(sourceFolder1.getName()).isEqualTo("a"); assertThat(sourceFolder2.getName()).isEqualTo("b"); assertThat(sourceFolder1.getFiles()).containsOnly(file1, file2); assertThat(sourceFolder2.getFiles()).containsOnly(file3, file4); }
@Test public void addAll() throws Exception { ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]); this.files.addFile("a", "myfile1", file1); ClassLoaderFiles toAdd = new ClassLoaderFiles(); ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); ClassLoaderFile file3 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); toAdd.addFile("a", "myfile2", file2); toAdd.addFile("b", "myfile3", file3); this.files.addAll(toAdd); Iterator<SourceFolder> sourceFolders = this.files.getSourceFolders().iterator(); SourceFolder sourceFolder1 = sourceFolders.next(); SourceFolder sourceFolder2 = sourceFolders.next(); assertThat(sourceFolders.hasNext()).isFalse(); assertThat(sourceFolder1.getName()).isEqualTo("a"); assertThat(sourceFolder2.getName()).isEqualTo("b"); assertThat(sourceFolder1.getFiles()).containsOnly(file1, file2); }
@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())); }
@Test public void getSourceFolders() throws Exception { ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]); ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); ClassLoaderFile file3 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); ClassLoaderFile file4 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); this.files.addFile("a", "myfile1", file1); this.files.addFile("a", "myfile2", file2); this.files.addFile("b", "myfile3", file3); this.files.addFile("b", "myfile4", file4); Iterator<SourceFolder> sourceFolders = this.files.getSourceFolders().iterator(); SourceFolder sourceFolder1 = sourceFolders.next(); SourceFolder sourceFolder2 = sourceFolders.next(); assertThat(sourceFolders.hasNext(), equalTo(false)); assertThat(sourceFolder1.getName(), equalTo("a")); assertThat(sourceFolder2.getName(), equalTo("b")); assertThat(new ArrayList<ClassLoaderFile>(sourceFolder1.getFiles()), equalTo(Arrays.asList(file1, file2))); assertThat(new ArrayList<ClassLoaderFile>(sourceFolder2.getFiles()), equalTo(Arrays.asList(file3, file4))); }
@Test public void addAll() throws Exception { ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]); this.files.addFile("a", "myfile1", file1); ClassLoaderFiles toAdd = new ClassLoaderFiles(); ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); ClassLoaderFile file3 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); toAdd.addFile("a", "myfile2", file2); toAdd.addFile("b", "myfile3", file3); this.files.addAll(toAdd); Iterator<SourceFolder> sourceFolders = this.files.getSourceFolders().iterator(); SourceFolder sourceFolder1 = sourceFolders.next(); SourceFolder sourceFolder2 = sourceFolders.next(); assertThat(sourceFolders.hasNext(), equalTo(false)); assertThat(sourceFolder1.getName(), equalTo("a")); assertThat(sourceFolder2.getName(), equalTo("b")); assertThat(new ArrayList<ClassLoaderFile>(sourceFolder1.getFiles()), equalTo(Arrays.asList(file1, file2))); }
@Override public URL getResource(String name) { ClassLoaderFile file = this.updatedFiles.getFile(name); if (file != null && file.getKind() == Kind.DELETED) { return null; } URL resource = findResource(name); if (resource != null) { return resource; } return getParent().getResource(name); }
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); }
@Test public void addTwice() throws Exception { ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]); ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); this.files.addFile("myfile", file1); this.files.addFile("myfile", file2); assertThat(this.files.getFile("myfile")).isEqualTo(file2); }
@Test public void addTwiceInDifferentSourceFolders() throws Exception { ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]); ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]); this.files.addFile("a", "myfile", file1); this.files.addFile("b", "myfile", file2); assertThat(this.files.getFile("myfile")).isEqualTo(file2); assertThat(this.files.getOrCreateSourceFolder("a").getFiles().size()) .isEqualTo(0); assertThat(this.files.getOrCreateSourceFolder("b").getFiles().size()) .isEqualTo(1); }
@Test public void serialize() throws Exception { ClassLoaderFile file = new ClassLoaderFile(Kind.ADDED, new byte[10]); this.files.addFile("myfile", file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this.files); oos.close(); ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream(bos.toByteArray())); ClassLoaderFiles readObject = (ClassLoaderFiles) ois.readObject(); assertThat(readObject.getFile("myfile")).isNotNull(); }
@Test public void getUpdatedResource() throws Exception { String name = PACKAGE_PATH + "/Sample.txt"; byte[] bytes = "abc".getBytes(); this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.MODIFIED, bytes)); URL resource = this.reloadClassLoader.getResource(name); assertThat(FileCopyUtils.copyToByteArray(resource.openStream())).isEqualTo(bytes); }
@Test public void getResourcesWithDeleted() throws Exception { String name = PACKAGE_PATH + "/Sample.txt"; this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.DELETED, null)); List<URL> resources = toList(this.reloadClassLoader.getResources(name)); assertThat(resources.size()).isEqualTo(0); }
@Test public void getResourcesWithUpdated() throws Exception { String name = PACKAGE_PATH + "/Sample.txt"; byte[] bytes = "abc".getBytes(); this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.MODIFIED, bytes)); List<URL> resources = toList(this.reloadClassLoader.getResources(name)); assertThat(FileCopyUtils.copyToByteArray(resources.get(0).openStream())) .isEqualTo(bytes); }