
aQute.bnd.build.WorkspaceExternalPluginHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of biz.aQute.bndlib Show documentation
Show all versions of biz.aQute.bndlib Show documentation
bndlib: A Swiss Army Knife for OSGi
The newest version!
package aQute.bnd.build;
import static java.util.Objects.requireNonNull;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.osgi.framework.Version;
import org.osgi.framework.VersionRange;
import org.osgi.resource.Capability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.exceptions.FunctionWithException;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.Parameters;
import aQute.bnd.memoize.Memoize;
import aQute.bnd.osgi.Descriptors;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.Processor.CL;
import aQute.bnd.osgi.Resource;
import aQute.bnd.osgi.resource.MainClassNamespace;
import aQute.bnd.result.Result;
import aQute.bnd.service.Plugin;
import aQute.bnd.service.RegistryPlugin;
import aQute.bnd.service.externalplugin.ExternalPluginNamespace;
import aQute.bnd.service.progress.ProgressPlugin.Task;
import aQute.bnd.service.progress.TaskManager;
import aQute.bnd.version.MavenVersion;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import aQute.libg.command.Command;
public class WorkspaceExternalPluginHandler implements AutoCloseable {
final static Logger logger = LoggerFactory.getLogger("aQute.bnd.build");
final static Method close = getMethod(AutoCloseable.class, "close");
final Workspace workspace;
final Map loaders = new HashMap<>();
WorkspaceExternalPluginHandler(Workspace workspace) {
this.workspace = workspace;
}
public Result call(String pluginName, Class c, FunctionWithException> f) {
return call(pluginName, null, c, f);
}
public Result call(String pluginName, VersionRange range, Class c,
FunctionWithException> f) {
try {
String filter = ExternalPluginNamespace.filter(pluginName, c);
List caps = workspace.findProviders(ExternalPluginNamespace.EXTERNAL_PLUGIN_NAMESPACE, filter)
.sorted(this::sort)
.collect(Collectors.toList());
if (caps.isEmpty())
return Result.err("no such plugin %s for type %s", pluginName, c.getName());
Capability cap = caps.get(0);
Result bundle = workspace.getBundle(cap.getResource());
if (bundle.isErr())
return bundle.asError();
String className = ExternalPluginNamespace.getImplementation(cap);
if (className == null)
return Result.err("no proper class attribute in plugin capability %s is %s", pluginName, cap);
URL url = bundle.unwrap()
.toURI()
.toURL();
try (URLClassLoader cl = new URLClassLoader(new URL[] {
url
}, WorkspaceExternalPluginHandler.class.getClassLoader())) {
@SuppressWarnings("unchecked")
Class> impl = cl.loadClass(className);
T instance = c.cast(impl.getConstructor()
.newInstance());
try {
return f.apply(instance);
} catch (Exception e) {
return Result.err("external plugin '%s' failed with: %s", pluginName, Exceptions.causes(e));
}
}
} catch (ClassNotFoundException e) {
return Result.err("no such class %s in %s for plugin %s", c.getName(), e.getMessage(), pluginName);
} catch (Exception e) {
return Result.err("could not instantiate class %s in %s for plugin %s: %s", c.getName(), e.getMessage(),
pluginName, Exceptions.causes(e));
}
}
public Result call(String mainClass, VersionRange range, Processor context, Map attrs,
List args, InputStream stdin, OutputStream stdout, OutputStream stderr) {
List cp = new ArrayList<>();
try {
String mainClassBin = Descriptors.fqnClassToBinary(mainClass);
boolean mainClassPresent = false;
Parameters cpp = new Parameters(attrs.get("classpath"));
for (Map.Entry e : cpp.entrySet()) {
String v = e.getValue()
.getVersion();
MavenVersion mv = MavenVersion.parseMavenString(v);
Result result = workspace.getBundle(e.getKey(), mv.getOSGiVersion(), null);
if (result.isErr())
return result.asError();
try (Jar jar = new Jar(result.unwrap())) {
Resource resource = jar.getResource(mainClassBin);
if (resource != null) {
mainClassPresent = true;
}
}
cp.add(result.unwrap());
}
String filter = MainClassNamespace.filter(mainClass, range);
List caps = workspace.findProviders(MainClassNamespace.MAINCLASS_NAMESPACE, filter)
.sorted(this::sort)
.collect(Collectors.toList());
if (caps.isEmpty()) {
if (!mainClassPresent)
return Result.err("no bundle found with main class %s", mainClass);
} else {
Capability cap = caps.get(0);
Result bundle = workspace.getBundle(cap.getResource());
if (bundle.isErr())
return bundle.asError();
cp.add(bundle.unwrap());
}
Command c = new Command();
c.setTrace();
File cwd = context.getBase();
String workingdir = attrs.get("workingdir");
if (workingdir != null) {
cwd = context.getFile(workingdir);
cwd.mkdirs();
if (!cwd.isDirectory()) {
return Result.err("Working dir set to %s but cannot make it a directory", cwd);
}
}
c.setCwd(cwd);
c.setTimeout(1, TimeUnit.MINUTES);
c.add(context.getProperty("java", IO.getJavaExecutablePath("java")));
c.add("-cp");
String classpath = Strings.join(File.pathSeparator, cp);
c.add(classpath);
c.add(mainClass);
for (String arg : args) {
c.add(arg);
}
int exitCode = TaskManager.with(getTask(c), () -> {
PrintWriter lstdout = IO.writer(stdout == null ? System.out : stdout);
PrintWriter lstderr = IO.writer(stderr == null ? System.err : stderr);
try {
return c.execute(stdin, lstdout, lstderr);
} finally {
lstdout.flush();
lstderr.flush();
}
});
return Result.ok(exitCode);
} catch (Exception e) {
return Result.err("Failed with: %s", Exceptions.causes(e));
}
}
private Task getTask(Command c) {
return new Task() {
private boolean canceled;
@Override
public void worked(int units) {}
@Override
public void done(String message, Throwable e) {}
@Override
public boolean isCanceled() {
return canceled;
}
@Override
public void abort() {
this.canceled = true;
c.cancel();
}
};
}
@Override
public void close() {
loaders.values()
.forEach(IO::close);
}
/**
* Returns list of external plugin proxies that implement the given
* interface. The proxies will load the actual plugin on demand when used.
* That is, the plugins will be quite cheap unless used.
*
* @param interf the interface listed in `-plugin`.
* @param attrs the attributes from the that interface, the name specifies
* the name of the plugin, wildcards allowed
* @return a list of plugins loaded from the external plugin set
*/
public Result> getImplementations(Class interf, Attrs attrs) {
assert interf.isInterface();
try {
String v = attrs.getVersion();
VersionRange r = null;
if (v != null) {
r = VersionRange.valueOf(v);
}
String filter = ExternalPluginNamespace.filter(attrs.getOrDefault("name", "*"), interf, r);
List externalCapabilities = workspace
.findProviders(ExternalPluginNamespace.EXTERNAL_PLUGIN_NAMESPACE, filter)
.sorted(this::sort)
.collect(Collectors.toList());
Class>[] interfaces = new Class[] {
interf, AutoCloseable.class, Plugin.class, RegistryPlugin.class
};
ClassLoader loader = new ProxyClassLoader(interf.getClassLoader(), interfaces);
List plugins = new ArrayList<>();
for (Capability c : externalCapabilities) {
Memoize
© 2015 - 2025 Weber Informatics LLC | Privacy Policy