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

io.datakernel.launcher.Launcher Maven / Gradle / Ivy

/*
 * Copyright (C) 2015 SoftIndex LLC.
 *
 * 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.datakernel.launcher;

import io.datakernel.di.core.Injector;
import io.datakernel.di.core.Key;
import io.datakernel.di.module.Module;
import io.datakernel.jmx.ConcurrentJmxMBean;
import io.datakernel.jmx.JmxAttribute;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;

import static io.datakernel.di.util.Utils.makeGraphVizGraph;
import static java.util.Collections.emptySet;
import static java.util.Comparator.comparingInt;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * Integrates all modules together and manages application lifecycle by
 * passing several steps:
 * 
    *
  • wiring modules
  • *
  • starting services
  • *
  • running
  • *
  • stopping services
  • *
*

* Example.
* Prerequisites: an application consists of three modules, which preferably * should be configured using separate configs and may depend on each other. *

 * public class ApplicationLauncher extends Launcher {
 *
 *    @Override
 *    protected Collection<Module> getModules() {
 *        return null;
 *    }
 *
 *    @Override
 *    protected void run() {
 *        System.out.println("Hello world");
 *    }
 *
 *    public static void main(String[] args) throws Exception {
 *        ApplicationLauncher launcher = new ApplicationLauncher();
 *        launcher.launch(true, args);
 *    }
 * }
 * 
*/ @SuppressWarnings({"WeakerAccess", "RedundantThrows", "unused"}) public abstract class Launcher implements ConcurrentJmxMBean { protected final Logger logger = getLogger(getClass()); protected final Logger logger0 = getLogger(getClass().getName() + ".0"); public static final String[] NO_ARGS = {}; @NotNull protected String[] args = NO_ARGS; private Thread mainThread; private volatile Throwable applicationError; private volatile Instant instantOfLaunch; private volatile Instant instantOfStart; private volatile Instant instantOfRun; private volatile Instant instantOfStop; private volatile Instant instantOfComplete; private final CountDownLatch shutdownLatch = new CountDownLatch(1); private final CountDownLatch completeLatch = new CountDownLatch(1); private final CompletableFuture onStartFuture = new CompletableFuture<>(); private final CompletableFuture onRunFuture = new CompletableFuture<>(); private final CompletableFuture onCompleteFuture = new CompletableFuture<>(); /** * Creates an injector with modules and overrides from this launcher. * On creation it does all the binding checks so calling this method * triggers a static check which causes an exception to be thrown on * any incorrect bindings (unsatisfied or cyclic dependencies) * which is highly useful for testing. */ public final void testInjector() { createInjector(); } /** * Launch application following few simple steps: *
    *
  • Inject dependencies
  • *
  • Starts application, {@link Launcher#onStart()} is called in this stage
  • *
  • Runs application, {@link Launcher#run()} is called in this stage
  • *
  • Stops application, {@link Launcher#onStop()} is called in this stage
  • *
* You can override methods mentioned above to execute your code in needed stage. * * @param args program args that will be injected into @Args string array */ public final void launch(@NotNull String[] args) throws Exception { mainThread = Thread.currentThread(); instantOfLaunch = Instant.now(); try { logger.info("=== INJECTING DEPENDENCIES"); Injector injector = createInjector(args); injector.getInstance(this.getClass()); if (logger0.isInfoEnabled()) { logger0.info("Effective Injector:\n\n" + makeGraphVizGraph(injector.getBindingsTrie())); } onInit(injector); logger0.info("EagerSingletons: " + injector.createEagerSingletons()); Set services = injector.getInstanceOr(new Key>() {}, emptySet()); Set startedServices = new HashSet<>(); logger0.info("Post-inject instances: " + injector.postInjectInstances()); logger.info("=== STARTING APPLICATION"); try { instantOfStart = Instant.now(); logger0.info("Starting RootServices: " + services); startServices(services, startedServices); onStart(); onStartFuture.complete(null); } catch (Exception e) { applicationError = e; logger.error("Start error", e); onStartFuture.completeExceptionally(e); } if (applicationError == null) { logger.info("=== RUNNING APPLICATION"); try { instantOfRun = Instant.now(); run(); onRunFuture.complete(null); } catch (Exception e) { applicationError = e; logger.error("Error", e); onRunFuture.completeExceptionally(e); } } else { onRunFuture.completeExceptionally(applicationError); } logger.info("=== STOPPING APPLICATION"); instantOfStop = Instant.now(); if (!onStartFuture.isCompletedExceptionally()) { try { onStop(); } catch (Exception e) { logger.error("Stop error", e); } } stopServices(startedServices); if (applicationError == null) { onCompleteFuture.complete(null); } else { onCompleteFuture.completeExceptionally(applicationError); throw applicationError; } } catch (Exception e) { throw e; } catch (Throwable e) { applicationError = e; logger.error("JVM Fatal Error", e); System.exit(-1); } finally { instantOfComplete = Instant.now(); completeLatch.countDown(); } } private void startServices(Collection services, Collection startedServices) throws Throwable { List exceptions = new ArrayList<>(); CountDownLatch latch = new CountDownLatch(services.size()); synchronized (this) { for (LauncherService service : services) { if (!exceptions.isEmpty()) { latch.countDown(); continue; } logger0.info("Starting RootService: " + service); service.start().whenComplete(($, e) -> { synchronized (this) { if (e == null) { startedServices.add(service); } else { exceptions.add( (e instanceof CompletionException || e instanceof ExecutionException) && e.getCause() != null ? e.getCause() : e); } latch.countDown(); } }); } } latch.await(); if (!exceptions.isEmpty()) { exceptions.sort(comparingInt(e -> (e instanceof RuntimeException) ? 1 : (e instanceof Error ? 0 : 2))); throw exceptions.get(0); } } private void stopServices(Collection startedServices) throws InterruptedException { CountDownLatch latch = new CountDownLatch(startedServices.size()); for (LauncherService service : startedServices) { logger0.info("Stopping RootService: " + service); service.stop().whenComplete(($, e) -> { if (e != null) { logger.error("Stop error in " + service, (e instanceof CompletionException || e instanceof ExecutionException) && e.getCause() != null ? e.getCause() : e); } latch.countDown(); }); } latch.await(); } @NotNull public final Injector createInjector(@NotNull String[] args) { this.args = args; return createInjector(); } @NotNull public final Injector createInjector() { return Injector.of(getInternalModule().combineWith(getModule()).overrideWith(getOverrideModule())); } @SuppressWarnings("unchecked") private Module getInternalModule() { Class launcherClass = (Class) getClass(); Key> completionStageKey = new Key>() {}; return Module.create() .bind(String[].class).annotatedWith(Args.class).toInstance(args) .bind(Launcher.class).to(launcherClass) .bind(launcherClass).toInstance(this) .postInjectInto(launcherClass) .bind(completionStageKey.named(OnStart.class)).toInstance(onStartFuture) .bind(completionStageKey.named(OnRun.class)).toInstance(onRunFuture) .bind(completionStageKey.named(OnComplete.class)).toInstance(onCompleteFuture) .deepScan(Launcher.this); } /** * Supplies business logic module for application(ConfigModule, EventloopModule, etc...) */ protected Module getModule() { return Module.empty(); } /** * This module overrides definitions in internal module / business logic module */ protected Module getOverrideModule() { return Module.empty(); } /** * This method runs prior using injector and wiring the application */ protected void onInit(Injector injector) throws Exception { } /** * This method runs when application is starting */ protected void onStart() throws Exception { } /** * Launcher's main method. */ protected abstract void run() throws Exception; /** * This method runs when application is stopping */ protected void onStop() throws Exception { } /** * Blocks current thread until shutdown notification releases it. *
* Shutdown notification is released on JVM shutdown or by calling {@link Launcher#shutdown()} */ protected final void awaitShutdown() throws InterruptedException { Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { shutdown(); completeLatch.await(); Thread.sleep(10); // wait a bit for things outside `launch` call, such as JUnit finishing or whatever } catch (InterruptedException e) { logger.error("Shutdown took too long", e); } }, "shutdownNotification")); shutdownLatch.await(); } /** * Releases all threads waiting for shutdown. * * @see Launcher#awaitShutdown() */ public final void shutdown() { shutdownLatch.countDown(); } @NotNull public final Thread getMainThread() { return mainThread; } @NotNull public final String[] getArgs() { return args; } public final CompletionStage getStartFuture() { return onStartFuture; } public final CompletionStage getRunFuture() { return onRunFuture; } public final CompletionStage getCompleteFuture() { return onCompleteFuture; } @JmxAttribute @Nullable public final Instant getInstantOfLaunch() { return instantOfLaunch; } @JmxAttribute @Nullable public final Instant getInstantOfStart() { return instantOfStart; } @JmxAttribute @Nullable public final Instant getInstantOfRun() { return instantOfRun; } @JmxAttribute @Nullable public final Instant getInstantOfStop() { return instantOfStop; } @JmxAttribute @Nullable public final Instant getInstantOfComplete() { return instantOfComplete; } @JmxAttribute @Nullable public final Duration getDurationOfStart() { if (instantOfLaunch == null) { return null; } return Duration.between(instantOfLaunch, instantOfRun == null ? Instant.now() : instantOfRun); } @JmxAttribute @Nullable public final Duration getDurationOfRun() { if (instantOfRun == null) { return null; } return Duration.between(instantOfRun, instantOfStop == null ? Instant.now() : instantOfStop); } @JmxAttribute @Nullable public final Duration getDurationOfStop() { if (instantOfStop == null) { return null; } return Duration.between(instantOfStop, instantOfComplete == null ? Instant.now() : instantOfComplete); } @JmxAttribute @Nullable public final Duration getDuration() { if (instantOfLaunch == null) { return null; } return Duration.between(instantOfLaunch, instantOfComplete == null ? Instant.now() : instantOfComplete); } @JmxAttribute @Nullable public final Throwable getApplicationError() { return applicationError; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy