io.smartspaces.workbench.project.java.BndOsgiContainerBundleCreator.java Source code

Java tutorial

Introduction

Here is the source code for io.smartspaces.workbench.project.java.BndOsgiContainerBundleCreator.java

Source

/*
 * Copyright (C) 2016 Keith M. Hughes
 * Copyright (C) 2013 Google Inc.
 *
 * 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 io.smartspaces.workbench.project.java;

import io.smartspaces.SimpleSmartSpacesException;
import io.smartspaces.SmartSpacesException;
import io.smartspaces.util.io.FileSupport;
import io.smartspaces.util.io.FileSupportImpl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipOutputStream;

import org.apache.commons.logging.Log;

import aQute.lib.osgi.Analyzer;
import aQute.lib.osgi.Constants;
import aQute.lib.osgi.Jar;
import aQute.lib.osgi.Verifier;

/**
 * Create an OSGi bundle from a source file using BND.
 *
 * @author Keith M. Hughes
 */
public class BndOsgiContainerBundleCreator implements ContainerBundleCreator {

    /**
     * The default fat jar name prefix.
     */
    private static final String DEFAULT_FAT_JAR_FILENAME_PREFIX = "smartspaces";

    /**
     * The default fat jar name suffix.
     */
    private static final String DEFAULT_FAT_JAR_FILENAME_SUFFIX = "-bundle.jar";

    /**
     * The prefix of the name for the merge jar before handed to BND.
     */
    private static final String MERGE_JAR_TEMP_DIRECTORY_PREFIX = "smartspaces-bundle";

    /**
     * The prefix for the filename for the ultimate OSGi bundle if no name is
     * specified.
     */
    private static final String GENERIC_BUNDLE_FILENAME_PREFIX = "smartspaces.";

    /**
     * Regular expression prefix for a guess at a symbolic name for a jar from its
     * filename.
     */
    private static final String PATTERN_PREFIX_SYMBOLIC_NAME_FROM_JAR = "(";

    /**
     * Regular expression suffix for a guess at a symbolic name for a jar from its
     * filename.
     */
    private static final String PATTERN_SUFFIX_SYMBOLIC_NAME_FROM_JAR = ")(-[0-9])?.*\\.jar";

    /**
     * BND declaration for all import packages should be considered optional.
     */
    private static final String ALL_IMPORT_PACKAGES_OPTIONAL = "*;resolution:=optional";

    /**
     * Size of IO buffers in bytes.
     */
    public static final int IO_BUFFER_SIZE = 1024;

    /**
     * A folder for temporary files.
     */
    private File baseTempDir;

    /**
     * The logger to use.
     */
    private Log log;

    /**
     * The file support to use for file operations.
     */
    private FileSupport fileSupport = FileSupportImpl.INSTANCE;

    /**
     * Construct a new creator.
     *
     * @param baseTempDir
     *          directory to be used for scratch files
     * @param log
     *          the logger to use
     */
    public BndOsgiContainerBundleCreator(File baseTempDir, Log log) {
        this.baseTempDir = baseTempDir;
        this.log = log;
    }

    @Override
    public void createBundle(List<File> sources, File output, File headers, List<File> classpath) {
        if (sources.size() > 1 && output == null) {
            throw new SimpleSmartSpacesException("An output name must be specified if merging multiple jars");
        }

        File sourceJarFile = getSourceJar(sources);

        Analyzer analyzer = new Analyzer();
        try {
            analyzer.setJar(sourceJarFile);
            analyzer.setPedantic(true);

            if (classpath != null) {
                analyzer.setClasspath((File[]) classpath.toArray());
            }

            Jar sourceJar = analyzer.getJar();

            if (headers != null) {
                analyzer.addProperties(headers);
            } else {
                analyzer.setProperty(Constants.IMPORT_PACKAGE, ALL_IMPORT_PACKAGES_OPTIONAL);

                Pattern p = Pattern.compile(PATTERN_PREFIX_SYMBOLIC_NAME_FROM_JAR + Verifier.SYMBOLICNAME.pattern()
                        + PATTERN_SUFFIX_SYMBOLIC_NAME_FROM_JAR);
                String base = sourceJarFile.getName();
                Matcher m = p.matcher(base);
                base = null;
                if (m.matches()) {
                    base = m.group(1);
                } else {
                    throw new SimpleSmartSpacesException("Can not calculate bundle name from the source jar, "
                            + "rename jar to be of form name-version.jar or use a headers file");
                }
                analyzer.setProperty(Constants.BUNDLE_SYMBOLICNAME, base);

                String export = analyzer.calculateExportsFromContents(sourceJar);
                analyzer.setProperty(Constants.EXPORT_PACKAGE, export);
            }

            analyzer.mergeManifest(sourceJar.getManifest());

            // Cleanup the version ..
            String version = analyzer.getProperty(Constants.BUNDLE_VERSION);
            if (version != null) {
                version = Analyzer.cleanupVersion(version);
                analyzer.setProperty(Constants.BUNDLE_VERSION, version);
            }

            if (output == null) {
                output = sourceJarFile.getAbsoluteFile().getParentFile();
            }

            String bundleFileName = GENERIC_BUNDLE_FILENAME_PREFIX + sourceJarFile.getName();

            if (output.isDirectory()) {
                output = fileSupport.newFile(output, bundleFileName);
            }

            analyzer.calcManifest();
            Jar finalJar = analyzer.getJar();

            List<String> errors = analyzer.getErrors();
            outputErrors(errors, log);
            outputWarnings(analyzer.getWarnings(), log);

            if (errors.isEmpty()) {
                finalJar.write(output);

                log.info(String.format("Created OSGi bundle %s", output.getAbsolutePath()));
            }
        } catch (Throwable e) {
            throw new SmartSpacesException("Error while creating OSGi bundle", e);
        } finally {
            fileSupport.close(analyzer, false);

            if (sources.size() > 1) {
                fileSupport.delete(sourceJarFile);
            }
        }
    }

