org.nuiton.jaxx.runtime.application.ApplicationBoot Maven / Gradle / Ivy
package org.nuiton.jaxx.runtime.application;
/*-
* #%L
* JAXX :: Runtime
* %%
* Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import io.ultreia.java4all.util.SingletonSupplier;
import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.jaxx.runtime.application.action.ActionExecutor;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
/**
* This class is entry point of an application.
*
* It contains a shared instance which is automatically initialized when the first instance of class in created.
*
* Nobody is allowed to modify this instance.
*
* Created by tchemit on 26/01/2018.
*
* @author Tony Chemit - [email protected]
*/
public final class ApplicationBoot> {
private static final Logger log = LogManager.getLogger(ApplicationBoot.class);
/**
* Boot scope bootLock, used to wait application end.
*
* Use method {@link #lockBoot()} or {@link #unlockBoot()}.
*/
private static final Object bootLock = new Object();
public static String BOOT_LOG_PREFIX = "";
/**
* Shared instance.
*/
private static ApplicationBoot, ?> INSTANCE;
/**
* Application initializer.
*/
private final ApplicationBootInitializer initializer;
/**
* Application config.
*/
private final SingletonSupplier configuration;
/**
* Application context.
*/
private final SingletonSupplier context;
/**
* To create new threads.
*/
private final SingletonSupplier threadFactory;
/**
* Application executor.
*/
private SingletonSupplier executor;
/**
* Internal state to know if boot was closed.
*/
private boolean closed;
/**
* Internal state to know if boot was shutdown.
*/
private boolean shutdown;
/**
* Internal state to know if boot should be reloaded at next close.
*/
private boolean reload;
private ApplicationBoot(ApplicationBootInitializer initializer) {
this.initializer = initializer;
this.initializer.initOnce();
this.configuration = SingletonSupplier.of(() -> this.initializer.createConfiguration(this));
this.context = SingletonSupplier.of(() -> this.initializer.createContext(this, getConfiguration()));
this.threadFactory = SingletonSupplier.of(ApplicationThreadFactory::new);
this.executor = SingletonSupplier.of(() -> this.initializer.createExecutor(this, getConfiguration(), getContext()));
INSTANCE = this;
}
/**
* Method to create a new boot.
*
* @param initializer application initializer
* @param config type
* @param context type
* @return new boot
* @throws IllegalStateException if boot was already instantiate
*/
public static synchronized > ApplicationBoot create(ApplicationBootInitializer initializer) {
if (INSTANCE != null) {
throw new IllegalStateException("Boot is already init");
}
return new ApplicationBoot<>(Objects.requireNonNull(initializer));
}
private static void lockBoot() throws InterruptedException {
synchronized (bootLock) {
bootLock.wait();
}
}
public static void unlockBoot() {
synchronized (bootLock) {
bootLock.notifyAll();
}
}
public static void handlingError(String message, Exception e) {
ApplicationInstances.context().handlingError(message, e);
}
public static void handlingError(Exception e) {
ApplicationInstances.context().handlingError(e);
}
public static ApplicationBootInitializer initializer() {
return boot().initializer;
}
public static C initializer(Class contextType) {
return contextType.cast(initializer());
}
public static ApplicationBoot, ?> boot() {
return Objects.requireNonNull(INSTANCE, "boot is not init.");
}
public static void cleanMemory() {
System.runFinalization();
System.gc();
}
public static O newInstanceWithParams(Class type, Object... constructorParams) {
try {
return ConstructorUtils.invokeConstructor(Objects.requireNonNull(type), constructorParams);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw new IllegalStateException("Can't instantiate " + type.getName(), e);
}
}
public void startInThread() {
Runtime.getRuntime().addShutdownHook(threadFactory.get().newThread(this::shutdown, "shutdown", false));
boolean shutdown = false;
while (!shutdown) {
Thread thread = threadFactory.get().newThread(this::start, "main", false);
thread.start();
try {
// lock boot
lockBoot();
// boot is free to close or reload
log.info(String.format("%s Application boot unlock...", BOOT_LOG_PREFIX));
} catch (InterruptedException e) {
log.error(String.format("Main thread was interrupted for reason %s", e.getMessage()), e);
} finally {
if (reload) {
try {
close();
} catch (Exception e) {
log.error(String.format("Could not close boot, will shutdown application: %s", e.getMessage()), e);
// if can't close, then shutdown
shutdown = true;
}
} else {
// if no reload, this means shutdown
shutdown = true;
}
}
}
shutdown();
}
public void start() {
// when starting, we are no more closing, nor reloading
reload = closed = closing = false;
try {
initializer.init(getConfiguration(), getContext());
} catch (ApplicationBootInitializerException e) {
log.error("Could not init boot", e);
shutdown();
}
if (shutdown) {
return;
}
try {
initializer.start(getConfiguration(), getContext());
} catch (Exception e) {
log.error("Could not start boot", e);
shutdown();
}
}
public void restart() {
reload = true;
unlockBoot();
}
public void shutdown() {
if (shutdown) {
return;
}
shutdown = true;
log.info(String.format("%s Ask to shutdown application at %s", BOOT_LOG_PREFIX, new Date()));
int exitCode = 0;
try {
close();
} catch (Exception e) {
exitCode = 1;
log.error(String.format("Can't shutdown properly application: %s", e.getMessage()), e);
} finally {
try {
initializer.close();
} finally {
INSTANCE = null;
exit(exitCode);
}
}
}
public void addAction(String actionLabel, Runnable action) {
getExecutor().addAction(actionLabel, action);
}
public ApplicationThreadFactory getThreadFactory() {
checkNotClosed("threadFactory");
return threadFactory.get();
}
public Context getContext() {
checkNotClosed("context");
return context.get();
}
public Config getConfiguration() {
checkNotClosed("configuration");
return configuration.get();
}
public boolean isClosed() {
return closed;
}
public void checkNotClosed(String stateName) {
if (closed) {
throw new IllegalStateException(String.format("Can't get access boot internal state: %s, boot is closing (or closed).", stateName));
}
}
@Override
protected final void finalize() throws Throwable {
if (!shutdown) {
shutdown();
}
super.finalize();
}
private boolean closing;
public boolean isClosing() {
return closing;
}
public void close() {
if (closed || closing) {
return;
}
closing = true;
try {
if (context.withValue()) {
context.get().close();
}
if (executor.withValue()) {
executor.get().close();
}
} catch (Exception e) {
throw new RuntimeException("Can't close boot for reason: " + e.getMessage(), e);
} finally {
cleanMemory();
configuration.clear();
context.clear();
executor.clear();
closed = true;
}
}
private void exit(int code) {
log.debug(String.format("%s Ask to exit application with code: %s", BOOT_LOG_PREFIX, code));
if (initializer.haltOnExit()) {
Runtime.getRuntime().halt(code);
}
log.info(String.format("%s Application exit at %s", BOOT_LOG_PREFIX, new Date()));
}
private ActionExecutor getExecutor() {
checkNotClosed("executor");
return executor.get();
}
public ThreadPoolExecutor getThreadPoolExecutor() {
return getExecutor().getWorkersExecutorService();
}
public static class ApplicationThreadFactory implements ThreadFactory {
private final ThreadFactory defaultFactory;
private ApplicationThreadFactory() {
defaultFactory = Executors.defaultThreadFactory();
}
@Override
public Thread newThread(@SuppressWarnings("NullableProblems") Runnable r) {
Thread thread = defaultFactory.newThread(Objects.requireNonNull(r));
thread.setName("Application." + thread.getName());
thread.setDaemon(true);
return thread;
}
public Thread newThread(Runnable r, String name, boolean daemon) {
Thread thread = newThread(Objects.requireNonNull(r));
thread.setName("Application." + Objects.requireNonNull(name));
thread.setDaemon(daemon);
return thread;
}
}
}