blob: f7cf587de2256b43aad5a96bedf5702d6a49392b [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;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070031import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;
32
33import java.io.File;
Brian O'Connore5817c92016-04-06 15:41:48 -070034import java.io.IOException;
35import java.nio.file.FileVisitResult;
36import java.nio.file.FileVisitor;
37import java.nio.file.Path;
38import java.nio.file.Paths;
39import java.nio.file.SimpleFileVisitor;
40import java.nio.file.attribute.BasicFileAttributes;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070041import java.util.Arrays;
42import java.util.HashSet;
43import java.util.List;
44import java.util.Map;
45import java.util.Objects;
46import java.util.Set;
47import java.util.jar.Manifest;
48
Brian O'Connore5817c92016-04-06 15:41:48 -070049import static java.nio.file.Files.walkFileTree;
50
Brian O'Connor42c38cf2016-04-05 17:05:57 -070051/**
52 * BND-based wrapper to convert Buck JARs to OSGi-compatible JARs.
53 */
54public class OSGiWrapper {
Brian O'Connore5817c92016-04-06 15:41:48 -070055 private static final String NONE = "NONE";
Brian O'Connor42c38cf2016-04-05 17:05:57 -070056
57 private String inputJar;
58 private String outputJar;
59 private List<String> classpath;
60
61 private String bundleName;
62 private String groupId;
63 private String bundleSymbolicName;
64 private String bundleVersion;
65
Brian O'Connore5817c92016-04-06 15:41:48 -070066 private String importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070067 private String dynamicimportPackages;
68
Brian O'Connore5817c92016-04-06 15:41:48 -070069 private String exportPackages;
70 private String includeResources;
71 private Set<String> includedResources = Sets.newHashSet();
72
Brian O'Connor42c38cf2016-04-05 17:05:57 -070073 private String bundleDescription;
74 private String bundleLicense;
75
76 private String webContext;
Ray Milkey25747d82018-06-13 14:12:51 -070077 private String webXmlRoot;
Ray Milkey7dac7da2017-08-01 16:56:05 -070078 private String destdir;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070079
Jian Li4ad86872018-08-05 03:34:35 +090080 private String bundleClasspath;
81
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070082 // FIXME should consider using Commons CLI, etc.
Brian O'Connor42c38cf2016-04-05 17:05:57 -070083 public static void main(String[] args) {
Jian Li4ad86872018-08-05 03:34:35 +090084 if (args.length < 14) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -070085 System.err.println("Not enough args");
86 System.exit(1);
87 }
88
89 String jar = args[0];
90 String output = args[1];
91 String cp = args[2];
92 String name = args[3];
93 String group = args[4];
94 String version = args[5];
95 String license = args[6];
Brian O'Connore5817c92016-04-06 15:41:48 -070096 String importPackages = args[7];
97 String exportPackages = args[8];
98 String includeResources = args[9];
99 String webContext = args[10];
Ray Milkey25747d82018-06-13 14:12:51 -0700100 String webXmlRoot = args[11];
101 String dynamicimportPackages = args[12];
102 String destdir = args[13];
Jian Li4ad86872018-08-05 03:34:35 +0900103 String bundleClasspath = args[14];
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700104 String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 12, args.length));
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700105
106 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
Jian Li4ad86872018-08-05 03:34:35 +0900107 name, group,
108 version, license,
109 importPackages, exportPackages,
110 includeResources,
111 webContext,
112 webXmlRoot,
113 dynamicimportPackages,
114 desc,
115 destdir,
116 bundleClasspath);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700117 wrapper.log(wrapper + "\n");
118 if (!wrapper.execute()) {
Brian O'Connore5817c92016-04-06 15:41:48 -0700119 System.err.printf("Error generating %s\n", name);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700120 System.exit(2);
121 }
122 }
123
124
125 public OSGiWrapper(String inputJar,
126 String outputJar,
127 String classpath,
128 String bundleName,
129 String groupId,
130 String bundleVersion,
131 String bundleLicense,
Brian O'Connore5817c92016-04-06 15:41:48 -0700132 String importPackages,
133 String exportPackages,
134 String includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700135 String webContext,
Ray Milkey25747d82018-06-13 14:12:51 -0700136 String webXmlRoot,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700137 String dynamicimportPackages,
Ray Milkey7dac7da2017-08-01 16:56:05 -0700138 String bundleDescription,
Jian Li4ad86872018-08-05 03:34:35 +0900139 String destdir,
140 String bundleClasspath) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700141 this.inputJar = inputJar;
142 this.classpath = Lists.newArrayList(classpath.split(":"));
143 if (!this.classpath.contains(inputJar)) {
144 this.classpath.add(0, inputJar);
145 }
146 this.outputJar = outputJar;
147
148 this.bundleName = bundleName;
149 this.groupId = groupId;
150 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
151
152 this.bundleVersion = bundleVersion;
153 this.bundleLicense = bundleLicense;
154 this.bundleDescription = bundleDescription;
155
Brian O'Connore5817c92016-04-06 15:41:48 -0700156 this.importPackages = importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700157 this.dynamicimportPackages = dynamicimportPackages;
158 if (Objects.equals(dynamicimportPackages, "''")) {
159 this.dynamicimportPackages = null;
160 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700161 this.exportPackages = exportPackages;
162 if (!Objects.equals(includeResources, NONE)) {
163 this.includeResources = includeResources;
164 }
165
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700166 this.webContext = webContext;
Ray Milkey25747d82018-06-13 14:12:51 -0700167 this.webXmlRoot = webXmlRoot;
Ray Milkey7dac7da2017-08-01 16:56:05 -0700168 this.destdir = destdir;
Jian Li4ad86872018-08-05 03:34:35 +0900169
170 this.bundleClasspath = bundleClasspath;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700171 }
172
173 private void setProperties(Analyzer analyzer) {
174 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
175 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
176 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
177
178 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
179 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
180
181 //TODO consider using stricter version policy
182 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
183 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
184
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700185 analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
186
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700187 // TODO include version in export, but not in import
Brian O'Connore5817c92016-04-06 15:41:48 -0700188 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700189
190 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
Brian O'Connore5817c92016-04-06 15:41:48 -0700191 if (includeResources != null) {
192 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
193 }
194
Jian Li4ad86872018-08-05 03:34:35 +0900195 // There are no good defaults so make sure you set the Import-Package
196 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
197
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700198 if (isWab()) {
Ray Milkey25747d82018-06-13 14:12:51 -0700199 analyzer.setProperty(Analyzer.WAB, webXmlRoot);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700200 analyzer.setProperty("Web-ContextPath", webContext);
Jian Li4ad86872018-08-05 03:34:35 +0900201 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages +
202 ",org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700203 }
204 }
205
206 public boolean execute() {
Brian O'Connor8aec1a12016-04-06 14:10:43 -0700207 Analyzer analyzer = new Builder();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700208 try {
209
210 Jar jar = new Jar(new File(inputJar)); // where our data is
211 analyzer.setJar(jar); // give bnd the contents
212
213 // You can provide additional class path entries to allow
214 // bnd to pickup export version from the packageinfo file,
215 // Version annotation, or their manifests.
216 analyzer.addClasspath(classpath);
217
218 setProperties(analyzer);
219
220// analyzer.setProperty("DESTDIR");
221// analyzer.setBase();
222
223 // ------------- let's begin... -------------------------
224
225 // Analyze the target JAR first
226 analyzer.analyze();
227
228 // Scan the JAR for Felix SCR annotations and generate XML files
229 Map<String, String> properties = Maps.newHashMap();
Ray Milkey7dac7da2017-08-01 16:56:05 -0700230 // destdir hack
231 properties.put("destdir", destdir);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700232 SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
233 scrDescriptorBndPlugin.setProperties(properties);
234 scrDescriptorBndPlugin.setReporter(analyzer);
235 scrDescriptorBndPlugin.analyzeJar(analyzer);
236
Brian O'Connore5817c92016-04-06 15:41:48 -0700237 if (includeResources != null) {
238 doIncludeResources(analyzer);
239 }
240
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700241 // Repack the JAR as a WAR
242 doWabStaging(analyzer);
243
244 // Calculate the manifest
245 Manifest manifest = analyzer.calcManifest();
246 //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
247 //manifest.write(s);
248 //s.close();
249
250 if (analyzer.isOk()) {
251 analyzer.getJar().setManifest(manifest);
Brian O'Connore5817c92016-04-06 15:41:48 -0700252 if (analyzer.save(new File(outputJar), true)) {
253 log("Saved!\n");
254 } else {
255 warn("Failed to create jar \n");
256 return false;
257 }
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700258 } else {
Brian O'Connore5817c92016-04-06 15:41:48 -0700259 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700260 return false;
261 }
262
263 analyzer.close();
264
265 return true;
266 } catch (Exception e) {
267 e.printStackTrace();
268 return false;
269 }
270 }
271
272 private boolean isWab() {
Brian O'Connore5817c92016-04-06 15:41:48 -0700273 return !Objects.equals(webContext, NONE);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700274 }
275
276 private void doWabStaging(Analyzer analyzer) throws Exception {
277 if (!isWab()) {
278 return;
279 }
280 String wab = analyzer.getProperty(analyzer.WAB);
281 Jar dot = analyzer.getJar();
282
283 log("wab %s", wab);
Ray Milkey6b3775a2018-06-28 11:18:44 -0700284
Jian Li4ad86872018-08-05 03:34:35 +0900285 String specifiedClasspath = this.bundleClasspath;
Ray Milkey6b3775a2018-06-28 11:18:44 -0700286 String bundleClasspath = "WEB-INF/classes";
287 if (specifiedClasspath != null) {
288 bundleClasspath += "," + specifiedClasspath;
289 }
290 analyzer.setBundleClasspath(bundleClasspath);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700291
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700292 Set<String> paths = new HashSet<>(dot.getResources().keySet());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700293
294 for (String path : paths) {
295 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
296 log("wab: moving: %s", path);
297 dot.rename(path, "WEB-INF/classes/" + path);
298 }
299 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700300
301 Path wabRoot = Paths.get(wab);
Ray Milkey25747d82018-06-13 14:12:51 -0700302 log("wab root " + wabRoot.toString());
Brian O'Connore5817c92016-04-06 15:41:48 -0700303 includeFiles(dot, null, wabRoot.toString());
304 }
305
306 /**
307 * Parse the Bundle-Includes header. Files in the bundles Include header are
308 * included in the jar. The source can be a directory or a file.
309 *
310 * @throws Exception
311 */
312 private void doIncludeResources(Analyzer analyzer) throws Exception {
313 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
314 if (includes == null) {
315 return;
316 }
317 Parameters clauses = analyzer.parseHeader(includes);
318 Jar jar = analyzer.getJar();
319
320 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
321 String name = entry.getKey();
322 Map<String, String> extra = entry.getValue();
323 // TODO consider doing something with extras
324
325 String[] parts = name.split("\\s*=\\s*");
326 String source = parts[0];
327 String destination = parts[0];
328 if (parts.length == 2) {
329 source = parts[1];
330 }
331
332 includeFiles(jar, destination, source);
333 }
334 }
335
336 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
337 throws IOException {
338 Path sourceRootPath = Paths.get(sourceRoot);
339 // iterate through sources
340 // put each source on the jar
341 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
342 @Override
343 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
344 Path relativePath = sourceRootPath.relativize(file);
345 String destination = destinationRoot != null ?
346 destinationRoot + "/" + relativePath.toString() : //TODO
347 relativePath.toString();
348
349 addFileToJar(jar, destination, file.toAbsolutePath().toString());
350 return FileVisitResult.CONTINUE;
351 }
352 };
353 File dir = new File(sourceRoot);
354 if (dir.isFile()) {
355 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
356 } else if (dir.isDirectory()) {
357 walkFileTree(sourceRootPath, visitor);
358 } else {
359 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
Jian Li4ad86872018-08-05 03:34:35 +0900360 bundleSymbolicName, sourceRoot);
Brian O'Connore5817c92016-04-06 15:41:48 -0700361 }
362 }
363
364 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
365 if (includedResources.contains(sourceAbsPath)) {
366 log("Skipping already included resource: %s\n", sourceAbsPath);
367 return false;
368 }
369 File file = new File(sourceAbsPath);
370 if (!file.isFile()) {
371 throw new RuntimeException(
372 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
373 }
374 Resource resource = new FileResource(file);
375 if (jar.getResource(destination) != null) {
376 warn("Skipping duplicate resource: %s\n", destination);
377 return false;
378 }
379 jar.putResource(destination, resource);
380 includedResources.add(sourceAbsPath);
381 log("Adding resource: %s\n", destination);
382 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700383 }
384
385 private void log(String format, Object... objects) {
386 //System.err.printf(format, objects);
387 }
388
389 private void warn(String format, Object... objects) {
390 System.err.printf(format, objects);
391 }
392
393 @Override
394 public String toString() {
395 return MoreObjects.toStringHelper(this)
396 .add("inputJar", inputJar)
397 .add("outputJar", outputJar)
398 .add("classpath", classpath)
399 .add("bundleName", bundleName)
400 .add("groupId", groupId)
401 .add("bundleSymbolicName", bundleSymbolicName)
402 .add("bundleVersion", bundleVersion)
403 .add("bundleDescription", bundleDescription)
404 .add("bundleLicense", bundleLicense)
Jian Li4ad86872018-08-05 03:34:35 +0900405 .add("bundleClassPath", bundleClasspath)
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700406 .toString();
407
408 }
409}