    /**
     * Get the source jar for BND analysis.
     *
     * <p>
     * If multiple sources this file should be deleted after the source jar is
     * processed.
     *
     * @param sources
     *          the source jars
     *
     * @return the source jar
     */
    private File getSourceJar(List<File> sources) {
        if (sources.size() > 1) {
            return mergeSources(sources);
        } else {
            return sources.get(0);
        }
    }

    /**
     * Merge the following sources into one large source.
     *
     * @param sources
     *          the source jars
     *
     * @return a source which contains everything from the sources
     */
    private File mergeSources(List<File> sources) {
        File tempExpansionFolder = null;
        File fatJar = null;
        try {
            tempExpansionFolder = fileSupport.createTempDirectory(baseTempDir, MERGE_JAR_TEMP_DIRECTORY_PREFIX);

            for (File source : sources) {
                fileSupport.unzip(source, tempExpansionFolder);
            }

            fatJar = fileSupport.createTempFile(baseTempDir, DEFAULT_FAT_JAR_FILENAME_PREFIX,
                    DEFAULT_FAT_JAR_FILENAME_SUFFIX);
            createJarFile(fatJar, tempExpansionFolder);

            return fatJar;
        } catch (Exception e) {
            if (fatJar != null) {
                fileSupport.delete(fatJar);
            }

            throw new SmartSpacesException("Exception while merging source jars", e);
        } finally {
            if (tempExpansionFolder != null) {
                fileSupport.delete(tempExpansionFolder);
            }
        }
    }

    /**
     * Output errors for the user.
     *
     * @param issues
     *          the issues
     * @param log
     *          the logger to use
     */
    private void outputErrors(List<String> issues, Log log) {
        for (String issue : issues) {
            log.error("OSGi Bundle Creator: " + issue);
        }
    }

    /**
     * Output warnings for the user.
     *
     * @param issues
     *          the issues
     * @param log
     *          the logger to use
     */
    private void outputWarnings(List<String> issues, Log log) {
        for (String issue : issues) {
            log.warn("OSGi Bundle Creator: " + issue);
        }
    }

    /**
     * Create a Jar file from a source folder.
     *
     * @param jarDestinationFile
     *          the jar file being created
     * @param sourceFolder
     *          the source folder containing the classes
     */
    private void createJarFile(File jarDestinationFile, File sourceFolder) {
        // Create a buffer for reading the files
        byte[] buf = new byte[IO_BUFFER_SIZE];

        JarOutputStream out = null;
        try {
            // Create the jar file
            out = new JarOutputStream(new FileOutputStream(jarDestinationFile));

            writeJarFile(sourceFolder, buf, out, "");

            // Complete the jar file
            out.close();
        } catch (Exception e) {
            throw new SmartSpacesException(
                    String.format("Failed creating jar file %s", jarDestinationFile.getAbsolutePath()), e);
        } finally {
            fileSupport.close(out, true);
        }
    }

    /**
     * Write out the contents of the folder to the distribution file.
     *
     * @param directory
     *          folder being written to the build
     * @param buf
     *          a buffer for caching info
     * @param jarOutputStream
     *          the stream where the jar is being written
     * @param parentPath
     *          path up to this point
     *
     * @throws IOException
     *           for IO access errors
     */
    private void writeJarFile(File directory, byte[] buf, ZipOutputStream jarOutputStream, String parentPath)
            throws IOException {
        File[] files = directory.listFiles();
        if (files == null || files.length == 0) {
            log.warn("No source files found in " + directory.getAbsolutePath());
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                writeJarFile(file, buf, jarOutputStream, parentPath + file.getName() + "/");
            } else {
                FileInputStream in = null;
                try {
                    in = new FileInputStream(file);

                    // Add ZIP entry to output stream.
                    jarOutputStream.putNextEntry(new JarEntry(parentPath + file.getName()));

                    // Transfer bytes from the file to the ZIP file
                    int len;
                    while ((len = in.read(buf)) > 0) {
                        jarOutputStream.write(buf, 0, len);
                    }

                    // Complete the entry
                    jarOutputStream.closeEntry();
                } finally {
                    fileSupport.close(in, false);
                }
            }
        }
    }
}