/** * 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); }
/** * Handle a server request. * @param request the request * @param response the response * @throws IOException in case of I/O errors */ public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException { try { Assert.state(request.getHeaders().getContentLength() > 0, "No content"); ObjectInputStream objectInputStream = new ObjectInputStream( request.getBody()); ClassLoaderFiles files = (ClassLoaderFiles) objectInputStream.readObject(); objectInputStream.close(); this.server.updateAndRestart(files); response.setStatusCode(HttpStatus.OK); } catch (Exception ex) { logger.warn("Unable to handler restart server HTTP request", ex); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); } }
@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()); }
@Override public void onApplicationEvent(ClassPathChangedEvent event) { try { ClassLoaderFiles classLoaderFiles = getClassLoaderFiles(event); ClientHttpRequest request = this.requestFactory.createRequest(this.uri, HttpMethod.POST); byte[] bytes = serialize(classLoaderFiles); HttpHeaders headers = request.getHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentLength(bytes.length); FileCopyUtils.copy(bytes, request.getBody()); logUpload(classLoaderFiles); ClientHttpResponse response = request.execute(); Assert.state(response.getStatusCode() == HttpStatus.OK, "Unexpected " + response.getStatusCode() + " response uploading class files"); } catch (IOException ex) { throw new IllegalStateException(ex); } }
@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 Throwable doStart() throws Exception { Assert.notNull(this.mainClassName, "Unable to find the main class to restart"); ClassLoader parent = this.applicationClassLoader; URL[] urls = this.urls.toArray(new URL[this.urls.size()]); ClassLoaderFiles updatedFiles = new ClassLoaderFiles(this.classLoaderFiles); ClassLoader classLoader = new RestartClassLoader(parent, urls, updatedFiles, this.logger); if (this.logger.isDebugEnabled()) { this.logger.debug("Starting application " + this.mainClassName + " with URLs " + Arrays.asList(urls)); } return relaunch(classLoader); }
/** * Called to restart the application. * @param urls the updated URLs * @param files the updated files */ protected void restart(Set<URL> urls, ClassLoaderFiles files) { Restarter restarter = Restarter.getInstance(); restarter.addUrls(urls); restarter.addClassLoaderFiles(files); restarter.restart(); }
@Override public void onApplicationEvent(ClassPathChangedEvent event) { try { ClassLoaderFiles classLoaderFiles = getClassLoaderFiles(event); byte[] bytes = serialize(classLoaderFiles); performUpload(classLoaderFiles, bytes); } catch (IOException ex) { throw new IllegalStateException(ex); } }
private byte[] serialize(ClassLoaderFiles classLoaderFiles) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(classLoaderFiles); objectOutputStream.close(); return outputStream.toByteArray(); }
private ClassLoaderFiles getClassLoaderFiles(ClassPathChangedEvent event) throws IOException { ClassLoaderFiles files = new ClassLoaderFiles(); for (ChangedFiles changedFiles : event.getChangeSet()) { String sourceFolder = changedFiles.getSourceFolder().getAbsolutePath(); for (ChangedFile changedFile : changedFiles) { files.addFile(sourceFolder, changedFile.getRelativeName(), asClassLoaderFile(changedFile)); } } return files; }
@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)); }
/** * Add additional {@link ClassLoaderFiles} to be included in the next restart. * @param classLoaderFiles the files to add */ public void addClassLoaderFiles(ClassLoaderFiles classLoaderFiles) { Assert.notNull(classLoaderFiles, "ClassLoaderFiles must not be null"); this.classLoaderFiles.addAll(classLoaderFiles); }