package forklift.deployment; import forklift.classloader.ChildFirstClassLoader; import forklift.classloader.RunAsClassLoader; import forklift.decorators.CoreService; import forklift.decorators.Queue; import forklift.decorators.Queues; import forklift.decorators.Service; import forklift.decorators.Topic; import forklift.decorators.Topics; import org.reflections.Reflections; import org.reflections.util.ConfigurationBuilder; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; public class FileDeployment implements Deployment{ private Set<Class<?>> queues = new HashSet<>(); private Set<Class<?>> topics = new HashSet<>(); private Set<Class<?>> services = new HashSet<>(); private Set<Class<?>> coreServices = new HashSet<>(); private ClassLoader cl; private File deployedFile; private Reflections reflections; public FileDeployment() { } public FileDeployment(File deployedFile) throws IOException { if (deployedFile == null) throw new IOException("Missing file"); this.deployedFile = deployedFile; if (!deployedFile.getName().endsWith(".jar") && !deployedFile.getName().endsWith(".zip")) { throw new IOException("Unhandled file type"); } // Read jars out of the deployed file. final JarFile jar = new JarFile(deployedFile); final List<URL> jarUrls = jar.stream().filter(entry -> { return entry.getName().endsWith(".jar") || entry.getName().endsWith(".zip"); }).map(entry -> { try { return jarEntryAsUri(jar, entry).toURL(); } catch (Exception e) { return null; } }).collect(Collectors.toList()); jarUrls.add(deployedFile.toURI().toURL()); // TODO - we should cleanup temp jars when the deploy is thrown away. final URL[] urls = jarUrls.toArray(new URL[0]); // Assign a new classloader to this deployment. cl = new ChildFirstClassLoader(urls, getClass().getClassLoader()); // Reflect the deployment to determine if there are any consumers // annotated. reflections = new Reflections(new ConfigurationBuilder() .addClassLoader(cl) .setUrls(urls)); RunAsClassLoader.run(cl, () -> { coreServices.addAll(reflections.getTypesAnnotatedWith(CoreService.class)); queues.addAll(reflections.getTypesAnnotatedWith(Queue.class)); queues.addAll(reflections.getTypesAnnotatedWith(Queues.class)); services.addAll(reflections.getTypesAnnotatedWith(Service.class)); topics.addAll(reflections.getTypesAnnotatedWith(Topic.class)); topics.addAll(reflections.getTypesAnnotatedWith(Topics.class)); }); if (coreServices.size() > 0 && (queues.size() > 0 || topics.size() > 0 || services.size() > 0)) throw new IOException("Invalid core service due to queues/topics/services being deployed along side."); } public boolean isJar() { return deployedFile.getPath().endsWith(".jar"); } public boolean isClass() { return deployedFile.getPath().endsWith(".class"); } public File getDeployedFile() { return deployedFile; } public void setDeployedFile(File deployedFile) { this.deployedFile = deployedFile; } public ClassLoader getClassLoader() { return cl; } @Override public Set<Class<?>> getCoreServices() { return coreServices; } @Override public Set<Class<?>> getServices() { return services; } @Override public Set<Class<?>> getQueues() { return queues; } @Override public Set<Class<?>> getTopics() { return topics; } public Reflections getReflections() { return reflections; } @Override public boolean equals(Object o) { if (((FileDeployment)o).getDeployedFile().equals(deployedFile)) return true; return false; } @Override public String toString() { return "FileDeployment [queues=" + queues + ", topics=" + topics + ", cl=" + cl + ", deployedFile=" + deployedFile + ", reflections=" + reflections + "]"; } private static URI jarEntryAsUri(JarFile jarFile, JarEntry jarEntry) throws IOException { if (jarFile == null || jarEntry == null) throw new IOException("Invalid jar file or entry"); InputStream input = null; OutputStream output = null; try { String name = jarEntry.getName().replace('/', '_'); int i = name.lastIndexOf("."); String extension = i > -1 ? name.substring(i) : ""; File file = File.createTempFile( name.substring(0, name.length() - extension.length()) + ".", extension); file.deleteOnExit(); input = jarFile.getInputStream(jarEntry); output = new FileOutputStream(file); int readCount; byte[] buffer = new byte[4096]; while ((readCount = input.read(buffer)) != -1) { output.write(buffer, 0, readCount); } return file.toURI(); } finally { if (input != null) input.close(); if (output != null) output.close(); } } }