Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
aQute.launcher.plugin.ProjectLauncherImpl Maven / Gradle / Ivy
package aQute.launcher.plugin;
import static java.util.stream.Collectors.joining;
import java.io.DataInput;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.osgi.annotation.bundle.Header;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.build.Container;
import aQute.bnd.build.Project;
import aQute.bnd.build.ProjectLauncher;
import aQute.bnd.header.Attrs;
import aQute.bnd.help.instructions.LauncherInstructions.Executable;
import aQute.bnd.help.instructions.LauncherInstructions.RunOption;
import aQute.bnd.osgi.Builder;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Domain;
import aQute.bnd.osgi.EmbeddedResource;
import aQute.bnd.osgi.FileResource;
import aQute.bnd.osgi.Instruction;
import aQute.bnd.osgi.Instructions;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Jar.Compression;
import aQute.bnd.osgi.JarResource;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.PropertiesResource;
import aQute.bnd.osgi.Resource;
import aQute.bnd.osgi.Verifier;
import aQute.launcher.constants.LauncherConstants;
import aQute.lib.collections.MultiMap;
import aQute.lib.io.ByteBufferDataInput;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.libg.cryptography.SHA1;
import aQute.libg.glob.Glob;
@Header(name = Constants.LAUNCHER_PLUGIN, value = "${@class}")
public class ProjectLauncherImpl extends ProjectLauncher {
private final static Logger logger = LoggerFactory.getLogger(ProjectLauncherImpl.class);
private static final String EMBEDDED_RUNPATH = "Embedded-Runpath";
private static final String LAUNCHER_PATH = "launcher.runpath";
private static final String EMBEDDED_LAUNCHER = "aQute.launcher.pre.EmbeddedLauncher";
static final String PRE_JAR = "biz.aQute.launcher.pre.jar";
private final Container container;
private final List launcherpath = new ArrayList<>();
private File preTemp;
final private File launchPropertiesFile;
boolean prepared;
DatagramSocket listenerComms;
public ProjectLauncherImpl(Project project, Container container) throws Exception {
super(project);
this.container = container;
logger.debug("created a aQute launcher plugin");
launchPropertiesFile = File.createTempFile("launch", ".properties", project.getTarget());
logger.debug("launcher plugin using temp launch file {}", launchPropertiesFile.getAbsolutePath());
addRunVM("-D" + LauncherConstants.LAUNCHER_PROPERTIES + "=" + launchPropertiesFile.getAbsolutePath());
if (project.getRunProperties()
.get("noframework") != null) {
setRunFramework(NONE);
project.warning(
"The noframework property in -runproperties is replaced by a project setting: '-runframework: none'");
}
}
@Override
protected void updateFromProject() throws Exception {
super.updateFromProject();
File launcher = container.getFile();
// Pre file will likely be next to launcher in embedded repo
File pre = new File(launcher.getParentFile(), PRE_JAR);
if (!pre.isFile()) {
if (preTemp != null) {
IO.delete(preTemp);
}
preTemp = pre = File.createTempFile("pre", ".jar");
try (Jar jar = new Jar(launcher)) {
Resource embeddedPre = jar.getResource(PRE_JAR);
try (OutputStream out = IO.outputStream(pre)) {
embeddedPre.write(out);
}
}
}
launcherpath.clear();
addClasspath(new Container(getProject(), pre), launcherpath);
// Make sure the launcher is on the runpath
addClasspath(container);
}
//
// Initialize the main class for a local launch start
//
@Override
protected int invoke(Class> main, String args[]) throws Exception {
LauncherConstants lc = getConstants(getRunBundles(), false);
Method mainMethod = main.getMethod("main", args.getClass(), Properties.class);
Object o = mainMethod.invoke(null, args, lc.getProperties(new UTF8Properties()));
if (o == null)
return 0;
return (Integer) o;
}
/**
* Cleanup the properties file. Is called after the process terminates.
*/
@Override
public void cleanup() {
IO.delete(launchPropertiesFile);
if (listenerComms != null) {
listenerComms.close();
listenerComms = null;
}
if (preTemp != null) {
IO.delete(preTemp);
preTemp = null;
}
prepared = false;
logger.debug("Deleted {}", launchPropertiesFile.getAbsolutePath());
super.cleanup();
}
@Override
public String getMainTypeName() {
Instructions instructions = new Instructions(getProject().getProperty(Constants.REMOVEHEADERS));
if (!instructions.isEmpty() && instructions.matches(MAIN_CLASS)) {
return EMBEDDED_LAUNCHER;
}
return getProject().getProperty(MAIN_CLASS, EMBEDDED_LAUNCHER);
}
@Override
public void update() throws Exception {
super.update();
writeProperties();
}
/**
* We override getClasspath to use it just for the embedded launcher.
*/
@Override
public Collection getClasspath() {
return launcherpath;
}
/**
* We override getRunVM to use it to add the runpath to the JVM execution as
* a system property to be processed by the embedded launcher.
*/
@Override
public Collection getRunVM() {
List list = new ArrayList<>(super.getRunVM());
list.add(getRunpath().stream()
.collect(Strings.joining(",", "-D" + LAUNCHER_PATH + "=", "", "")));
return list;
}
@Override
public int launch() throws Exception {
prepare();
return super.launch();
}
@Override
public void prepare() throws Exception {
if (prepared)
return;
prepared = true;
super.prepare();
writeProperties();
}
void writeProperties() throws Exception {
LauncherConstants lc = getConstants(getRunBundles(), false);
try (OutputStream out = IO.outputStream(launchPropertiesFile)) {
lc.getProperties(new UTF8Properties())
.store(out, "Launching " + getProject());
}
}
/**
* @throws Exception
* @throws FileNotFoundException
* @throws IOException
*/
private LauncherConstants getConstants(Collection runbundles, boolean exported)
throws Exception, FileNotFoundException, IOException {
logger.debug("preparing the aQute launcher plugin");
LauncherConstants lc = new LauncherConstants();
lc.noreferences = getProject().is(Constants.RUNNOREFERENCES);
lc.runProperties = getRunProperties();
lc.storageDir = getStorageDir();
lc.keep = isKeep();
lc.runbundles.addAll(runbundles);
lc.trace = getTrace();
lc.timeout = getTimeout();
lc.services = getRunFramework() == SERVICES ? true : false;
lc.activators.addAll(getActivators());
lc.name = getProject().getName();
lc.activationEager = launcherInstrs.runoptions()
.contains(RunOption.eager);
lc.frameworkRestart = isRunFrameworkRestart();
if (!exported && !getNotificationListeners().isEmpty()) {
if (listenerComms == null) {
listenerComms = new DatagramSocket(new InetSocketAddress(InetAddress.getByName(null), 0));
new Thread(() -> {
DatagramSocket socket = listenerComms;
DatagramPacket packet = new DatagramPacket(new byte[65536], 65536);
while (!socket.isClosed()) {
try {
socket.receive(packet);
ByteBuffer bb = ByteBuffer.wrap(packet.getData(), packet.getOffset(), packet.getLength());
DataInput dai = ByteBufferDataInput.wrap(bb);
NotificationType type = NotificationType.values()[dai.readInt()];
String message = dai.readUTF();
if ((type == NotificationType.ERROR) && bb.hasRemaining()) {
message += "\n" + dai.readUTF();
}
for (NotificationListener listener : getNotificationListeners()) {
listener.notify(type, message);
}
} catch (IOException e) {}
}
}).start();
}
lc.notificationPort = listenerComms.getLocalPort();
} else {
lc.notificationPort = -1;
}
try {
// If the workspace contains a newer version of biz.aQute.launcher
// than the version of bnd(tools) used
// then this could throw NoSuchMethodError. For now just ignore it.
Map> systemPkgs = getSystemPackages();
if (systemPkgs != null && !systemPkgs.isEmpty())
lc.systemPackages = Processor.printClauses(systemPkgs);
} catch (Throwable e) {}
try {
// If the workspace contains a newer version of biz.aQute.launcher
// than the version of bnd(tools) used
// then this could throw NoSuchMethodError. For now just ignore it.
String systemCaps = getSystemCapabilities();
if (systemCaps != null) {
systemCaps = systemCaps.trim();
if (systemCaps.length() > 0)
lc.systemCapabilities = systemCaps;
}
} catch (Throwable e) {}
return lc;
}
/**
* Create a standalone executable. All entries on the runpath are rolled out
* into the JAR and the runbundles are copied to a directory in the jar. The
* launcher will see that it starts in embedded mode and will automatically
* detect that it should load the bundles from inside. This is driven by the
* launcher.embedded flag.
*/
@Override
public Jar executable() throws Exception {
Executable instrs = launcherInstrs.executable();
Optional rejar = instrs.rejar();
logger.debug("rejar {}", rejar);
Map> strip = extractStripMapping(instrs.strip());
logger.debug("strip {}", strip);
try (Builder builder = new Builder()) {
Project project = getProject();
builder.setBase(project.getBase());
Jar jar = new Jar(project.getName());
builder.setJar(jar);
builder.addClasspath(jar);
// Use properties from the project
copyProperties(project::getProperty, builder::setProperty,
// jar properties
Constants.COMPRESSION, Constants.REPRODUCIBLE, Constants.DIGESTS,
// jpms properties
Constants.JPMS_MODULE_INFO, Constants.JPMS_MODULE_INFO_OPTIONS, Constants.AUTOMATIC_MODULE_NAME);
copyProperties(project::mergeProperties, builder::setProperty,
// include resource properties
Constants.INCLUDERESOURCE, Constants.INCLUDE_RESOURCE);
List classpath = new ArrayList<>();
List runpath = getRunpath();
for (String path : runpath) {
logger.debug("embedding runpath {}", path);
File file = getOriginalFile(path);
if (file.isFile()) {
String newPath = nonCollidingPath(file, jar, null);
jar.putResource(newPath, getJarFileResource(file, rejar, strip));
classpath.add(newPath);
}
}
// Copy the bundles to the JAR
List actualPaths = new ArrayList<>();
Collection runbundles = getRunBundles();
for (String path : runbundles) {
logger.debug("embedding run bundles {}", path);
File file = getOriginalFile(path);
if (!file.isFile())
project.error("Invalid entry in -runbundles %s", file);
else {
String newPath = nonCollidingPath(file, jar, instrs.location());
jar.putResource(newPath, getJarFileResource(file, rejar, strip));
actualPaths.add(newPath);
}
}
String moduleProperty = builder.getProperty(JPMS_MODULE_INFO);
if (moduleProperty != null) {
List bcpList = new ArrayList<>();
bcpList.add(".");
bcpList.addAll(classpath);
bcpList.addAll(actualPaths);
builder.setProperty(BUNDLE_CLASSPATH, bcpList.stream()
.collect(joining(",")));
builder.setProperty(EXPORT_CONTENTS, "aQute.launcher.pre");
// Ignore common warnings resulting from the Bundle-ClassPath
// which
// are irrelevant in this use case
builder.setProperty(FIXUPMESSAGES,
"Classes found in the wrong directory, private references, Export-Package duplicate package name, Invalid package name: * in Export-Package");
}
LauncherConstants lc = getConstants(actualPaths, true);
lc.embedded = true;
jar.putResource(LauncherConstants.DEFAULT_LAUNCHER_PROPERTIES,
new PropertiesResource(lc.getProperties(new UTF8Properties())));
Properties flattenedProperties = project.getFlattenedProperties();
Instructions instructions = new Instructions(project.getProperty(Constants.REMOVEHEADERS));
Collection result = instructions.select(flattenedProperties.keySet(), false);
flattenedProperties.keySet()
.removeAll(result);
Manifest manifest = new Manifest();
Attributes main = manifest.getMainAttributes();
main.putValue(Constants.MAIN_CLASS, EMBEDDED_LAUNCHER);
main.putValue(EMBEDDED_RUNPATH, join(classpath));
for (Entry e : flattenedProperties.entrySet()) {
String key = (String) e.getKey();
String value = (String) e.getValue();
if (Strings.nonNullOrEmpty(key) && Character.isUpperCase(key.charAt(0))
&& Strings.nonNullOrEmpty(value)) {
main.putValue(key, value.trim());
}
}
Resource preJar = Resource.fromURL(this.getClass()
.getResource("/" + PRE_JAR));
try (Jar pre = Jar.fromResource("pre", preJar)) {
jar.addAll(pre, new Instruction("!{META-INF,OSGI-OPT}/*"));
}
String embeddedLauncherName = main.getValue(Constants.MAIN_CLASS);
logger.debug("Use '{}' launcher class", embeddedLauncherName);
// jpms properties
builder.setProperty(Constants.MAIN_CLASS, embeddedLauncherName);
doStart(jar, embeddedLauncherName);
jar = builder.build();
// overwrite calculated information
jar.setName(project.getName());
jar.setManifest(manifest);
project.getInfo(builder);
cleanup();
builder.removeClose(jar); // detach jar from builder
return jar;
}
}
private void copyProperties(Function getProperty, BiConsumer setProperty,
String... keys) {
for (String key : keys) {
String value = getProperty.apply(key);
if (Strings.nonNullOrEmpty(value)) {
setProperty.accept(key, value);
}
}
}
private Map> extractStripMapping(List strip) {
MultiMap map = new MultiMap<>();
for (String s : strip) {
int n = s.indexOf(':');
Glob key = Glob.ALL;
if (n > 0) {
key = new Glob(s.substring(0, n));
}
Glob value = new Glob(s.substring(n + 1));
map.add(key, value);
}
return map;
}
private Resource getJarFileResource(File file, Optional compression, Map> strip)
throws IOException {
if (strip.isEmpty() && !compression.isPresent()) {
return new FileResource(file);
}
Jar jar = new Jar(file);
jar.setDoNotTouchManifest();
compression.ifPresent(jar::setCompression);
logger.debug("compression {}", compression);
stripContent(strip, jar);
JarResource resource = new JarResource(jar, true);
return resource;
}
private void stripContent(Map> strip, Jar jar) {
Set remove = new HashSet<>();
for (Map.Entry> e : strip.entrySet()) {
Glob fileMatch = e.getKey();
if (!fileMatch.matcher(jar.getName())
.matches()) {
continue;
}
logger.debug("strip {}", e.getValue());
List value = e.getValue();
for (String path : jar.getResources()
.keySet()) {
if (Glob.in(value, path)) {
logger.debug("strip {}", path);
remove.add(path);
}
}
}
remove.forEach(jar::remove);
logger.debug("resources {}", Strings.join("\n", jar.getResources()
.keySet()));
}
File getOriginalFile(String path) {
File file = new File(path);
if (file.getName()
.startsWith("+") && file.exists()) { // file has source attached
File originalFile = new File(file.getParentFile(), file.getName()
.substring(1));
if (originalFile.exists()) {
return originalFile;
}
}
return file;
}
String nonCollidingPath(File file, Jar jar, String locationFormat) throws Exception {
if (locationFormat == null) {
String fileName = file.getName();
String path = "jar/" + fileName;
Resource resource = jar.getResource(path);
if (resource != null) {
if ((file.length() == resource.size()) && (SHA1.digest(file)
.equals(SHA1.digest(resource.openInputStream())))) {
return path; // same resource
}
String[] parts = Strings.extension(fileName);
if (parts == null) {
parts = new String[] {
fileName, ""
};
}
for (int i = 1; jar.exists(path); i++) {
path = String.format("jar/%s[%d].%s", parts[0], i, parts[1]);
}
}
return path;
}
try {
Domain domain = Domain.domain(file);
Entry bundleSymbolicName = domain.getBundleSymbolicName();
if (bundleSymbolicName == null) {
warning("Cannot find bsn in %s, required because it is on the -runbundles", file);
return nonCollidingPath(file, jar, null);
}
String bundleVersion = domain.getBundleVersion();
if (bundleVersion == null)
bundleVersion = "0";
else {
if (!Verifier.isVersion(bundleVersion)) {
error("Invalid bundle version in %s", file);
return nonCollidingPath(file, jar, null);
}
}
try (Processor p = new Processor(this)) {
p.setProperty("@bsn", bundleSymbolicName.getKey());
p.setProperty("@version", bundleVersion);
p.setProperty("@name", file.getName());
String fileName = p.getReplacer()
.process(locationFormat);
if (fileName.contains("/")) {
error("Invalid bundle version in %s", file);
return nonCollidingPath(file, jar, null);
}
String filePath = "jar/" + fileName;
if (jar.getResources()
.containsKey(filePath)) {
error("Duplicate locations for %s for file %s", filePath, file);
}
return filePath;
}
} catch (Exception e) {
exception(e, "failed to use location pattern %s for location for file %", locationFormat, file);
return nonCollidingPath(file, jar, null);
}
}
/*
* Useful for when exported as folder or unzipped
*/
void doStart(Jar jar, String fqn) throws UnsupportedEncodingException {
Collection arguments = getProject().getMergedParameters(RUNVM)
.keyList();
Collection progArgs = getProject().getMergedParameters(RUNPROGRAMARGS)
.keyList();
String nix = "#!/bin/sh\njava -cp . " + renderArguments(arguments, false) + " " + fqn
+ (progArgs.isEmpty() ? "" : " " + renderArguments(progArgs, false)) + "\n";
String pc = "java -cp . " + renderArguments(arguments, true) + " " + fqn
+ (progArgs.isEmpty() ? "" : " " + renderArguments(progArgs, true)) + "\r\n";
jar.putResource("start", new EmbeddedResource(nix, 0L));
jar.putResource("start.bat", new EmbeddedResource(pc, 0L));
}
}