blob: 2bdc2b360ff1ac0505300273a6436d1e1423aa1a [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
Daniele Moro27527592020-02-03 23:33:55 -080089 private String fragmentHost;
90
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070091 // FIXME should consider using Commons CLI, etc.
Brian O'Connor42c38cf2016-04-05 17:05:57 -070092 public static void main(String[] args) {
Daniele Moro27527592020-02-03 23:33:55 -080093 if (args.length < 17) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -070094 System.err.println("Not enough args");
95 System.exit(1);
96 }
97
98 String jar = args[0];
99 String output = args[1];
100 String cp = args[2];
101 String name = args[3];
102 String group = args[4];
103 String version = args[5];
104 String license = args[6];
Brian O'Connore5817c92016-04-06 15:41:48 -0700105 String importPackages = args[7];
106 String exportPackages = args[8];
107 String includeResources = args[9];
108 String webContext = args[10];
Ray Milkey25747d82018-06-13 14:12:51 -0700109 String webXmlRoot = args[11];
110 String dynamicimportPackages = args[12];
111 String destdir = args[13];
Jian Li4ad86872018-08-05 03:34:35 +0900112 String bundleClasspath = args[14];
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700113 String karafCommands = args[15];
Daniele Moro27527592020-02-03 23:33:55 -0800114 String fragmentHost = args[16];
115 String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 17, args.length));
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700116
117 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
Jian Li4ad86872018-08-05 03:34:35 +0900118 name, group,
119 version, license,
120 importPackages, exportPackages,
121 includeResources,
122 webContext,
123 webXmlRoot,
124 dynamicimportPackages,
125 desc,
126 destdir,
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700127 bundleClasspath,
Daniele Moro27527592020-02-03 23:33:55 -0800128 karafCommands,
129 fragmentHost);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700130 wrapper.log(wrapper + "\n");
131 if (!wrapper.execute()) {
Brian O'Connore5817c92016-04-06 15:41:48 -0700132 System.err.printf("Error generating %s\n", name);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700133 System.exit(2);
134 }
135 }
136
137
138 public OSGiWrapper(String inputJar,
139 String outputJar,
140 String classpath,
141 String bundleName,
142 String groupId,
143 String bundleVersion,
144 String bundleLicense,
Brian O'Connore5817c92016-04-06 15:41:48 -0700145 String importPackages,
146 String exportPackages,
147 String includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700148 String webContext,
Ray Milkey25747d82018-06-13 14:12:51 -0700149 String webXmlRoot,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700150 String dynamicimportPackages,
Ray Milkey7dac7da2017-08-01 16:56:05 -0700151 String bundleDescription,
Jian Li4ad86872018-08-05 03:34:35 +0900152 String destdir,
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700153 String bundleClasspath,
Daniele Moro27527592020-02-03 23:33:55 -0800154 String karafCommands,
155 String fragmentHost) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700156 this.inputJar = inputJar;
157 this.classpath = Lists.newArrayList(classpath.split(":"));
158 if (!this.classpath.contains(inputJar)) {
159 this.classpath.add(0, inputJar);
160 }
161 this.outputJar = outputJar;
162
163 this.bundleName = bundleName;
164 this.groupId = groupId;
165 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
166
167 this.bundleVersion = bundleVersion;
168 this.bundleLicense = bundleLicense;
169 this.bundleDescription = bundleDescription;
170
Brian O'Connore5817c92016-04-06 15:41:48 -0700171 this.importPackages = importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700172 this.dynamicimportPackages = dynamicimportPackages;
173 if (Objects.equals(dynamicimportPackages, "''")) {
174 this.dynamicimportPackages = null;
175 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700176 this.exportPackages = exportPackages;
177 if (!Objects.equals(includeResources, NONE)) {
178 this.includeResources = includeResources;
179 }
180
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700181 this.webContext = webContext;
Ray Milkey25747d82018-06-13 14:12:51 -0700182 this.webXmlRoot = webXmlRoot;
Ray Milkey7dac7da2017-08-01 16:56:05 -0700183 this.destdir = destdir;
Jian Li4ad86872018-08-05 03:34:35 +0900184
185 this.bundleClasspath = bundleClasspath;
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700186 this.karafCommands = karafCommands;
Daniele Moro27527592020-02-03 23:33:55 -0800187
188 this.fragmentHost = fragmentHost;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700189 }
190
191 private void setProperties(Analyzer analyzer) {
192 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
193 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
194 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
195
196 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
197 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
198
199 //TODO consider using stricter version policy
200 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
201 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
202
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700203 analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700204 analyzer.setProperty(Analyzer.DSANNOTATIONS_OPTIONS, "inherit");
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700205
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700206 // TODO include version in export, but not in import
Brian O'Connore5817c92016-04-06 15:41:48 -0700207 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700208
209 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
Brian O'Connore5817c92016-04-06 15:41:48 -0700210 if (includeResources != null) {
211 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
212 }
213
Jian Li4ad86872018-08-05 03:34:35 +0900214 // There are no good defaults so make sure you set the Import-Package
215 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
216
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700217 if (isWab()) {
Ray Milkey25747d82018-06-13 14:12:51 -0700218 analyzer.setProperty(Analyzer.WAB, webXmlRoot);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700219 analyzer.setProperty("Web-ContextPath", webContext);
Jian Li4ad86872018-08-05 03:34:35 +0900220 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages +
221 ",org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700222 }
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700223 analyzer.setProperty("Karaf-Commands", karafCommands);
Daniele Moro27527592020-02-03 23:33:55 -0800224
225 analyzer.setProperty(Analyzer.FRAGMENT_HOST, fragmentHost);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700226 }
227
228 public boolean execute() {
Brian O'Connor8aec1a12016-04-06 14:10:43 -0700229 Analyzer analyzer = new Builder();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700230 try {
Thomas Vachuskae4436942018-08-07 19:27:10 -0700231 // Extract the input jar contents into the specified output directory
232 expandJar(inputJar, new File(destdir));
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700233
234 Jar jar = new Jar(new File(inputJar)); // where our data is
235 analyzer.setJar(jar); // give bnd the contents
236
237 // You can provide additional class path entries to allow
238 // bnd to pickup export version from the packageinfo file,
239 // Version annotation, or their manifests.
240 analyzer.addClasspath(classpath);
241
242 setProperties(analyzer);
243
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700244 // ------------- let's begin... -------------------------
245
246 // Analyze the target JAR first
247 analyzer.analyze();
248
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700249 //// Scan the JAR for Felix SCR annotations and generate XML files
250 //Map<String, String> properties = Maps.newHashMap();
251 //// destdir hack
252 //properties.put("destdir", destdir);
253 //SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
254 //scrDescriptorBndPlugin.setProperties(properties);
255 //scrDescriptorBndPlugin.setReporter(analyzer);
256 //scrDescriptorBndPlugin.analyzeJar(analyzer);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700257
Brian O'Connore5817c92016-04-06 15:41:48 -0700258 if (includeResources != null) {
259 doIncludeResources(analyzer);
260 }
261
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700262 // Repack the JAR as a WAR
263 doWabStaging(analyzer);
264
265 // Calculate the manifest
266 Manifest manifest = analyzer.calcManifest();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700267
268 if (analyzer.isOk()) {
269 analyzer.getJar().setManifest(manifest);
Brian O'Connore5817c92016-04-06 15:41:48 -0700270 if (analyzer.save(new File(outputJar), true)) {
271 log("Saved!\n");
272 } else {
273 warn("Failed to create jar \n");
274 return false;
275 }
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700276 } else {
Brian O'Connore5817c92016-04-06 15:41:48 -0700277 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700278 return false;
279 }
280
281 analyzer.close();
282
283 return true;
284 } catch (Exception e) {
285 e.printStackTrace();
286 return false;
287 }
288 }
289
Thomas Vachuskae4436942018-08-07 19:27:10 -0700290 // Expands the specified jar file into the given directory
291 private void expandJar(String inputJar, File intoDir) throws IOException {
292 try (JarInputStream jis = new JarInputStream(new FileInputStream(inputJar))) {
293 JarEntry entry;
294 while ((entry = jis.getNextJarEntry()) != null) {
295 if (!entry.isDirectory()) {
296 byte[] data = ByteStreams.toByteArray(jis);
297 jis.closeEntry();
298 if (!entry.getName().contains("..")) {
299 File file = new File(intoDir, entry.getName());
300 createParentDirs(file);
301 write(data, file);
302 } else {
303 throw new IOException("Corrupt jar file");
304 }
305 }
306 }
307 }
308 }
309
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700310 private boolean isWab() {
Brian O'Connore5817c92016-04-06 15:41:48 -0700311 return !Objects.equals(webContext, NONE);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700312 }
313
314 private void doWabStaging(Analyzer analyzer) throws Exception {
315 if (!isWab()) {
316 return;
317 }
318 String wab = analyzer.getProperty(analyzer.WAB);
319 Jar dot = analyzer.getJar();
320
321 log("wab %s", wab);
Ray Milkey6b3775a2018-06-28 11:18:44 -0700322
Jian Li4ad86872018-08-05 03:34:35 +0900323 String specifiedClasspath = this.bundleClasspath;
Ray Milkey6b3775a2018-06-28 11:18:44 -0700324 String bundleClasspath = "WEB-INF/classes";
325 if (specifiedClasspath != null) {
326 bundleClasspath += "," + specifiedClasspath;
327 }
328 analyzer.setBundleClasspath(bundleClasspath);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700329
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700330 Set<String> paths = new HashSet<>(dot.getResources().keySet());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700331
332 for (String path : paths) {
333 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
334 log("wab: moving: %s", path);
335 dot.rename(path, "WEB-INF/classes/" + path);
336 }
337 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700338
339 Path wabRoot = Paths.get(wab);
Ray Milkey25747d82018-06-13 14:12:51 -0700340 log("wab root " + wabRoot.toString());
Brian O'Connore5817c92016-04-06 15:41:48 -0700341 includeFiles(dot, null, wabRoot.toString());
342 }
343
344 /**
345 * Parse the Bundle-Includes header. Files in the bundles Include header are
346 * included in the jar. The source can be a directory or a file.
347 *
348 * @throws Exception
349 */
350 private void doIncludeResources(Analyzer analyzer) throws Exception {
351 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
352 if (includes == null) {
353 return;
354 }
355 Parameters clauses = analyzer.parseHeader(includes);
356 Jar jar = analyzer.getJar();
357
358 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
359 String name = entry.getKey();
360 Map<String, String> extra = entry.getValue();
361 // TODO consider doing something with extras
362
363 String[] parts = name.split("\\s*=\\s*");
364 String source = parts[0];
365 String destination = parts[0];
366 if (parts.length == 2) {
367 source = parts[1];
368 }
369
370 includeFiles(jar, destination, source);
371 }
372 }
373
374 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
375 throws IOException {
376 Path sourceRootPath = Paths.get(sourceRoot);
377 // iterate through sources
378 // put each source on the jar
379 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
380 @Override
381 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
382 Path relativePath = sourceRootPath.relativize(file);
383 String destination = destinationRoot != null ?
384 destinationRoot + "/" + relativePath.toString() : //TODO
385 relativePath.toString();
386
387 addFileToJar(jar, destination, file.toAbsolutePath().toString());
388 return FileVisitResult.CONTINUE;
389 }
390 };
391 File dir = new File(sourceRoot);
392 if (dir.isFile()) {
393 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
394 } else if (dir.isDirectory()) {
395 walkFileTree(sourceRootPath, visitor);
396 } else {
397 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
Jian Li4ad86872018-08-05 03:34:35 +0900398 bundleSymbolicName, sourceRoot);
Brian O'Connore5817c92016-04-06 15:41:48 -0700399 }
400 }
401
Ray Milkey51bee8d2018-09-10 10:01:35 -0700402 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) throws IOException {
Brian O'Connore5817c92016-04-06 15:41:48 -0700403 if (includedResources.contains(sourceAbsPath)) {
404 log("Skipping already included resource: %s\n", sourceAbsPath);
405 return false;
406 }
407 File file = new File(sourceAbsPath);
408 if (!file.isFile()) {
409 throw new RuntimeException(
410 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
411 }
412 Resource resource = new FileResource(file);
413 if (jar.getResource(destination) != null) {
414 warn("Skipping duplicate resource: %s\n", destination);
415 return false;
416 }
417 jar.putResource(destination, resource);
418 includedResources.add(sourceAbsPath);
419 log("Adding resource: %s\n", destination);
420 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700421 }
422
423 private void log(String format, Object... objects) {
424 //System.err.printf(format, objects);
425 }
426
427 private void warn(String format, Object... objects) {
428 System.err.printf(format, objects);
429 }
430
431 @Override
432 public String toString() {
433 return MoreObjects.toStringHelper(this)
434 .add("inputJar", inputJar)
435 .add("outputJar", outputJar)
436 .add("classpath", classpath)
437 .add("bundleName", bundleName)
438 .add("groupId", groupId)
439 .add("bundleSymbolicName", bundleSymbolicName)
440 .add("bundleVersion", bundleVersion)
441 .add("bundleDescription", bundleDescription)
442 .add("bundleLicense", bundleLicense)
Jian Li4ad86872018-08-05 03:34:35 +0900443 .add("bundleClassPath", bundleClasspath)
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700444 .toString();
445
446 }
447}