blob: c819ebd93497b8403b94ba445ea726a2dd4518d7 [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);
276 analyzer.setBundleClasspath("WEB-INF/classes," +
277 analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
278
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700279 Set<String> paths = new HashSet<>(dot.getResources().keySet());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700280
281 for (String path : paths) {
282 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
283 log("wab: moving: %s", path);
284 dot.rename(path, "WEB-INF/classes/" + path);
285 }
286 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700287
288 Path wabRoot = Paths.get(wab);
Ray Milkey25747d82018-06-13 14:12:51 -0700289 log("wab root " + wabRoot.toString());
Brian O'Connore5817c92016-04-06 15:41:48 -0700290 includeFiles(dot, null, wabRoot.toString());
291 }
292
293 /**
294 * Parse the Bundle-Includes header. Files in the bundles Include header are
295 * included in the jar. The source can be a directory or a file.
296 *
297 * @throws Exception
298 */
299 private void doIncludeResources(Analyzer analyzer) throws Exception {
300 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
301 if (includes == null) {
302 return;
303 }
304 Parameters clauses = analyzer.parseHeader(includes);
305 Jar jar = analyzer.getJar();
306
307 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
308 String name = entry.getKey();
309 Map<String, String> extra = entry.getValue();
310 // TODO consider doing something with extras
311
312 String[] parts = name.split("\\s*=\\s*");
313 String source = parts[0];
314 String destination = parts[0];
315 if (parts.length == 2) {
316 source = parts[1];
317 }
318
319 includeFiles(jar, destination, source);
320 }
321 }
322
323 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
324 throws IOException {
325 Path sourceRootPath = Paths.get(sourceRoot);
326 // iterate through sources
327 // put each source on the jar
328 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
329 @Override
330 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
331 Path relativePath = sourceRootPath.relativize(file);
332 String destination = destinationRoot != null ?
333 destinationRoot + "/" + relativePath.toString() : //TODO
334 relativePath.toString();
335
336 addFileToJar(jar, destination, file.toAbsolutePath().toString());
337 return FileVisitResult.CONTINUE;
338 }
339 };
340 File dir = new File(sourceRoot);
341 if (dir.isFile()) {
342 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
343 } else if (dir.isDirectory()) {
344 walkFileTree(sourceRootPath, visitor);
345 } else {
346 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
347 bundleSymbolicName, sourceRoot);
348 }
349 }
350
351 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
352 if (includedResources.contains(sourceAbsPath)) {
353 log("Skipping already included resource: %s\n", sourceAbsPath);
354 return false;
355 }
356 File file = new File(sourceAbsPath);
357 if (!file.isFile()) {
358 throw new RuntimeException(
359 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
360 }
361 Resource resource = new FileResource(file);
362 if (jar.getResource(destination) != null) {
363 warn("Skipping duplicate resource: %s\n", destination);
364 return false;
365 }
366 jar.putResource(destination, resource);
367 includedResources.add(sourceAbsPath);
368 log("Adding resource: %s\n", destination);
369 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700370 }
371
372 private void log(String format, Object... objects) {
373 //System.err.printf(format, objects);
374 }
375
376 private void warn(String format, Object... objects) {
377 System.err.printf(format, objects);
378 }
379
380 @Override
381 public String toString() {
382 return MoreObjects.toStringHelper(this)
383 .add("inputJar", inputJar)
384 .add("outputJar", outputJar)
385 .add("classpath", classpath)
386 .add("bundleName", bundleName)
387 .add("groupId", groupId)
388 .add("bundleSymbolicName", bundleSymbolicName)
389 .add("bundleVersion", bundleVersion)
390 .add("bundleDescription", bundleDescription)
391 .add("bundleLicense", bundleLicense)
392 .toString();
393
394 }
395}