]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsJShell.java
Merge tag 'v2.3.27' into testing
[lgpl/argeo-commons.git] / org.argeo.cms.jshell / src / org / argeo / cms / jshell / CmsJShell.java
1 package org.argeo.cms.jshell;
2
3 import java.io.IOException;
4 import java.nio.file.ClosedWatchServiceException;
5 import java.nio.file.DirectoryStream;
6 import java.nio.file.FileSystems;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
9 import java.nio.file.StandardWatchEventKinds;
10 import java.nio.file.WatchEvent;
11 import java.nio.file.WatchKey;
12 import java.nio.file.WatchService;
13 import java.util.HashMap;
14 import java.util.Map;
15 import java.util.UUID;
16
17 import org.argeo.api.cms.CmsLog;
18 import org.argeo.api.cms.CmsState;
19 import org.argeo.api.uuid.UuidFactory;
20 import org.argeo.cms.util.FsUtils;
21 import org.argeo.cms.util.OS;
22 import org.argeo.internal.cms.jshell.osgi.OsgiExecutionControlProvider;
23 import org.osgi.framework.Bundle;
24
25 /** A factory for JShell sessions. */
26 public class CmsJShell {
27 private final static CmsLog log = CmsLog.getLog(CmsJShell.class);
28 static ClassLoader loader = CmsJShell.class.getClassLoader();
29
30 public static UuidFactory uuidFactory = null;
31
32 private CmsState cmsState;
33
34 private Map<Path, LocalJShellSession> localSessions = new HashMap<>();
35 private Map<Path, Path> bundleDirs = new HashMap<>();
36
37 private Path stateRunDir;
38 private Path jshBase;
39 private Path jshLinkedDir;
40 private Path jtermBase;
41 private Path jtermLinkedDir;
42
43 private WatchService watchService;
44
45 public void start() throws Exception {
46 // TODO better define application id, make it configurable
47 String applicationID;
48 if (Files.exists(cmsState.getStatePath("dev.properties"))) { // in Eclipse
49 applicationID = cmsState.getStatePath("").getFileName().toString();
50 } else {
51 applicationID = cmsState.getStatePath("").getParent().getFileName().toString();
52 }
53
54 // TODO centralise state run dir
55 stateRunDir = OS.getRunDir().resolve(applicationID);
56
57 // TODO factorise create/delete pattern
58 jshBase = stateRunDir.resolve(JShellClient.JSH);
59 if (Files.exists(jshBase)) {
60 log.warn(jshBase + " already exists, deleting it...");
61 FsUtils.delete(jshBase);
62 }
63 Files.createDirectories(jshBase);
64 jshLinkedDir = cmsState.getStatePath(JShellClient.JSH);
65 if (Files.exists(jshLinkedDir)) {
66 log.warn(jshLinkedDir + " already exists, deleting it...");
67 FsUtils.delete(jshLinkedDir);
68 }
69 Files.createSymbolicLink(jshLinkedDir, jshBase);
70
71 jtermBase = stateRunDir.resolve(JShellClient.JTERM);
72 if (Files.exists(jtermBase)) {
73 log.warn(jtermBase + " already exists, deleting it...");
74 FsUtils.delete(jtermBase);
75 }
76 Files.createDirectories(jtermBase);
77 jtermLinkedDir = cmsState.getStatePath(JShellClient.JTERM);
78 if (Files.exists(jtermLinkedDir)) {
79 log.warn(jtermLinkedDir + " already exists, deleting it...");
80 FsUtils.delete(jtermLinkedDir);
81 }
82 Files.createSymbolicLink(jtermLinkedDir, jtermBase);
83
84 log.info("Local JShell on " + jshBase + ", linked to " + jshLinkedDir);
85 log.info("Local JTerm on " + jtermBase + ", linked to " + jtermLinkedDir);
86
87 new Thread(() -> {
88 try {
89 watchService = FileSystems.getDefault().newWatchService();
90
91 jshBase.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
92 StandardWatchEventKinds.ENTRY_DELETE);
93 try (DirectoryStream<Path> bundleSns = Files.newDirectoryStream(jshBase)) {
94 for (Path bundleSnDir : bundleSns) {
95 addBundleSnDir(bundleSnDir, watchService);
96 }
97 }
98 jtermBase.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
99 StandardWatchEventKinds.ENTRY_DELETE);
100 try (DirectoryStream<Path> bundleSns = Files.newDirectoryStream(jtermBase)) {
101 for (Path bundleSnDir : bundleSns) {
102 addBundleSnDir(bundleSnDir, watchService);
103 }
104 }
105
106 WatchKey key;
107 while ((key = watchService.take()) != null) {
108 events: for (WatchEvent<?> event : key.pollEvents()) {
109 // System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context() + ".");
110 Path parent = (Path) key.watchable();
111 // sessions
112 if (Files.isSameFile(jshBase, parent)) {
113 Path bundleSnDir = jshBase.resolve((Path) event.context());
114 if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
115 addBundleSnDir(bundleSnDir, watchService);
116 } else if (StandardWatchEventKinds.ENTRY_DELETE.equals(event.kind())) {
117 }
118 } else if (Files.isSameFile(jtermBase, parent)) {
119 Path bundleSnDir = jtermBase.resolve((Path) event.context());
120 if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
121 addBundleSnDir(bundleSnDir, watchService);
122 } else if (StandardWatchEventKinds.ENTRY_DELETE.equals(event.kind())) {
123 }
124 } else {
125 Path path = parent.resolve((Path) event.context());
126 if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
127 if (!Files.isDirectory(path)) {
128 log.warn("Ignoring " + path + " as it is not a directory");
129 continue events;
130 }
131 try {
132 UUID.fromString(path.getFileName().toString());
133 } catch (IllegalArgumentException e) {
134 log.warn("Ignoring " + path + " as it is not named as UUID");
135 continue events;
136 }
137
138 boolean interactive;
139 if (Files.isSameFile(jshBase, parent.getParent())) {
140 interactive = false;
141 } else if (Files.isSameFile(jtermBase, parent.getParent())) {
142 interactive = true;
143 } else {
144 log.warn("Ignoring " + path + " as we don't know whether it is interactive or not");
145 continue events;
146 }
147 Path bundleIdDir = bundleDirs.get(parent);
148 LocalJShellSession localSession = new LocalJShellSession(path, bundleIdDir,
149 interactive);
150 localSessions.put(path, localSession);
151 } else if (StandardWatchEventKinds.ENTRY_DELETE.equals(event.kind())) {
152 localSessions.remove(path);
153 }
154 }
155 }
156 key.reset();
157 }
158 } catch (ClosedWatchServiceException e) {
159 if (log.isTraceEnabled())
160 log.trace("JShell file watch service was closed");
161 } catch (IOException | InterruptedException e) {
162 log.error("Unexpected exception in JShell file watch service", e);
163 }
164 }, "JShell local sessions watcher").start();
165 }
166
167 private void addBundleSnDir(Path bundleSnDir, WatchService watchService) throws IOException {
168 String symbolicName = bundleSnDir.getFileName().toString();
169 Bundle fromBundle = OsgiExecutionControlProvider.getBundleFromSn(symbolicName);
170 if (fromBundle == null) {
171 log.error("Removing directory for bundle " + symbolicName + " because it was not found in runtime...");
172 FsUtils.delete(bundleSnDir);
173 return;
174 }
175 Long bundleId = fromBundle.getBundleId();
176 Path bundleIdDir = stateRunDir.resolve(bundleId.toString());
177 Files.createDirectories(bundleIdDir);
178 bundleDirs.put(bundleSnDir, bundleIdDir);
179
180 bundleSnDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE);
181 }
182
183 public void stop() {
184 if (watchService != null)
185 try {
186 watchService.close();
187 } catch (IOException e) {
188 log.error("Cannot close JShell watch service", e);
189 }
190 try {
191 Files.delete(jshLinkedDir);
192 } catch (IOException e) {
193 log.error("Cannot remove " + jshLinkedDir, e);
194 }
195 try {
196 FsUtils.delete(jshBase);
197 } catch (IOException e) {
198 log.error("Cannot remove " + jshBase, e);
199 }
200 try {
201 Files.delete(jtermLinkedDir);
202 } catch (IOException e) {
203 log.error("Cannot remove " + jtermLinkedDir, e);
204 }
205 try {
206 FsUtils.delete(jtermBase);
207 } catch (IOException e) {
208 log.error("Cannot remove " + jtermBase, e);
209 }
210 }
211
212 public void setCmsState(CmsState cmsState) {
213 this.cmsState = cmsState;
214 }
215 }