package ru.kirillius.XCP; import lombok.Getter; import ru.kirillius.XCP.Commons.Config; import ru.kirillius.XCP.Commons.ConfigManager; import ru.kirillius.XCP.Commons.Context; import ru.kirillius.XCP.Commons.Service; import ru.kirillius.XCP.Logging.Logger; import ru.kirillius.XCP.Logging.LoggingSystem; import ru.kirillius.XCP.Logging.LoggingSystemImpl; import ru.kirillius.XCP.Persistence.RepositoryServiceImpl; import ru.kirillius.XCP.Security.ConfigManagerImpl; import ru.kirillius.XCP.Security.SecurityManager; import ru.kirillius.XCP.Security.SecurityManagerImpl; import ru.kirillius.XCP.Services.ServiceLoadPriority; import ru.kirillius.XCP.Services.WebService; import ru.kirillius.XCP.web.WebServiceImpl; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Getter public class Application implements Context { @Getter private final SecurityManager securityManager; @Getter private final LoggingSystem loggingSystem; private final Logger log; @Getter private final List launchArgs; @Getter private final Config config; @Getter private final ConfigManager configManager; private final Map, Service> services = new ConcurrentHashMap<>(); private Config loadConfig() { var configFile = configManager.getConfigFile().getAbsolutePath(); try { configFile = configManager.getConfigFile().getCanonicalPath(); } catch (IOException e) { log.warning("Unable to determine real path of file " + configFile); } Config config; if (configManager.isExist()) { try { config = configManager.load(); log.info("Loaded config file: " + configFile); } catch (IOException e) { log.error("Error loading config file " + configFile, e); throw new RuntimeException(e); } } else { log.warning("Unable to find config file " + configFile + ". Using default values."); config = configManager.create(); log.info("Saving default config file to " + configFile); try { configManager.save(config); } catch (IOException e) { throw new RuntimeException("Unable to save config file", e); } } return config; } public Application(String[] args) { launchArgs = Arrays.stream(args).toList(); loggingSystem = new LoggingSystemImpl(this); log = loggingSystem.createLogger(Application.class); configManager = new ConfigManagerImpl(this); config = loadConfig(); securityManager = new SecurityManagerImpl(); try { loadServices(); } catch (Throwable throwable) { log.error(throwable); shutdown(); throw new RuntimeException("Error loading services"); } Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); ((WebServiceImpl) getService(WebService.class)).join(); } private void loadServices() { var servicesToLoad = List.of(RepositoryServiceImpl.class, WebServiceImpl.class); servicesToLoad.stream().sorted(Comparator.comparingInt(aClass -> { var order = aClass.getAnnotation(ServiceLoadPriority.class); return order == null ? 100000 : order.value(); })).forEach(aClass -> { @SuppressWarnings("unchecked") var facade = (Class) Arrays.stream(aClass.getInterfaces()) .filter(Service.class::isAssignableFrom) .findFirst(). orElseThrow(() -> new ClassCastException("Unable to get service interface from class " + aClass.getSimpleName())); log.info("Loading service " + facade.getSimpleName()); try { var constructor = aClass.getConstructor(); try { var service = constructor.newInstance(); try { service.initialize(this); services.put(facade, service); } catch (Throwable e) { try { service.close(); } catch (IOException ex) { e.addSuppressed(ex); } throw new RuntimeException("Failed to start " + facade.getSimpleName(), e); } } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Failed to instantiate " + facade.getSimpleName(), e); } } catch (NoSuchMethodException e) { throw new RuntimeException("Failed to find default constructor of " + facade.getSimpleName(), e); } }); } @SuppressWarnings("unchecked") @Override public S getService(Class serviceClass) { return (S) services.get(serviceClass); } @Override public void shutdown() { try { services.forEach((serviceClass, service) -> { try { log.info("Shutting down service " + serviceClass.getSimpleName()); service.close(); } catch (IOException e) { log.error("Error shutting down service " + serviceClass.getSimpleName(), e); } }); loggingSystem.close(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public boolean isDebuggingEnabled() { return launchArgs.contains("--debug"); } public static void main(String[] args) { try { new Application(args); } catch (Throwable e) { System.err.println("Error starting application"); e.printStackTrace(System.err); System.exit(1); } } }