/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This 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 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.deployer; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.OVERFLOW; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.DelayQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import org.helios.apmrouter.deployer.event.HotDeployedContextClosedEvent; import org.helios.apmrouter.deployer.event.HotDeployedContextRefreshedEvent; import org.helios.apmrouter.destination.event.DestinationEvent; import org.helios.apmrouter.jmx.ConfigurationHelper; import org.helios.apmrouter.server.ServerComponentBean; import org.helios.apmrouter.server.unification.pipeline.http.HttpRequestHandlerStarted; import org.helios.apmrouter.server.unification.pipeline.http.HttpRequestHandlerStopped; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ApplicationContextEvent; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SmartApplicationListener; import org.springframework.context.support.GenericApplicationContext; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedOperationParameter; import org.springframework.jmx.export.annotation.ManagedOperationParameters; /** * <p>Title: SpringHotDeployer</p> * <p>Description: Hot deploy/undeploy service for child app contexts</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.deployer.SpringHotDeployer</code></p> */ public class SpringHotDeployer extends ServerComponentBean { /** The watch keys registered for each directory keyed by the path of the watched directory */ protected Map<Path, WatchKey> hotDirs = new ConcurrentHashMap<Path, WatchKey>(); /** A map of file paths to Spring contexts to associate the ctx to the file it was booted from */ protected final Map<String, GenericApplicationContext> deployedContexts = new ConcurrentHashMap<String, GenericApplicationContext>(); /** The watch event handling thread */ protected Thread watchThread = null; /** The processingQueue handling thread */ protected Thread processingThread = null; /** A set of application event types that should be propagated */ protected final Set<Class<? extends ApplicationEvent>> propagates = new CopyOnWriteArraySet<Class<? extends ApplicationEvent>>(); /** The watch service */ protected WatchService watcher = null; /** The keep running flag */ protected final AtomicBoolean keepRunning = new AtomicBoolean(false); /** The processing delay queue that ensures the same file is not processed concurrently for two different events */ protected final DelayQueue<FileEvent> processingQueue = new DelayQueue<FileEvent>(); /** A set of file events that are in process */ protected Set<FileEvent> inProcess = new CopyOnWriteArraySet<FileEvent>(); /** The application context deployer */ protected ApplicationContextDeployer deployer; /** The regex pattern used to filter in the desired hot deployed files in the hot dirs. Defaults to <b><code>.*.apmrouter.xml</code></b> */ protected String pattern = ".*\\.apmrouter.xml"; /** The compiled file name filter pattern */ protected Pattern fileNamePattern = null; /** Configuration added hot dir names */ protected final Set<String> hotDirNames = new HashSet<String>(); // ======================================================================================= // These settings control how much automated deployment will be done // ======================================================================================= /** Indicates if the default hot deploy directory at <code>${user.home}/.apmrouter/hotdir</code> should be disabled. By default it is enabled */ protected boolean disableDefaultHotDir = false; /** Indicates if the default module hot deploy lib directory class loading should be disabled. By default it is enabled */ protected boolean disableHotDirLibs = false; /** Indicates if the default module hot deploy app directory loading should be disabled. By default it is enabled */ protected boolean disableHotDirApps = false; /** The hot deploy application directory name pattern. This is the name of a hot deployed application directory which, unless disabled by {@link #disableHotDirApps}, when found in the root of * a hot directory will be automatically deployed. By default it is <code>XXX.app</code> */ protected String hotDeployAppDirectoryExt = ".app"; // ======================================================================================= /** The application listener on the root context that forwards to the child contexts */ protected final ApplicationListener<?> childForwarder = new ApplicationListener() { @Override public void onApplicationEvent(ApplicationEvent event) { if(!(event instanceof ApplicationContextEvent)) { // If an application event from root context is not an ApplicationContextEvent // it should be forwarded to all the child contexts for(GenericApplicationContext appCtx: deployedContexts.values()) { appCtx.publishEvent(event); } } else { // If an application event from root context is an ApplicationContextEvent // the only ones we care about are STOP and CLOSE. ApplicationContextEvent appCtxEvent = (ApplicationContextEvent)event; } } }; /** The name of the default hot deploy directory */ public static final String DEFAULT_HOT_DIR = System.getProperty("user.home") + File.separator + ".apmrouter" + File.separator + "hotdir"; /** The system prop that specifies the hot deploy directories */ public static final String HOT_DIR_PROP = "org.helios.apmrouter.spring.hotdir"; /** * Creates a new SpringHotDeployer */ public SpringHotDeployer() { propagates.add(HttpRequestHandlerStopped.class); propagates.add(HttpRequestHandlerStarted.class); propagates.add(ApplicationContextEvent.class); propagates.add(DestinationEvent.class); } /** * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#doStart() */ @Override protected void doStart() throws Exception { super.doStart(); deployer = new ApplicationContextDeployer(disableHotDirLibs); initDefaultHotDir(); initEnvHotDirs(); validateInitialHotDirs(); if(hotDirNames.isEmpty()) { warn("No hot deploy directories were defined or found. New directories can be added through the JMX interface."); } else { StringBuilder b = new StringBuilder("\n\t====================\n\tHot Deploy Directories\n\t===================="); for(String s: hotDirNames) { b.append("\n\t").append(s); } b.append("\n"); info(b); } } /** * Initializes the default hot dir unless it has been disabled. */ protected void initDefaultHotDir() { if(!disableDefaultHotDir) { String defaultHotDirName = System.getProperty(HOT_DIR_PROP, DEFAULT_HOT_DIR); File defaultHotDir = new File(defaultHotDirName); if(defaultHotDir.exists()) { if(!defaultHotDir.isDirectory()) { warn("\n\t###########################################\n\tProblem: The default hot deploy directory [", defaultHotDir, "] exists but it is a file\n\t###########################################\n"); } else { hotDirNames.add(defaultHotDir.getAbsolutePath()); scanForApplications(defaultHotDir.toPath(), true); } } else { if(!defaultHotDir.mkdirs()) { warn("\n\t###########################################\n\tProblem: Failed to create hot deploy directory [", defaultHotDir, "]\n\t###########################################\n"); } else { hotDirNames.add(defaultHotDir.getAbsolutePath()); scanForApplications(defaultHotDir.toPath(), true); } } } } /** * Validates the initial hot directories, removing any that are invalid */ protected void validateInitialHotDirs() { for(Iterator<String> iter = hotDirNames.iterator(); iter.hasNext();) { File f = new File(iter.next().trim()); if(!f.exists() || !f.isDirectory()) { warn("Configured hot dir path was invalid [", f, "]"); iter.remove(); } } } /** * Configures watches for system prop or environment defined hot directories */ protected void initEnvHotDirs() { String[] hds = ConfigurationHelper.getSystemThenEnvProperty(HOT_DIR_PROP, DEFAULT_HOT_DIR).split(","); for(String hd: hds) { if(hd.trim().isEmpty()) continue; hd = hd.trim(); Path hdPath = Paths.get(hd); if(!Files.exists(hdPath) || Files.isDirectory(hdPath)) continue; hotDirNames.add(hd); scanForApplications(hdPath, true); } } /** * Scans the passed path for application directories and adds them to the watched set, unless apps have been disabled * @param hotDir The path to scan * @param add If true, the located application directories will be added, if false, just returns the names, taking no further action * @return A set of the application directory names that were added */ protected Set<String> scanForApplications(Path hotDir, boolean add) { Set<String> added = new HashSet<String>(); if(!disableHotDirApps && add) { for(File f: hotDir.toFile().listFiles()) { if(f.isDirectory() && f.getName().toLowerCase().endsWith(hotDeployAppDirectoryExt)) { hotDirNames.add(f.getAbsolutePath()); added.add(f.getAbsolutePath()); } } } return added; } /** * Adds a hot deploy directory * @param dirName the name of the hot deploy directory to add * @return a string message summarizing the results of the operation */ @ManagedOperation(description="Adds a hot deploy directory") @ManagedOperationParameters({ @ManagedOperationParameter(name="dirName", description="The name of the hot deploy directory to add") }) public String addHotDir(String dirName) { if(dirName==null || dirName.trim().isEmpty()) { return "Null or empty directory name"; } File f = new File(dirName.trim()); StringBuilder b = new StringBuilder("Adding hot directory [").append(dirName).append("]"); if(f.exists() && f.isDirectory()) { hotDirNames.add(f.getAbsolutePath()); b.append("\n\tAdded [").append(f.getAbsolutePath()).append("]"); Set<String> apps = scanForApplications(f.toPath(), true); if(!apps.isEmpty()) { for(String appDir: apps) { b.append("\n\tAdded [").append(appDir).append("]"); } } } else { b.append("\n\tDirectory did not exist"); } b.append("\n"); try { updateWatchers(); } catch (IOException ioe) { error("Failure during updateWatchers", ioe); } return b.toString(); } /** * Removes a hot deploy directory * @param dirName the name of the hot deploy directory to remove * @return a string message summarizing the results of the operation */ @ManagedOperation(description="Removes a hot deploy directory") @ManagedOperationParameters({ @ManagedOperationParameter(name="dirName", description="The name of the hot deploy directory to remove") }) public String removeHotDir(String dirName) { if(dirName==null || dirName.trim().isEmpty()) { return "Null or empty directory name"; } File f = new File(dirName.trim()); StringBuilder b = new StringBuilder("Removing hot directory [").append(dirName).append("]"); WatchKey watchKey = null; if(f.exists() && f.isDirectory()) { hotDirNames.remove(f.getAbsolutePath()); watchKey = hotDirs.remove(f.toPath()); if(watchKey!=null) watchKey.cancel(); b.append("\n\tRemoved [").append(f.getAbsolutePath()).append("]"); Set<String> apps = scanForApplications(f.toPath(), false); if(!apps.isEmpty()) { for(String appDir: apps) { f = new File(appDir); hotDirNames.remove(appDir); watchKey = hotDirs.remove(f.toPath()); if(watchKey!=null) watchKey.cancel(); b.append("\n\tRemoved [").append(appDir).append("]"); } } } else { b.append("\n\tDirectory did not exist"); } b.append("\n"); return b.toString(); } /** * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#doStop() */ @Override protected void doStop() { for(WatchKey wk: hotDirs.values()) { wk.cancel(); } hotDirs.clear(); keepRunning.set(false); watchThread.interrupt(); processingThread.interrupt(); processingQueue.clear(); for(GenericApplicationContext appCtx: deployedContexts.values()) { deployer.undeploy(appCtx); } deployedContexts.clear(); super.doStop(); } /** * <p>Responds <code>true</code> for {@link ContextRefreshedEvent}s. * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#supportsEventType(java.lang.Class) */ @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { return true; } /** * <p>Responds <code>true</code>. * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#supportsSourceType(java.lang.Class) */ @Override public boolean supportsSourceType(Class<?> sourceType) { return true; } protected SmartApplicationListener innerListener = new SmartApplicationListener() { public void onApplicationEvent(ApplicationEvent event) { if(event.getSource()==this || event.getSource()==applicationContext) { info("Dropping event. Came from [", event.getSource(), "]"); } info("AppEvent [", new Date(event.getTimestamp()), "]:[", event.getClass().getName(), "] source: [", event.getSource(), "]"); if(event instanceof ContextRefreshedEvent) { ContextRefreshedEvent cre = (ContextRefreshedEvent)event; String id = cre.getApplicationContext().getId(); if(id!=null && id.startsWith("HotDeployedContext#")) { applicationContext.publishEvent(new HotDeployedContextRefreshedEvent(cre.getApplicationContext())); info("Propagated HotDeployedContextRefreshedEvent for [", id, "]"); } } else if(event instanceof ContextClosedEvent) { ContextClosedEvent cre = (ContextClosedEvent)event; String id = cre.getApplicationContext().getId(); String name = cre.getApplicationContext().getDisplayName(); debug("Received [", event.getClass().getSimpleName(), "] from child context [", name, "]. Removing inert context"); deployedContexts.remove(name); if(id!=null && id.startsWith("HotDeployedContext#")) { applicationContext.publishEvent(new HotDeployedContextClosedEvent(cre.getApplicationContext())); info("Propagated HotDeployedContextClosedEvent for [", id, "]"); } } else if(shouldPropagate(event.getClass())) { if(event instanceof ApplicationContextEvent) return; info("Propagating Accepted Event of Type [", event.getClass().getName(), "] from [", event.getSource(), "] with listeners:", listAppListeners()); //eventMulticaster.multicastEvent(event); applicationContext.publishEvent(event); } } protected String listAppListeners() { StringBuilder b = new StringBuilder(); for(ApplicationListener app : applicationContext.getApplicationListeners()) { b.append("\n\tAppListener:").append(app).append(" (").append(app.getClass().getSimpleName()).append(")"); } return b.toString(); } @Override public int getOrder() { // TODO Auto-generated method stub return 1; } @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { return shouldPropagate(eventType); } @Override public boolean supportsSourceType(Class<?> sourceType) { return true; } }; /** * @param ctx */ /** * Callback when the current app context refreshes * @param cse The context refreshed event */ public void onApplicationContextRefresh(ContextRefreshedEvent cse) { info("Root AppCtx Started [", new Date(cse.getTimestamp()), "]:[", cse.getApplicationContext().getDisplayName(), "]"); keepRunning.set(true); startFileEventListener(); } /** * Scans the hot diretory names and registers a watcher for any unwatched names, * then removes any registered watchers that are no longer in the hot diretory names set * @throws IOException thrown on IO exceptions related to paths */ protected synchronized void updateWatchers() throws IOException { Map<Path, WatchKey> hotDirSnapshot = new HashMap<Path, WatchKey>(hotDirs); for(String fn: hotDirNames) { Path path = Paths.get(fn); if(hotDirs.containsKey(path)) { hotDirSnapshot.remove(path); } else { WatchKey watchKey = path.register(watcher, ENTRY_DELETE, ENTRY_MODIFY); hotDirs.put(path, watchKey); info("Added watched deployer directory [", path, "]"); } } for(Map.Entry<Path, WatchKey> remove: hotDirSnapshot.entrySet()) { remove.getValue().cancel(); info("Cancelled watch on deployer directory [", remove.getKey(), "]"); } hotDirSnapshot.clear(); } /** * Starts the file change listener */ public void startFileEventListener() { fileNamePattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); startProcessingThread(); try { watcher = FileSystems.getDefault().newWatchService(); scanHotDirsAtStart(); updateWatchers(); watchThread = new Thread("SpringHotDeployerWatchThread"){ WatchKey watchKey = null; public void run() { info("Started HotDeployer File Watcher Thread"); while(keepRunning.get()) { try { watchKey = watcher.take(); debug("Got watch key for [" + watchKey.watchable() + "]"); debug("File Event Queue:", processingQueue.size()); } catch (InterruptedException ie) { interrupted(); // check state continue; } if(watchKey!=null) { for (WatchEvent<?> event: watchKey.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); if (kind == OVERFLOW) { warn("OVERFLOW OCCURED"); if(!watchKey.reset()) { info("Hot Dir for watch key [", watchKey, "] is no longer valid"); watchKey.cancel(); Path dir = (Path)watchKey.watchable(); hotDirNames.remove(dir.toFile().getAbsolutePath()); hotDirs.remove(dir); } continue; } WatchEvent<Path> ev = (WatchEvent<Path>)event; Path dir = (Path)watchKey.watchable(); Path fileName = Paths.get(dir.toString(), ev.context().toString()); if(fileNamePattern.matcher(fileName.toFile().getAbsolutePath()).matches()) { enqueueFileEvent(500, new FileEvent(fileName.toFile().getAbsolutePath(), ev.kind())); } else if(fileName.toFile().isDirectory() && fileName.toFile().getName().endsWith(hotDeployAppDirectoryExt)) { addHotDir(fileName.toFile().getAbsolutePath()); } } } boolean valid = watchKey.reset(); // FIXME: This stops polling completely. if (!valid) { warn("Watch Key for [" , watchKey , "] is no longer valid. Polling will stop"); break; } } } }; watchThread.setDaemon(true); keepRunning.set(true); watchThread.start(); info("HotDeploy watcher started on [" + hotDirs.keySet() + "]"); } catch (Exception ex) { error("Failed to start hot deployer", ex); } } /** * Scans the hot dirs looking for files to deploy at startup. * Since there's no file change events, we need to go and look for them. */ protected void scanHotDirsAtStart() { for(String hotDirPathName: hotDirNames) { File hotDirPath = new File(hotDirPathName); for(File f: hotDirPath.listFiles()) { if(f.isDirectory() || !f.canRead()) continue; if(fileNamePattern.matcher(f.getName()).matches()) { enqueueFileEvent(500, new FileEvent(f.getAbsolutePath(), ENTRY_MODIFY)); } } } } /** * Enqueues a file event, removing any older instances that this instance will replace * @param delay The delay to add to the passed file event to give the queue a chance to conflate obsolete events already queued * @param fe The file event to enqueue */ protected void enqueueFileEvent(long delay, FileEvent fe) { int removes = 0; while(processingQueue.remove(fe)) {removes++;}; fe.addDelay(delay); processingQueue.add(fe); debug("Queued File Event for [", fe.getFileName(), "] and dropped [" , removes , "] older versions"); } protected static final AtomicLong serial = new AtomicLong(0L); /** * Starts the processing queue processor thread */ void startProcessingThread() { processingThread = new Thread("SpringHotDeployerProcessingThread") { @Override public void run() { info("Started HotDeployer Queue Processor Thread"); while(keepRunning.get()) { try { final FileEvent fe = processingQueue.take(); if(fe!=null) { debug("Processing File Event [" , fe.getFileName(), "]" ); if(inProcess.contains(fe)) { enqueueFileEvent(2000, fe); } else { Thread t = new Thread("SpringHotDeployer#" + serial.incrementAndGet()) { public void run() { if(fe.getEventType()==ENTRY_DELETE) { killAppCtx(fe); } else { redeployAppCtx(fe); } } }; t.setDaemon(true); t.start(); } } } catch (Exception e) { if(interrupted()) interrupted(); } } } }; processingThread.setDaemon(true); processingThread.start(); } /** * Stops and undeploys the application context associated with the deleted file * @param fe The deleted file event */ protected void killAppCtx(FileEvent fe) { GenericApplicationContext appCtx = deployedContexts.remove(fe.getFileName()); if(appCtx!=null) { deployer.undeploy(appCtx); } } /** * Deploys the application context associated with the modified or new file. * If the application context is already deployed, it will be undeployed first. * @param fe the modified or new file event */ protected void redeployAppCtx(FileEvent fe) { if(deployedContexts.containsKey(fe.getFileName())) { killAppCtx(fe); } GenericApplicationContext appCtx = deployer.deploy(applicationContext, eventExecutor, innerListener, fe); deployedContexts.put(fe.getFileName(), appCtx); info("\n\t***********************************************\n\tHot Deployed Context [", fe.getFileName(), "]\n\t***********************************************\n"); } /** * Returns the regex pattern used to filter in the desired hot deployed files in the hot dirs. Defaults to <b><code>.*.apmrouter.xml</code></b> * @return the hot deployed file name filter pattern */ @ManagedAttribute(description="The hot deploy file pattern") public String getPattern() { return pattern; } /** * Sets the regex pattern used to filter in the desired hot deployed files in the hot dirs. Defaults to <b><code>.*.apmrouter.xml</code></b> * @param pattern the hot deployed file name filter pattern */ public void setPattern(String pattern) { this.pattern = pattern; } /** * Returns the hot directory names * @return the hot directory names */ @ManagedAttribute(description="The registered hot deploy directories") public Set<String> getHotDirNames() { return Collections.unmodifiableSet(hotDirNames); } /** * Sets the hot directory names * @param hotDirNames the hot directory names */ public void setHotDirNames(Set<String> hotDirNames) { if(hotDirNames!=null) { this.hotDirNames.addAll(hotDirNames); } } /** * Returns the disabled state of the default hot directory in <code>${user.home}/.apmrouter/hotdir</code> * @return true if disabled, false if enabled */ @ManagedAttribute(description="The disabled state of the default hot directory") public boolean isDisableDefaultHotDir() { return disableDefaultHotDir; } /** * Sets the disabled state of the default hot directory in <code>${user.home}/.apmrouter/hotdir</code> * @param disableDefaultHotDir true to disable, false to enable */ @ManagedAttribute(description="The disabled state of the default hot directory") public void setDisableDefaultHotDir(boolean disableDefaultHotDir) { this.disableDefaultHotDir = disableDefaultHotDir; } /** * Returns the disabled state of the hot deployer's automatic jar library classpath extender * @return true if disabled, false if enabled */ @ManagedAttribute(description="The disabled state of the hot deployer's automatic jar library classpath extender") public boolean isDisableHotDirLibs() { return disableHotDirLibs; } /** * Sets the disabled state of the hot deployer's automatic jar library classpath extender * for <code>.lib</code> sub directories in hot directories or hot application directories * @param disableHotDirLibs true to disable, false to enable */ @ManagedAttribute(description="The disabled state of the hot deployer's automatic jar library classpath extender") public void setDisableHotDirLibs(boolean disableHotDirLibs) { this.disableHotDirLibs = disableHotDirLibs; } /** * Returns the disabled state of the hot deployer's automatic application deployer * @return true if disabled, false if enabled */ @ManagedAttribute(description="The disabled state of the hot deployer's automatic hot directory application subdirectories") public boolean isDisableHotDirApps() { return disableHotDirApps; } /** * Sets the disabled state of the hot deployer's automatic application deployer * @param disableHotDirApps true to disable, false to enable */ @ManagedAttribute(description="The disabled state of the hot deployer's automatic hot directory application subdirectories") public void setDisableHotDirApps(boolean disableHotDirApps) { this.disableHotDirApps = disableHotDirApps; } /** * Returns the extension of hot deployed application directories. * @return the extension of hot deployed application directories. */ @ManagedAttribute(description="The hot directory application subdirectory extension") public String getHotDeployAppDirectoryExt() { return hotDeployAppDirectoryExt; } /** * Sets the extension of hot deployed application directories. * @param hotDeployAppDirectoryExt the extension of hot deployed application directories */ @ManagedAttribute(description="The hot directory application subdirectory extension") public void setHotDeployAppDirectoryExt(String hotDeployAppDirectoryExt) { this.hotDeployAppDirectoryExt = hotDeployAppDirectoryExt; } /** * Tests the passed class to see if it is an ApplicationEvent type that should be propagated * @param clazz The class to test * @return true to propagate, false otherwise */ protected boolean shouldPropagate(Class<?> clazz) { if(clazz==null) return false; if(!ApplicationEvent.class.isAssignableFrom(clazz)) return false; if(propagates.contains(clazz)) return true; Class<? extends ApplicationEvent> addClass = null; try { for(Class<? extends ApplicationEvent> pclass : propagates) { if(pclass.isAssignableFrom(clazz)) { addClass = (Class<? extends ApplicationEvent>) clazz; return true; } } return false; } finally { if(addClass!=null) { propagates.add(addClass); } } } /** * Adds a set of the class names of application events that should be propagated when emitted from a hot deployed context * @param clazzNames set of class names */ public void setPropagates(Set<String> clazzNames) { if(clazzNames!=null) { for(String clazzName: clazzNames) { try { Class<?> clazz = Class.forName(clazzName); if(ApplicationEvent.class.isAssignableFrom(clazz)) { propagates.add((Class<? extends ApplicationEvent>) clazz); info("Added [", clazz.getName(), "] to propagates set"); } } catch (Exception ex) { warn("Failed to resolved propagation class name [", clazzName, "]:", ex.toString()); } } } } /** * Returns a set of the class names of application events that should be propagated when emitted from a hot deployed context * @return a set of class names */ public Set<String> getPropagates() { Set<String> names = new HashSet<String>(propagates.size()); for(Class<? extends ApplicationEvent> clazz : propagates) { names.add(clazz.getName()); } return names; } }