package com.exteso.lab.pf.config.reload; import com.exteso.lab.pf.config.reload.reloader.JacksonReloader; import com.exteso.lab.pf.config.reload.reloader.LiquibaseReloader; import com.exteso.lab.pf.config.reload.reloader.SpringReloader; import org.apache.commons.lang.ClassUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RestController; import javax.persistence.Entity; import java.util.ArrayList; import java.util.List; /** * This thread stores classes to reload, to reload them all in one batch. */ public class JHipsterReloaderThread implements Runnable { private static Logger log = LoggerFactory.getLogger(JHipsterReloaderThread.class); private static Object lock = new Object(); public static boolean isStarted; private static boolean hotReloadTriggered = false; private static boolean isWaitingForNewClasses = false; /** * How long does the thread wait until running a new batch. */ private static final int BATCH_DELAY = 250; /** * Reloads Spring beans. */ private static SpringReloader springReloader; /** * Reloads Jackson classes. */ private static JacksonReloader jacksonReloader; /** * Reloads Database, Entity and Hibernate Factory */ private static LiquibaseReloader liquibaseReloader; /** * Stores the Spring controllers reloaded in the batch. */ private List<Class> controllers = new ArrayList<>(); /** * Stores the Spring services reloaded in the batch. */ private List<Class> services = new ArrayList<>(); /** * Stores the Spring repositories reloaded in the batch. */ private List<Class> repositories = new ArrayList<>(); /** * Stores the Spring components reloaded in the batch. */ private List<Class> components = new ArrayList<>(); /** * Stores the JPA entities reloaded in the batch. */ private List<Class> entities = new ArrayList<>(); /** * Stores the DTOs reloaded in the batch. */ private List<Class> dtos = new ArrayList<>(); public JHipsterReloaderThread(ConfigurableApplicationContext applicationContext) { isStarted = true; springReloader = new SpringReloader(applicationContext); springReloader.afterPropertiesSet(); jacksonReloader = new JacksonReloader(applicationContext); liquibaseReloader = new LiquibaseReloader(applicationContext); } public void reloadEvent(String typename, Class<?> clazz) { synchronized (lock) { log.trace("Hot reloading - checking if this is a Spring bean: {}", typename); boolean startReloading = false; if (AnnotationUtils.findAnnotation(clazz, Repository.class) != null || ClassUtils.isAssignable(clazz, org.springframework.data.repository.Repository.class)) { log.trace("{} is a Spring Repository", typename); repositories.add(clazz); startReloading = true; } else if (AnnotationUtils.findAnnotation(clazz, Service.class) != null) { log.trace("{} is a Spring Service", typename); services.add(clazz); startReloading = true; } else if (AnnotationUtils.findAnnotation(clazz, Controller.class) != null || AnnotationUtils.findAnnotation(clazz, RestController.class) != null) { log.trace("{} is a Spring Controller", typename); controllers.add(clazz); startReloading = true; } else if (AnnotationUtils.findAnnotation(clazz, Component.class) != null) { log.trace("{} is a Spring Component", typename); components.add(clazz); startReloading = true; } else if (typename.startsWith("com.exteso.lab.pf.domain")) { log.trace("{} is in the JPA package, checking if it is an entity", typename); if (AnnotationUtils.findAnnotation(clazz, Entity.class) != null) { log.trace("{} is a JPA Entity", typename); entities.add(clazz); startReloading = true; } } else if (typename.startsWith("com.exteso.lab.pf.web.rest.dto")) { log.debug("{} is a REST DTO", typename); dtos.add(clazz); startReloading = true; } if (startReloading) { hotReloadTriggered = true; isWaitingForNewClasses = true; } } } public void run() { while (isStarted) { try { Thread.sleep(BATCH_DELAY); if (hotReloadTriggered) { if (isWaitingForNewClasses) { log.info("Batch reload has been triggered, waiting for new classes for {} ms", BATCH_DELAY); isWaitingForNewClasses = false; } else { batchReload(); hotReloadTriggered = springReloader.hasBeansToReload(); } } else { log.trace("Waiting for batch reload"); } } catch (InterruptedException e) { log.error("JHipsterReloaderThread was awaken", e); } } } private void batchReload() { synchronized (lock) { log.info("Batch reload in progress..."); if (entities.size() > 0 || dtos.size() > 0) { log.debug("There are {} entities and {} dtos updated, invalidating Jackson cache", entities.size(), dtos.size()); jacksonReloader.reloadEvent(); if (entities.size() > 0) { liquibaseReloader.reloadEvent(entities); springReloader.hasNewEntityBean(); entities.clear(); } } addSpringBeans("repositories", repositories); addSpringBeans("services", services); addSpringBeans("components", components); addSpringBeans("controllers", controllers); // Start to reload all Spring beans if (springReloader.hasBeansToReload()) { springReloader.start(); } } } private void addSpringBeans(String type, List<Class> list) { if (list.size() > 0) { log.debug("There are {} Spring {} updated, adding them to be reloaded", list.size(), type); for (Class clazz : list) { springReloader.reloadEvent(clazz); } } list.clear(); } /** * Register the thread and starts it. */ public static void register(JHipsterReloaderThread jHipsterReloaderThread) { try { final Thread thread = new Thread(jHipsterReloaderThread); thread.setDaemon(true); thread.start(); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { JHipsterReloaderThread.isStarted = false; try { thread.join(); } catch (InterruptedException e) { log.error("Failed during the JVM shutdown", e); } } }); } catch (Exception e) { log.error("Failed to start the reloader thread. Classes will not be reloaded correctly.", e); } } }