ch.vorburger.osgi.builder.internal.SourceInstallServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for ch.vorburger.osgi.builder.internal.SourceInstallServiceImpl.java

Source

/**
 * ch.vorburger.osgi.gradle
 *
 * Copyright (C) 2016 - 2017 Michael Vorburger.ch <mike@vorburger.ch>
 *
 * 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 ch.vorburger.osgi.builder.internal;

import ch.vorburger.osgi.builder.gradle.internal.GradleBuildService;
import ch.vorburger.osgi.builder.maven.internal.MavenBuildService;
import ch.vorburger.fswatch.DirectoryWatcher;
import ch.vorburger.fswatch.FileWatcherBuilder;
import ch.vorburger.osgi.builder.SourceInstallService;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of SourceInstallService.
 *
 * @author Michael Vorburger
 */
// TODO @Component
public class SourceInstallServiceImpl
        implements SourceInstallService, ch.vorburger.osgi.gradle.SourceInstallService, AutoCloseable {

    private static final Logger LOG = LoggerFactory.getLogger(SourceInstallServiceImpl.class);

    private final BundleContext bundleContext;
    private final BuildService gradleBuildService = new GradleBuildService();
    private final BuildService mavenBuildService = new MavenBuildService();

    private DirectoryWatcher bundleFileWatcher;

    public SourceInstallServiceImpl(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }

    @Override
    public ListenableFuture<Bundle> installSourceBundle(File projectDirectoryOrBundleJAR) {
        SettableFuture<Bundle> installFuture = SettableFuture.create();
        BuildServiceSingleFileOutputListener listener = singleProducedFile -> {
            try (InputStream inputStream = new FileInputStream(singleProducedFile)) {
                String location = getBundleLocation(projectDirectoryOrBundleJAR);
                Bundle bundle = bundleContext.getBundle(location);
                if (bundle == null) {
                    LOG.info("Installing Bundle from {}", location);
                    bundle = bundleContext.installBundle(location, inputStream);
                    bundle.start();
                } else {
                    LOG.info("Updating Bundle from {}", location);
                    bundle.update(inputStream);
                    // We (possibly "re")-start here, because it's possible that
                    // an initial (or previous) start() failed due to some bug in the bundle
                    // and that could have meanwhile be fixed, but OSGi won't re-try starting
                    // a bundle an update if we don't tell it to...
                    bundle.start();
                }
                installFuture.set(bundle);
            } catch (BundleException | IOException e) {
                LOG.error("Problem reading/installing bundle JAR: {}", singleProducedFile, e);
                installFuture.setException(e);
            }
        };

        ListenableFuture<Void> buildFuture;
        if (new File(projectDirectoryOrBundleJAR, "pom.xml").exists()) {
            LOG.info("Found a POM in directory, will continously build with Maven: {}",
                    projectDirectoryOrBundleJAR);
            buildFuture = mavenBuildService.buildContinously(projectDirectoryOrBundleJAR, "install", listener);
        } else if (projectDirectoryOrBundleJAR.isDirectory()) {
            LOG.info("Found directory (but no POM), will continously build with Gradle: {}",
                    projectDirectoryOrBundleJAR);
            buildFuture = gradleBuildService.buildContinously(projectDirectoryOrBundleJAR, "build", listener);
        } else if (projectDirectoryOrBundleJAR.isFile() && projectDirectoryOrBundleJAR.getName().endsWith(".jar")) {
            LOG.info("Found JAR, will install and update on update: {}", projectDirectoryOrBundleJAR);
            // The JAR is already ready now, and can be started by caller:
            try {
                // NB: The default quietPeriod of 100ms is often not enough while Gradle updates the JAR and leads to ZipException, so 500ms:
                bundleFileWatcher = new FileWatcherBuilder().path(projectDirectoryOrBundleJAR).quietPeriodInMS(500)
                        .listener((path, changeKind) -> {
                            switch (changeKind) {
                            case MODIFIED:
                                // NB: FileWatcherBuilder invoked the listener once on start, and then on subsequent changes
                                listener.buildSucceeded(projectDirectoryOrBundleJAR);
                                break;

                            case DELETED:
                                String location = getBundleLocation(projectDirectoryOrBundleJAR);
                                LOG.info("Uninstalling Bundle from {}", location);
                                bundleContext.getBundle(location).uninstall();
                                break;

                            default:
                                LOG.error("Unsupported file watcher change kind, ignored: {}", changeKind);
                                break;
                            }

                            System.out.println(changeKind.name() + " " + path.toString());
                        }).build();
                buildFuture = Futures.immediateFuture(null);
            } catch (IOException e) {
                buildFuture = Futures.immediateFailedFuture(e);
            }
            // But we make sure than upon changes it gets reloaded:
            // TODO!!!!
        } else {
            buildFuture = Futures.immediateFailedFuture(
                    new IllegalArgumentException("Neither a directory (with or w.o. pom.xml) nor a JAR, "
                            + "how I am supposed to (build and) install this as an OSGi bundle: "
                            + projectDirectoryOrBundleJAR));
        }

        Futures.addCallback(buildFuture, new FutureCallback<Void>() {

            @Override
            public void onFailure(Throwable throwable) {
                // If this happens, then the listener above will never get invoked
                // because the (first, as it's continous) build failed before, so:
                installFuture.setException(throwable);
            }

            @Override
            public void onSuccess(Void nothing) {
            }
        });
        return installFuture;
    }

    private String getBundleLocation(File projectDirectoryOrBundleJAR) {
        String location = projectDirectoryOrBundleJAR.toURI().toString();
        if (projectDirectoryOrBundleJAR.isDirectory()) {
            location = "source:" + location;
        }
        return location;
    }

    @Override
    // TODO @Deactivate
    public void close() throws Exception {
        gradleBuildService.close();
        mavenBuildService.close();
        if (bundleFileWatcher != null) {
            bundleFileWatcher.close();
        }
    }

}