blob: b5669fe3d0742799656c2077a800b6c198efca1b [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
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070080 // FIXME should consider using Commons CLI, etc.
Brian O'Connor42c38cf2016-04-05 17:05:57 -070081 public static void main(String[] args) {
Ray Milkey7dac7da2017-08-01 16:56:05 -070082 if (args.length < 13) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -070083 System.err.println("Not enough args");
84 System.exit(1);
85 }
86
87 String jar = args[0];
88 String output = args[1];
89 String cp = args[2];
90 String name = args[3];
91 String group = args[4];
92 String version = args[5];
93 String license = args[6];
Brian O'Connore5817c92016-04-06 15:41:48 -070094 String importPackages = args[7];
95 String exportPackages = args[8];
96 String includeResources = args[9];
97 String webContext = args[10];
Ray Milkey25747d82018-06-13 14:12:51 -070098 String webXmlRoot = args[11];
99 String dynamicimportPackages = args[12];
100 String destdir = args[13];
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700101 String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 12, args.length));
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700102
103 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
104 name, group,
105 version, license,
Brian O'Connore5817c92016-04-06 15:41:48 -0700106 importPackages, exportPackages,
107 includeResources,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700108 webContext,
Ray Milkey25747d82018-06-13 14:12:51 -0700109 webXmlRoot,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700110 dynamicimportPackages,
Ray Milkey7dac7da2017-08-01 16:56:05 -0700111 desc,
112 destdir);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700113 wrapper.log(wrapper + "\n");
114 if (!wrapper.execute()) {
Brian O'Connore5817c92016-04-06 15:41:48 -0700115 System.err.printf("Error generating %s\n", name);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700116 System.exit(2);
117 }
118 }
119
120
121 public OSGiWrapper(String inputJar,
122 String outputJar,
123 String classpath,
124 String bundleName,
125 String groupId,
126 String bundleVersion,
127 String bundleLicense,
Brian O'Connore5817c92016-04-06 15:41:48 -0700128 String importPackages,
129 String exportPackages,
130 String includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700131 String webContext,
Ray Milkey25747d82018-06-13 14:12:51 -0700132 String webXmlRoot,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700133 String dynamicimportPackages,
Ray Milkey7dac7da2017-08-01 16:56:05 -0700134 String bundleDescription,
135 String destdir) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700136 this.inputJar = inputJar;
137 this.classpath = Lists.newArrayList(classpath.split(":"));
138 if (!this.classpath.contains(inputJar)) {
139 this.classpath.add(0, inputJar);
140 }
141 this.outputJar = outputJar;
142
143 this.bundleName = bundleName;
144 this.groupId = groupId;
145 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
146
147 this.bundleVersion = bundleVersion;
148 this.bundleLicense = bundleLicense;
149 this.bundleDescription = bundleDescription;
150
Brian O'Connore5817c92016-04-06 15:41:48 -0700151 this.importPackages = importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700152 this.dynamicimportPackages = dynamicimportPackages;
153 if (Objects.equals(dynamicimportPackages, "''")) {
154 this.dynamicimportPackages = null;
155 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700156 this.exportPackages = exportPackages;
157 if (!Objects.equals(includeResources, NONE)) {
158 this.includeResources = includeResources;
159 }
160
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700161 this.webContext = webContext;
Ray Milkey25747d82018-06-13 14:12:51 -0700162 this.webXmlRoot = webXmlRoot;
Ray Milkey7dac7da2017-08-01 16:56:05 -0700163 this.destdir = destdir;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700164 }
165
166 private void setProperties(Analyzer analyzer) {
167 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
168 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
169 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
170
171 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
172 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
173
174 //TODO consider using stricter version policy
175 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
176 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
177
178 // There are no good defaults so make sure you set the Import-Package
Brian O'Connore5817c92016-04-06 15:41:48 -0700179 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700180
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700181 analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
182
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700183 // TODO include version in export, but not in import
Brian O'Connore5817c92016-04-06 15:41:48 -0700184 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700185
186 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
Brian O'Connore5817c92016-04-06 15:41:48 -0700187 if (includeResources != null) {
188 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
189 }
190
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700191 if (isWab()) {
Ray Milkey25747d82018-06-13 14:12:51 -0700192 analyzer.setProperty(Analyzer.WAB, webXmlRoot);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700193 analyzer.setProperty("Web-ContextPath", webContext);
Brian O'Connore5817c92016-04-06 15:41:48 -0700194 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700195 }
196 }
197
198 public boolean execute() {
Brian O'Connor8aec1a12016-04-06 14:10:43 -0700199 Analyzer analyzer = new Builder();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700200 try {
201
202 Jar jar = new Jar(new File(inputJar)); // where our data is
203 analyzer.setJar(jar); // give bnd the contents
204
205 // You can provide additional class path entries to allow
206 // bnd to pickup export version from the packageinfo file,
207 // Version annotation, or their manifests.
208 analyzer.addClasspath(classpath);
209
210 setProperties(analyzer);
211
212// analyzer.setProperty("DESTDIR");
213// analyzer.setBase();
214
215 // ------------- let's begin... -------------------------
216
217 // Analyze the target JAR first
218 analyzer.analyze();
219
220 // Scan the JAR for Felix SCR annotations and generate XML files
221 Map<String, String> properties = Maps.newHashMap();
Ray Milkey7dac7da2017-08-01 16:56:05 -0700222 // destdir hack
223 properties.put("destdir", destdir);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700224 SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
225 scrDescriptorBndPlugin.setProperties(properties);
226 scrDescriptorBndPlugin.setReporter(analyzer);
227 scrDescriptorBndPlugin.analyzeJar(analyzer);
228
Brian O'Connore5817c92016-04-06 15:41:48 -0700229 if (includeResources != null) {
230 doIncludeResources(analyzer);
231 }
232
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700233 // Repack the JAR as a WAR
234 doWabStaging(analyzer);
235
236 // Calculate the manifest
237 Manifest manifest = analyzer.calcManifest();
238 //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
239 //manifest.write(s);
240 //s.close();
241
242 if (analyzer.isOk()) {
243 analyzer.getJar().setManifest(manifest);
Brian O'Connore5817c92016-04-06 15:41:48 -0700244 if (analyzer.save(new File(outputJar), true)) {
245 log("Saved!\n");
246 } else {
247 warn("Failed to create jar \n");
248 return false;
249 }
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700250 } else {
Brian O'Connore5817c92016-04-06 15:41:48 -0700251 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700252 return false;
253 }
254
255 analyzer.close();
256
257 return true;
258 } catch (Exception e) {
259 e.printStackTrace();
260 return false;
261 }
262 }
263
264 private boolean isWab() {
Brian O'Connore5817c92016-04-06 15:41:48 -0700265 return !Objects.equals(webContext, NONE);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700266 }
267
268 private void doWabStaging(Analyzer analyzer) throws Exception {
269 if (!isWab()) {
270 return;
271 }
272 String wab = analyzer.getProperty(analyzer.WAB);
273 Jar dot = analyzer.getJar();
274
275 log("wab %s", wab);
Ray Milkey6b3775a2018-06-28 11:18:44 -0700276
277 String specifiedClasspath = analyzer.getProperty(analyzer.BUNDLE_CLASSPATH);
278 String bundleClasspath = "WEB-INF/classes";
279 if (specifiedClasspath != null) {
280 bundleClasspath += "," + specifiedClasspath;
281 }
282 analyzer.setBundleClasspath(bundleClasspath);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700283
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700284 Set<String> paths = new HashSet<>(dot.getResources().keySet());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700285
286 for (String path : paths) {
287 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
288 log("wab: moving: %s", path);
289 dot.rename(path, "WEB-INF/classes/" + path);
290 }
291 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700292
293 Path wabRoot = Paths.get(wab);
Ray Milkey25747d82018-06-13 14:12:51 -0700294 log("wab root " + wabRoot.toString());
Brian O'Connore5817c92016-04-06 15:41:48 -0700295 includeFiles(dot, null, wabRoot.toString());
296 }
297
298 /**
299 * Parse the Bundle-Includes header. Files in the bundles Include header are
300 * included in the jar. The source can be a directory or a file.
301 *
302 * @throws Exception
303 */
304 private void doIncludeResources(Analyzer analyzer) throws Exception {
305 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
306 if (includes == null) {
307 return;
308 }
309 Parameters clauses = analyzer.parseHeader(includes);
310 Jar jar = analyzer.getJar();
311
312 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
313 String name = entry.getKey();
314 Map<String, String> extra = entry.getValue();
315 // TODO consider doing something with extras
316
317 String[] parts = name.split("\\s*=\\s*");
318 String source = parts[0];
319 String destination = parts[0];
320 if (parts.length == 2) {
321 source = parts[1];
322 }
323
324 includeFiles(jar, destination, source);
325 }
326 }
327
328 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
329 throws IOException {
330 Path sourceRootPath = Paths.get(sourceRoot);
331 // iterate through sources
332 // put each source on the jar
333 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
334 @Override
335 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
336 Path relativePath = sourceRootPath.relativize(file);
337 String destination = destinationRoot != null ?
338 destinationRoot + "/" + relativePath.toString() : //TODO
339 relativePath.toString();
340
341 addFileToJar(jar, destination, file.toAbsolutePath().toString());
342 return FileVisitResult.CONTINUE;
343 }
344 };
345 File dir = new File(sourceRoot);
346 if (dir.isFile()) {
347 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
348 } else if (dir.isDirectory()) {
349 walkFileTree(sourceRootPath, visitor);
350 } else {
351 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
352 bundleSymbolicName, sourceRoot);
353 }
354 }
355
356 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
357 if (includedResources.contains(sourceAbsPath)) {
358 log("Skipping already included resource: %s\n", sourceAbsPath);
359 return false;
360 }
361 File file = new File(sourceAbsPath);
362 if (!file.isFile()) {
363 throw new RuntimeException(
364 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
365 }
366 Resource resource = new FileResource(file);
367 if (jar.getResource(destination) != null) {
368 warn("Skipping duplicate resource: %s\n", destination);
369 return false;
370 }
371 jar.putResource(destination, resource);
372 includedResources.add(sourceAbsPath);
373 log("Adding resource: %s\n", destination);
374 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700375 }
376
377 private void log(String format, Object... objects) {
378 //System.err.printf(format, objects);
379 }
380
381 private void warn(String format, Object... objects) {
382 System.err.printf(format, objects);
383 }
384
385 @Override
386 public String toString() {
387 return MoreObjects.toStringHelper(this)
388 .add("inputJar", inputJar)
389 .add("outputJar", outputJar)
390 .add("classpath", classpath)
391 .add("bundleName", bundleName)
392 .add("groupId", groupId)
393 .add("bundleSymbolicName", bundleSymbolicName)
394 .add("bundleVersion", bundleVersion)
395 .add("bundleDescription", bundleDescription)
396 .add("bundleLicense", bundleLicense)
397 .toString();
398
399 }
400}