blob: fe286cb8bf4f87491fb8c455c59e3ddd6a795ee7 [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 Milkey7dac7da2017-08-01 16:56:05 -070077 private String destdir;
Brian O'Connor42c38cf2016-04-05 17:05:57 -070078
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070079 // FIXME should consider using Commons CLI, etc.
Brian O'Connor42c38cf2016-04-05 17:05:57 -070080 public static void main(String[] args) {
Ray Milkey7dac7da2017-08-01 16:56:05 -070081 if (args.length < 13) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -070082 System.err.println("Not enough args");
83 System.exit(1);
84 }
85
86 String jar = args[0];
87 String output = args[1];
88 String cp = args[2];
89 String name = args[3];
90 String group = args[4];
91 String version = args[5];
92 String license = args[6];
Brian O'Connore5817c92016-04-06 15:41:48 -070093 String importPackages = args[7];
94 String exportPackages = args[8];
95 String includeResources = args[9];
96 String webContext = args[10];
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070097 String dynamicimportPackages = args[11];
Ray Milkey7dac7da2017-08-01 16:56:05 -070098 String destdir = args[12];
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070099 String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 12, args.length));
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700100
101 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
102 name, group,
103 version, license,
Brian O'Connore5817c92016-04-06 15:41:48 -0700104 importPackages, exportPackages,
105 includeResources,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700106 webContext,
107 dynamicimportPackages,
Ray Milkey7dac7da2017-08-01 16:56:05 -0700108 desc,
109 destdir);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700110 wrapper.log(wrapper + "\n");
111 if (!wrapper.execute()) {
Brian O'Connore5817c92016-04-06 15:41:48 -0700112 System.err.printf("Error generating %s\n", name);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700113 System.exit(2);
114 }
115 }
116
117
118 public OSGiWrapper(String inputJar,
119 String outputJar,
120 String classpath,
121 String bundleName,
122 String groupId,
123 String bundleVersion,
124 String bundleLicense,
Brian O'Connore5817c92016-04-06 15:41:48 -0700125 String importPackages,
126 String exportPackages,
127 String includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700128 String webContext,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700129 String dynamicimportPackages,
Ray Milkey7dac7da2017-08-01 16:56:05 -0700130 String bundleDescription,
131 String destdir) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700132 this.inputJar = inputJar;
133 this.classpath = Lists.newArrayList(classpath.split(":"));
134 if (!this.classpath.contains(inputJar)) {
135 this.classpath.add(0, inputJar);
136 }
137 this.outputJar = outputJar;
138
139 this.bundleName = bundleName;
140 this.groupId = groupId;
141 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
142
143 this.bundleVersion = bundleVersion;
144 this.bundleLicense = bundleLicense;
145 this.bundleDescription = bundleDescription;
146
Brian O'Connore5817c92016-04-06 15:41:48 -0700147 this.importPackages = importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700148 this.dynamicimportPackages = dynamicimportPackages;
149 if (Objects.equals(dynamicimportPackages, "''")) {
150 this.dynamicimportPackages = null;
151 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700152 this.exportPackages = exportPackages;
153 if (!Objects.equals(includeResources, NONE)) {
154 this.includeResources = includeResources;
155 }
156
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700157 this.webContext = webContext;
Ray Milkey7dac7da2017-08-01 16:56:05 -0700158 this.destdir = destdir;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700159 }
160
161 private void setProperties(Analyzer analyzer) {
162 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
163 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
164 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
165
166 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
167 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
168
169 //TODO consider using stricter version policy
170 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
171 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
172
173 // There are no good defaults so make sure you set the Import-Package
Brian O'Connore5817c92016-04-06 15:41:48 -0700174 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700175
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700176 analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
177
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700178 // TODO include version in export, but not in import
Brian O'Connore5817c92016-04-06 15:41:48 -0700179 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700180
181 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
Brian O'Connore5817c92016-04-06 15:41:48 -0700182 if (includeResources != null) {
183 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
184 }
185
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700186 if (isWab()) {
187 analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
188 analyzer.setProperty("Web-ContextPath", webContext);
Brian O'Connore5817c92016-04-06 15:41:48 -0700189 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700190 }
191 }
192
193 public boolean execute() {
Brian O'Connor8aec1a12016-04-06 14:10:43 -0700194 Analyzer analyzer = new Builder();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700195 try {
196
197 Jar jar = new Jar(new File(inputJar)); // where our data is
198 analyzer.setJar(jar); // give bnd the contents
199
200 // You can provide additional class path entries to allow
201 // bnd to pickup export version from the packageinfo file,
202 // Version annotation, or their manifests.
203 analyzer.addClasspath(classpath);
204
205 setProperties(analyzer);
206
207// analyzer.setProperty("DESTDIR");
208// analyzer.setBase();
209
210 // ------------- let's begin... -------------------------
211
212 // Analyze the target JAR first
213 analyzer.analyze();
214
215 // Scan the JAR for Felix SCR annotations and generate XML files
216 Map<String, String> properties = Maps.newHashMap();
Ray Milkey7dac7da2017-08-01 16:56:05 -0700217 // destdir hack
218 properties.put("destdir", destdir);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700219 SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
220 scrDescriptorBndPlugin.setProperties(properties);
221 scrDescriptorBndPlugin.setReporter(analyzer);
222 scrDescriptorBndPlugin.analyzeJar(analyzer);
223
Brian O'Connore5817c92016-04-06 15:41:48 -0700224 if (includeResources != null) {
225 doIncludeResources(analyzer);
226 }
227
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700228 // Repack the JAR as a WAR
229 doWabStaging(analyzer);
230
231 // Calculate the manifest
232 Manifest manifest = analyzer.calcManifest();
233 //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
234 //manifest.write(s);
235 //s.close();
236
237 if (analyzer.isOk()) {
238 analyzer.getJar().setManifest(manifest);
Brian O'Connore5817c92016-04-06 15:41:48 -0700239 if (analyzer.save(new File(outputJar), true)) {
240 log("Saved!\n");
241 } else {
242 warn("Failed to create jar \n");
243 return false;
244 }
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700245 } else {
Brian O'Connore5817c92016-04-06 15:41:48 -0700246 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700247 return false;
248 }
249
250 analyzer.close();
251
252 return true;
253 } catch (Exception e) {
254 e.printStackTrace();
255 return false;
256 }
257 }
258
259 private boolean isWab() {
Brian O'Connore5817c92016-04-06 15:41:48 -0700260 return !Objects.equals(webContext, NONE);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700261 }
262
263 private void doWabStaging(Analyzer analyzer) throws Exception {
264 if (!isWab()) {
265 return;
266 }
267 String wab = analyzer.getProperty(analyzer.WAB);
268 Jar dot = analyzer.getJar();
269
270 log("wab %s", wab);
271 analyzer.setBundleClasspath("WEB-INF/classes," +
272 analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
273
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700274 Set<String> paths = new HashSet<>(dot.getResources().keySet());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700275
276 for (String path : paths) {
277 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
278 log("wab: moving: %s", path);
279 dot.rename(path, "WEB-INF/classes/" + path);
280 }
281 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700282
283 Path wabRoot = Paths.get(wab);
284 includeFiles(dot, null, wabRoot.toString());
285 }
286
287 /**
288 * Parse the Bundle-Includes header. Files in the bundles Include header are
289 * included in the jar. The source can be a directory or a file.
290 *
291 * @throws Exception
292 */
293 private void doIncludeResources(Analyzer analyzer) throws Exception {
294 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
295 if (includes == null) {
296 return;
297 }
298 Parameters clauses = analyzer.parseHeader(includes);
299 Jar jar = analyzer.getJar();
300
301 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
302 String name = entry.getKey();
303 Map<String, String> extra = entry.getValue();
304 // TODO consider doing something with extras
305
306 String[] parts = name.split("\\s*=\\s*");
307 String source = parts[0];
308 String destination = parts[0];
309 if (parts.length == 2) {
310 source = parts[1];
311 }
312
313 includeFiles(jar, destination, source);
314 }
315 }
316
317 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
318 throws IOException {
319 Path sourceRootPath = Paths.get(sourceRoot);
320 // iterate through sources
321 // put each source on the jar
322 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
323 @Override
324 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
325 Path relativePath = sourceRootPath.relativize(file);
326 String destination = destinationRoot != null ?
327 destinationRoot + "/" + relativePath.toString() : //TODO
328 relativePath.toString();
329
330 addFileToJar(jar, destination, file.toAbsolutePath().toString());
331 return FileVisitResult.CONTINUE;
332 }
333 };
334 File dir = new File(sourceRoot);
335 if (dir.isFile()) {
336 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
337 } else if (dir.isDirectory()) {
338 walkFileTree(sourceRootPath, visitor);
339 } else {
340 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
341 bundleSymbolicName, sourceRoot);
342 }
343 }
344
345 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
346 if (includedResources.contains(sourceAbsPath)) {
347 log("Skipping already included resource: %s\n", sourceAbsPath);
348 return false;
349 }
350 File file = new File(sourceAbsPath);
351 if (!file.isFile()) {
352 throw new RuntimeException(
353 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
354 }
355 Resource resource = new FileResource(file);
356 if (jar.getResource(destination) != null) {
357 warn("Skipping duplicate resource: %s\n", destination);
358 return false;
359 }
360 jar.putResource(destination, resource);
361 includedResources.add(sourceAbsPath);
362 log("Adding resource: %s\n", destination);
363 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700364 }
365
366 private void log(String format, Object... objects) {
367 //System.err.printf(format, objects);
368 }
369
370 private void warn(String format, Object... objects) {
371 System.err.printf(format, objects);
372 }
373
374 @Override
375 public String toString() {
376 return MoreObjects.toStringHelper(this)
377 .add("inputJar", inputJar)
378 .add("outputJar", outputJar)
379 .add("classpath", classpath)
380 .add("bundleName", bundleName)
381 .add("groupId", groupId)
382 .add("bundleSymbolicName", bundleSymbolicName)
383 .add("bundleVersion", bundleVersion)
384 .add("bundleDescription", bundleDescription)
385 .add("bundleLicense", bundleLicense)
386 .toString();
387
388 }
389}