/* * Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com] * 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 de.ks.launch; import com.google.common.util.concurrent.ThreadFactoryBuilder; import de.ks.SubclassInstantiator; import de.ks.preload.LaunchListener; import de.ks.preload.LaunchListenerAdapter; import de.ks.preload.PreloaderApplication; import javafx.application.Application; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; public class Launcher { public static final Launcher instance = new Launcher(true); private static final Logger log = LoggerFactory.getLogger(Launcher.class); public static final String SERVICE_PACKAGES = "service.packages"; public static final String SERVICE_PROPERTIES_FILENAME = "service.properties"; public static final String PACKAGE_SEPARATOR = ","; private final List<Service> services = new ArrayList<>(); private final ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("launcher-%d").build()); private final List<Throwable> startupExceptions = Collections.synchronizedList(new ArrayList<>()); private final SubclassInstantiator instantiator; private volatile CountDownLatch latch; private volatile LaunchListener launchListener = new LaunchListenerAdapter(); private Class<? extends PreloaderApplication> preloader; private Future<?> preloaderFuture; private PreloaderApplication preloaderInstance; private final CountDownLatch preloaderLatch = new CountDownLatch(1); protected Launcher(boolean excludeTestResources) { instantiator = new SubclassInstantiator(executorService, getClass().getPackage(), SERVICE_PROPERTIES_FILENAME, SERVICE_PACKAGES, PACKAGE_SEPARATOR); instantiator.setExcludeTestResources(excludeTestResources); } public List<Service> discoverServices() { List<Service> services = instantiator.instantiateSubclasses(Service.class); services.sort((o1, o2) -> Integer.compare(o1.getPriority(), o2.getPriority())); return services; } public List<Service> getServices() { if (services.isEmpty()) { services.addAll(discoverServices()); } return services; } public <S extends Service> void removeService(Class<S> clazz) { S service = getService(clazz); services.remove(service); } @SuppressWarnings("unchecked") public <S extends Service> S getService(Class<S> clazz) { List<Service> collect = getServices().stream().filter((s) -> s.getClass().equals(clazz)).collect(Collectors.toList()); if (collect.isEmpty()) { return null; } else { return (S) collect.get(0); } } @SuppressWarnings("unchecked") public <S extends Service> S getService(String name) { List<Service> collect = getServices().stream().filter((s) -> s.getName().equals(name)).collect(Collectors.toList()); if (collect.isEmpty()) { return null; } else { return (S) collect.get(0); } } public TreeMap<Integer, List<Service>> getServiceWaves() { TreeMap<Integer, List<Service>> retval = new TreeMap<>(); getServices().forEach((service) -> { int priority = service.getPriority(); retval.putIfAbsent(priority, new ArrayList<>()); retval.get(priority).add(service); }); return retval; } public void startAll(String... args) { if (preloader != null) { startPreloader(); } TreeMap<Integer, List<Service>> waves = getServiceWaves(); launchListener.totalWaves(waves.keySet().size()); launchListener.wavePriorities(waves.keySet()); latch = new CountDownLatch(waves.keySet().size()); Iterator<Integer> iter = waves.keySet().iterator(); startWave(iter, waves, args); log.info("Launching done!"); } private void startWave(Iterator<Integer> iter, TreeMap<Integer, List<Service>> waves, String[] args) { if (!iter.hasNext()) { return; } Integer prio = iter.next(); launchListener.waveStarted(prio); log.info("Starting services with prio {}", prio); List<CompletableFuture<Void>> waveFutures = waves.get(prio).stream()// .map((s) -> { return CompletableFuture.supplyAsync(() -> { s.initialize(this, executorService, args); return s.start(); }, executorService)// .thenAccept((service) -> log.info("Successfully started service {}", service.getName())); }).collect(Collectors.toList()); CompletableFuture<Void> allOf = CompletableFuture.allOf(waveFutures.toArray(new CompletableFuture[waveFutures.size()])); allOf.thenRun(() -> log.info("Started services with prio {}", prio))// .thenRun(() -> latch.countDown())// .thenRun(() -> launchListener.waveFinished(prio))// .thenRun(() -> startWave(iter, waves, args))// .exceptionally((t) -> { while (latch.getCount() > 0) { latch.countDown(); } startupExceptions.add(t); launchListener.failure(t.toString()); //throw new RuntimeException(t); return null; }); } public void awaitStart() { try { latch.await(); if (!startupExceptions.isEmpty()) { RuntimeException runtimeException = new RuntimeException("Startup failed"); startupExceptions.forEach((t) -> { log.error("Failed startup.", t); runtimeException.addSuppressed(t); }); throw runtimeException; } } catch (InterruptedException e) { throw new RuntimeException(e); } } public boolean isStarted() { for (Service service : getServices()) { if (!service.isRunning()) { return false; } } return true; } public void stopAll() { try { latch.await(); } catch (InterruptedException e) { log.error("Could not await latch.", e); } TreeMap<Integer, List<Service>> waves = getServiceWaves(); latch = new CountDownLatch(waves.keySet().size()); Iterator<Integer> iter = waves.descendingKeySet().iterator(); stopWave(iter, waves); } private void stopWave(Iterator<Integer> iter, TreeMap<Integer, List<Service>> waves) { if (!iter.hasNext()) { return; } Integer prio = iter.next(); log.info("Stopping services with prio {}", prio); List<CompletableFuture<Void>> waveFutures = waves.get(prio).stream()// .map((s) -> { if (s.isStopped()) { return CompletableFuture.<Void>completedFuture(null); } else { return CompletableFuture.supplyAsync(() -> s.stop(), executorService)// .thenAccept((service) -> log.info("Successfully stopped service {}", service.getName())); } }).collect(Collectors.toList()); CompletableFuture<Void> allOf = CompletableFuture.allOf(waveFutures.toArray(new CompletableFuture[waveFutures.size()])); allOf.thenRun(() -> log.info("Stopped services with prio {}", prio))// .thenRun(() -> latch.countDown())// .thenRun(() -> stopWave(iter, waves))// .exceptionally((t) -> { log.info("Failed to stop services", t); return null; }); } public void awaitStop() { if (latch != null) { try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } waitForPreloader(); } public void waitForPreloader() { if (preloaderFuture != null) { try { preloaderFuture.get(); } catch (InterruptedException e) { //ok } catch (ExecutionException e) { log.error("Error from preloader ", e); } } } public void startPreloader() { preloaderFuture = executorService.submit(() -> Application.launch(preloader)); try { preloaderLatch.await(); } catch (InterruptedException e) { // } } public ExecutorService getExecutorService() { return executorService; } public void setPreloader(Class<? extends PreloaderApplication> preloader) { this.preloader = preloader; } public Class<? extends PreloaderApplication> getPreloader() { return preloader; } public void setLaunchListener(LaunchListener launchListener) { this.launchListener = launchListener; } public LaunchListener getLaunchListener() { return launchListener; } public void setPreloaderInstance(PreloaderApplication preloaderInstance) { this.preloaderInstance = preloaderInstance; preloaderLatch.countDown(); } public PreloaderApplication getPreloaderInstance() { return preloaderInstance; } }