blob: 5059e74e807c9e783424a141d305460b7fd31bf6 [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;
67 private String exportPackages;
68 private String includeResources;
69 private Set<String> includedResources = Sets.newHashSet();
70
Brian O'Connor42c38cf2016-04-05 17:05:57 -070071 private String bundleDescription;
72 private String bundleLicense;
73
74 private String webContext;
75
76 public static void main(String[] args) {
Brian O'Connorb5c11122016-04-29 15:22:57 -070077 if (args.length < 11) {
Brian O'Connor42c38cf2016-04-05 17:05:57 -070078 System.err.println("Not enough args");
79 System.exit(1);
80 }
81
82 String jar = args[0];
83 String output = args[1];
84 String cp = args[2];
85 String name = args[3];
86 String group = args[4];
87 String version = args[5];
88 String license = args[6];
Brian O'Connore5817c92016-04-06 15:41:48 -070089 String importPackages = args[7];
90 String exportPackages = args[8];
91 String includeResources = args[9];
92 String webContext = args[10];
93 String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 11, args.length));
Brian O'Connor42c38cf2016-04-05 17:05:57 -070094
95 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
96 name, group,
97 version, license,
Brian O'Connore5817c92016-04-06 15:41:48 -070098 importPackages, exportPackages,
99 includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700100 webContext, desc);
101 wrapper.log(wrapper + "\n");
102 if (!wrapper.execute()) {
Brian O'Connore5817c92016-04-06 15:41:48 -0700103 System.err.printf("Error generating %s\n", name);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700104 System.exit(2);
105 }
106 }
107
108
109 public OSGiWrapper(String inputJar,
110 String outputJar,
111 String classpath,
112 String bundleName,
113 String groupId,
114 String bundleVersion,
115 String bundleLicense,
Brian O'Connore5817c92016-04-06 15:41:48 -0700116 String importPackages,
117 String exportPackages,
118 String includeResources,
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700119 String webContext,
120 String bundleDescription) {
121 this.inputJar = inputJar;
122 this.classpath = Lists.newArrayList(classpath.split(":"));
123 if (!this.classpath.contains(inputJar)) {
124 this.classpath.add(0, inputJar);
125 }
126 this.outputJar = outputJar;
127
128 this.bundleName = bundleName;
129 this.groupId = groupId;
130 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
131
132 this.bundleVersion = bundleVersion;
133 this.bundleLicense = bundleLicense;
134 this.bundleDescription = bundleDescription;
135
Brian O'Connore5817c92016-04-06 15:41:48 -0700136 this.importPackages = importPackages;
137 this.exportPackages = exportPackages;
138 if (!Objects.equals(includeResources, NONE)) {
139 this.includeResources = includeResources;
140 }
141
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700142 this.webContext = webContext;
143 }
144
145 private void setProperties(Analyzer analyzer) {
146 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
147 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
148 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
149
150 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
151 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
152
153 //TODO consider using stricter version policy
154 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
155 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
156
157 // There are no good defaults so make sure you set the Import-Package
Brian O'Connore5817c92016-04-06 15:41:48 -0700158 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700159
160 // TODO include version in export, but not in import
Brian O'Connore5817c92016-04-06 15:41:48 -0700161 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700162
163 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
Brian O'Connore5817c92016-04-06 15:41:48 -0700164 if (includeResources != null) {
165 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
166 }
167
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700168 if (isWab()) {
169 analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
170 analyzer.setProperty("Web-ContextPath", webContext);
Brian O'Connore5817c92016-04-06 15:41:48 -0700171 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700172 }
173 }
174
175 public boolean execute() {
Brian O'Connor8aec1a12016-04-06 14:10:43 -0700176 Analyzer analyzer = new Builder();
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700177 try {
178
179 Jar jar = new Jar(new File(inputJar)); // where our data is
180 analyzer.setJar(jar); // give bnd the contents
181
182 // You can provide additional class path entries to allow
183 // bnd to pickup export version from the packageinfo file,
184 // Version annotation, or their manifests.
185 analyzer.addClasspath(classpath);
186
187 setProperties(analyzer);
188
189// analyzer.setProperty("DESTDIR");
190// analyzer.setBase();
191
192 // ------------- let's begin... -------------------------
193
194 // Analyze the target JAR first
195 analyzer.analyze();
196
197 // Scan the JAR for Felix SCR annotations and generate XML files
198 Map<String, String> properties = Maps.newHashMap();
199 SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
200 scrDescriptorBndPlugin.setProperties(properties);
201 scrDescriptorBndPlugin.setReporter(analyzer);
202 scrDescriptorBndPlugin.analyzeJar(analyzer);
203
Brian O'Connore5817c92016-04-06 15:41:48 -0700204 if (includeResources != null) {
205 doIncludeResources(analyzer);
206 }
207
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700208 // Repack the JAR as a WAR
209 doWabStaging(analyzer);
210
211 // Calculate the manifest
212 Manifest manifest = analyzer.calcManifest();
213 //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
214 //manifest.write(s);
215 //s.close();
216
217 if (analyzer.isOk()) {
218 analyzer.getJar().setManifest(manifest);
Brian O'Connore5817c92016-04-06 15:41:48 -0700219 if (analyzer.save(new File(outputJar), true)) {
220 log("Saved!\n");
221 } else {
222 warn("Failed to create jar \n");
223 return false;
224 }
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700225 } else {
Brian O'Connore5817c92016-04-06 15:41:48 -0700226 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700227 return false;
228 }
229
230 analyzer.close();
231
232 return true;
233 } catch (Exception e) {
234 e.printStackTrace();
235 return false;
236 }
237 }
238
239 private boolean isWab() {
Brian O'Connore5817c92016-04-06 15:41:48 -0700240 return !Objects.equals(webContext, NONE);
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700241 }
242
243 private void doWabStaging(Analyzer analyzer) throws Exception {
244 if (!isWab()) {
245 return;
246 }
247 String wab = analyzer.getProperty(analyzer.WAB);
248 Jar dot = analyzer.getJar();
249
250 log("wab %s", wab);
251 analyzer.setBundleClasspath("WEB-INF/classes," +
252 analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
253
254 Set<String> paths = new HashSet<String>(dot.getResources().keySet());
255
256 for (String path : paths) {
257 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
258 log("wab: moving: %s", path);
259 dot.rename(path, "WEB-INF/classes/" + path);
260 }
261 }
Brian O'Connore5817c92016-04-06 15:41:48 -0700262
263 Path wabRoot = Paths.get(wab);
264 includeFiles(dot, null, wabRoot.toString());
265 }
266
267 /**
268 * Parse the Bundle-Includes header. Files in the bundles Include header are
269 * included in the jar. The source can be a directory or a file.
270 *
271 * @throws Exception
272 */
273 private void doIncludeResources(Analyzer analyzer) throws Exception {
274 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
275 if (includes == null) {
276 return;
277 }
278 Parameters clauses = analyzer.parseHeader(includes);
279 Jar jar = analyzer.getJar();
280
281 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
282 String name = entry.getKey();
283 Map<String, String> extra = entry.getValue();
284 // TODO consider doing something with extras
285
286 String[] parts = name.split("\\s*=\\s*");
287 String source = parts[0];
288 String destination = parts[0];
289 if (parts.length == 2) {
290 source = parts[1];
291 }
292
293 includeFiles(jar, destination, source);
294 }
295 }
296
297 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
298 throws IOException {
299 Path sourceRootPath = Paths.get(sourceRoot);
300 // iterate through sources
301 // put each source on the jar
302 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
303 @Override
304 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
305 Path relativePath = sourceRootPath.relativize(file);
306 String destination = destinationRoot != null ?
307 destinationRoot + "/" + relativePath.toString() : //TODO
308 relativePath.toString();
309
310 addFileToJar(jar, destination, file.toAbsolutePath().toString());
311 return FileVisitResult.CONTINUE;
312 }
313 };
314 File dir = new File(sourceRoot);
315 if (dir.isFile()) {
316 addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
317 } else if (dir.isDirectory()) {
318 walkFileTree(sourceRootPath, visitor);
319 } else {
320 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
321 bundleSymbolicName, sourceRoot);
322 }
323 }
324
325 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
326 if (includedResources.contains(sourceAbsPath)) {
327 log("Skipping already included resource: %s\n", sourceAbsPath);
328 return false;
329 }
330 File file = new File(sourceAbsPath);
331 if (!file.isFile()) {
332 throw new RuntimeException(
333 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
334 }
335 Resource resource = new FileResource(file);
336 if (jar.getResource(destination) != null) {
337 warn("Skipping duplicate resource: %s\n", destination);
338 return false;
339 }
340 jar.putResource(destination, resource);
341 includedResources.add(sourceAbsPath);
342 log("Adding resource: %s\n", destination);
343 return true;
Brian O'Connor42c38cf2016-04-05 17:05:57 -0700344 }
345
346 private void log(String format, Object... objects) {
347 //System.err.printf(format, objects);
348 }
349
350 private void warn(String format, Object... objects) {
351 System.err.printf(format, objects);
352 }
353
354 @Override
355 public String toString() {
356 return MoreObjects.toStringHelper(this)
357 .add("inputJar", inputJar)
358 .add("outputJar", outputJar)
359 .add("classpath", classpath)
360 .add("bundleName", bundleName)
361 .add("groupId", groupId)
362 .add("bundleSymbolicName", bundleSymbolicName)
363 .add("bundleVersion", bundleVersion)
364 .add("bundleDescription", bundleDescription)
365 .add("bundleLicense", bundleLicense)
366 .toString();
367
368 }
369}