package org.apereo.cas.config.monitor;
import com.google.common.base.Throwables;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.support.events.config.CasConfigurationCreatedEvent;
import org.apereo.cas.support.events.config.CasConfigurationDeletedEvent;
import org.apereo.cas.support.events.config.CasConfigurationModifiedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
/**
* This is {@link ConfigurationDirectoryPathWatchService}.
* The general intent of this component is to watch configuration directory
* and notify CAS of changes.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
public class ConfigurationDirectoryPathWatchService implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationDirectoryPathWatchService.class);
private static final long MONITOR_INTERVAL = 5_000;
private final WatchService watcher;
private final Path directory;
private final ApplicationEventPublisher eventPublisher;
public ConfigurationDirectoryPathWatchService(final Path directory, final ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
try {
this.directory = directory;
this.watcher = FileSystems.getDefault().newWatchService();
this.directory.register(this.watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY);
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Watch the directory for changes.
*/
public void watch() {
long lastModified = System.currentTimeMillis();
while (true) {
final WatchKey key;
try {
key = watcher.take();
} catch (final InterruptedException e) {
LOGGER.warn(e.getMessage(), e);
return;
}
for (final WatchEvent<?> event : key.pollEvents()) {
final WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
LOGGER.warn("An overflow event occurred. File system events may be lost or discarded.");
continue;
}
final WatchEvent<Path> ev = (WatchEvent<Path>) event;
final Path filename = ev.context();
try {
final Path child = this.directory.resolve(filename);
if (System.currentTimeMillis() - lastModified >= MONITOR_INTERVAL) {
LOGGER.debug("Detected configuration change [{}]", kind.name());
if (StringUtils.equalsIgnoreCase(StandardWatchEventKinds.ENTRY_CREATE.name(), kind.name())) {
this.eventPublisher.publishEvent(new CasConfigurationCreatedEvent(this, child));
}
if (StringUtils.equalsIgnoreCase(StandardWatchEventKinds.ENTRY_DELETE.name(), kind.name())) {
this.eventPublisher.publishEvent(new CasConfigurationDeletedEvent(this, child));
}
if (StringUtils.equalsIgnoreCase(StandardWatchEventKinds.ENTRY_MODIFY.name(), kind.name())) {
this.eventPublisher.publishEvent(new CasConfigurationModifiedEvent(this, child));
}
lastModified = System.currentTimeMillis();
}
} catch (final Exception e) {
LOGGER.warn(e.getMessage(), e);
continue;
}
}
final boolean valid = key.reset();
if (!valid) {
break;
}
}
}
@Override
public void run() {
watch();
}
}