1 package dst.ass2.aop.impl;
3 import dst.ass2.aop.IPluginExecutable;
6 import java.io.FileInputStream;
7 import java.io.IOException;
8 import java.net.MalformedURLException;
10 import java.net.URLClassLoader;
11 import java.nio.file.*;
12 import java.security.MessageDigest;
13 import java.security.NoSuchAlgorithmException;
15 import java.util.concurrent.ConcurrentHashMap;
16 import java.util.concurrent.ExecutorService;
17 import java.util.concurrent.Executors;
18 import java.util.zip.ZipEntry;
19 import java.util.zip.ZipInputStream;
21 import static java.nio.file.StandardWatchEventKinds.*;
23 public class PluginExecutor implements dst.ass2.aop.IPluginExecutor, Runnable {
24 private final WatchService watcher;
25 private final Map<WatchKey, Path> keys;
26 private final Map<File, String> startedPlugins;
27 private Thread myThread;
28 private ExecutorService executorTP = Executors.newFixedThreadPool(5);
30 public PluginExecutor() {
32 watcher = FileSystems.getDefault().newWatchService();
33 keys = new ConcurrentHashMap<>();
34 startedPlugins = new ConcurrentHashMap<>();
35 } catch (IOException e) {
36 throw new RuntimeException("Most likely irrecoverable error when creating watch service.", e);
40 private static String getFileChecksum(MessageDigest digest, File file) throws IOException {
41 FileInputStream fis = new FileInputStream(file);
42 byte[] byteArray = new byte[1024];
44 while ((bytesCount = fis.read(byteArray)) != -1) {
45 digest.update(byteArray, 0, bytesCount);
48 byte[] bytes = digest.digest();
49 StringBuilder sb = new StringBuilder();
50 for (byte aByte : bytes) {
51 sb.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
57 public void monitor(File dir) {
59 Path dirPath = FileSystems.getDefault().getPath(dir.getAbsolutePath());
60 WatchKey key = dirPath.register(watcher, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
61 keys.put(key, dirPath);
62 } catch (IOException e) {
63 throw new RuntimeException("Could not listen on directory: " + dir.getAbsolutePath(), e);
68 public void stopMonitoring(File dir) {
69 for (WatchKey key : keys.keySet()) {
70 if (keys.get(key).toAbsolutePath().equals(dir.toPath().toAbsolutePath())) {
73 System.out.println("stopMonitoring(): " + dir.getAbsolutePath());
81 for (Path path : keys.values()) {
82 for (File file : path.toAbsolutePath().toFile().listFiles()) {
86 System.out.println("start(): existing files checked. starting watcher.");
87 // Unsure if interrupted is alive.
88 // should not matter, but we handle that case in run() anyway.
89 if (myThread == null || !myThread.isAlive()) {
90 myThread = new Thread(this);
97 for (WatchKey key : keys.keySet()) {
98 stopMonitoring(keys.get(key).toAbsolutePath().toFile());
100 System.out.println("stop()");
105 while (!keys.isEmpty() && myThread == Thread.currentThread()) {
108 key = watcher.take();
109 } catch (InterruptedException x) {
113 Path dir = keys.get(key);
115 System.out.println("WatchKey not recognized!");
119 for (WatchEvent<?> event : key.pollEvents()) {
120 @SuppressWarnings("rawtypes")
121 WatchEvent.Kind kind = event.kind();
123 // Context for directory entry event is the file name of entry
124 @SuppressWarnings("unchecked")
125 Path name = dir.resolve(((WatchEvent<Path>) event).context());
128 System.out.format("%s: %s\n", event.kind().name(), name);
130 if (kind == ENTRY_CREATE || kind == ENTRY_MODIFY) {
131 execPlugin(name.toAbsolutePath().toFile());
132 } else if (kind == ENTRY_DELETE) {
133 startedPlugins.remove(name.toAbsolutePath().toFile());
137 // reset key and remove from set if directory no longer accessible
138 boolean valid = key.reset();
144 System.out.println("run(): ended - keys.isEmpty() == true");
147 private void execPlugin(File file) {
148 if (file.isDirectory() || !file.getAbsolutePath().endsWith(".jar")) {
152 // we use SHA-1 to prevent multiple executions of the same plugin.
153 String currFileChecksum = "";
155 currFileChecksum = getFileChecksum(MessageDigest.getInstance("SHA-1"), file);
156 } catch (IOException | NoSuchAlgorithmException e) {
160 String startedFileChecksum = startedPlugins.get(file.getAbsoluteFile());
161 if (currFileChecksum.equals(startedFileChecksum)) {
165 // now we are finally set to execute the plugin...
166 System.out.println("execPlugin(): " + file.getAbsolutePath());
167 startedPlugins.put(file.getAbsoluteFile(), currFileChecksum);
169 // For each class in jar, try to load and cast it to IPluginExecutable.
171 // If successful (=not aborted by exception) execute IPluginExecutable.execute() in a threadpool.
173 ZipInputStream zip = new ZipInputStream(new FileInputStream(file.getAbsoluteFile()));
174 for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) {
175 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
176 String className = entry.getName().replace('/', '.');
177 className = className.substring(0, className.length() - ".class".length());
179 ClassLoader cl = new URLClassLoader(new URL[]{file.toURL()});
180 IPluginExecutable extension = (IPluginExecutable) cl.loadClass(className).newInstance();
181 executorTP.execute(new Thread() {
187 } catch (MalformedURLException e) {
189 } catch (IllegalAccessException | InstantiationException | ClassNotFoundException | ClassCastException ignored) {
193 } catch (IOException e) {