]> git.argeo.org Git - lgpl/argeo-commons.git/blob - jshell/osgi/OsgiExecutionControlProvider.java
Prepare next development cycle
[lgpl/argeo-commons.git] / 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.FrameworkUtil;
26 import org.osgi.framework.Version;
27 import org.osgi.framework.namespace.PackageNamespace;
28 import org.osgi.framework.wiring.BundleRevision;
29 import org.osgi.framework.wiring.BundleWire;
30 import org.osgi.framework.wiring.BundleWiring;
31
32 import jdk.jshell.spi.ExecutionControl;
33 import jdk.jshell.spi.ExecutionControlProvider;
34 import jdk.jshell.spi.ExecutionEnv;
35
36 public class OsgiExecutionControlProvider implements ExecutionControlProvider {
37 private final static CmsLog log = CmsLog.getLog(OsgiExecutionControlProvider.class);
38
39 public final static String PROVIDER_NAME = "osgi";
40 public final static String BUNDLE_PARAMETER = "bundle";
41
42 @Override
43 public String name() {
44 return PROVIDER_NAME;
45 }
46
47 @Override
48 public Map<String, String> defaultParameters() {
49 Map<String, String> defaultParameters = new HashMap<>();
50 defaultParameters.put(BUNDLE_PARAMETER, null);
51 return defaultParameters;
52 }
53
54 @Override
55 public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters) throws Throwable {
56 Long bundleId = Long.parseLong(parameters.get(BUNDLE_PARAMETER));
57 Bundle fromBundle = getBundleFromId(bundleId);
58
59 BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
60 ClassLoader fromBundleClassLoader = fromBundleWiring.getClassLoader();
61
62 ExecutionControl executionControl = new CmsExecutionControl(env,
63 new WrappingLoaderDelegate(env, fromBundleClassLoader));
64 log.trace(() -> "JShell from " + fromBundle.getSymbolicName() + "_" + fromBundle.getVersion() + " ["
65 + fromBundle.getBundleId() + "]");
66 return executionControl;
67 }
68
69 public static Bundle getBundleFromSn(String symbolicName) {
70 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
71 Objects.requireNonNull(symbolicName);
72 NavigableMap<Version, Bundle> bundles = new TreeMap<Version, Bundle>();
73 for (Bundle b : bc.getBundles()) {
74 if (symbolicName.equals(b.getSymbolicName()))
75 bundles.put(b.getVersion(), b);
76 }
77 if (bundles.isEmpty())
78 return null;
79 Bundle fromBundle = bundles.lastEntry().getValue();
80 return fromBundle;
81 }
82
83 public static Bundle getBundleFromId(Long bundleId) {
84 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
85 Bundle fromBundle = bc.getBundle(bundleId);
86 return fromBundle;
87 }
88
89 public static Path getBundleStartupScript(Long bundleId) {
90 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
91 Bundle fromBundle = bc.getBundle(bundleId);
92 Path bundleStartupScript = fromBundle.getDataFile("BUNDLE.jsh").toPath();
93
94 BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
95 ClassLoader fromBundleClassLoader = fromBundleWiring.getClassLoader();
96
97 Set<String> packagesToImport = new TreeSet<>();
98
99 // from bundle packages
100 for (Package pkg : fromBundleClassLoader.getDefinedPackages()) {
101 packagesToImport.add(pkg.getName());
102 }
103
104 List<BundleWire> bundleWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
105 for (BundleWire bw : bundleWires) {
106 packagesToImport.add(bw.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).toString());
107 }
108
109 try (Writer writer = Files.newBufferedWriter(bundleStartupScript, StandardCharsets.UTF_8)) {
110 for (String p : packagesToImport) {
111 writer.write("import " + p + ".*;\n");
112 }
113
114 String std = """
115 /open DEFAULT
116 import jdk.jshell.spi.ExecutionEnv;
117 import java.util.function.*;
118
119 /** Redirected standard IO. */
120 public class Std {
121 final static InputStream in = new Supplier<InputStream>() {
122
123 @Override
124 public InputStream get() {
125 return ((ExecutionEnv) getClass().getClassLoader()).userIn();
126 }
127
128 }.get();
129 final static PrintStream out = new Supplier<PrintStream>() {
130
131 @Override
132 public PrintStream get() {
133 return ((ExecutionEnv) getClass().getClassLoader()).userOut();
134 }
135
136 }.get();
137 final static PrintStream err = new Supplier<PrintStream>() {
138
139 @Override
140 public PrintStream get() {
141 return ((ExecutionEnv) getClass().getClassLoader()).userErr();
142 }
143
144 }.get();
145
146 }
147 """;
148 writer.write(std);
149 } catch (IOException e) {
150 throw new RuntimeException("Cannot writer bundle startup script to " + bundleStartupScript, e);
151 }
152
153 return bundleStartupScript;
154 }
155
156 public static String getBundleClasspath(Long bundleId) throws IOException {
157 String framework = System.getProperty("osgi.framework");
158 Path frameworkLocation = Paths.get(URI.create(framework)).toAbsolutePath();
159 BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
160 Bundle fromBundle = bc.getBundle(bundleId);
161
162 BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
163
164 Set<Bundle> bundlesToAddToCompileClasspath = new TreeSet<>();
165
166 // from bundle
167 bundlesToAddToCompileClasspath.add(fromBundle);
168
169 List<BundleWire> bundleWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
170 for (BundleWire bw : bundleWires) {
171 bundlesToAddToCompileClasspath.add(bw.getProviderWiring().getBundle());
172 }
173
174 StringJoiner classpath = new StringJoiner(File.pathSeparator);
175 bundles: for (Bundle b : bundlesToAddToCompileClasspath) {
176 if (b.getBundleId() == 0) {// system bundle
177 classpath.add(frameworkLocation.toString());
178 continue bundles;
179 }
180 Path p = bundleToPath(frameworkLocation, b);
181 classpath.add(p.toString());
182 }
183
184 return classpath.toString();
185 }
186
187 static Path bundleToPath(Path frameworkLocation, Bundle bundle) throws IOException {
188 String location = bundle.getLocation();
189 if (location.startsWith("initial@reference:file:")) {
190 location = location.substring("initial@reference:file:".length());
191 Path p = frameworkLocation.getParent().resolve(location).toRealPath();
192 // TODO load dev.properties from OSGi configuration directory
193 if (Files.isDirectory(p))
194 p = p.resolve("bin");
195 return p;
196 }
197 Path p = Paths.get(location);
198 return p;
199 }
200 }