From da4e122bb98914df01f56e49ba950e151ed8a4f4 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 17 Jun 2020 19:03:17 -0400 Subject: [PATCH] Tidy up the ServiceMonitor class --- .../autopsy/core/ServicesMonitor.java | 403 +++++++++--------- 1 file changed, 193 insertions(+), 210 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java b/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java index 861c17ac9a..ec91e09a89 100644 --- a/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2015 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit 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 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 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 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 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 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 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 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 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); - } - } }