blob: aa529fc95e7c26b811a04ba28fe3d68a7b400909 [file] [log] [blame]
Brian O'Connoree674952016-09-13 16:31:45 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
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.onosproject.onosjar;
18
19import aQute.bnd.header.Attrs;
20import aQute.bnd.header.Parameters;
21import aQute.bnd.osgi.Analyzer;
22import aQute.bnd.osgi.Builder;
23import aQute.bnd.osgi.FileResource;
24import aQute.bnd.osgi.Jar;
25import aQute.bnd.osgi.Resource;
26import com.facebook.buck.step.ExecutionContext;
27import com.facebook.buck.step.Step;
28import com.facebook.buck.step.StepExecutionResult;
29import com.google.common.base.MoreObjects;
30import com.google.common.collect.ImmutableSortedSet;
31import com.google.common.collect.Lists;
32import com.google.common.collect.Maps;
33import com.google.common.collect.Sets;
34import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;
35
36import java.io.File;
37import java.io.IOException;
38import java.io.PrintStream;
39import java.nio.file.FileVisitResult;
40import java.nio.file.FileVisitor;
41import java.nio.file.Path;
42import java.nio.file.Paths;
43import java.nio.file.SimpleFileVisitor;
44import java.nio.file.attribute.BasicFileAttributes;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Map;
48import java.util.Set;
49import java.util.jar.Manifest;
50import java.util.stream.Collectors;
51
52import static java.nio.file.Files.walkFileTree;
53
54/**
55 * BND-based wrapper to convert Buck JARs to OSGi-compatible JARs.
56 */
57public class OSGiWrapper implements Step {
58
59 private Path inputJar;
60 private Path outputJar;
61 private Path sourcesDir;
62 private Path classesDir;
63 private List<String> classpath;
64
65 private String bundleName;
66 private String groupId;
67 private String bundleSymbolicName;
68 private String bundleVersion;
69
70 private String importPackages;
71 private String dynamicimportPackages;
72
73 private String exportPackages;
74 private String includeResources;
75 private Set<String> includedResources = Sets.newHashSet();
76
77 private String bundleDescription;
78 private String bundleLicense;
79
80 private String webContext;
81
82 private PrintStream stderr = System.err;
83
84 public OSGiWrapper(Path inputJar,
85 Path outputJar,
86 Path sourcesDir,
87 Path classesDir,
88 ImmutableSortedSet<Path> classpath,
89 String bundleName,
90 String groupId,
91 String bundleVersion,
92 String bundleLicense,
93 String importPackages,
94 String exportPackages,
95 String includeResources,
96 String webContext,
97 String dynamicimportPackages,
98 String bundleDescription) {
99 this.inputJar = inputJar;
100 this.sourcesDir = sourcesDir;
101 this.classesDir = classesDir;
102 this.classpath = Lists.newArrayList(
103 classpath.stream().map(Path::toString).collect(Collectors.toList()));
104 if (!this.classpath.contains(inputJar.toString())) {
105 this.classpath.add(0, inputJar.toString());
106 }
107 this.outputJar = outputJar;
108
109 this.bundleName = bundleName;
110 this.groupId = groupId;
111 this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
112
113 this.bundleVersion = bundleVersion;
114 this.bundleLicense = bundleLicense;
115 this.bundleDescription = bundleDescription;
116
117 this.importPackages = importPackages;
118 this.dynamicimportPackages = dynamicimportPackages;
119 this.exportPackages = exportPackages;
120 this.includeResources = includeResources;
121
122 this.webContext = webContext;
123 }
124
125 private void setProperties(Analyzer analyzer) {
126 analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
127 analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
128 analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
129
130 if (bundleDescription != null) {
131 analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
132 }
133 if (bundleLicense != null) {
134 analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
135 }
136
137 //TODO consider using stricter version policy
138 //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
139 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
140
141 // There are no good defaults so make sure you set the Import-Package
142 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
143
144 analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
145
146 // TODO include version in export, but not in import
147 analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
148
149 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
150 if (includeResources != null) {
151 analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
152 }
153
154 if (isWab()) {
155 analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
156 analyzer.setProperty("Web-ContextPath", webContext);
157 analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
158 }
159 }
160
161 public boolean execute() {
162 Analyzer analyzer = new Builder();
163 try {
164
165 Jar jar = new Jar(inputJar.toFile()); // where our data is
166 analyzer.setJar(jar); // give bnd the contents
167
168 // You can provide additional class path entries to allow
169 // bnd to pickup export version from the packageinfo file,
170 // Version annotation, or their manifests.
171 analyzer.addClasspath(classpath);
172
173 setProperties(analyzer);
174
175 //analyzer.setBase(classesDir.toFile());
176
177// analyzer.setProperty("DESTDIR");
178// analyzer.setBase();
179
180 // ------------- let's begin... -------------------------
181
182 // Analyze the target JAR first
183 analyzer.analyze();
184
185 // Scan the JAR for Felix SCR annotations and generate XML files
186 Map<String, String> properties = Maps.newHashMap();
187 properties.put("destdir", classesDir.toAbsolutePath().toString());
188 SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
189 scrDescriptorBndPlugin.setProperties(properties);
190 scrDescriptorBndPlugin.setReporter(analyzer);
191 scrDescriptorBndPlugin.analyzeJar(analyzer);
192
193 if (includeResources != null) {
194 doIncludeResources(analyzer);
195 }
196
197 // Repack the JAR as a WAR
198 doWabStaging(analyzer);
199
200 // Calculate the manifest
201 Manifest manifest = analyzer.calcManifest();
202 //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
203 //manifest.write(s);
204 //s.close();
205
206 if (analyzer.isOk()) {
207 analyzer.getJar().setManifest(manifest);
208 if (analyzer.save(outputJar.toFile(), true)) {
209 log("Saved!\n");
210 } else {
211 warn("Failed to create jar \n");
212 return false;
213 }
214 } else {
215 warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
216 return false;
217 }
218
219 analyzer.close();
220
221 return true;
222 } catch (Exception e) {
223 e.printStackTrace();
224 return false;
225 }
226 }
227
228 private boolean isWab() {
229 return webContext != null;
230 }
231
232 private void doWabStaging(Analyzer analyzer) throws Exception {
233 if (!isWab()) {
234 return;
235 }
236 String wab = analyzer.getProperty(analyzer.WAB);
237 Jar dot = analyzer.getJar();
238
239 log("wab %s", wab);
240 analyzer.setBundleClasspath("WEB-INF/classes," +
241 analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
242
243 Set<String> paths = new HashSet<>(dot.getResources().keySet());
244
245 for (String path : paths) {
246 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
247 log("wab: moving: %s", path);
248 dot.rename(path, "WEB-INF/classes/" + path);
249 }
250 }
251
252 Path wabRoot = Paths.get(wab);
253 includeFiles(dot, null, wabRoot.toString());
254 }
255
256 /**
257 * Parse the Bundle-Includes header. Files in the bundles Include header are
258 * included in the jar. The source can be a directory or a file.
259 *
260 * @throws Exception
261 */
262 private void doIncludeResources(Analyzer analyzer) throws Exception {
263 String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
264 if (includes == null) {
265 return;
266 }
267 Parameters clauses = analyzer.parseHeader(includes);
268 Jar jar = analyzer.getJar();
269
270 for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
271 String name = entry.getKey();
272 Map<String, String> extra = entry.getValue();
273 // TODO consider doing something with extras
274
275 String[] parts = name.split("\\s*=\\s*");
276 String source = parts[0];
277 String destination = parts[0];
278 if (parts.length == 2) {
279 source = parts[1];
280 }
281
282 includeFiles(jar, destination, source);
283 }
284 }
285
286 private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
287 throws IOException {
288
289 Path classesBasedPath = classesDir.resolve(sourceRoot);
290 Path sourceBasedPath = sourcesDir.resolve(sourceRoot);
291
292 File classFile = classesBasedPath.toFile();
293 File sourceFile = sourceBasedPath.toFile();
294
295 if (classFile.isFile()) {
296 addFileToJar(jar, destinationRoot, classesBasedPath.toAbsolutePath().toString());
297 } else if (sourceFile.isFile()) {
298 addFileToJar(jar, destinationRoot, sourceBasedPath.toAbsolutePath().toString());
299 } else if (classFile.isDirectory()) {
300 includeDirectory(jar, destinationRoot, classesBasedPath);
301 } else if (sourceFile.isDirectory()) {
302 includeDirectory(jar, destinationRoot, sourceBasedPath);
303 } else {
304 warn("Skipping resource in bundle %s: %s (File Not Found)\n",
305 bundleSymbolicName, sourceRoot);
306 }
307 }
308
309 private void includeDirectory(Jar jar, String destinationRoot, Path sourceRoot)
310 throws IOException {
311 // iterate through sources
312 // put each source on the jar
313 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
314 @Override
315 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
316 Path relativePath = sourceRoot.relativize(file);
317 String destination = destinationRoot != null ?
318 destinationRoot + "/" + relativePath.toString() : //TODO
319 relativePath.toString();
320
321 addFileToJar(jar, destination, file.toAbsolutePath().toString());
322 return FileVisitResult.CONTINUE;
323 }
324 };
325
326 walkFileTree(sourceRoot, visitor);
327 }
328
329 private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
330 if (includedResources.contains(sourceAbsPath)) {
331 log("Skipping already included resource: %s\n", sourceAbsPath);
332 return false;
333 }
334 File file = new File(sourceAbsPath);
335 if (!file.isFile()) {
336 throw new RuntimeException(
337 String.format("Skipping non-existent file: %s\n", sourceAbsPath));
338 }
339 Resource resource = new FileResource(file);
340 if (jar.getResource(destination) != null) {
341 warn("Skipping duplicate resource: %s\n", destination);
342 return false;
343 }
344 jar.putResource(destination, resource);
345 includedResources.add(sourceAbsPath);
346 log("Adding resource: %s\n", destination);
347 return true;
348 }
349
350 private void log(String format, Object... objects) {
351 //System.err.printf(format, objects);
352 }
353
354 private void warn(String format, Object... objects) {
355 stderr.printf(format, objects);
356 }
357
358 @Override
359 public String toString() {
360 return MoreObjects.toStringHelper(this)
361 .add("inputJar", inputJar)
362 .add("outputJar", outputJar)
363 .add("classpath", classpath)
364 .add("bundleName", bundleName)
365 .add("groupId", groupId)
366 .add("bundleSymbolicName", bundleSymbolicName)
367 .add("bundleVersion", bundleVersion)
368 .add("bundleDescription", bundleDescription)
369 .add("bundleLicense", bundleLicense)
370 .toString();
371
372 }
373
374 @Override
375 public StepExecutionResult execute(ExecutionContext executionContext)
376 throws IOException, InterruptedException {
377 stderr = executionContext.getStdErr();
378 boolean success = execute();
379 stderr = System.err;
380 return success ? StepExecutionResult.SUCCESS : StepExecutionResult.ERROR;
381 }
382
383 @Override
384 public String getShortName() {
385 return "osgiwrap";
386 }
387
388 @Override
389 public String getDescription(ExecutionContext executionContext) {
390 return "osgiwrap"; //FIXME
391 }
392}