blob: 22afe9d589eaa957f06acfffc796dd9c9b8dbb8b [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.osgiwrap;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Builder;
import aQute.bnd.osgi.FileResource;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Resource;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.jar.Manifest;
import static java.nio.file.Files.walkFileTree;
/**
* BND-based wrapper to convert Buck JARs to OSGi-compatible JARs.
*/
public class OSGiWrapper {
private static final String NONE = "NONE";
private String inputJar;
private String outputJar;
private List<String> classpath;
private String bundleName;
private String groupId;
private String bundleSymbolicName;
private String bundleVersion;
private String importPackages;
private String dynamicimportPackages;
private String exportPackages;
private String includeResources;
private Set<String> includedResources = Sets.newHashSet();
private String bundleDescription;
private String bundleLicense;
private String webContext;
// FIXME should consider using Commons CLI, etc.
public static void main(String[] args) {
if (args.length < 12) {
System.err.println("Not enough args");
System.exit(1);
}
String jar = args[0];
String output = args[1];
String cp = args[2];
String name = args[3];
String group = args[4];
String version = args[5];
String license = args[6];
String importPackages = args[7];
String exportPackages = args[8];
String includeResources = args[9];
String webContext = args[10];
String dynamicimportPackages = args[11];
String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 12, args.length));
OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
name, group,
version, license,
importPackages, exportPackages,
includeResources,
webContext,
dynamicimportPackages,
desc);
wrapper.log(wrapper + "\n");
if (!wrapper.execute()) {
System.err.printf("Error generating %s\n", name);
System.exit(2);
}
}
public OSGiWrapper(String inputJar,
String outputJar,
String classpath,
String bundleName,
String groupId,
String bundleVersion,
String bundleLicense,
String importPackages,
String exportPackages,
String includeResources,
String webContext,
String dynamicimportPackages,
String bundleDescription) {
this.inputJar = inputJar;
this.classpath = Lists.newArrayList(classpath.split(":"));
if (!this.classpath.contains(inputJar)) {
this.classpath.add(0, inputJar);
}
this.outputJar = outputJar;
this.bundleName = bundleName;
this.groupId = groupId;
this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
this.bundleVersion = bundleVersion;
this.bundleLicense = bundleLicense;
this.bundleDescription = bundleDescription;
this.importPackages = importPackages;
this.dynamicimportPackages = dynamicimportPackages;
if (Objects.equals(dynamicimportPackages, "''")) {
this.dynamicimportPackages = null;
}
this.exportPackages = exportPackages;
if (!Objects.equals(includeResources, NONE)) {
this.includeResources = includeResources;
}
this.webContext = webContext;
}
private void setProperties(Analyzer analyzer) {
analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
//TODO consider using stricter version policy
//analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
//analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
// There are no good defaults so make sure you set the Import-Package
analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
// TODO include version in export, but not in import
analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
// TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
if (includeResources != null) {
analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
}
if (isWab()) {
analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
analyzer.setProperty("Web-ContextPath", webContext);
analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
}
}
public boolean execute() {
Analyzer analyzer = new Builder();
try {
Jar jar = new Jar(new File(inputJar)); // where our data is
analyzer.setJar(jar); // give bnd the contents
// You can provide additional class path entries to allow
// bnd to pickup export version from the packageinfo file,
// Version annotation, or their manifests.
analyzer.addClasspath(classpath);
setProperties(analyzer);
// analyzer.setProperty("DESTDIR");
// analyzer.setBase();
// ------------- let's begin... -------------------------
// Analyze the target JAR first
analyzer.analyze();
// Scan the JAR for Felix SCR annotations and generate XML files
Map<String, String> properties = Maps.newHashMap();
SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
scrDescriptorBndPlugin.setProperties(properties);
scrDescriptorBndPlugin.setReporter(analyzer);
scrDescriptorBndPlugin.analyzeJar(analyzer);
if (includeResources != null) {
doIncludeResources(analyzer);
}
// Repack the JAR as a WAR
doWabStaging(analyzer);
// Calculate the manifest
Manifest manifest = analyzer.calcManifest();
//OutputStream s = new FileOutputStream("/tmp/foo2.txt");
//manifest.write(s);
//s.close();
if (analyzer.isOk()) {
analyzer.getJar().setManifest(manifest);
if (analyzer.save(new File(outputJar), true)) {
log("Saved!\n");
} else {
warn("Failed to create jar \n");
return false;
}
} else {
warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
return false;
}
analyzer.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private boolean isWab() {
return !Objects.equals(webContext, NONE);
}
private void doWabStaging(Analyzer analyzer) throws Exception {
if (!isWab()) {
return;
}
String wab = analyzer.getProperty(analyzer.WAB);
Jar dot = analyzer.getJar();
log("wab %s", wab);
analyzer.setBundleClasspath("WEB-INF/classes," +
analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
Set<String> paths = new HashSet<>(dot.getResources().keySet());
for (String path : paths) {
if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
log("wab: moving: %s", path);
dot.rename(path, "WEB-INF/classes/" + path);
}
}
Path wabRoot = Paths.get(wab);
includeFiles(dot, null, wabRoot.toString());
}
/**
* Parse the Bundle-Includes header. Files in the bundles Include header are
* included in the jar. The source can be a directory or a file.
*
* @throws Exception
*/
private void doIncludeResources(Analyzer analyzer) throws Exception {
String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
if (includes == null) {
return;
}
Parameters clauses = analyzer.parseHeader(includes);
Jar jar = analyzer.getJar();
for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
String name = entry.getKey();
Map<String, String> extra = entry.getValue();
// TODO consider doing something with extras
String[] parts = name.split("\\s*=\\s*");
String source = parts[0];
String destination = parts[0];
if (parts.length == 2) {
source = parts[1];
}
includeFiles(jar, destination, source);
}
}
private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
throws IOException {
Path sourceRootPath = Paths.get(sourceRoot);
// iterate through sources
// put each source on the jar
FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path relativePath = sourceRootPath.relativize(file);
String destination = destinationRoot != null ?
destinationRoot + "/" + relativePath.toString() : //TODO
relativePath.toString();
addFileToJar(jar, destination, file.toAbsolutePath().toString());
return FileVisitResult.CONTINUE;
}
};
File dir = new File(sourceRoot);
if (dir.isFile()) {
addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
} else if (dir.isDirectory()) {
walkFileTree(sourceRootPath, visitor);
} else {
warn("Skipping resource in bundle %s: %s (File Not Found)\n",
bundleSymbolicName, sourceRoot);
}
}
private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
if (includedResources.contains(sourceAbsPath)) {
log("Skipping already included resource: %s\n", sourceAbsPath);
return false;
}
File file = new File(sourceAbsPath);
if (!file.isFile()) {
throw new RuntimeException(
String.format("Skipping non-existent file: %s\n", sourceAbsPath));
}
Resource resource = new FileResource(file);
if (jar.getResource(destination) != null) {
warn("Skipping duplicate resource: %s\n", destination);
return false;
}
jar.putResource(destination, resource);
includedResources.add(sourceAbsPath);
log("Adding resource: %s\n", destination);
return true;
}
private void log(String format, Object... objects) {
//System.err.printf(format, objects);
}
private void warn(String format, Object... objects) {
System.err.printf(format, objects);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("inputJar", inputJar)
.add("outputJar", outputJar)
.add("classpath", classpath)
.add("bundleName", bundleName)
.add("groupId", groupId)
.add("bundleSymbolicName", bundleSymbolicName)
.add("bundleVersion", bundleVersion)
.add("bundleDescription", bundleDescription)
.add("bundleLicense", bundleLicense)
.toString();
}
}