All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yudichev.jiotty.common.app.Application Maven / Gradle / Ivy

package net.yudichev.jiotty.common.app;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.inject.*;
import net.yudichev.jiotty.common.inject.LifecycleComponent;
import net.yudichev.jiotty.common.lang.MoreThrowables;
import net.yudichev.jiotty.common.lang.TypedBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;

public final class Application {
    private static final Logger logger = LoggerFactory.getLogger(Application.class);

    static {
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        SysOutOverSLF4J.sendSystemOutAndErrToSLF4J();
    }

    private final List componentsAttemptedToStart = new CopyOnWriteArrayList<>();
    private final AtomicBoolean restarting = new AtomicBoolean();
    private final AtomicBoolean jvmShuttingDown = new AtomicBoolean();
    private final AtomicBoolean startedAllComponentsSuccessfully = new AtomicBoolean();
    private final AtomicBoolean runCalled = new AtomicBoolean();
    private final Injector injector;

    private CountDownLatch shutdownLatch;
    private Thread runThread;

    private Application(Supplier moduleSupplier) {
        ApplicationLifecycleControl applicationLifecycleControl = new ApplicationLifecycleControl() {
            @Override
            public void initiateShutdown() {
                logger.info("Application requested shutdown");
                initiateStop();
            }

            @Override
            public void initiateRestart() {
                checkState(!jvmShuttingDown.get(), "Cannot initiate restart while JVM is shutting down");
                checkState(restarting.compareAndSet(false, true), "Already restarting");
                logger.info("Application requested restart");
                initiateStop();
            }
        };

        injector = Guice.createInjector(new ApplicationSupportModule(applicationLifecycleControl), moduleSupplier.get());
    }

    /**
     * Start all {@link LifecycleComponent}s.
     *
     * @throws InterruptedException if the thread was interrupted while starting
     * @throws RuntimeException     if one of the components failed to start; note components that are already started won't be stopped, use
     *                              {@link #stop()} for that.
     */
    public void start() throws InterruptedException {
        startedAllComponentsSuccessfully.set(false);
        componentsAttemptedToStart.clear();
        logger.info("Initialising components");
        List allComponents = injector
                .findBindingsByType(new TypeLiteral() {})
                .stream()
                .map(lifecycleComponentBinding -> lifecycleComponentBinding.getProvider().get())
                .collect(toImmutableList());

        logger.info("Starting components");
        for (LifecycleComponent component : allComponents) {
            if (Thread.interrupted()) {
                throw new InterruptedException(String.format("Interrupted while starting; components attempted to start: %s out of %s",
                        componentsAttemptedToStart.size(), allComponents.size()));
            }
            componentsAttemptedToStart.add(component);
            start(component);
        }

        startedAllComponentsSuccessfully.set(true);
        logger.info("Started");
    }

    public Injector getInjector() {
        return injector;
    }

    /**
     * Stop all components that have been started - must be called on same thread that called {@link #start()}.
     */
    public void stop() {
        logger.info("Shutting down");
        stop(componentsAttemptedToStart);
        componentsAttemptedToStart.clear();
        logger.info("Shut down");
    }

    /**
     * Run as a daemon: start and blocks until application initiates shutdown (or JVM is requested to shut down) and all components are stopped.
     */
    public void run() {
        checkState(runCalled.compareAndSet(false, true), "Application.run() can only be called once");
        runThread = Thread.currentThread();

        AtomicReference fullyStoppedLatchRef = new AtomicReference<>();
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            jvmShuttingDown.set(true);
            CountDownLatch fullyStoppedLatch = fullyStoppedLatchRef.get();
            if (fullyStoppedLatch != null && fullyStoppedLatch.getCount() > 0) {
                logger.info("Shutdown hook fired");
                initiateStop();
                MoreThrowables.asUnchecked(() -> {
                    if (!fullyStoppedLatch.await(1, TimeUnit.MINUTES)) {
                        logger.warn("Timed out waiting for partially initialised application to shut down");
                    }
                });
            }
        }));

        do {
            logger.info("Starting");

            shutdownLatch = new CountDownLatch(1);
            CountDownLatch fullyStoppedLatch = new CountDownLatch(1);
            fullyStoppedLatchRef.set(fullyStoppedLatch);

            try {
                start();
            } catch (InterruptedException | RuntimeException e) {
                logger.error("Unable to initialize", e);
                shutdownLatch.countDown();
                // intentionally clearing the interrupted flag to guarantee the immediately following latch await not to fail
                //noinspection ResultOfMethodCallIgnored
                Thread.interrupted();
            }

            MoreThrowables.asUnchecked(shutdownLatch::await);
            stop();

            fullyStoppedLatch.countDown();
        } while (restarting.getAndSet(false));
    }

    public static Builder builder() {
        return new Builder();
    }

    private void initiateStop() {
        if (startedAllComponentsSuccessfully.get()) {
            shutdownLatch.countDown();
        } else {
            logger.info("Interrupting startup sequence");
            runThread.interrupt();
        }
    }

    private static void start(LifecycleComponent lifecycleComponent) {
        logger.info("Starting component {}", lifecycleComponent.name());
        lifecycleComponent.start();
        logger.info("Started component {}", lifecycleComponent.name());
    }

    private static void stop(List lifecycleComponents) {
        Lists.reverse(lifecycleComponents).forEach(lifecycleComponent -> {
            try {
                logger.info("Stopping component {}", lifecycleComponent.name());
                lifecycleComponent.stop();
                logger.info("Stopped component {}", lifecycleComponent.name());
            } catch (Throwable e) {
                logger.error("Failed stopping component {}", lifecycleComponent.name(), e);
            }
        });
    }

    public static final class Builder implements TypedBuilder {
        private final ImmutableList.Builder> moduleSupplierListBuilder = ImmutableList.builder();

        public Builder addModule(Supplier moduleSupplier) {
            moduleSupplierListBuilder.add(moduleSupplier);
            return this;
        }

        @Override
        public Application build() {
            List> moduleSuppliers = moduleSupplierListBuilder.build();
            Module module = new AbstractModule() {
                @Override
                protected void configure() {
                    moduleSuppliers.stream()
                            .map(Supplier::get)
                            .forEach(this::install);
                }
            };
            return new Application(() -> module);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy