blob: 22afe9d589eaa957f06acfffc796dd9c9b8dbb8b [file] [log] [blame]
Brian O'Connor42c38cf2016-04-05 17:05:57 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
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;
77
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070078 // FIXME should consider using Commons CLI, etc.
Brian O'Connor42c38cf2016-04-05 17:05:57 -070079 public static void main(String[] args) {
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070080 if (args.length < 12) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -070081 System.err.println("Not enough args");
82 System.exit(1);
83 }
84
85 String jar = args[0];
86 String output = args[1];
87 String cp = args[2];
88 String name = args[3];
89 String group = args[4];
90 String version = args[5];
91 String license = args[6];
Brian O'Connore5817c92016-04-06 15:41:48 -070092 String importPackages = args[7];
93 String exportPackages = args[8];
94 String includeResources = args[9];
95 String webContext = args[10];
Yuta HIGUCHIf05db402016-08-12 18:36:33 -070096 String dynamicimportPackages = args[11];
97 String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 12, args.length));
Brian O'Connor42c38cf2016-04-05 17:05:57 -070098
99 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
100 name, group,
101 version, license,
Brian O'Connore5817c92016-04-06 15:41:48 -0700102 importPackages, exportPackages,
103 includeResources,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700104 webContext,
105 dynamicimportPackages,
106 desc);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700107 wrapper.log(wrapper + "\n");
108 if (!wrapper.execute()) {
Brian O'Connore5817c92016-04-06 15:41:48 -0700109 System.err.printf("Error generating %s\n", name);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700110 System.exit(2);
111 }
112 }
113
114
115 public OSGiWrapper(String inputJar,
116 String outputJar,
117 String classpath,
118 String bundleName,
119 String groupId,
120 String bundleVersion,
121 String bundleLicense,
Brian O'Connore5817c92016-04-06 15:41:48 -0700122 String importPackages,
123 String exportPackages,
124 String includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700125 String webContext,
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700126 String dynamicimportPackages,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700127 String bundleDescription) {
128 this.inputJar = inputJar;
129 this.classpath = Lists.newArrayList(classpath.split(":"));
130 if (!this.classpath.contains(inputJar)) {
131 this.classpath.add(0, inputJar);
132 }
133 this.outputJar = outputJar;
134
135 this.bundleName = bundleName;
136 this.groupId = groupId;
137 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
138
139 this.bundleVersion = bundleVersion;
140 this.bundleLicense = bundleLicense;
141 this.bundleDescription = bundleDescription;
142
Brian O'Connore5817c92016-04-06 15:41:48 -0700143 this.importPackages = importPackages;
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700144 this.dynamicimportPackages = dynamicimportPackages;
145 if (Objects.equals(dynamicimportPackages, "''")) {
146 this.dynamicimportPackages = null;
147 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700148 this.exportPackages = exportPackages;
149 if (!Objects.equals(includeResources, NONE)) {
150 this.includeResources = includeResources;
151 }
152
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700153 this.webContext = webContext;
154 }
155
156 private void setProperties(Analyzer analyzer) {
157 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
158 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
159 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
160
161 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
162 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
163
164 //TODO consider using stricter version policy
165 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
166 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
167
168 // There are no good defaults so make sure you set the Import-Package
Brian O'Connore5817c92016-04-06 15:41:48 -0700169 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700170
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700171 analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
172
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700173 // TODO include version in export, but not in import
Brian O'Connore5817c92016-04-06 15:41:48 -0700174 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700175
176 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
Brian O'Connore5817c92016-04-06 15:41:48 -0700177 if (includeResources != null) {
178 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
179 }
180
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700181 if (isWab()) {
182 analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
183 analyzer.setProperty("Web-ContextPath", webContext);
Brian O'Connore5817c92016-04-06 15:41:48 -0700184 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700185 }
186 }
187
188 public boolean execute() {
Brian O'Connor8aec1a12016-04-06 14:10:43 -0700189 Analyzer analyzer = new Builder();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700190 try {
191
192 Jar jar = new Jar(new File(inputJar)); // where our data is
193 analyzer.setJar(jar); // give bnd the contents
194
195 // You can provide additional class path entries to allow
196 // bnd to pickup export version from the packageinfo file,
197 // Version annotation, or their manifests.
198 analyzer.addClasspath(classpath);
199
200 setProperties(analyzer);
201
202// analyzer.setProperty("DESTDIR");
203// analyzer.setBase();
204
205 // ------------- let's begin... -------------------------
206
207 // Analyze the target JAR first
208 analyzer.analyze();
209
210 // Scan the JAR for Felix SCR annotations and generate XML files
211 Map<String, String> properties = Maps.newHashMap();
212 SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
213 scrDescriptorBndPlugin.setProperties(properties);
214 scrDescriptorBndPlugin.setReporter(analyzer);
215 scrDescriptorBndPlugin.analyzeJar(analyzer);
216
Brian O'Connore5817c92016-04-06 15:41:48 -0700217 if (includeResources != null) {
218 doIncludeResources(analyzer);
219 }
220
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700221 // Repack the JAR as a WAR
222 doWabStaging(analyzer);
223
224 // Calculate the manifest
225 Manifest manifest = analyzer.calcManifest();
226 //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
227 //manifest.write(s);
228 //s.close();
229
230 if (analyzer.isOk()) {
231 analyzer.getJar().setManifest(manifest);
Brian O'Connore5817c92016-04-06 15:41:48 -0700232 if (analyzer.save(new File(outputJar), true)) {
233 log("Saved!\n");
234 } else {
235 warn("Failed to create jar \n");
236 return false;
237 }
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700238 } else {
Brian O'Connore5817c92016-04-06 15:41:48 -0700239 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700240 return false;
241 }
242
243 analyzer.close();
244
245 return true;
246 } catch (Exception e) {
247 e.printStackTrace();
248 return false;
249 }
250 }
251
252 private boolean isWab() {
Brian O'Connore5817c92016-04-06 15:41:48 -0700253 return !Objects.equals(webContext, NONE);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700254 }
255
256 private void doWabStaging(Analyzer analyzer) throws Exception {
257 if (!isWab()) {
258 return;
259 }
260 String wab = analyzer.getProperty(analyzer.WAB);
261 Jar dot = analyzer.getJar();
262
263 log("wab %s", wab);
264 analyzer.setBundleClasspath("WEB-INF/classes," +
265 analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
266
Yuta HIGUCHIf05db402016-08-12 18:36:33 -0700267 Set<String> paths = new HashSet<>(dot.getResources().keySet());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700268
269 for (String path : paths) {
270 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
271 log("wab: moving: %s", path);
272 dot.rename(path, "WEB-INF/classes/" + path);
273 }
274 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700275
276 Path wabRoot = Paths.get(wab);
277 includeFiles(dot, null, wabRoot.toString());
278 }
279
280 /**
281 * Parse the Bundle-Includes header. Files in the bundles Include header are
282 * included in the jar. The source can be a directory or a file.
283 *
284 * @throws Exception
285 */
286 private void doIncludeResources(Analyzer analyzer) throws Exception {
287 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
288 if (includes == null) {
289 return;
290 }
291 Parameters clauses = analyzer.parseHeader(includes);
292 Jar jar = analyzer.getJar();
293
294 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
295 String name = entry.getKey();
296 Map<String, String> extra = entry.getValue();
297 // TODO consider doing something with extras
298
299 String[] parts = name.split("\\s*=\\s*");
300 String source = parts[0];
301 String destination = parts[0];
302 if (parts.length == 2) {
303 source = parts[1];
304 }
305
306 includeFiles(jar, destination, source);
307 }
308 }
309
310 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
311 throws IOException {
312 Path sourceRootPath = Paths.get(sourceRoot);
313 // iterate through sources
314 // put each source on the jar
315 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
316 @Override
317 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
318 Path relativePath = sourceRootPath.relativize(file);
319 String destination = destinationRoot != null ?
320 destinationRoot + "/" + relativePath.toString() : //TODO
321 relativePath.toString();
322
323 addFileToJar(jar, destination, file.toAbsolutePath().toString());
324 return FileVisitResult.CONTINUE;
325 }
326 };
327 File dir = new File(sourceRoot);
328 if (dir.isFile()) {
329 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
330 } else if (dir.isDirectory()) {
331 walkFileTree(sourceRootPath, visitor);
332 } else {
333 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
334 bundleSymbolicName, sourceRoot);
335 }
336 }
337
338 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
339 if (includedResources.contains(sourceAbsPath)) {
340 log("Skipping already included resource: %s\n", sourceAbsPath);
341 return false;
342 }
343 File file = new File(sourceAbsPath);
344 if (!file.isFile()) {
345 throw new RuntimeException(
346 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
347 }
348 Resource resource = new FileResource(file);
349 if (jar.getResource(destination) != null) {
350 warn("Skipping duplicate resource: %s\n", destination);
351 return false;
352 }
353 jar.putResource(destination, resource);
354 includedResources.add(sourceAbsPath);
355 log("Adding resource: %s\n", destination);
356 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700357 }
358
359 private void log(String format, Object... objects) {
360 //System.err.printf(format, objects);
361 }
362
363 private void warn(String format, Object... objects) {
364 System.err.printf(format, objects);
365 }
366
367 @Override
368 public String toString() {
369 return MoreObjects.toStringHelper(this)
370 .add("inputJar", inputJar)
371 .add("outputJar", outputJar)
372 .add("classpath", classpath)
373 .add("bundleName", bundleName)
374 .add("groupId", groupId)
375 .add("bundleSymbolicName", bundleSymbolicName)
376 .add("bundleVersion", bundleVersion)
377 .add("bundleDescription", bundleDescription)
378 .add("bundleLicense", bundleLicense)
379 .toString();
380
381 }
382}