1
0
mirror of https://github.com/elisspace/autopsy.git synced 2026-09-01 00:43:53 +00:00

Tidy up the ServiceMonitor class

This commit is contained in:
Richard Cordovano
2020-06-17 19:03:17 -04:00
parent fa3082d410
commit da4e122bb9

View File

@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2015 Basis Technology Corp.
* Copyright 2013-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -42,65 +42,29 @@ import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* This class periodically checks availability of collaboration resources -
* remote database, remote keyword search server, messaging service - and
* reports status updates to the user in case of a gap in service.
* Monitors the status of services and publishes events and user notifications
* when the status of a service changes. The database server, keyword search
* server, and messaging service are considered to be core services in a
* collaborative, multi-user case environment. Additional services can provide
* current status by calling the setServiceStatus() method.
*/
public class ServicesMonitor {
private AutopsyEventPublisher eventPublisher;
private static final Logger logger = Logger.getLogger(ServicesMonitor.class.getName());
private final ScheduledThreadPoolExecutor periodicTasksExecutor;
private static final String PERIODIC_TASK_THREAD_NAME = "services-monitor-periodic-task-%d"; //NON-NLS
private static final int NUMBER_OF_PERIODIC_TASK_THREADS = 1;
private static final long CRASH_DETECTION_INTERVAL_MINUTES = 15;
private static final Set<String> servicesList = Stream.of(ServicesMonitor.Service.values())
.map(Service::toString)
.collect(Collectors.toSet());
/**
* The service monitor maintains a mapping of each service to it's last
* status update.
*/
private final ConcurrentHashMap<String, String> statusByService;
/**
* Call constructor on start-up so that the first check of services is done
* as soon as possible.
*/
private static ServicesMonitor instance = new ServicesMonitor();
/**
* List of services that are being monitored. The service names should be
* representative of the service functionality and readable as they get
* logged when service outage occurs.
* An enumeration of the core services in a collaborative, multi-user case
* environment. The display names provided here can be used to identify the
* service status events published for these services and to directly query
* the ServicesMonitor for the current status of these services.
*/
public enum Service {
/**
* Property change event fired when remote case database service status
* changes. New value is set to updated ServiceStatus, old value is
* null.
*/
REMOTE_CASE_DATABASE(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.remoteCaseDatabase.displayName.text")),
/**
* Property change event fired when remote keyword search service status
* changes. New value is set to updated ServiceStatus, old value is
* null.
*/
REMOTE_KEYWORD_SEARCH(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.remoteKeywordSearch.displayName.text")),
/**
* Property change event fired when messaging service status changes.
* New value is set to updated ServiceStatus, old value is null.
*/
MESSAGING(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.messaging.displayName.text"));
private final String displayName;
private Service(String name) {
this.displayName = name;
private Service(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
@@ -109,121 +73,249 @@ public class ServicesMonitor {
};
/**
* List of possible service statuses.
* An enumeration of the standard service statuses.
*/
public enum ServiceStatus {
/**
* Service is currently up.
*/
UP,
/**
* Service is currently down.
*/
DOWN
};
private static final Logger logger = Logger.getLogger(ServicesMonitor.class.getName());
private static final String PERIODIC_TASK_THREAD_NAME = "services-monitor-periodic-task-%d"; //NON-NLS
private static final int NUMBER_OF_PERIODIC_TASK_THREADS = 1;
private static final long CRASH_DETECTION_INTERVAL_MINUTES = 15;
private static final Set<String> coreServices = Stream.of(ServicesMonitor.Service.values()).map(Service::toString).collect(Collectors.toSet());
private static ServicesMonitor servicesMonitor = new ServicesMonitor();
private final ScheduledThreadPoolExecutor periodicTasksExecutor;
private final ConcurrentHashMap<String, String> statusByService;
private final AutopsyEventPublisher eventPublisher;
/**
* Gets the services monitor that monitors the status of services and
* publishes events and user notifications when the status of a service
* changes.
*
* @return The services monitor singleton.
*/
public synchronized static ServicesMonitor getInstance() {
if (instance == null) {
instance = new ServicesMonitor();
if (servicesMonitor == null) {
servicesMonitor = new ServicesMonitor();
}
return instance;
}
private ServicesMonitor() {
this.eventPublisher = new AutopsyEventPublisher();
this.statusByService = new ConcurrentHashMap<>();
// First check is triggered immediately on current thread.
checkAllServices();
/**
* Start periodic task that check the availability of key collaboration
* services.
*/
periodicTasksExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_PERIODIC_TASK_THREADS, new ThreadFactoryBuilder().setNameFormat(PERIODIC_TASK_THREAD_NAME).build());
periodicTasksExecutor.scheduleWithFixedDelay(new CrashDetectionTask(), CRASH_DETECTION_INTERVAL_MINUTES, CRASH_DETECTION_INTERVAL_MINUTES, TimeUnit.MINUTES);
return servicesMonitor;
}
/**
* Updates service status and publishes the service status update if it is
* different from previous status. Event is published locally. Logs status
* Constructs a services monitor that monitors the status of services and
* publishes events and user notifications when the status of a service
* changes.
*/
private ServicesMonitor() {
eventPublisher = new AutopsyEventPublisher();
statusByService = new ConcurrentHashMap<>();
/*
* The first service statuses check is performed immediately in the
* current thread.
*/
checkAllServices();
/**
* Start a periodic task to do ongoing service status checks.
*/
periodicTasksExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_PERIODIC_TASK_THREADS, new ThreadFactoryBuilder().setNameFormat(PERIODIC_TASK_THREAD_NAME).build());
periodicTasksExecutor.scheduleWithFixedDelay(new ServicesMonitoringTask(), CRASH_DETECTION_INTERVAL_MINUTES, CRASH_DETECTION_INTERVAL_MINUTES, TimeUnit.MINUTES);
}
/**
* Records the status of a service and publishes a service status event if
* the current status is different from the previously reported status.
*
* @param service Name of the service.
* @param status Updated status for the service.
* @param details Details of the event.
* @param status Current status of the service.
* @param details Additional status details.
*
*/
public void setServiceStatus(String service, String status, String details) {
// if the status update is for an existing service who's status hasn't changed - do nothing.
if (statusByService.containsKey(service) && status.equals(statusByService.get(service))) {
return;
}
// new service or status has changed - identify service's display name
statusByService.put(service, status);
String serviceDisplayName;
try {
serviceDisplayName = ServicesMonitor.Service.valueOf(service).getDisplayName();
} catch (IllegalArgumentException ignore) {
// custom service that is not listed in ServicesMonitor.Service enum. Use service name as display name.
serviceDisplayName = service;
}
if (status.equals(ServiceStatus.UP.toString())) {
logger.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS
MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"),
MessageNotifyUtil.Notify.info(
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"),
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.msg", serviceDisplayName));
} else if (status.equals(ServiceStatus.DOWN.toString())) {
logger.log(Level.SEVERE, "Failed to connect to {0}. Reason: {1}", new Object[]{serviceDisplayName, details}); //NON-NLS
MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"),
MessageNotifyUtil.Notify.error(
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"),
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.msg", serviceDisplayName));
} else {
logger.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS
MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.title"),
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.msg", new Object[]{serviceDisplayName, status}));
logger.log(Level.INFO, "Status for {0} is {1} ({2})", new Object[]{serviceDisplayName, status}); //NON-NLS
MessageNotifyUtil.Notify.info(
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.title"),
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.msg", new Object[]{serviceDisplayName, status, details}));
}
// update and publish new status
statusByService.put(service, status);
eventPublisher.publishLocally(new ServiceEvent(service, status, details));
}
/**
* Get last status update for a service.
* Get last recorded status for a service.
*
* @param service Name of the service.
*
* @return ServiceStatus Status for the service.
*
* @throws ServicesMonitorException If service name is null or service
* doesn't exist.
* @throws ServicesMonitorException If the service name is unknown to the
* services monitor.
*/
public String getServiceStatus(String service) throws ServicesMonitorException {
if (service == null) {
if (service == null || service.isEmpty()) {
throw new ServicesMonitorException(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.nullServiceName.excepton.txt"));
}
// if request is for one of our "core" services - perform an on demand check
// to make sure we have the latest status.
if (servicesList.contains(service)) {
/*
* If the request is for a core service, perform an "on demand" check to
* get the current status.
*/
if (coreServices.contains(service)) {
checkServiceStatus(service);
}
String status = statusByService.get(service);
if (status == null) {
// no such service
throw new ServicesMonitorException(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.unknownServiceName.excepton.txt", service));
}
return status;
}
/**
* Performs service availability status check.
* Adds a subscriber to service status events for the core services.
*
* @param service Name of the service.
* @param subscriber The subscriber to add.
*/
public void addSubscriber(PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(coreServices, subscriber);
}
/**
* Adds a subscriber to service status events for a subset of the services
* known to the services monitor.
*
* @param services The services the subscriber is interested in.
* @param subscriber The subscriber to add.
*/
public void addSubscriber(Set<String> services, PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(services, subscriber);
}
/**
* Adds a subscriber to service status events for a specific service known
* to the services monitor.
*
* @param service The service the subscriber is interested in.
* @param subscriber The subscriber to add.
*/
public void addSubscriber(String service, PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(service, subscriber);
}
/**
* Removes a subscriber to service status events for the core services.
*
* @param subscriber The subscriber to remove.
*/
public void removeSubscriber(PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(coreServices, subscriber);
}
/**
* Removes a subscriber to service status events for a subset of the
* services known to the services monitor.
*
* @param services The services the subscriber is no longer interested in.
* @param subscriber The subscriber to remove.
*/
public void removeSubscriber(Set<String> services, PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(services, subscriber);
}
/**
* Adds a subscriber to service status events for a specific service known
* to the services monitor.
*
* @param service The service the subscriber is no longer interested in.
* @param subscriber The subscriber to remove.
*/
public void removeSubscriber(String service, PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(service, subscriber);
}
/**
* Checks the status of the core services in a collaborative, multi-user
* case environment: the database server, the keyword search server and the
* messaging service. Publishes a service event and user notification if the
* status of a service has changed since the last check.
*/
private void checkAllServices() {
if (!UserPreferences.getIsMultiUserModeEnabled()) {
return;
}
for (String service : coreServices) {
checkServiceStatus(service);
}
}
/**
* A task that checks the status of the core services in a collaborative,
* multi-user case environment: the database server, the keyword search
* server and the messaging service. Publishes a service event and user
* notification if the status of a service has changed since the last check.
*/
private final class ServicesMonitoringTask implements Runnable {
@Override
public void run() {
try {
checkAllServices();
} catch (Exception ex) { // Exception firewall
logger.log(Level.SEVERE, "An error occurred during services monitoring", ex); //NON-NLS
}
}
}
/**
* Exception thrown if an error occurs during a service status query.
*/
public class ServicesMonitorException extends Exception {
private static final long serialVersionUID = 1L;
public ServicesMonitorException(String message) {
super(message);
}
public ServicesMonitorException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Performs a core service availability status check.
*
* @param service Name of the service to check.
*/
private void checkServiceStatus(String service) {
if (service.equals(Service.REMOTE_CASE_DATABASE.toString())) {
@@ -236,7 +328,7 @@ public class ServicesMonitor {
}
/**
* Performs case database service availability status check.
* Performs a database server availability status check.
*/
private void checkDatabaseConnectionStatus() {
CaseDbConnectionInfo info;
@@ -256,7 +348,7 @@ public class ServicesMonitor {
}
/**
* Performs keyword search service availability status check.
* Performs a keyword search service availability status check.
*/
private void checkKeywordSearchServerConnectionStatus() {
KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class);
@@ -303,113 +395,4 @@ public class ServicesMonitor {
}
}
/**
* Adds an event subscriber to this publisher. Subscriber will be subscribed
* to all events from this publisher.
*
* @param subscriber The subscriber to add.
*/
public void addSubscriber(PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(servicesList, subscriber);
}
/**
* Adds an event subscriber to this publisher.
*
* @param eventNames The events the subscriber is interested in.
* @param subscriber The subscriber to add.
*/
public void addSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(eventNames, subscriber);
}
/**
* Adds an event subscriber to this publisher.
*
* @param eventName The event the subscriber is interested in.
* @param subscriber The subscriber to add.
*/
public void addSubscriber(String eventName, PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(eventName, subscriber);
}
/**
* Removes an event subscriber from this publisher.
*
* @param eventNames The events the subscriber is no longer interested in.
* @param subscriber The subscriber to remove.
*/
public void removeSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(eventNames, subscriber);
}
/**
* Removes an event subscriber from this publisher.
*
* @param eventName The event the subscriber is no longer interested in.
* @param subscriber The subscriber to remove.
*/
public void removeSubscriber(String eventName, PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(eventName, subscriber);
}
/**
* Removes an event subscriber to this publisher. Subscriber will be removed
* from all event notifications from this publisher.
*
* @param subscriber The subscriber to remove.
*/
public void removeSubscriber(PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(servicesList, subscriber);
}
/**
* Verifies connectivity to all services.
*/
private void checkAllServices() {
if (!UserPreferences.getIsMultiUserModeEnabled()) {
return;
}
for (String service : servicesList) {
checkServiceStatus(service);
}
}
/**
* A Runnable task that periodically checks the availability of
* collaboration resources (remote database, remote keyword search service,
* message broker) and reports status to the user in case of a gap in
* service.
*/
private final class CrashDetectionTask implements Runnable {
/**
* Monitor the availability of collaboration resources
*/
@Override
public void run() {
try {
checkAllServices();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Unexpected exception in CrashDetectionTask", ex); //NON-NLS
}
}
}
/**
* Exception thrown when service status query results in an error.
*/
public class ServicesMonitorException extends Exception {
private static final long serialVersionUID = 1L;
public ServicesMonitorException(String message) {
super(message);
}
public ServicesMonitorException(String message, Throwable cause) {
super(message, cause);
}
}
}