]> git.somenet.org - pub/jan/dst18.git/blob - ass2-aop/src/main/java/dst/ass2/aop/impl/PluginExecutor.java
[2.3.1] working plugin finder and executor.
[pub/jan/dst18.git] / ass2-aop / src / main / java / dst / ass2 / aop / impl / PluginExecutor.java
1 package dst.ass2.aop.impl;
2
3 import dst.ass2.aop.IPluginExecutable;
4
5 import java.io.File;
6 import java.io.FileInputStream;
7 import java.io.IOException;
8 import java.net.MalformedURLException;
9 import java.net.URL;
10 import java.net.URLClassLoader;
11 import java.nio.file.*;
12 import java.security.MessageDigest;
13 import java.security.NoSuchAlgorithmException;
14 import java.util.Map;
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;
20
21 import static java.nio.file.StandardWatchEventKinds.*;
22
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);
29
30     public PluginExecutor() {
31         try {
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);
37         }
38     }
39
40     private static String getFileChecksum(MessageDigest digest, File file) throws IOException {
41         FileInputStream fis = new FileInputStream(file);
42         byte[] byteArray = new byte[1024];
43         int bytesCount = 0;
44         while ((bytesCount = fis.read(byteArray)) != -1) {
45             digest.update(byteArray, 0, bytesCount);
46         }
47         fis.close();
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));
52         }
53         return sb.toString();
54     }
55
56     @Override
57     public void monitor(File dir) {
58         try {
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);
64         }
65     }
66
67     @Override
68     public void stopMonitoring(File dir) {
69         for (WatchKey key : keys.keySet()) {
70             if (keys.get(key).toAbsolutePath().equals(dir.toPath().toAbsolutePath())) {
71                 key.cancel();
72                 keys.remove(key);
73                 System.out.println("stopMonitoring(): " + dir.getAbsolutePath());
74                 return;
75             }
76         }
77     }
78
79     @Override
80     public void start() {
81         for (Path path : keys.values()) {
82             for (File file : path.toAbsolutePath().toFile().listFiles()) {
83                 execPlugin(file);
84             }
85         }
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);
91             myThread.start();
92         }
93     }
94
95     @Override
96     public void stop() {
97         for (WatchKey key : keys.keySet()) {
98             stopMonitoring(keys.get(key).toAbsolutePath().toFile());
99         }
100         System.out.println("stop()");
101     }
102
103     @Override
104     public void run() {
105         while (!keys.isEmpty() && myThread == Thread.currentThread()) {
106             WatchKey key;
107             try {
108                 key = watcher.take();
109             } catch (InterruptedException x) {
110                 return;
111             }
112
113             Path dir = keys.get(key);
114             if (dir == null) {
115                 System.out.println("WatchKey not recognized!");
116                 continue;
117             }
118
119             for (WatchEvent<?> event : key.pollEvents()) {
120                 @SuppressWarnings("rawtypes")
121                 WatchEvent.Kind kind = event.kind();
122
123                 // Context for directory entry event is the file name of entry
124                 @SuppressWarnings("unchecked")
125                 Path name = dir.resolve(((WatchEvent<Path>) event).context());
126
127                 // print out event
128                 System.out.format("%s: %s\n", event.kind().name(), name);
129
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());
134                 }
135             }
136
137             // reset key and remove from set if directory no longer accessible
138             boolean valid = key.reset();
139             if (!valid) {
140                 keys.remove(key);
141
142             }
143         }
144         System.out.println("run(): ended - keys.isEmpty() == true");
145     }
146
147     private void execPlugin(File file) {
148         if (file.isDirectory() || !file.getAbsolutePath().endsWith(".jar")) {
149             return;
150         }
151
152         // we use SHA-1 to prevent multiple executions of the same plugin.
153         String currFileChecksum = "";
154         try {
155             currFileChecksum = getFileChecksum(MessageDigest.getInstance("SHA-1"), file);
156         } catch (IOException | NoSuchAlgorithmException e) {
157             e.printStackTrace();
158         }
159
160         String startedFileChecksum = startedPlugins.get(file.getAbsoluteFile());
161         if (currFileChecksum.equals(startedFileChecksum)) {
162             return;
163         }
164
165         // now we are finally set to execute the plugin...
166         System.out.println("execPlugin(): " + file.getAbsolutePath());
167         startedPlugins.put(file.getAbsoluteFile(), currFileChecksum);
168
169         // For each class in jar, try to load and cast it to IPluginExecutable.
170         // Ignore errors.
171         // If successful (=not aborted by exception) execute IPluginExecutable.execute() in a threadpool.
172         try {
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());
178                     try {
179                         ClassLoader cl = new URLClassLoader(new URL[]{file.toURL()});
180                         IPluginExecutable extension = (IPluginExecutable) cl.loadClass(className).newInstance();
181                         executorTP.execute(new Thread() {
182                             @Override
183                             public void run() {
184                                 extension.execute();
185                             }
186                         });
187                     } catch (MalformedURLException e) {
188                         e.printStackTrace();
189                     } catch (IllegalAccessException | InstantiationException | ClassNotFoundException | ClassCastException ignored) {
190                     }
191                 }
192             }
193         } catch (IOException e) {
194             e.printStackTrace();
195         }
196     }
197 }