blob: b57dab5b34aba938da10d10afa88da4ed35ef159 [file] [log] [blame]
Brian O'Connor42c38cf2016-04-05 17:05:57 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Brian O'Connor42c38cf2016-04-05 17:05:57 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onlab.osgiwrap;
18
Brian O'Connore5817c92016-04-06 15:41:48 -070019import aQute.bnd.header.Attrs;
20import aQute.bnd.header.Parameters;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070021import aQute.bnd.osgi.Analyzer;
Brian O'Connor8aec1a12016-04-06 14:10:43 -070022import aQute.bnd.osgi.Builder;
Brian O'Connore5817c92016-04-06 15:41:48 -070023import aQute.bnd.osgi.FileResource;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070024import aQute.bnd.osgi.Jar;
Brian O'Connore5817c92016-04-06 15:41:48 -070025import aQute.bnd.osgi.Resource;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070026import com.google.common.base.Joiner;
27import com.google.common.base.MoreObjects;
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
Brian O'Connore5817c92016-04-06 15:41:48 -070030import com.google.common.collect.Sets;
Thomas Vachuskae4436942018-08-07 19:27:10 -070031import com.google.common.io.ByteStreams;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070032import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;
33
34import java.io.File;
Thomas Vachuskae4436942018-08-07 19:27:10 -070035import java.io.FileInputStream;
Brian O'Connore5817c92016-04-06 15:41:48 -070036import java.io.IOException;
37import java.nio.file.FileVisitResult;
38import java.nio.file.FileVisitor;
39import java.nio.file.Path;
40import java.nio.file.Paths;
41import java.nio.file.SimpleFileVisitor;
42import java.nio.file.attribute.BasicFileAttributes;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070043import java.util.Arrays;
44import java.util.HashSet;
45import java.util.List;
46import java.util.Map;
47import java.util.Objects;
48import java.util.Set;
Thomas Vachuskae4436942018-08-07 19:27:10 -070049import java.util.jar.JarEntry;
50import java.util.jar.JarInputStream;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070051import java.util.jar.Manifest;
52
Thomas Vachuskae4436942018-08-07 19:27:10 -070053import static com.google.common.io.Files.createParentDirs;
54import static com.google.common.io.Files.write;
Brian O'Connore5817c92016-04-06 15:41:48 -070055import static java.nio.file.Files.walkFileTree;
56
Brian O'Connor42c38cf2016-04-05 17:05:57 -070057/**
58 * BND-based wrapper to convert Buck JARs to OSGi-compatible JARs.
59 */
60public class OSGiWrapper {
Brian O'Connore5817c92016-04-06 15:41:48 -070061 private static final String NONE = "NONE";
Brian O'Connor42c38cf2016-04-05 17:05:57 -070062
63 private String inputJar;
64 private String outputJar;
65 private List<String> classpath;
66
67 private String bundleName;
68 private String groupId;
69 private String bundleSymbolicName;
70 private String bundleVersion;
71
Brian O'Connore5817c92016-04-06 15:41:48 -070072 private String importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070073 private String dynamicimportPackages;
74
Brian O'Connore5817c92016-04-06 15:41:48 -070075 private String exportPackages;
76 private String includeResources;
77 private Set<String> includedResources = Sets.newHashSet();
78
Brian O'Connor42c38cf2016-04-05 17:05:57 -070079 private String bundleDescription;
80 private String bundleLicense;
81
82 private String webContext;
Ray Milkey25747d82018-06-13 14:12:51 -070083 private String webXmlRoot;
Ray Milkey7dac7da2017-08-01 16:56:05 -070084 private String destdir;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070085
Jian Li4ad86872018-08-05 03:34:35 +090086 private String bundleClasspath;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070087 private String karafCommands;
Jian Li4ad86872018-08-05 03:34:35 +090088
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070089 // FIXME should consider using Commons CLI, etc.
Brian O'Connor42c38cf2016-04-05 17:05:57 -070090 public static void main(String[] args) {
Jian Li4ad86872018-08-05 03:34:35 +090091 if (args.length < 14) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -070092 System.err.println("Not enough args");
93 System.exit(1);
94 }
95
96 String jar = args[0];
97 String output = args[1];
98 String cp = args[2];
99 String name = args[3];
100 String group = args[4];
101 String version = args[5];
102 String license = args[6];
Brian O'Connore5817c92016-04-06 15:41:48 -0700103 String importPackages = args[7];
104 String exportPackages = args[8];
105 String includeResources = args[9];
106 String webContext = args[10];
Ray Milkey25747d82018-06-13 14:12:51 -0700107 String webXmlRoot = args[11];
108 String dynamicimportPackages = args[12];
109 String destdir = args[13];
Jian Li4ad86872018-08-05 03:34:35 +0900110 String bundleClasspath = args[14];
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700111 String karafCommands = args[15];
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700112 String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 12, args.length));
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700113
114 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
Jian Li4ad86872018-08-05 03:34:35 +0900115 name, group,
116 version, license,
117 importPackages, exportPackages,
118 includeResources,
119 webContext,
120 webXmlRoot,
121 dynamicimportPackages,
122 desc,
123 destdir,
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700124 bundleClasspath,
125 karafCommands);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700126 wrapper.log(wrapper + "\n");
127 if (!wrapper.execute()) {
Brian O'Connore5817c92016-04-06 15:41:48 -0700128 System.err.printf("Error generating %s\n", name);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700129 System.exit(2);
130 }
131 }
132
133
134 public OSGiWrapper(String inputJar,
135 String outputJar,
136 String classpath,
137 String bundleName,
138 String groupId,
139 String bundleVersion,
140 String bundleLicense,
Brian O'Connore5817c92016-04-06 15:41:48 -0700141 String importPackages,
142 String exportPackages,
143 String includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700144 String webContext,
Ray Milkey25747d82018-06-13 14:12:51 -0700145 String webXmlRoot,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700146 String dynamicimportPackages,
Ray Milkey7dac7da2017-08-01 16:56:05 -0700147 String bundleDescription,
Jian Li4ad86872018-08-05 03:34:35 +0900148 String destdir,
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700149 String bundleClasspath,
150 String karafCommands) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700151 this.inputJar = inputJar;
152 this.classpath = Lists.newArrayList(classpath.split(":"));
153 if (!this.classpath.contains(inputJar)) {
154 this.classpath.add(0, inputJar);
155 }
156 this.outputJar = outputJar;
157
158 this.bundleName = bundleName;
159 this.groupId = groupId;
160 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
161
162 this.bundleVersion = bundleVersion;
163 this.bundleLicense = bundleLicense;
164 this.bundleDescription = bundleDescription;
165
Brian O'Connore5817c92016-04-06 15:41:48 -0700166 this.importPackages = importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700167 this.dynamicimportPackages = dynamicimportPackages;
168 if (Objects.equals(dynamicimportPackages, "''")) {
169 this.dynamicimportPackages = null;
170 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700171 this.exportPackages = exportPackages;
172 if (!Objects.equals(includeResources, NONE)) {
173 this.includeResources = includeResources;
174 }
175
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700176 this.webContext = webContext;
Ray Milkey25747d82018-06-13 14:12:51 -0700177 this.webXmlRoot = webXmlRoot;
Ray Milkey7dac7da2017-08-01 16:56:05 -0700178 this.destdir = destdir;
Jian Li4ad86872018-08-05 03:34:35 +0900179
180 this.bundleClasspath = bundleClasspath;
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700181 this.karafCommands = karafCommands;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700182 }
183
184 private void setProperties(Analyzer analyzer) {
185 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
186 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
187 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
188
189 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
190 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
191
192 //TODO consider using stricter version policy
193 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
194 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
195
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700196 analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700197 analyzer.setProperty(Analyzer.DSANNOTATIONS_OPTIONS, "inherit");
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700198
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700199 // TODO include version in export, but not in import
Brian O'Connore5817c92016-04-06 15:41:48 -0700200 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700201
202 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
Brian O'Connore5817c92016-04-06 15:41:48 -0700203 if (includeResources != null) {
204 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
205 }
206
Jian Li4ad86872018-08-05 03:34:35 +0900207 // There are no good defaults so make sure you set the Import-Package
208 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
209
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700210 if (isWab()) {
Ray Milkey25747d82018-06-13 14:12:51 -0700211 analyzer.setProperty(Analyzer.WAB, webXmlRoot);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700212 analyzer.setProperty("Web-ContextPath", webContext);
Jian Li4ad86872018-08-05 03:34:35 +0900213 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages +
214 ",org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700215 }
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700216 analyzer.setProperty("Karaf-Commands", karafCommands);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700217 }
218
219 public boolean execute() {
Brian O'Connor8aec1a12016-04-06 14:10:43 -0700220 Analyzer analyzer = new Builder();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700221 try {
Thomas Vachuskae4436942018-08-07 19:27:10 -0700222 // Extract the input jar contents into the specified output directory
223 expandJar(inputJar, new File(destdir));
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700224
225 Jar jar = new Jar(new File(inputJar)); // where our data is
226 analyzer.setJar(jar); // give bnd the contents
227
228 // You can provide additional class path entries to allow
229 // bnd to pickup export version from the packageinfo file,
230 // Version annotation, or their manifests.
231 analyzer.addClasspath(classpath);
232
233 setProperties(analyzer);
234
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700235 // ------------- let's begin... -------------------------
236
237 // Analyze the target JAR first
238 analyzer.analyze();
239
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700240 //// Scan the JAR for Felix SCR annotations and generate XML files
241 //Map<String, String> properties = Maps.newHashMap();
242 //// destdir hack
243 //properties.put("destdir", destdir);
244 //SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
245 //scrDescriptorBndPlugin.setProperties(properties);
246 //scrDescriptorBndPlugin.setReporter(analyzer);
247 //scrDescriptorBndPlugin.analyzeJar(analyzer);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700248
Brian O'Connore5817c92016-04-06 15:41:48 -0700249 if (includeResources != null) {
250 doIncludeResources(analyzer);
251 }
252
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700253 // Repack the JAR as a WAR
254 doWabStaging(analyzer);
255
256 // Calculate the manifest
257 Manifest manifest = analyzer.calcManifest();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700258
259 if (analyzer.isOk()) {
260 analyzer.getJar().setManifest(manifest);
Brian O'Connore5817c92016-04-06 15:41:48 -0700261 if (analyzer.save(new File(outputJar), true)) {
262 log("Saved!\n");
263 } else {
264 warn("Failed to create jar \n");
265 return false;
266 }
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700267 } else {
Brian O'Connore5817c92016-04-06 15:41:48 -0700268 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700269 return false;
270 }
271
272 analyzer.close();
273
274 return true;
275 } catch (Exception e) {
276 e.printStackTrace();
277 return false;
278 }
279 }
280
Thomas Vachuskae4436942018-08-07 19:27:10 -0700281 // Expands the specified jar file into the given directory
282 private void expandJar(String inputJar, File intoDir) throws IOException {
283 try (JarInputStream jis = new JarInputStream(new FileInputStream(inputJar))) {
284 JarEntry entry;
285 while ((entry = jis.getNextJarEntry()) != null) {
286 if (!entry.isDirectory()) {
287 byte[] data = ByteStreams.toByteArray(jis);
288 jis.closeEntry();
289 if (!entry.getName().contains("..")) {
290 File file = new File(intoDir, entry.getName());
291 createParentDirs(file);
292 write(data, file);
293 } else {
294 throw new IOException("Corrupt jar file");
295 }
296 }
297 }
298 }
299 }
300
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700301 private boolean isWab() {
Brian O'Connore5817c92016-04-06 15:41:48 -0700302 return !Objects.equals(webContext, NONE);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700303 }
304
305 private void doWabStaging(Analyzer analyzer) throws Exception {
306 if (!isWab()) {
307 return;
308 }
309 String wab = analyzer.getProperty(analyzer.WAB);
310 Jar dot = analyzer.getJar();
311
312 log("wab %s", wab);
Ray Milkey6b3775a2018-06-28 11:18:44 -0700313
Jian Li4ad86872018-08-05 03:34:35 +0900314 String specifiedClasspath = this.bundleClasspath;
Ray Milkey6b3775a2018-06-28 11:18:44 -0700315 String bundleClasspath = "WEB-INF/classes";
316 if (specifiedClasspath != null) {
317 bundleClasspath += "," + specifiedClasspath;
318 }
319 analyzer.setBundleClasspath(bundleClasspath);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700320
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700321 Set<String> paths = new HashSet<>(dot.getResources().keySet());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700322
323 for (String path : paths) {
324 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
325 log("wab: moving: %s", path);
326 dot.rename(path, "WEB-INF/classes/" + path);
327 }
328 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700329
330 Path wabRoot = Paths.get(wab);
Ray Milkey25747d82018-06-13 14:12:51 -0700331 log("wab root " + wabRoot.toString());
Brian O'Connore5817c92016-04-06 15:41:48 -0700332 includeFiles(dot, null, wabRoot.toString());
333 }
334
335 /**
336 * Parse the Bundle-Includes header. Files in the bundles Include header are
337 * included in the jar. The source can be a directory or a file.
338 *
339 * @throws Exception
340 */
341 private void doIncludeResources(Analyzer analyzer) throws Exception {
342 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
343 if (includes == null) {
344 return;
345 }
346 Parameters clauses = analyzer.parseHeader(includes);
347 Jar jar = analyzer.getJar();
348
349 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
350 String name = entry.getKey();
351 Map<String, String> extra = entry.getValue();
352 // TODO consider doing something with extras
353
354 String[] parts = name.split("\\s*=\\s*");
355 String source = parts[0];
356 String destination = parts[0];
357 if (parts.length == 2) {
358 source = parts[1];
359 }
360
361 includeFiles(jar, destination, source);
362 }
363 }
364
365 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
366 throws IOException {
367 Path sourceRootPath = Paths.get(sourceRoot);
368 // iterate through sources
369 // put each source on the jar
370 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
371 @Override
372 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
373 Path relativePath = sourceRootPath.relativize(file);
374 String destination = destinationRoot != null ?
375 destinationRoot + "/" + relativePath.toString() : //TODO
376 relativePath.toString();
377
378 addFileToJar(jar, destination, file.toAbsolutePath().toString());
379 return FileVisitResult.CONTINUE;
380 }
381 };
382 File dir = new File(sourceRoot);
383 if (dir.isFile()) {
384 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
385 } else if (dir.isDirectory()) {
386 walkFileTree(sourceRootPath, visitor);
387 } else {
388 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
Jian Li4ad86872018-08-05 03:34:35 +0900389 bundleSymbolicName, sourceRoot);
Brian O'Connore5817c92016-04-06 15:41:48 -0700390 }
391 }
392
Ray Milkey51bee8d2018-09-10 10:01:35 -0700393 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) throws IOException {
Brian O'Connore5817c92016-04-06 15:41:48 -0700394 if (includedResources.contains(sourceAbsPath)) {
395 log("Skipping already included resource: %s\n", sourceAbsPath);
396 return false;
397 }
398 File file = new File(sourceAbsPath);
399 if (!file.isFile()) {
400 throw new RuntimeException(
401 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
402 }
403 Resource resource = new FileResource(file);
404 if (jar.getResource(destination) != null) {
405 warn("Skipping duplicate resource: %s\n", destination);
406 return false;
407 }
408 jar.putResource(destination, resource);
409 includedResources.add(sourceAbsPath);
410 log("Adding resource: %s\n", destination);
411 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700412 }
413
414 private void log(String format, Object... objects) {
415 //System.err.printf(format, objects);
416 }
417
418 private void warn(String format, Object... objects) {
419 System.err.printf(format, objects);
420 }
421
422 @Override
423 public String toString() {
424 return MoreObjects.toStringHelper(this)
425 .add("inputJar", inputJar)
426 .add("outputJar", outputJar)
427 .add("classpath", classpath)
428 .add("bundleName", bundleName)
429 .add("groupId", groupId)
430 .add("bundleSymbolicName", bundleSymbolicName)
431 .add("bundleVersion", bundleVersion)
432 .add("bundleDescription", bundleDescription)
433 .add("bundleLicense", bundleLicense)
Jian Li4ad86872018-08-05 03:34:35 +0900434 .add("bundleClassPath", bundleClasspath)
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700435 .toString();
436
437 }
438}