Enhancing OnosJar to build OSGi jars and wars if required.

Also, simplifying onos.bucklet by using the rule

Change-Id: If89633db2d83cbfc56a8e70d2bea665ffaf186ff
diff --git a/tools/build/buck-plugin/src/main/java/org/onosproject/onosjar/OSGiWrapper.java b/tools/build/buck-plugin/src/main/java/org/onosproject/onosjar/OSGiWrapper.java
new file mode 100644
index 0000000..aa529fc
--- /dev/null
+++ b/tools/build/buck-plugin/src/main/java/org/onosproject/onosjar/OSGiWrapper.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.onosjar;
+
+import aQute.bnd.header.Attrs;
+import aQute.bnd.header.Parameters;
+import aQute.bnd.osgi.Analyzer;
+import aQute.bnd.osgi.Builder;
+import aQute.bnd.osgi.FileResource;
+import aQute.bnd.osgi.Jar;
+import aQute.bnd.osgi.Resource;
+import com.facebook.buck.step.ExecutionContext;
+import com.facebook.buck.step.Step;
+import com.facebook.buck.step.StepExecutionResult;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+import static java.nio.file.Files.walkFileTree;
+
+/**
+ * BND-based wrapper to convert Buck JARs to OSGi-compatible JARs.
+ */
+public class OSGiWrapper implements Step {
+
+    private Path inputJar;
+    private Path outputJar;
+    private Path sourcesDir;
+    private Path classesDir;
+    private List<String> classpath;
+
+    private String bundleName;
+    private String groupId;
+    private String bundleSymbolicName;
+    private String bundleVersion;
+
+    private String importPackages;
+    private String dynamicimportPackages;
+
+    private String exportPackages;
+    private String includeResources;
+    private Set<String> includedResources = Sets.newHashSet();
+
+    private String bundleDescription;
+    private String bundleLicense;
+
+    private String webContext;
+
+    private PrintStream stderr = System.err;
+
+    public OSGiWrapper(Path inputJar,
+                       Path outputJar,
+                       Path sourcesDir,
+                       Path classesDir,
+                       ImmutableSortedSet<Path> classpath,
+                       String bundleName,
+                       String groupId,
+                       String bundleVersion,
+                       String bundleLicense,
+                       String importPackages,
+                       String exportPackages,
+                       String includeResources,
+                       String webContext,
+                       String dynamicimportPackages,
+                       String bundleDescription) {
+        this.inputJar = inputJar;
+        this.sourcesDir = sourcesDir;
+        this.classesDir = classesDir;
+        this.classpath = Lists.newArrayList(
+                classpath.stream().map(Path::toString).collect(Collectors.toList()));
+        if (!this.classpath.contains(inputJar.toString())) {
+            this.classpath.add(0, inputJar.toString());
+        }
+        this.outputJar = outputJar;
+
+        this.bundleName = bundleName;
+        this.groupId = groupId;
+        this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
+
+        this.bundleVersion = bundleVersion;
+        this.bundleLicense = bundleLicense;
+        this.bundleDescription = bundleDescription;
+
+        this.importPackages = importPackages;
+        this.dynamicimportPackages = dynamicimportPackages;
+        this.exportPackages = exportPackages;
+        this.includeResources = includeResources;
+
+        this.webContext = webContext;
+    }
+
+    private void setProperties(Analyzer analyzer) {
+        analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
+        analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
+        analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
+
+        if (bundleDescription != null) {
+            analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
+        }
+        if (bundleLicense != null) {
+            analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
+        }
+
+        //TODO consider using stricter version policy
+        //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
+        //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
+
+        // There are no good defaults so make sure you set the Import-Package
+        analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
+
+        analyzer.setProperty(Analyzer.DYNAMICIMPORT_PACKAGE, dynamicimportPackages);
+
+        // TODO include version in export, but not in import
+        analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
+
+        // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
+        if (includeResources != null) {
+            analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
+        }
+
+        if (isWab()) {
+            analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
+            analyzer.setProperty("Web-ContextPath", webContext);
+            analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
+        }
+    }
+
+    public boolean execute() {
+        Analyzer analyzer = new Builder();
+        try {
+
+            Jar jar = new Jar(inputJar.toFile());  // where our data is
+            analyzer.setJar(jar);                   // give bnd the contents
+
+            // You can provide additional class path entries to allow
+            // bnd to pickup export version from the packageinfo file,
+            // Version annotation, or their manifests.
+            analyzer.addClasspath(classpath);
+
+            setProperties(analyzer);
+
+            //analyzer.setBase(classesDir.toFile());
+
+//            analyzer.setProperty("DESTDIR");
+//            analyzer.setBase();
+
+            // ------------- let's begin... -------------------------
+
+            // Analyze the target JAR first
+            analyzer.analyze();
+
+            // Scan the JAR for Felix SCR annotations and generate XML files
+            Map<String, String> properties = Maps.newHashMap();
+            properties.put("destdir", classesDir.toAbsolutePath().toString());
+            SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
+            scrDescriptorBndPlugin.setProperties(properties);
+            scrDescriptorBndPlugin.setReporter(analyzer);
+            scrDescriptorBndPlugin.analyzeJar(analyzer);
+
+            if (includeResources != null) {
+                doIncludeResources(analyzer);
+            }
+
+            // Repack the JAR as a WAR
+            doWabStaging(analyzer);
+
+            // Calculate the manifest
+            Manifest manifest = analyzer.calcManifest();
+            //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
+            //manifest.write(s);
+            //s.close();
+
+            if (analyzer.isOk()) {
+                analyzer.getJar().setManifest(manifest);
+                if (analyzer.save(outputJar.toFile(), true)) {
+                    log("Saved!\n");
+                } else {
+                    warn("Failed to create jar \n");
+                    return false;
+                }
+            } else {
+                warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
+                return false;
+            }
+
+            analyzer.close();
+
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    private boolean isWab() {
+        return webContext != null;
+    }
+
+    private void doWabStaging(Analyzer analyzer) throws Exception {
+        if (!isWab()) {
+            return;
+        }
+        String wab = analyzer.getProperty(analyzer.WAB);
+        Jar dot = analyzer.getJar();
+
+        log("wab %s", wab);
+        analyzer.setBundleClasspath("WEB-INF/classes," +
+                                    analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
+
+        Set<String> paths = new HashSet<>(dot.getResources().keySet());
+
+        for (String path : paths) {
+            if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
+                log("wab: moving: %s", path);
+                dot.rename(path, "WEB-INF/classes/" + path);
+            }
+        }
+
+        Path wabRoot = Paths.get(wab);
+        includeFiles(dot, null, wabRoot.toString());
+    }
+
+    /**
+     * Parse the Bundle-Includes header. Files in the bundles Include header are
+     * included in the jar. The source can be a directory or a file.
+     *
+     * @throws Exception
+     */
+    private void doIncludeResources(Analyzer analyzer) throws Exception {
+        String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
+        if (includes == null) {
+            return;
+        }
+        Parameters clauses = analyzer.parseHeader(includes);
+        Jar jar = analyzer.getJar();
+
+        for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
+            String name = entry.getKey();
+            Map<String, String> extra = entry.getValue();
+            // TODO consider doing something with extras
+
+            String[] parts = name.split("\\s*=\\s*");
+            String source = parts[0];
+            String destination = parts[0];
+            if (parts.length == 2) {
+                source = parts[1];
+            }
+
+            includeFiles(jar, destination, source);
+        }
+    }
+
+    private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
+            throws IOException {
+
+        Path classesBasedPath = classesDir.resolve(sourceRoot);
+        Path sourceBasedPath = sourcesDir.resolve(sourceRoot);
+
+        File classFile = classesBasedPath.toFile();
+        File sourceFile = sourceBasedPath.toFile();
+
+        if (classFile.isFile()) {
+            addFileToJar(jar, destinationRoot, classesBasedPath.toAbsolutePath().toString());
+        } else if (sourceFile.isFile()) {
+            addFileToJar(jar, destinationRoot, sourceBasedPath.toAbsolutePath().toString());
+        } else if (classFile.isDirectory()) {
+            includeDirectory(jar, destinationRoot, classesBasedPath);
+        } else if (sourceFile.isDirectory()) {
+            includeDirectory(jar, destinationRoot, sourceBasedPath);
+        } else {
+            warn("Skipping resource in bundle %s: %s (File Not Found)\n",
+                 bundleSymbolicName, sourceRoot);
+        }
+    }
+
+    private void includeDirectory(Jar jar, String destinationRoot, Path sourceRoot)
+            throws IOException {
+        // iterate through sources
+        // put each source on the jar
+        FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                Path relativePath = sourceRoot.relativize(file);
+                String destination = destinationRoot != null ?
+                        destinationRoot + "/" + relativePath.toString() : //TODO
+                        relativePath.toString();
+
+                addFileToJar(jar, destination, file.toAbsolutePath().toString());
+                return FileVisitResult.CONTINUE;
+            }
+        };
+
+        walkFileTree(sourceRoot, visitor);
+    }
+
+    private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
+        if (includedResources.contains(sourceAbsPath)) {
+            log("Skipping already included resource: %s\n", sourceAbsPath);
+            return false;
+        }
+        File file = new File(sourceAbsPath);
+        if (!file.isFile()) {
+            throw new RuntimeException(
+                    String.format("Skipping non-existent file: %s\n", sourceAbsPath));
+        }
+        Resource resource = new FileResource(file);
+        if (jar.getResource(destination) != null) {
+            warn("Skipping duplicate resource: %s\n", destination);
+            return false;
+        }
+        jar.putResource(destination, resource);
+        includedResources.add(sourceAbsPath);
+        log("Adding resource: %s\n", destination);
+        return true;
+    }
+
+    private void log(String format, Object... objects) {
+        //System.err.printf(format, objects);
+    }
+
+    private void warn(String format, Object... objects) {
+        stderr.printf(format, objects);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("inputJar", inputJar)
+                .add("outputJar", outputJar)
+                .add("classpath", classpath)
+                .add("bundleName", bundleName)
+                .add("groupId", groupId)
+                .add("bundleSymbolicName", bundleSymbolicName)
+                .add("bundleVersion", bundleVersion)
+                .add("bundleDescription", bundleDescription)
+                .add("bundleLicense", bundleLicense)
+                .toString();
+
+    }
+
+    @Override
+    public StepExecutionResult execute(ExecutionContext executionContext)
+            throws IOException, InterruptedException {
+        stderr = executionContext.getStdErr();
+        boolean success = execute();
+        stderr = System.err;
+        return success ? StepExecutionResult.SUCCESS : StepExecutionResult.ERROR;
+    }
+
+    @Override
+    public String getShortName() {
+        return "osgiwrap";
+    }
+
+    @Override
+    public String getDescription(ExecutionContext executionContext) {
+        return "osgiwrap"; //FIXME
+    }
+}