]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.jshell/src/org/argeo/internal/cms/jshell/osgi/OsgiExecutionControlProvider.java
Merge tag 'v2.3.23' into testing
[lgpl/argeo-commons.git] / org.argeo.cms.jshell / src / org / argeo / internal / cms / jshell / osgi / OsgiExecutionControlProvider.java
1 package org.argeo.internal.cms.jshell.osgi;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.Writer;
6 import java.net.URI;
7 import java.nio.charset.StandardCharsets;
8 import java.nio.file.Files;
9 import java.nio.file.Path;
10 import java.nio.file.Paths;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.NavigableMap;
15 import java.util.Objects;
16 import java.util.Set;
17 import java.util.StringJoiner;
18 import java.util.TreeMap;
19 import java.util.TreeSet;
20
21 import org.argeo.api.cms.CmsLog;
22 import org.argeo.cms.jshell.CmsExecutionControl;
23 import org.osgi.framework.Bundle;
24 import org.osgi.framework.BundleContext;
25 import org.osgi.framework.BundleException;
26 import org.osgi.framework.FrameworkUtil;
27 import org.osgi.framework.Version;
28 import org.osgi.framework.namespace.PackageNamespace;
29 import org.osgi.framework.wiring.BundleRevision;
30 import org.osgi.framework.wiring.BundleWire;
31 import org.osgi.framework.wiring.BundleWiring;
32
33 import jdk.jshell.spi.ExecutionControl;
34 import jdk.jshell.spi.ExecutionControlProvider;
35 import jdk.jshell.spi.ExecutionEnv;
36
37 public class OsgiExecutionControlProvider implements ExecutionControlProvider {
38 private final static CmsLog log = CmsLog.getLog(OsgiExecutionControlProvider.class);
39
40 public final static String PROVIDER_NAME = "osgi";
41 public final static String BUNDLE_PARAMETER = "bundle";
42
43 @Override
44 public String name() {
45 return PROVIDER_NAME;
46 }
47
48 @Override
49 public Map<String, String> defaultParameters() {
50 Map<String, String> defaultParameters = new HashMap<>();
51 defaultParameters.put(BUNDLE_PARAMETER, null);
52 return defaultParameters;
53 }
54
55 @Override
56 public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters) throws Throwable {
57 Long bundleId = Long.parseLong(parameters.get(BUNDLE_PARAMETER));
58 Bundle fromBundle = getBundleFromId(bundleId);
59
60 BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
61 ClassLoader fromBundleClassLoader = fromBundleWiring.getClassLoader();
62
63 ExecutionControl executionControl = new CmsExecutionControl(env,
64 new WrappingLoaderDelegate(env, fromBundleClassLoader));
65 log.trace(() -> "JShell from " + fromBundle.getSymbolicName() + "_" + fromBundle.getVersion() + " ["
66 + fromBundle.getBundleId() + "]");
67 return executionControl;
68 }
69
70 public static Bundle getBundleFromSn(String symbolicName) {
71 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
72 Objects.requireNonNull(symbolicName);
73 NavigableMap<Version, Bundle> bundles = new TreeMap<Version, Bundle>();
74 for (Bundle b : bc.getBundles()) {
75 if (symbolicName.equals(b.getSymbolicName()))
76 bundles.put(b.getVersion(), b);
77 }
78 if (bundles.isEmpty())
79 return null;
80 Bundle fromBundle = bundles.lastEntry().getValue();
81 return fromBundle;
82 }
83
84 public static Bundle getBundleFromId(Long bundleId) {
85 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
86 Bundle fromBundle = bc.getBundle(bundleId);
87 return fromBundle;
88 }
89
90 public static Path getBundleStartupScript(Long bundleId) {
91 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
92 Bundle fromBundle = bc.getBundle(bundleId);
93
94 int bundleState = fromBundle.getState();
95 if (Bundle.INSTALLED == bundleState)
96 throw new IllegalStateException("Bundle " + fromBundle.getSymbolicName() + " is not resolved");
97 if (Bundle.RESOLVED == bundleState) {
98 try {
99 fromBundle.start();
100 } catch (BundleException e) {
101 throw new IllegalStateException("Cannot start bundle " + fromBundle.getSymbolicName(), e);
102 }
103 while (Bundle.ACTIVE != fromBundle.getState())
104 try {
105 Thread.sleep(100);
106 } catch (InterruptedException e) {
107 // we assume the session has been closed
108 throw new RuntimeException("Bundle " + fromBundle.getSymbolicName() + " is not active", e);
109 }
110 }
111
112 Path bundleStartupScript = fromBundle.getDataFile("BUNDLE.jsh").toPath();
113
114 BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
115 ClassLoader fromBundleClassLoader = fromBundleWiring.getClassLoader();
116
117 Set<String> packagesToImport = new TreeSet<>();
118
119 // from bundle packages
120 for (Package pkg : fromBundleClassLoader.getDefinedPackages()) {
121 packagesToImport.add(pkg.getName());
122 }
123
124 // List<BundleWire> exportedWires = fromBundleWiring.getProvidedWires(BundleRevision.PACKAGE_NAMESPACE);
125 // for (BundleWire bw : exportedWires) {
126 // packagesToImport.add(bw.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).toString());
127 // }
128
129 List<BundleWire> importedWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
130 for (BundleWire bw : importedWires) {
131 packagesToImport.add(bw.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).toString());
132 }
133
134 try (Writer writer = Files.newBufferedWriter(bundleStartupScript, StandardCharsets.UTF_8)) {
135 for (String p : packagesToImport) {
136 writer.write("import " + p + ".*;\n");
137 }
138
139 String std = """
140 /open DEFAULT
141 import jdk.jshell.spi.ExecutionEnv;
142 import java.util.function.*;
143
144 /** Redirected standard IO. */
145 public class Std {
146 final static InputStream in = new Supplier<InputStream>() {
147
148 @Override
149 public InputStream get() {
150 return ((ExecutionEnv) getClass().getClassLoader()).userIn();
151 }
152
153 }.get();
154 final static PrintStream out = new Supplier<PrintStream>() {
155
156 @Override
157 public PrintStream get() {
158 return ((ExecutionEnv) getClass().getClassLoader()).userOut();
159 }
160
161 }.get();
162 final static PrintStream err = new Supplier<PrintStream>() {
163
164 @Override
165 public PrintStream get() {
166 return ((ExecutionEnv) getClass().getClassLoader()).userErr();
167 }
168
169 }.get();
170
171 }
172 """;
173 writer.write(std);
174 } catch (IOException e) {
175 throw new RuntimeException("Cannot writer bundle startup script to " + bundleStartupScript, e);
176 }
177
178 return bundleStartupScript;
179 }
180
181 public static String getBundleClasspath(Long bundleId) throws IOException {
182 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
183 String framework = bc.getProperty("osgi.framework");
184 Path frameworkLocation = Paths.get(URI.create(framework)).toAbsolutePath();
185 Bundle fromBundle = bc.getBundle(bundleId);
186
187 BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
188
189 Set<Bundle> bundlesToAddToCompileClasspath = new TreeSet<>();
190
191 // from bundle
192 bundlesToAddToCompileClasspath.add(fromBundle);
193
194 List<BundleWire> bundleWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
195 for (BundleWire bw : bundleWires) {
196 bundlesToAddToCompileClasspath.add(bw.getProviderWiring().getBundle());
197 }
198
199 StringJoiner classpath = new StringJoiner(File.pathSeparator);
200 bundles: for (Bundle b : bundlesToAddToCompileClasspath) {
201 if (b.getBundleId() == 0) {// system bundle
202 classpath.add(frameworkLocation.toString());
203 continue bundles;
204 }
205 Path p = bundleToPath(frameworkLocation, b);
206 if (p != null)
207 classpath.add(p.toString());
208 }
209
210 return classpath.toString();
211 }
212
213 static Path bundleToPath(Path frameworkLocation, Bundle bundle) throws IOException {
214 String location = bundle.getLocation();
215 if (location.startsWith("initial@reference:file:")) {
216 location = location.substring("initial@reference:file:".length());
217 Path p = frameworkLocation.getParent().resolve(location).toAbsolutePath();
218 if (Files.exists(p)) {
219 p = p.toRealPath();
220 // TODO load dev.properties from OSGi configuration directory
221 if (Files.isDirectory(p))
222 p = p.resolve("bin");
223 return p;
224 } else {
225 log.warn("Ignore bundle " + p + " as it does not exist");
226 return null;
227 }
228 }
229 Path p = Paths.get(location);
230 return p;
231 }
232 }