Browse Source

pwmapplication reInit refactoring

Jason Rivard 3 years ago
parent
commit
9b1ac84d25
64 changed files with 976 additions and 654 deletions
  1. 10 9
      server/src/main/java/password/pwm/PwmAboutProperty.java
  2. 106 458
      server/src/main/java/password/pwm/PwmApplication.java
  3. 355 0
      server/src/main/java/password/pwm/PwmApplicationUtil.java
  4. 18 5
      server/src/main/java/password/pwm/PwmDomain.java
  5. 230 0
      server/src/main/java/password/pwm/PwmDomainUtil.java
  6. 8 6
      server/src/main/java/password/pwm/bean/SessionLabel.java
  7. 6 0
      server/src/main/java/password/pwm/config/AppConfig.java
  8. 6 21
      server/src/main/java/password/pwm/config/DomainConfig.java
  9. 2 0
      server/src/main/java/password/pwm/config/SettingReader.java
  10. 26 0
      server/src/main/java/password/pwm/config/StoredSettingReader.java
  11. 4 4
      server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java
  12. 1 1
      server/src/main/java/password/pwm/health/HealthService.java
  13. 6 6
      server/src/main/java/password/pwm/http/ContextManager.java
  14. 2 2
      server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java
  15. 2 1
      server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  16. 3 1
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java
  17. 2 2
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java
  18. 3 3
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java
  19. 5 5
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  20. 1 1
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchService.java
  21. 1 1
      server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java
  22. 1 1
      server/src/main/java/password/pwm/http/state/SessionStateService.java
  23. 1 1
      server/src/main/java/password/pwm/ldap/LdapConnectionService.java
  24. 1 1
      server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java
  25. 15 5
      server/src/main/java/password/pwm/svc/AbstractPwmService.java
  26. 1 1
      server/src/main/java/password/pwm/svc/PwmService.java
  27. 1 1
      server/src/main/java/password/pwm/svc/PwmServiceManager.java
  28. 1 1
      server/src/main/java/password/pwm/svc/cache/CacheService.java
  29. 1 1
      server/src/main/java/password/pwm/svc/cr/CrService.java
  30. 1 1
      server/src/main/java/password/pwm/svc/db/DatabaseService.java
  31. 1 1
      server/src/main/java/password/pwm/svc/email/EmailService.java
  32. 1 1
      server/src/main/java/password/pwm/svc/event/AuditService.java
  33. 27 1
      server/src/main/java/password/pwm/svc/event/CEFAuditFormatter.java
  34. 1 1
      server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java
  35. 2 2
      server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java
  36. 2 2
      server/src/main/java/password/pwm/svc/intruder/IntruderSystemService.java
  37. 1 1
      server/src/main/java/password/pwm/svc/node/NodeService.java
  38. 1 1
      server/src/main/java/password/pwm/svc/otp/OtpService.java
  39. 1 1
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java
  40. 1 1
      server/src/main/java/password/pwm/svc/report/ReportRecordLocalDBStorageService.java
  41. 2 2
      server/src/main/java/password/pwm/svc/report/ReportService.java
  42. 1 1
      server/src/main/java/password/pwm/svc/secure/AbstractSecureService.java
  43. 1 1
      server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java
  44. 1 1
      server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java
  45. 33 1
      server/src/main/java/password/pwm/svc/sms/SmsQueueService.java
  46. 1 1
      server/src/main/java/password/pwm/svc/stats/StatisticsService.java
  47. 1 1
      server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java
  48. 3 2
      server/src/main/java/password/pwm/svc/token/TokenService.java
  49. 1 1
      server/src/main/java/password/pwm/svc/userhistory/UserHistoryService.java
  50. 1 1
      server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java
  51. 1 1
      server/src/main/java/password/pwm/svc/wordlist/SharedHistoryService.java
  52. 2 2
      server/src/main/java/password/pwm/util/OnejarHelper.java
  53. 38 57
      server/src/main/java/password/pwm/util/PwmScheduler.java
  54. 2 2
      server/src/main/java/password/pwm/util/cli/CliEnvironment.java
  55. 5 5
      server/src/main/java/password/pwm/util/cli/MainClass.java
  56. 4 4
      server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java
  57. 4 4
      server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java
  58. 4 4
      server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java
  59. 4 4
      server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java
  60. 4 4
      server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java
  61. 1 1
      server/src/main/java/password/pwm/util/localdb/LocalDBService.java
  62. 1 1
      server/src/main/java/password/pwm/util/logging/LocalDBLogger.java
  63. 2 1
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  64. 1 1
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties

+ 10 - 9
server/src/main/java/password/pwm/PwmAboutProperty.java

@@ -48,13 +48,13 @@ import java.util.stream.Collectors;
 public enum PwmAboutProperty
 {
 
-    app_version( null, pwmApplication -> PwmConstants.SERVLET_VERSION ),
-    app_chaiApiVersion( null, pwmApplication -> PwmConstants.CHAI_API_VERSION ),
-    app_currentTime( null, pwmApplication -> format( Instant.now() ) ),
-    app_startTime( null, pwmApplication -> format( pwmApplication.getStartupTime() ) ),
-    app_installTime( null, pwmApplication -> format( pwmApplication.getInstallTime() ) ),
-    app_siteUrl( null, pwmApplication -> pwmApplication.getConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ) ),
-    app_instanceID( null, PwmApplication::getInstanceID ),
+    app_version( "App Version", pwmApplication -> PwmConstants.SERVLET_VERSION ),
+    app_chaiApiVersion( "App Chai Version", pwmApplication -> PwmConstants.CHAI_API_VERSION ),
+    app_currentTime( "App Current Time", pwmApplication -> format( Instant.now() ) ),
+    app_startTime( "App Startup Time", pwmApplication -> format( pwmApplication.getStartupTime() ) ),
+    app_installTime( "App Install Time", pwmApplication -> format( pwmApplication.getInstallTime() ) ),
+    app_siteUrl( "App Site URL", pwmApplication -> pwmApplication.getConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ) ),
+    app_instanceID( "App InstanceID", PwmApplication::getInstanceID ),
     app_trialMode( null, pwmApplication -> Boolean.toString( PwmConstants.TRIAL_MODE ) ),
     app_mode_appliance( null, pwmApplication -> Boolean.toString( pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.Appliance ) ) ),
     app_mode_docker( null, pwmApplication -> Boolean.toString( pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.Docker ) ) ),
@@ -79,8 +79,9 @@ public enum PwmAboutProperty
     app_secureHashAlgorithm( null, pwmApplication -> pwmApplication.getSecureService().getDefaultHashAlgorithm().toString() ),
     app_ldapProfileCount( null, pwmApplication -> Integer.toString( LdapConnectionService.totalLdapProfileCount( pwmApplication ) ) ),
     app_ldapConnectionCount( null, pwmApplication -> Long.toString( LdapConnectionService.totalLdapConnectionCount( pwmApplication ) ) ),
-    app_activeSessionCount( "Active Session Count", pwmApplication -> Integer.toString( pwmApplication.getSessionTrackService().sessionCount() ) ),
-    app_activeRequestCount( "Active Request Count", pwmApplication -> Integer.toString( pwmApplication.getActiveServletRequests().get() ) ),
+    app_activeSessionCount( "App Active Session Count", pwmApplication -> Integer.toString( pwmApplication.getSessionTrackService().sessionCount() ) ),
+    app_activeRequestCount( "App Active Request Count", pwmApplication -> Integer.toString( pwmApplication.getActiveServletRequests().get() ) ),
+    app_definedDomainCount( "App Defined Domain Count", pwmApplication -> Integer.toString( pwmApplication.domains().size() ) ),
 
     build_Time( "Build Time", pwmApplication -> PwmConstants.BUILD_TIME ),
     build_Number( "Build Number", pwmApplication -> PwmConstants.BUILD_NUMBER ),

+ 106 - 458
server/src/main/java/password/pwm/PwmApplication.java

@@ -23,14 +23,10 @@ package password.pwm;
 import lombok.Value;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.SessionLabel;
-import password.pwm.bean.SmsItemBean;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingMetaDataReader;
 import password.pwm.config.PwmSettingScope;
-import password.pwm.config.stored.StoredConfigKey;
-import password.pwm.config.stored.StoredConfiguration;
-import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
@@ -64,34 +60,19 @@ import password.pwm.svc.wordlist.SeedlistService;
 import password.pwm.svc.wordlist.SharedHistoryService;
 import password.pwm.svc.wordlist.WordlistService;
 import password.pwm.util.MBeanUtility;
-import password.pwm.util.PasswordData;
 import password.pwm.util.PwmScheduler;
-import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
-import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.localdb.LocalDB;
-import password.pwm.util.localdb.LocalDBFactory;
 import password.pwm.util.logging.LocalDBLogger;
-import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogManager;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroRequest;
-import password.pwm.util.secure.HttpsServerCertificateManager;
 import password.pwm.util.secure.PwmRandom;
-import password.pwm.util.secure.X509Utils;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.Serializable;
-import java.net.URI;
-import java.nio.file.Files;
-import java.security.KeyStore;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -103,42 +84,45 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class PwmApplication
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmApplication.class );
-    private static final String DEFAULT_INSTANCE_ID = "-1";
 
     private final AtomicInteger activeServletRequests = new AtomicInteger( 0 );
 
-    private Map<DomainID, PwmDomain> domains;
+    private volatile Map<DomainID, PwmDomain> domains = new HashMap<>();
     private String runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
 
-    private final PwmServiceManager pwmServiceManager = new PwmServiceManager(
-            SessionLabel.SYSTEM_LABEL,
-            this, DomainID.systemId(), PwmServiceEnum.forScope( PwmSettingScope.SYSTEM ) );
+    private final SessionLabel sessionLabel;
+    private final PwmServiceManager pwmServiceManager;
 
     private final Instant startupTime = Instant.now();
+
     private Instant installTime = Instant.now();
     private ErrorInformation lastLocalDBFailure;
     private PwmEnvironment pwmEnvironment;
     private FileLocker fileLocker;
     private PwmScheduler pwmScheduler;
-    private String instanceID = DEFAULT_INSTANCE_ID;
+    private String instanceID = PwmApplicationUtil.DEFAULT_INSTANCE_ID;
     private LocalDB localDB;
     private LocalDBLogger localDBLogger;
 
-
     public PwmApplication( final PwmEnvironment pwmEnvironment )
             throws PwmUnrecoverableException
     {
         this.pwmEnvironment = Objects.requireNonNull( pwmEnvironment );
+        this.sessionLabel = pwmEnvironment.isInternalRuntimeInstance()
+                ? SessionLabel.RUNTIME_LABEL
+                : SessionLabel.SYSTEM_LABEL;
+
+        this.pwmServiceManager = new PwmServiceManager(
+                sessionLabel, this, DomainID.systemId(), PwmServiceEnum.forScope( PwmSettingScope.SYSTEM ) );
 
         if ( !pwmEnvironment.isInternalRuntimeInstance() )
         {
@@ -151,46 +135,27 @@ public class PwmApplication
         }
         catch ( final PwmUnrecoverableException e )
         {
-            LOGGER.fatal( e::getMessage );
+            LOGGER.fatal( sessionLabel, e::getMessage );
             throw e;
         }
     }
 
-    public static Optional<String> deriveLocalServerHostname( final AppConfig appConfig )
+    private void initRuntimeNonce()
     {
-        if ( appConfig != null )
-        {
-            final String siteUrl = appConfig.readSettingAsString( PwmSetting.PWM_SITE_URL );
-            if ( StringUtil.notEmpty( siteUrl ) )
-            {
-                try
-                {
-                    final URI parsedUri = URI.create( siteUrl );
-                    {
-                        final String uriHost = parsedUri.getHost();
-                        return Optional.ofNullable( uriHost );
-                    }
-                }
-                catch ( final IllegalArgumentException e )
-                {
-                    LOGGER.trace( () -> " error parsing siteURL hostname: " + e.getMessage() );
-                }
-            }
-        }
-        return Optional.empty();
+        runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
     }
 
-    private void initialize( )
+    private void initialize()
             throws PwmUnrecoverableException
     {
         final Instant startTime = Instant.now();
 
-        runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
+        initRuntimeNonce();
 
-        this.domains = Initializer.initializeDomains( this );
+        domains = PwmDomainUtil.createDomainInstances( this );
 
-        // initialize log4j
-        Initializer.initializeLogging( this );
+        // initialize logger
+        PwmApplicationUtil.initializeLogging( this );
 
         // get file lock
         if ( !pwmEnvironment.isInternalRuntimeInstance() )
@@ -205,7 +170,7 @@ public class PwmApplication
             final File tempFileDirectory = getTempDirectory();
             try
             {
-                LOGGER.debug( () -> "deleting directory (and sub-directory) contents in " + tempFileDirectory );
+                LOGGER.debug( sessionLabel, () -> "deleting directory (and sub-directory) contents in " + tempFileDirectory );
                 FileSystemUtility.deleteDirectoryContentsRecursively( tempFileDirectory.toPath() );
             }
             catch ( final Exception e )
@@ -218,7 +183,7 @@ public class PwmApplication
 
         if ( getApplicationMode() != PwmApplicationMode.READ_ONLY )
         {
-            LOGGER.info( () -> "initializing, application mode=" + getApplicationMode()
+            LOGGER.info( sessionLabel, () -> "initializing, application mode=" + getApplicationMode()
                     + ", applicationPath=" + ( pwmEnvironment.getApplicationPath() == null ? "null" : pwmEnvironment.getApplicationPath().getAbsolutePath() )
                     + ", configFile=" + ( pwmEnvironment.getConfigurationFile() == null ? "null" : pwmEnvironment.getConfigurationFile().getAbsolutePath() )
             );
@@ -228,13 +193,13 @@ public class PwmApplication
         {
             if ( getApplicationMode() == PwmApplicationMode.ERROR )
             {
-                LOGGER.warn( () -> "skipping LocalDB open due to application mode " + getApplicationMode() );
+                LOGGER.warn( sessionLabel, () -> "skipping LocalDB open due to application mode " + getApplicationMode() );
             }
             else
             {
                 if ( localDB == null )
                 {
-                    this.localDB = Initializer.initializeLocalDB( this, pwmEnvironment );
+                    this.localDB = PwmApplicationUtil.initializeLocalDB( this, pwmEnvironment );
                 }
             }
         }
@@ -242,25 +207,25 @@ public class PwmApplication
         this.localDBLogger = PwmLogManager.initializeLocalDBLogger( this );
 
         // log the loaded configuration
-        LOGGER.debug( () -> "configuration load completed" );
+        LOGGER.debug( sessionLabel, () -> "configuration load completed" );
 
         // read the pwm servlet instance id
-        instanceID = fetchInstanceID( localDB, this );
-        LOGGER.debug( () -> "using '" + getInstanceID() + "' for instance's ID (instanceID)" );
+        instanceID = PwmApplicationUtil.fetchInstanceID( this, localDB );
+        LOGGER.debug( sessionLabel, () -> "using '" + getInstanceID() + "' for instance's ID (instanceID)" );
 
         // read the pwm installation date
         installTime = fetchInstallDate( startupTime );
-        LOGGER.debug( () -> "this application instance first installed on " + StringUtil.toIsoDate( installTime ) );
+        LOGGER.debug( sessionLabel, () -> "this application instance first installed on " + StringUtil.toIsoDate( installTime ) );
 
-        LOGGER.debug( () -> "application environment flags: " + JsonFactory.get().serializeCollection( pwmEnvironment.getFlags() ) );
-        LOGGER.debug( () -> "application environment parameters: "
+        LOGGER.debug( sessionLabel, () -> "application environment flags: " + JsonFactory.get().serializeCollection( pwmEnvironment.getFlags() ) );
+        LOGGER.debug( sessionLabel, () -> "application environment parameters: "
                 + JsonFactory.get().serializeMap( pwmEnvironment.getParameters(), PwmEnvironment.ApplicationParameter.class, String.class ) );
 
         pwmScheduler = new PwmScheduler( this );
 
         pwmServiceManager.initAllServices();
 
-        initAllDomains();
+            PwmDomainUtil.initDomains( this, domains().values() );
 
         final boolean skipPostInit = pwmEnvironment.isInternalRuntimeInstance()
                 || pwmEnvironment.getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance );
@@ -268,68 +233,45 @@ public class PwmApplication
         if ( !skipPostInit )
         {
             final TimeDuration totalTime = TimeDuration.fromCurrent( startTime );
-            LOGGER.info( () -> PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " open for bidness! (" + totalTime.asCompactString() + ")" );
+            LOGGER.info( sessionLabel, () -> PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " open for bidness! (" + totalTime.asCompactString() + ")" );
             StatisticsClient.incrementStat( this, Statistic.PWM_STARTUPS );
-            LOGGER.debug( () -> "buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE );
+            LOGGER.debug( sessionLabel, () -> "buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE );
 
             pwmScheduler.immediateExecuteRunnableInNewThread( this::postInitTasks, this.getClass().getSimpleName() + " postInit tasks" );
         }
 
     }
 
-    private void initAllDomains()
-            throws PwmUnrecoverableException
+    public void reInit( final PwmEnvironment pwmEnvironment )
+            throws PwmException
     {
-        final Instant domainInitStartTime = Instant.now();
-        LOGGER.trace( () -> "beginning domain initializations" );
+        final Instant startTime = Instant.now();
+        LOGGER.debug( sessionLabel, () -> "beginning application restart" );
+        final AppConfig oldConfig = this.pwmEnvironment.getConfig();
+        this.pwmEnvironment = pwmEnvironment;
+        final AppConfig newConfig = this.pwmEnvironment.getConfig();
 
-        final List<Callable<?>> callables = domains.values().stream().<Callable<?>>map( pwmDomain -> () ->
+        if ( !Objects.equals( oldConfig.getValueHash(), newConfig.getValueHash() ) )
         {
-            pwmDomain.initialize();
-            return null;
-        } ).collect( Collectors.toList() );
-        pwmScheduler.executeImmediateThreadPerJobAndAwaitCompletion( callables, "domain initializer" );
+            processPwmAppRestart( );
+        }
 
-        LOGGER.trace( () -> "completed domain initialization for all domains", () -> TimeDuration.fromCurrent( domainInitStartTime ) );
-    }
+        domains = PwmDomainUtil.reInitDomains( this, newConfig, oldConfig );
 
+        runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
 
-    public void reInit( final PwmEnvironment pwmEnvironment )
-            throws PwmException
-    {
-        final Instant startTime = Instant.now();
-        LOGGER.debug( () -> "beginning application restart" );
-        shutdown( true );
-        this.pwmEnvironment = pwmEnvironment;
-        initialize();
-        LOGGER.debug( () -> "completed application restart", () -> TimeDuration.fromCurrent( startTime ) );
+        LOGGER.debug( sessionLabel, () -> "completed application restart", () -> TimeDuration.fromCurrent( startTime ) );
     }
 
-    private void postInitTasks( )
+    private void postInitTasks()
     {
         final Instant startTime = Instant.now();
 
         getPwmScheduler().immediateExecuteRunnableInNewThread( UserAgentUtils::initializeCache, "initialize useragent cache" );
         getPwmScheduler().immediateExecuteRunnableInNewThread( PwmSettingMetaDataReader::initCache, "initialize PwmSetting cache" );
 
-        if ( Boolean.parseBoolean( getConfig().readAppProperty( AppProperty.LOGGING_OUTPUT_CONFIGURATION ) ) )
-        {
-            outputConfigurationToLog( this );
-            outputNonDefaultPropertiesToLog( this );
-        }
-
         // send system audit event
-        AuditServiceClient.submitSystemEvent( this, SessionLabel.SYSTEM_LABEL, AuditEvent.STARTUP );
-
-        try
-        {
-            final Map<PwmAboutProperty, String> infoMap = PwmAboutProperty.makeInfoBean( this );
-            LOGGER.trace( () ->  "application info: " + JsonFactory.get().serializeMap( infoMap, PwmAboutProperty.class, String.class ) );
-        }
-        catch ( final Exception e )
-        {
-            LOGGER.error( () -> "error generating about application bean: " + e.getMessage(), e );
-        }
+        AuditServiceClient.submitSystemEvent( this, sessionLabel, AuditEvent.STARTUP );
 
         try
         {
@@ -337,41 +279,27 @@ public class PwmApplication
         }
         catch ( final Exception e )
         {
-            LOGGER.warn( () -> "error while clearing configmanager-intruder-username from intruder table: " + e.getMessage() );
+            LOGGER.warn( sessionLabel, () -> "error while clearing config manager-intruder-username from intruder table: " + e.getMessage() );
         }
 
         if ( !pwmEnvironment.isInternalRuntimeInstance() )
         {
-            try
-            {
-                outputKeystore( this );
-            }
-            catch ( final Exception e )
-            {
-                LOGGER.debug( () -> "error while generating keystore output: " + e.getMessage() );
-            }
-
-            try
-            {
-                outputTomcatConf( this );
-            }
-            catch ( final Exception e )
-            {
-                LOGGER.debug( () -> "error while generating tomcat conf output: " + e.getMessage() );
-            }
+            PwmApplicationUtil.outputKeystore( this );
+            PwmApplicationUtil.outputTomcatConf( this );
         }
 
         if ( Boolean.parseBoolean( getConfig().readAppProperty( AppProperty.LOGGING_OUTPUT_CONFIGURATION ) ) )
         {
             getPwmScheduler().immediateExecuteRunnableInNewThread( () ->
             {
-                outputConfigurationToLog( this );
-                outputNonDefaultPropertiesToLog( this );
+                PwmApplicationUtil.outputApplicationInfoToLog( this );
+                PwmApplicationUtil.outputConfigurationToLog( this, DomainID.systemId() );
+                PwmApplicationUtil.outputNonDefaultPropertiesToLog( this );
             }, "output configuration to log" );
         }
 
         MBeanUtility.registerMBean( this );
-        LOGGER.trace( () -> "completed post init tasks", () -> TimeDuration.fromCurrent( startTime ) );
+        LOGGER.trace( sessionLabel, () -> "completed post init tasks", () -> TimeDuration.fromCurrent( startTime ) );
     }
 
     public static PwmApplication createPwmApplication( final PwmEnvironment pwmEnvironment )
@@ -380,6 +308,11 @@ public class PwmApplication
         return new PwmApplication( pwmEnvironment );
     }
 
+    public SessionLabel getSessionLabel()
+    {
+        return sessionLabel;
+    }
+
     public Map<DomainID, PwmDomain> domains()
     {
         return domains;
@@ -416,75 +349,76 @@ public class PwmApplication
         return domains().get( getConfig().getAdminDomainID() );
     }
 
-    public void shutdown( )
+    private void processPwmAppRestart()
+            throws PwmUnrecoverableException
     {
-        shutdown( false );
+        LOGGER.debug( sessionLabel, () -> "system config settings modified, system services restart required" );
+        AuditServiceClient.submitSystemEvent( this, sessionLabel, AuditEvent.RESTART );
+
+        initRuntimeNonce();
+
+        pwmServiceManager.shutdownAllServices();
+
+
+        // initialize logger
+        PwmApplicationUtil.initializeLogging( this );
+
+        pwmServiceManager.initAllServices();
+
+        if ( !pwmEnvironment.isInternalRuntimeInstance() )
+        {
+            PwmApplicationUtil.outputKeystore( this );
+            PwmApplicationUtil.outputTomcatConf( this );
+        }
     }
 
-    public void shutdown( final boolean keepServicesRunning )
+    public void shutdown( )
     {
         final Instant startTime = Instant.now();
 
-        if ( keepServicesRunning )
-        {
-            LOGGER.warn( () -> "preparing for restart" );
-            AuditServiceClient.submitSystemEvent( this, SessionLabel.SYSTEM_LABEL, AuditEvent.RESTART );
-        }
-        else
-        {
-            LOGGER.warn( () -> "shutting down" );
-            AuditServiceClient.submitSystemEvent( this, SessionLabel.SYSTEM_LABEL, AuditEvent.SHUTDOWN );
-        }
+        LOGGER.warn( sessionLabel, () -> "shutting down" );
+        AuditServiceClient.submitSystemEvent( this, sessionLabel, AuditEvent.SHUTDOWN );
 
         MBeanUtility.unregisterMBean( this );
 
-        if ( !keepServicesRunning )
-        {
-            try
-            {
-                final List<Callable<?>> callables = domains.values().stream().<Callable<?>>map( pwmDomain -> () ->
-                {
-                    pwmDomain.shutdown();
-                    return null;
-                } ).collect( Collectors.toList() );
-                pwmScheduler.executeImmediateThreadPerJobAndAwaitCompletion( callables, "domain shutdown task" );
-            }
-            catch ( final PwmUnrecoverableException e )
-            {
-                LOGGER.error( () -> "error shutting down domain services: " + e.getMessage(), e );
-            }
+        pwmServiceManager.shutdownAllServices();
 
-            pwmServiceManager.shutdownAllServices();
+        try
+        {
+            final List<Callable<Object>> callables = domains.values().stream()
+                    .map( pwmDomain -> Executors.callable( pwmDomain::shutdown ) )
+                    .collect( Collectors.toList() );
+            pwmScheduler.executeImmediateThreadPerJobAndAwaitCompletion( callables, "domain shutdown task" );
+        }
+        catch ( final PwmUnrecoverableException e )
+        {
+            LOGGER.error( sessionLabel, () -> "error shutting down domain services: " + e.getMessage(), e );
         }
 
         if ( localDBLogger != null )
         {
             try
             {
-                localDBLogger.close();
+                localDBLogger.shutdownImpl();
             }
             catch ( final Exception e )
             {
-                LOGGER.error( () -> "error closing localDBLogger: " + e.getMessage(), e );
+                LOGGER.error( sessionLabel, () -> "error closing localDBLogger: " + e.getMessage(), e );
             }
             localDBLogger = null;
         }
 
-        if ( keepServicesRunning )
-        {
-            LOGGER.trace( () -> "skipping close of LocalDB (restart request)" );
-        }
-        else if ( localDB != null )
+        if ( localDB != null )
         {
             try
             {
                 final Instant startCloseDbTime = Instant.now();
-                LOGGER.debug( () -> "beginning close of LocalDB" );
+                LOGGER.debug( sessionLabel, () -> "beginning close of LocalDB" );
                 localDB.close();
                 final TimeDuration closeLocalDbDuration = TimeDuration.fromCurrent( startCloseDbTime );
                 if ( closeLocalDbDuration.isLongerThan( TimeDuration.SECONDS_10 ) )
                 {
-                    LOGGER.info( () -> "completed close of LocalDB", () -> closeLocalDbDuration );
+                    LOGGER.info( sessionLabel, () -> "completed close of LocalDB", () -> closeLocalDbDuration );
                 }
             }
             catch ( final Exception e )
@@ -501,133 +435,10 @@ public class PwmApplication
 
         pwmScheduler.shutdown();
 
-        LOGGER.info( () -> PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION
+        LOGGER.info( sessionLabel, () -> PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION
                 + " closed for bidness, cya!", () -> TimeDuration.fromCurrent( startTime ) );
     }
 
-    private static void outputKeystore( final PwmApplication pwmApplication ) throws Exception
-    {
-        final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = pwmApplication.getPwmEnvironment().getParameters();
-        final String keystoreFileString = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStoreFile );
-        if ( StringUtil.isEmpty( keystoreFileString ) )
-        {
-            return;
-        }
-
-        final File keyStoreFile = new File( keystoreFileString );
-        final String password = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStorePassword );
-        final String alias = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStoreAlias );
-        final KeyStore keyStore = HttpsServerCertificateManager.keyStoreForApplication( pwmApplication, new PasswordData( password ), alias );
-        X509Utils.outputKeystore( keyStore, keyStoreFile, password );
-        PwmApplication.LOGGER.info( () -> "exported application https key to keystore file " + keyStoreFile.getAbsolutePath() );
-    }
-
-    private static void outputTomcatConf( final PwmApplication pwmDomain ) throws IOException
-    {
-        final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = pwmDomain.getPwmEnvironment().getParameters();
-        final String tomcatOutputFileStr = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoWriteTomcatConfOutputFile );
-        if ( tomcatOutputFileStr != null && !tomcatOutputFileStr.isEmpty() )
-        {
-            LOGGER.trace( () -> "attempting to output tomcat configuration file as configured by environment parameters to " + tomcatOutputFileStr );
-            final File tomcatOutputFile = new File( tomcatOutputFileStr );
-            final File tomcatSourceFile;
-            {
-                final String tomcatSourceFileStr = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoWriteTomcatConfSourceFile );
-                if ( tomcatSourceFileStr != null && !tomcatSourceFileStr.isEmpty() )
-                {
-                    tomcatSourceFile = new File( tomcatSourceFileStr );
-                    if ( !tomcatSourceFile.exists() )
-                    {
-                        LOGGER.error( () -> "can not output tomcat configuration file, source file does not exist: " + tomcatSourceFile.getAbsolutePath() );
-                        return;
-                    }
-                }
-                else
-                {
-                    LOGGER.error( () -> "can not output tomcat configuration file, source file parameter '"
-                            + PwmEnvironment.ApplicationParameter.AutoWriteTomcatConfSourceFile + "' is not specified." );
-                    return;
-                }
-            }
-
-            try ( ByteArrayOutputStream outputContents = new ByteArrayOutputStream() )
-            {
-                try ( InputStream fileInputStream = Files.newInputStream( tomcatOutputFile.toPath() ) )
-                {
-                    ExportHttpsTomcatConfigCommand.TomcatConfigWriter.writeOutputFile(
-                            pwmDomain.getConfig(),
-                            fileInputStream,
-                            outputContents
-                    );
-                }
-
-                if ( tomcatOutputFile.exists() )
-                {
-                    LOGGER.trace( () -> "deleting existing tomcat configuration file " + tomcatOutputFile.getAbsolutePath() );
-                    if ( tomcatOutputFile.delete() )
-                    {
-                        LOGGER.trace( () -> "deleted existing tomcat configuration file: " + tomcatOutputFile.getAbsolutePath() );
-                    }
-                }
-
-                try ( OutputStream fileOutputStream = Files.newOutputStream( tomcatOutputFile.toPath() ) )
-                {
-                    fileOutputStream.write( outputContents.toByteArray() );
-                }
-            }
-
-            LOGGER.info( () -> "successfully wrote tomcat configuration to file " + tomcatOutputFile.getAbsolutePath() );
-        }
-    }
-
-    private static void outputConfigurationToLog( final PwmApplication pwmApplication )
-    {
-        final Instant startTime = Instant.now();
-
-        final Function<Map.Entry<String, String>, String> valueFormatter = entry ->
-        {
-            final String spacedValue = entry.getValue().replace( "\n", "\n   " );
-            return " " + entry.getKey() + "\n   " + spacedValue + "\n";
-        };
-
-        final StoredConfiguration storedConfiguration = pwmApplication.getConfig().getStoredConfiguration();
-        final List<StoredConfigKey> keys = CollectionUtil.iteratorToStream( storedConfiguration.keys() ).collect( Collectors.toList() );
-        final Map<String, String> debugStrings = StoredConfigurationUtil.makeDebugMap(
-                storedConfiguration,
-                keys,
-                PwmConstants.DEFAULT_LOCALE );
-
-        LOGGER.trace( () -> "--begin current configuration output--" );
-        final long itemCount = debugStrings.entrySet().stream()
-                .map( valueFormatter )
-                .map( s -> ( Supplier<CharSequence> ) () -> s )
-                .peek( LOGGER::trace )
-                .count();
-
-        LOGGER.trace( () -> "--end current configuration output of " + itemCount + " items --",
-                () -> TimeDuration.fromCurrent( startTime ) );
-    }
-
-    private static void outputNonDefaultPropertiesToLog( final PwmApplication pwmApplication )
-    {
-        final Instant startTime = Instant.now();
-
-        final Map<AppProperty, String> nonDefaultProperties = pwmApplication.getConfig().readAllNonDefaultAppProperties();
-        if ( !CollectionUtil.isEmpty( nonDefaultProperties ) )
-        {
-            LOGGER.trace( () -> "--begin non-default app properties output--" );
-            nonDefaultProperties.entrySet().stream()
-                    .map( entry -> "AppProperty: " + entry.getKey().getKey() + " -> " + entry.getValue() )
-                    .map( s -> ( Supplier<CharSequence> ) () -> s )
-                    .forEach( LOGGER::trace );
-            LOGGER.trace( () -> "--end non-default app properties output--", () -> TimeDuration.fromCurrent( startTime ) );
-        }
-        else
-        {
-            LOGGER.trace( () -> "no non-default app properties in configuration" );
-        }
-    }
-
     public String getInstanceID( )
     {
         return instanceID;
@@ -815,47 +626,6 @@ public class PwmApplication
         return Instant.now();
     }
 
-    private String fetchInstanceID( final LocalDB localDB, final PwmApplication pwmApplication )
-    {
-        {
-            final String newInstanceID = pwmApplication.getPwmEnvironment().getParameters().get( PwmEnvironment.ApplicationParameter.InstanceID );
-
-            if ( !StringUtil.isTrimEmpty( newInstanceID ) )
-            {
-                return newInstanceID;
-            }
-        }
-
-        if ( pwmApplication.getLocalDB() == null || pwmApplication.getApplicationMode() != PwmApplicationMode.RUNNING )
-        {
-            return DEFAULT_INSTANCE_ID;
-        }
-
-        {
-            final Optional<String> optionalStoredInstanceID = readAppAttribute( AppAttribute.INSTANCE_ID, String.class );
-            if ( optionalStoredInstanceID.isPresent() )
-            {
-                final String instanceID = optionalStoredInstanceID.get();
-                if ( !StringUtil.isTrimEmpty( instanceID ) )
-                {
-                    LOGGER.trace( () -> "retrieved instanceID " + instanceID + "" + " from localDB" );
-                    return instanceID;
-                }
-            }
-        }
-
-        final PwmRandom pwmRandom = PwmRandom.getInstance();
-        final String newInstanceID = Long.toHexString( pwmRandom.nextLong() ).toUpperCase();
-        LOGGER.debug( () -> "generated new random instanceID " + newInstanceID );
-
-        if ( localDB != null )
-        {
-            writeAppAttribute( AppAttribute.INSTANCE_ID, newInstanceID );
-        }
-
-        return newInstanceID;
-    }
-
     public SharedHistoryService getSharedHistoryManager( )
     {
         return ( SharedHistoryService ) pwmServiceManager.getService( PwmServiceEnum.SharedHistoryManager );
@@ -953,6 +723,11 @@ public class PwmApplication
         return lastLocalDBFailure;
     }
 
+    void setLastLocalDBFailure( final ErrorInformation lastLocalDBFailure )
+    {
+        this.lastLocalDBFailure = lastLocalDBFailure;
+    }
+
     public SessionTrackService getSessionTrackService( )
     {
         return ( SessionTrackService ) pwmServiceManager.getService( PwmServiceEnum.SessionTrackService );
@@ -1011,134 +786,6 @@ public class PwmApplication
         return this.getConfig().isMultiDomain();
     }
 
-    public void sendSmsUsingQueue(
-            final String to,
-            final String message,
-            final SessionLabel sessionLabel,
-            final MacroRequest macroRequest
-    )
-    {
-        final SmsQueueService smsQueue = getSmsQueue();
-        if ( smsQueue == null )
-        {
-            LOGGER.error( sessionLabel, () -> "SMS queue is unavailable, unable to send SMS to: " + to );
-            return;
-        }
-
-        final SmsItemBean smsItemBean = new SmsItemBean(
-                macroRequest.expandMacros( to ),
-                macroRequest.expandMacros( message ),
-                sessionLabel
-        );
-
-        try
-        {
-            smsQueue.addSmsToQueue( smsItemBean );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.warn( () -> "unable to add sms to queue: " + e.getMessage() );
-        }
-    }
-
-    private static class Initializer
-    {
-        public static LocalDB initializeLocalDB( final PwmApplication pwmApplication, final PwmEnvironment pwmEnvironment )
-                throws PwmUnrecoverableException
-        {
-            final File databaseDirectory;
-
-            try
-            {
-                final String localDBLocationSetting = pwmApplication.getConfig().readAppProperty( AppProperty.LOCALDB_LOCATION );
-                databaseDirectory = FileSystemUtility.figureFilepath( localDBLocationSetting, pwmApplication.pwmEnvironment.getApplicationPath() );
-            }
-            catch ( final Exception e )
-            {
-                pwmApplication.lastLocalDBFailure = new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "error locating configured LocalDB directory: " + e.getMessage() );
-                LOGGER.warn( () -> pwmApplication.lastLocalDBFailure.toDebugStr() );
-                throw new PwmUnrecoverableException( pwmApplication.lastLocalDBFailure );
-            }
-
-            LOGGER.debug( () -> "using localDB path " + databaseDirectory );
-
-            // initialize the localDB
-            try
-            {
-                final boolean readOnly = pwmApplication.getApplicationMode() == PwmApplicationMode.READ_ONLY;
-                return LocalDBFactory.getInstance( databaseDirectory, readOnly, pwmEnvironment, pwmApplication.getConfig() );
-            }
-            catch ( final Exception e )
-            {
-                pwmApplication.lastLocalDBFailure = new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "unable to initialize LocalDB: " + e.getMessage() );
-                LOGGER.warn( () -> pwmApplication.lastLocalDBFailure.toDebugStr() );
-                throw new PwmUnrecoverableException( pwmApplication.lastLocalDBFailure );
-            }
-        }
-
-        private static Map<DomainID, PwmDomain> initializeDomains( final PwmApplication pwmApplication )
-        {
-            final Map<DomainID, PwmDomain> domainMap = new TreeMap<>();
-            for ( final String domainIdString : pwmApplication.getPwmEnvironment().getConfig().getDomainIDs() )
-            {
-                final DomainID domainID = DomainID.create( domainIdString );
-                final PwmDomain newDomain = new PwmDomain( pwmApplication, domainID );
-                domainMap.put( domainID, newDomain );
-            }
-
-            return Collections.unmodifiableMap( domainMap );
-        }
-
-        public static void initializeLogging( final PwmApplication pwmApplication )
-        {
-            final PwmEnvironment pwmEnvironment = pwmApplication.getPwmEnvironment();
-
-            if ( !pwmEnvironment.isInternalRuntimeInstance() && !pwmEnvironment.getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance ) )
-            {
-                final String log4jFileName = pwmEnvironment.getConfig().readSettingAsString( PwmSetting.EVENTS_JAVA_LOG4JCONFIG_FILE );
-                final File log4jFile = FileSystemUtility.figureFilepath( log4jFileName, pwmEnvironment.getApplicationPath() );
-                final String consoleLevel;
-                final String fileLevel;
-
-                switch ( pwmApplication.getApplicationMode() )
-                {
-                    case ERROR:
-                    case NEW:
-                        consoleLevel = PwmLogLevel.TRACE.toString();
-                        fileLevel = PwmLogLevel.TRACE.toString();
-                        break;
-
-                    default:
-                        consoleLevel = pwmEnvironment.getConfig().readSettingAsString( PwmSetting.EVENTS_JAVA_STDOUT_LEVEL );
-                        fileLevel = pwmEnvironment.getConfig().readSettingAsString( PwmSetting.EVENTS_FILE_LEVEL );
-                        break;
-                }
-
-                PwmLogManager.initializeLogger(
-                        pwmApplication,
-                        pwmApplication.getConfig(),
-                        log4jFile,
-                        consoleLevel,
-                        pwmEnvironment.getApplicationPath(),
-                        fileLevel );
-
-                switch ( pwmApplication.getApplicationMode() )
-                {
-                    case RUNNING:
-                        break;
-
-                    case ERROR:
-                        LOGGER.fatal( () -> "starting up in ERROR mode! Check log or health check information for cause" );
-                        break;
-
-                    default:
-                        LOGGER.trace( () -> "setting log level to TRACE because application mode is " + pwmApplication.getApplicationMode() );
-                        break;
-                }
-            }
-        }
-    }
-
     public File getTempDirectory( ) throws PwmUnrecoverableException
     {
         if ( pwmEnvironment.getApplicationPath() == null )
@@ -1224,4 +871,5 @@ public class PwmApplication
 
         return conditions.stream().allMatch( ( c ) -> c.matches( this ) );
     }
+
 }

+ 355 - 0
server/src/main/java/password/pwm/PwmApplicationUtil.java

@@ -0,0 +1,355 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm;
+
+import password.pwm.bean.DomainID;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.StoredConfigKey;
+import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationUtil;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.PasswordData;
+import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
+import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.FileSystemUtility;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.localdb.LocalDB;
+import password.pwm.util.localdb.LocalDBFactory;
+import password.pwm.util.logging.PwmLogLevel;
+import password.pwm.util.logging.PwmLogManager;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.HttpsServerCertificateManager;
+import password.pwm.util.secure.PwmRandom;
+import password.pwm.util.secure.X509Utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.security.KeyStore;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+class PwmApplicationUtil
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmApplicationUtil.class );
+
+    static final String DEFAULT_INSTANCE_ID = "-1";
+
+    static LocalDB initializeLocalDB( final PwmApplication pwmApplication, final PwmEnvironment pwmEnvironment )
+            throws PwmUnrecoverableException
+    {
+        final File databaseDirectory;
+
+        try
+        {
+            final String localDBLocationSetting = pwmApplication.getConfig().readAppProperty( AppProperty.LOCALDB_LOCATION );
+            databaseDirectory = FileSystemUtility.figureFilepath( localDBLocationSetting, pwmApplication.getPwmEnvironment().getApplicationPath() );
+        }
+        catch ( final Exception e )
+        {
+            pwmApplication.setLastLocalDBFailure( new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "error locating configured LocalDB directory: " + e.getMessage() ) );
+            LOGGER.warn( pwmApplication.getSessionLabel(), () -> pwmApplication.getLastLocalDBFailure().toDebugStr() );
+            throw new PwmUnrecoverableException( pwmApplication.getLastLocalDBFailure() );
+        }
+
+        LOGGER.debug( pwmApplication.getSessionLabel(), () -> "using localDB path " + databaseDirectory );
+
+        // initialize the localDB
+        try
+        {
+            final boolean readOnly = pwmApplication.getApplicationMode() == PwmApplicationMode.READ_ONLY;
+            return LocalDBFactory.getInstance( databaseDirectory, readOnly, pwmEnvironment, pwmApplication.getConfig() );
+        }
+        catch ( final Exception e )
+        {
+            pwmApplication.setLastLocalDBFailure( new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "unable to initialize LocalDB: " + e.getMessage() ) );
+            LOGGER.warn( pwmApplication.getSessionLabel(), () -> pwmApplication.getLastLocalDBFailure().toDebugStr() );
+            throw new PwmUnrecoverableException( pwmApplication.getLastLocalDBFailure() );
+        }
+    }
+
+    static void initializeLogging( final PwmApplication pwmApplication )
+    {
+        final PwmEnvironment pwmEnvironment = pwmApplication.getPwmEnvironment();
+
+        if ( !pwmEnvironment.isInternalRuntimeInstance() && !pwmEnvironment.getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance ) )
+        {
+            final String log4jFileName = pwmEnvironment.getConfig().readSettingAsString( PwmSetting.EVENTS_JAVA_LOG4JCONFIG_FILE );
+            final File log4jFile = FileSystemUtility.figureFilepath( log4jFileName, pwmEnvironment.getApplicationPath() );
+            final String consoleLevel;
+            final String fileLevel;
+
+            switch ( pwmApplication.getApplicationMode() )
+            {
+                case ERROR:
+                case NEW:
+                    consoleLevel = PwmLogLevel.TRACE.toString();
+                    fileLevel = PwmLogLevel.TRACE.toString();
+                    break;
+
+                default:
+                    consoleLevel = pwmEnvironment.getConfig().readSettingAsString( PwmSetting.EVENTS_JAVA_STDOUT_LEVEL );
+                    fileLevel = pwmEnvironment.getConfig().readSettingAsString( PwmSetting.EVENTS_FILE_LEVEL );
+                    break;
+            }
+
+            PwmLogManager.initializeLogger(
+                    pwmApplication,
+                    pwmApplication.getConfig(),
+                    log4jFile,
+                    consoleLevel,
+                    pwmEnvironment.getApplicationPath(),
+                    fileLevel );
+
+            switch ( pwmApplication.getApplicationMode() )
+            {
+                case RUNNING:
+                    break;
+
+                case ERROR:
+                    LOGGER.fatal( pwmApplication.getSessionLabel(), () -> "starting up in ERROR mode! Check log or health check information for cause" );
+                    break;
+
+                default:
+                    LOGGER.trace( pwmApplication.getSessionLabel(), () -> "setting log level to TRACE because application mode is " + pwmApplication.getApplicationMode() );
+                    break;
+            }
+        }
+    }
+
+    static String fetchInstanceID(
+            final PwmApplication pwmApplication,
+            final LocalDB localDB
+    )
+    {
+        {
+            final String newInstanceID = pwmApplication.getPwmEnvironment().getParameters().get( PwmEnvironment.ApplicationParameter.InstanceID );
+
+            if ( !StringUtil.isTrimEmpty( newInstanceID ) )
+            {
+                return newInstanceID;
+            }
+        }
+
+        if ( pwmApplication.getLocalDB() == null || pwmApplication.getApplicationMode() != PwmApplicationMode.RUNNING )
+        {
+            return DEFAULT_INSTANCE_ID;
+        }
+
+        {
+            final Optional<String> optionalStoredInstanceID = pwmApplication.readAppAttribute( AppAttribute.INSTANCE_ID, String.class );
+            if ( optionalStoredInstanceID.isPresent() )
+            {
+                final String instanceID = optionalStoredInstanceID.get();
+                if ( !StringUtil.isTrimEmpty( instanceID ) )
+                {
+                    LOGGER.trace( pwmApplication.getSessionLabel(), () -> "retrieved instanceID " + instanceID + "" + " from localDB" );
+                    return instanceID;
+                }
+            }
+        }
+
+        final PwmRandom pwmRandom = PwmRandom.getInstance();
+        final String newInstanceID = Long.toHexString( pwmRandom.nextLong() ).toUpperCase();
+        LOGGER.debug( pwmApplication.getSessionLabel(), () -> "generated new random instanceID " + newInstanceID );
+
+        if ( localDB != null )
+        {
+            pwmApplication.writeAppAttribute( AppAttribute.INSTANCE_ID, newInstanceID );
+        }
+
+        return newInstanceID;
+    }
+
+    static void outputKeystore( final PwmApplication pwmApplication )
+    {
+        try
+        {
+
+            final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = pwmApplication.getPwmEnvironment().getParameters();
+            final String keystoreFileString = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStoreFile );
+            if ( StringUtil.isEmpty( keystoreFileString ) )
+            {
+                return;
+            }
+
+            final File keyStoreFile = new File( keystoreFileString );
+            final String password = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStorePassword );
+            final String alias = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStoreAlias );
+            final KeyStore keyStore = HttpsServerCertificateManager.keyStoreForApplication( pwmApplication, new PasswordData( password ), alias );
+            X509Utils.outputKeystore( keyStore, keyStoreFile, password );
+            LOGGER.info( pwmApplication.getSessionLabel(), () -> "exported application https key to keystore file " + keyStoreFile.getAbsolutePath() );
+        }
+        catch ( final Exception e )
+        {
+            LOGGER.debug( pwmApplication.getSessionLabel(), () -> "error while generating keystore output: " + e.getMessage() );
+        }
+    }
+
+    static void outputTomcatConf( final PwmApplication pwmApplication )
+    {
+        try
+        {
+            final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = pwmApplication.getPwmEnvironment().getParameters();
+            final String tomcatOutputFileStr = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoWriteTomcatConfOutputFile );
+            if ( tomcatOutputFileStr != null && !tomcatOutputFileStr.isEmpty() )
+            {
+                LOGGER.trace( pwmApplication.getSessionLabel(),
+                        () -> "attempting to output tomcat configuration file as configured by environment parameters to " + tomcatOutputFileStr );
+                final File tomcatOutputFile = new File( tomcatOutputFileStr );
+                final File tomcatSourceFile;
+                {
+                    final String tomcatSourceFileStr = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoWriteTomcatConfSourceFile );
+                    if ( tomcatSourceFileStr != null && !tomcatSourceFileStr.isEmpty() )
+                    {
+                        tomcatSourceFile = new File( tomcatSourceFileStr );
+                        if ( !tomcatSourceFile.exists() )
+                        {
+                            LOGGER.error( pwmApplication.getSessionLabel(),
+                                    () -> "can not output tomcat configuration file, source file does not exist: " + tomcatSourceFile.getAbsolutePath() );
+                            return;
+                        }
+                    }
+                    else
+                    {
+                        LOGGER.error( pwmApplication.getSessionLabel(),
+                                () -> "can not output tomcat configuration file, source file parameter '"
+                                        + PwmEnvironment.ApplicationParameter.AutoWriteTomcatConfSourceFile + "' is not specified." );
+                        return;
+                    }
+                }
+
+                try ( ByteArrayOutputStream outputContents = new ByteArrayOutputStream() )
+                {
+                    try ( InputStream fileInputStream = Files.newInputStream( tomcatOutputFile.toPath() ) )
+                    {
+                        ExportHttpsTomcatConfigCommand.TomcatConfigWriter.writeOutputFile(
+                                pwmApplication.getConfig(),
+                                fileInputStream,
+                                outputContents
+                        );
+                    }
+
+                    if ( tomcatOutputFile.exists() )
+                    {
+                        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "deleting existing tomcat configuration file " + tomcatOutputFile.getAbsolutePath() );
+                        if ( tomcatOutputFile.delete() )
+                        {
+                            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "deleted existing tomcat configuration file: " + tomcatOutputFile.getAbsolutePath() );
+                        }
+                    }
+
+                    try ( OutputStream fileOutputStream = Files.newOutputStream( tomcatOutputFile.toPath() ) )
+                    {
+                        fileOutputStream.write( outputContents.toByteArray() );
+                    }
+                }
+
+                LOGGER.info( pwmApplication.getSessionLabel(), () -> "successfully wrote tomcat configuration to file " + tomcatOutputFile.getAbsolutePath() );
+            }
+        }
+        catch ( final Exception e )
+        {
+            LOGGER.debug( pwmApplication.getSessionLabel(), () -> "error while generating tomcat conf output: " + e.getMessage() );
+        }
+
+    }
+
+    static void outputConfigurationToLog( final PwmApplication pwmApplication, final DomainID domainID )
+    {
+        final Instant startTime = Instant.now();
+
+        final Function<Map.Entry<String, String>, String> valueFormatter = entry ->
+        {
+            final String spacedValue = entry.getValue().replace( "\n", "\n   " );
+            return " " + entry.getKey() + "\n   " + spacedValue + "\n";
+        };
+
+        final StoredConfiguration storedConfiguration = pwmApplication.getConfig().getStoredConfiguration();
+        final List<StoredConfigKey> keys = CollectionUtil.iteratorToStream( storedConfiguration.keys() )
+                .filter( key -> key.getDomainID().equals( domainID ) )
+                .collect( Collectors.toList() );
+        final Map<String, String> debugStrings = StoredConfigurationUtil.makeDebugMap(
+                storedConfiguration,
+                keys,
+                PwmConstants.DEFAULT_LOCALE );
+
+        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--begin current configuration output for domainID '" + domainID + "'--" );
+        debugStrings.entrySet().stream()
+                .map( valueFormatter )
+                .map( s -> ( Supplier<CharSequence> ) () -> s )
+                .forEach( s -> LOGGER.trace( pwmApplication.getSessionLabel(), s ) );
+
+        final long itemCount = debugStrings.size();
+        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--end current configuration output of " + itemCount + " items --",
+                () -> TimeDuration.fromCurrent( startTime ) );
+    }
+
+    static void outputNonDefaultPropertiesToLog( final PwmApplication pwmApplication )
+    {
+        final Instant startTime = Instant.now();
+
+        final Map<AppProperty, String> nonDefaultProperties = pwmApplication.getConfig().readAllNonDefaultAppProperties();
+        if ( !CollectionUtil.isEmpty( nonDefaultProperties ) )
+        {
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--begin non-default app properties output--" );
+            nonDefaultProperties.entrySet().stream()
+                    .map( entry -> "AppProperty: " + entry.getKey().getKey() + " -> " + entry.getValue() )
+                    .map( s -> ( Supplier<CharSequence> ) () -> s )
+                    .forEach( s -> LOGGER.trace( pwmApplication.getSessionLabel(), s ) );
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--end non-default app properties output--", () -> TimeDuration.fromCurrent( startTime ) );
+        }
+        else
+        {
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "no non-default app properties in configuration" );
+        }
+    }
+
+    static void outputApplicationInfoToLog( final PwmApplication pwmApplication )
+    {
+        final Instant startTime = Instant.now();
+
+        final Map<PwmAboutProperty, String> aboutProperties = PwmAboutProperty.makeInfoBean( pwmApplication );
+        if ( !CollectionUtil.isEmpty( aboutProperties ) )
+        {
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--begin application info--" );
+            aboutProperties.entrySet().stream()
+                    .map( entry -> "AppProperty: " + entry.getKey().getLabel() + " -> " + entry.getValue() )
+                    .map( s -> ( Supplier<CharSequence> ) () -> s )
+                    .forEach( s -> LOGGER.trace( pwmApplication.getSessionLabel(), s ) );
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--end application info--", () -> TimeDuration.fromCurrent( startTime ) );
+        }
+        else
+        {
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "no non-default app properties in configuration" );
+        }
+    }
+}

+ 18 - 5
server/src/main/java/password/pwm/PwmDomain.java

@@ -72,15 +72,18 @@ public class PwmDomain
 
     private final PwmApplication pwmApplication;
     private final DomainID domainID;
-
     private final PwmServiceManager pwmServiceManager;
+    private final SessionLabel sessionLabel;
 
     public PwmDomain( final PwmApplication pwmApplication, final DomainID domainID )
     {
         this.pwmApplication = Objects.requireNonNull( pwmApplication );
         this.domainID = Objects.requireNonNull( domainID );
 
-        final SessionLabel sessionLabel = SessionLabel.builder().domain( domainID.stringValue() ).build();
+        this.sessionLabel = pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
+                ? SessionLabel.RUNTIME_LABEL.toBuilder().domain( domainID.stringValue() ).build()
+                : SessionLabel.SYSTEM_LABEL.toBuilder().domain( domainID.stringValue() ).build();
+
         this.pwmServiceManager = new PwmServiceManager( sessionLabel, pwmApplication, domainID, PwmServiceEnum.forScope( PwmSettingScope.DOMAIN ) );
     }
 
@@ -89,7 +92,7 @@ public class PwmDomain
 
     {
         final Instant startTime = Instant.now();
-        LOGGER.trace( () -> "initializing domain " + domainID.stringValue() );
+        LOGGER.trace( sessionLabel, () -> "initializing domain " + domainID.stringValue() );
         pwmServiceManager.initAllServices();
 
         {
@@ -97,7 +100,12 @@ public class PwmDomain
             pwmApplication.getPwmScheduler().scheduleDailyZuluZeroStartJob( new DailySummaryJob( this ), executorService, TimeDuration.ZERO );
         }
 
-        LOGGER.trace( () -> "completed initializing domain " + domainID.stringValue(), () -> TimeDuration.fromCurrent( startTime ) );
+        if ( Boolean.parseBoolean( getConfig().readAppProperty( AppProperty.LOGGING_OUTPUT_CONFIGURATION ) ) )
+        {
+            PwmApplicationUtil.outputConfigurationToLog( pwmApplication, domainID );
+        }
+
+        LOGGER.trace( sessionLabel, () -> "completed initializing domain " + domainID.stringValue(), () -> TimeDuration.fromCurrent( startTime ) );
     }
 
     public DomainConfig getConfig( )
@@ -236,9 +244,14 @@ public class PwmDomain
         return ( UserHistoryService ) pwmServiceManager.getService( PwmServiceEnum.UserHistoryService );
     }
 
+    public SessionLabel getSessionLabel()
+    {
+        return sessionLabel;
+    }
+
     public void shutdown()
     {
-        LOGGER.trace( () -> "beginning shutdown domain " + domainID.stringValue() );
+        LOGGER.trace( sessionLabel, () -> "beginning shutdown domain " + domainID.stringValue() );
         pwmServiceManager.shutdownAllServices();
     }
 

+ 230 - 0
server/src/main/java/password/pwm/PwmDomainUtil.java

@@ -0,0 +1,230 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm;
+
+import password.pwm.bean.DomainID;
+import password.pwm.config.AppConfig;
+import password.pwm.config.DomainConfig;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.PwmScheduler;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
+
+class PwmDomainUtil
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmDomainUtil.class );
+
+    static Map<DomainID, PwmDomain> createDomainInstances( final PwmApplication pwmApplication )
+            throws PwmUnrecoverableException
+    {
+        final Map<DomainID, PwmDomain> domainMap = new TreeMap<>();
+
+        if ( pwmApplication.getPwmEnvironment().isInternalRuntimeInstance() )
+        {
+            final DomainID domainID = pwmApplication.getPwmEnvironment().getConfig().getAdminDomainID();
+            domainMap.put( domainID, new PwmDomain( pwmApplication, domainID ) );
+        }
+        else
+        {
+            for ( final String domainIdString : pwmApplication.getPwmEnvironment().getConfig().getDomainIDs() )
+            {
+                final DomainID domainID = DomainID.create( domainIdString );
+                final PwmDomain newDomain = new PwmDomain( pwmApplication, domainID );
+                domainMap.put( domainID, newDomain );
+            }
+        }
+
+        return Collections.unmodifiableMap( domainMap );
+    }
+
+    static void initDomains(
+            final PwmApplication pwmApplication,
+            final Collection<PwmDomain> domains
+
+    )
+            throws PwmUnrecoverableException
+    {
+        final Instant domainInitStartTime = Instant.now();
+        LOGGER.trace( () -> "beginning domain initializations" );
+
+        final List<Callable<Optional<PwmUnrecoverableException>>> callables = domains.stream()
+                .map( DomainInitializingCallable::new )
+                .collect( Collectors.toUnmodifiableList() );
+
+        final  List<Optional<PwmUnrecoverableException>> domainStartException = pwmApplication.getPwmScheduler()
+                .executeImmediateThreadPerJobAndAwaitCompletion( callables, "domain initializer" );
+
+        final Optional<PwmUnrecoverableException> domainStartupException = domainStartException.stream()
+                .filter( Optional::isPresent )
+                .map( Optional::get )
+                .findAny();
+
+        if ( domainStartupException.isPresent() )
+        {
+            throw domainStartupException.get();
+        }
+
+        LOGGER.trace( () -> "completed domain initialization for domains", () -> TimeDuration.fromCurrent( domainInitStartTime ) );
+    }
+
+    private static class DomainInitializingCallable implements Callable<Optional<PwmUnrecoverableException>>
+    {
+        private final PwmDomain pwmDomain;
+
+        DomainInitializingCallable( final PwmDomain pwmDomain )
+        {
+            this.pwmDomain = pwmDomain;
+        }
+
+        @Override
+        public Optional<PwmUnrecoverableException> call()
+                throws Exception
+        {
+            try
+            {
+                pwmDomain.initialize();
+                return Optional.empty();
+            }
+            catch ( final PwmUnrecoverableException e )
+            {
+                return Optional.of( e );
+            }
+        }
+    }
+
+    static Map<DomainID, PwmDomain> reInitDomains(
+            final PwmApplication pwmApplication,
+            final AppConfig newConfig,
+            final AppConfig oldConfig
+    )
+            throws PwmUnrecoverableException
+    {
+        final Map<DomainModifyCategory, Set<DomainID>> categorizedDomains = categorizeDomainModifications( newConfig, oldConfig );
+
+        final Set<PwmDomain> deletedDomains = pwmApplication.domains().entrySet().stream()
+                .filter( e -> categorizedDomains.get( DomainModifyCategory.obsolete ).contains( e.getKey() ) )
+                .map( Map.Entry::getValue ).collect( Collectors.toSet() );
+
+        final Set<PwmDomain> newDomains = pwmApplication.domains().entrySet().stream()
+                .filter( e -> categorizedDomains.get( DomainModifyCategory.created ).contains( e.getKey() ) )
+                .map( Map.Entry::getValue ).collect( Collectors.toSet() );
+
+        final Map<DomainID, PwmDomain> returnDomainMap = new TreeMap<>( pwmApplication.domains().entrySet().stream()
+                .filter( e -> categorizedDomains.get( DomainModifyCategory.unchanged ).contains( e.getKey() ) )
+                .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) );
+
+        for ( final DomainID modifiedDomainID : categorizedDomains.get( DomainModifyCategory.modified ) )
+        {
+            deletedDomains.add( pwmApplication.domains().get( modifiedDomainID ) );
+            final PwmDomain newDomain = new PwmDomain( pwmApplication, modifiedDomainID );
+            newDomains.add( newDomain );
+            returnDomainMap.put( modifiedDomainID, newDomain );
+        }
+
+
+        initDomains( pwmApplication, newDomains );
+
+        processDeletedDomains( pwmApplication, deletedDomains );
+
+        return Collections.unmodifiableMap( returnDomainMap );
+    }
+
+    private static void processDeletedDomains(
+            final PwmApplication pwmApplication,
+            final Set<PwmDomain> deletedDomains
+    )
+    {
+        // 1 minute later ( to avoid interrupting any in-progress requests, shutdown any obsoleted domains
+        if ( !deletedDomains.isEmpty() )
+        {
+            pwmApplication.getPwmScheduler().scheduleJob( () ->
+                    {
+                        LOGGER.debug( () -> "shutting down obsoleted domain services" );
+                        deletedDomains.forEach( PwmDomain::shutdown );
+                    },
+                    PwmScheduler.makeSingleThreadExecutorService( pwmApplication, pwmApplication.getClass() ),
+                    TimeDuration.MINUTE );
+        }
+
+    }
+
+    enum DomainModifyCategory
+    {
+        obsolete,
+        unchanged,
+        modified,
+        created,
+    }
+
+    private static Map<DomainModifyCategory, Set<DomainID>> categorizeDomainModifications(
+            final AppConfig newConfig,
+            final AppConfig oldConfig
+    )
+    {
+        final Map<DomainModifyCategory, Set<DomainID>> types = new EnumMap<>( DomainModifyCategory.class );
+
+        {
+            final Set<DomainID> obsoleteDomains = new HashSet<>( oldConfig.getDomainConfigs().keySet() );
+            obsoleteDomains.removeAll( newConfig.getDomainConfigs().keySet() );
+            types.put( DomainModifyCategory.obsolete, Collections.unmodifiableSet( obsoleteDomains ) );
+        }
+
+        {
+            final Set<DomainID> createdDomains = new HashSet<>( newConfig.getDomainConfigs().keySet() );
+            createdDomains.removeAll( oldConfig.getDomainConfigs().keySet() );
+            types.put( DomainModifyCategory.created, Collections.unmodifiableSet( createdDomains ) );
+        }
+
+        final Set<DomainID> unchangedDomains = new HashSet<>();
+        final Set<DomainID> modifiedDomains = new HashSet<>();
+        for ( final DomainID domainID : newConfig.getDomainConfigs().keySet() )
+        {
+            final DomainConfig newDomainConfig = newConfig.getDomainConfigs().get( domainID );
+            final String oldValueHash = oldConfig.getDomainConfigs().get( newDomainConfig.getDomainID() ).getValueHash();
+
+            if ( Objects.equals( oldValueHash, newDomainConfig.getValueHash() ) )
+            {
+                unchangedDomains.add( domainID );
+            }
+            else
+            {
+                modifiedDomains.add( domainID );
+            }
+        }
+        types.put( DomainModifyCategory.unchanged, Collections.unmodifiableSet( unchangedDomains ) );
+        types.put( DomainModifyCategory.modified, Collections.unmodifiableSet( modifiedDomains ) );
+        return Collections.unmodifiableMap( types );
+    }
+}

+ 8 - 6
server/src/main/java/password/pwm/bean/SessionLabel.java

@@ -31,11 +31,13 @@ import java.io.Serializable;
 @Builder( toBuilder = true )
 public class SessionLabel implements Serializable
 {
-    public static final String SESSION_LABEL_SESSION_ID = "#";
-    public static final SessionLabel SYSTEM_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( PwmConstants.PWM_APP_NAME ).build();
-    public static final SessionLabel TEST_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "test" ).build();
-    public static final SessionLabel CLI_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "cli" ).build();
-    public static final SessionLabel CONTEXT_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "context" ).build();
+    private static final String SYSTEM_LABEL_SESSION_ID = "#";
+    private static final String RUNTIME_LABEL_SESSION_ID = "#";
+    public static final SessionLabel SYSTEM_LABEL = SessionLabel.builder().sessionID( SYSTEM_LABEL_SESSION_ID ).username( PwmConstants.PWM_APP_NAME ).build();
+    public static final SessionLabel RUNTIME_LABEL = SessionLabel.builder().sessionID( RUNTIME_LABEL_SESSION_ID ).username( "internal" ).build();
+    public static final SessionLabel TEST_SESSION_LABEL = SessionLabel.builder().sessionID( SYSTEM_LABEL_SESSION_ID ).username( "test" ).build();
+    public static final SessionLabel CLI_SESSION_LABEL = SessionLabel.builder().sessionID( SYSTEM_LABEL_SESSION_ID ).username( "cli" ).build();
+    public static final SessionLabel CONTEXT_SESSION_LABEL = SessionLabel.builder().sessionID( SYSTEM_LABEL_SESSION_ID ).username( "context" ).build();
 
     private final String sessionID;
     private final String requestID;
@@ -49,7 +51,7 @@ public class SessionLabel implements Serializable
     public static SessionLabel forPwmService( final PwmService pwmService, final DomainID domainID )
     {
         return SessionLabel.builder()
-                .sessionID( SESSION_LABEL_SESSION_ID )
+                .sessionID( SYSTEM_LABEL_SESSION_ID )
                 .username( pwmService.getClass().getSimpleName() )
                 .domain( domainID.stringValue() )
                 .build();

+ 6 - 0
server/src/main/java/password/pwm/config/AppConfig.java

@@ -433,4 +433,10 @@ public class AppConfig implements SettingReader
         }
         return Collections.unmodifiableMap( localeFlagMap );
     }
+
+    @Override
+    public String getValueHash()
+    {
+        return settingReader.getValueHash();
+    }
 }

+ 6 - 21
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -21,7 +21,6 @@
 package password.pwm.config;
 
 import password.pwm.AppProperty;
-import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
@@ -44,7 +43,6 @@ import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.FileValue;
-import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.value.data.NamedSecretData;
@@ -57,10 +55,8 @@ import password.pwm.util.PasswordData;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
 
-import java.security.MessageDigest;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -115,7 +111,7 @@ public class DomainConfig implements SettingReader
                 ) ) );
 
         this.ldapProfiles = makeLdapProfileMap( this );
-        this.domainSecurityKey = makeDomainSecurityKey( appConfig, domainID );
+        this.domainSecurityKey = makeDomainSecurityKey( appConfig, settingReader.getValueHash() );
     }
 
     public AppConfig getAppConfig()
@@ -415,13 +411,12 @@ public class DomainConfig implements SettingReader
 
     private static PwmSecurityKey makeDomainSecurityKey(
             final AppConfig appConfig,
-            final DomainID domainID
+            final String valueHash
     )
     {
         try
         {
-            final String hashedData = valueHash( appConfig.getStoredConfiguration(), domainID );
-            final PwmSecurityKey domainKey = new PwmSecurityKey( hashedData );
+            final PwmSecurityKey domainKey = new PwmSecurityKey( valueHash );
             return appConfig.getSecurityKey().add( domainKey );
         }
         catch ( final PwmUnrecoverableException e )
@@ -430,19 +425,9 @@ public class DomainConfig implements SettingReader
         }
     }
 
-
-    private static String valueHash( final StoredConfiguration storedConfiguration, final DomainID domainID )
+    @Override
+    public String getValueHash()
     {
-        final MessageDigest messageDigest = PwmHashAlgorithm.SHA512.newMessageDigest();
-        messageDigest.update( domainID.stringValue().getBytes( PwmConstants.DEFAULT_CHARSET ) );
-
-        CollectionUtil.iteratorToStream( storedConfiguration.keys() )
-                .filter( key -> Objects.equals( key.getDomainID(), domainID ) )
-                .map( storedConfiguration::readStoredValue )
-                .flatMap( Optional::stream )
-                .map( StoredValue::valueHash )
-                .forEach( s -> messageDigest.update( s.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
-
-        return JavaHelper.binaryArrayToHex( messageDigest.digest() );
+        return settingReader.getValueHash();
     }
 }

+ 2 - 0
server/src/main/java/password/pwm/config/SettingReader.java

@@ -62,4 +62,6 @@ public interface SettingReader
     PasswordData readSettingAsPassword( PwmSetting setting );
 
     Optional<Map<Locale, String>> readLocalizedBundle( PwmLocaleBundle className, String keyName );
+
+    String getValueHash();
 }

+ 26 - 0
server/src/main/java/password/pwm/config/StoredSettingReader.java

@@ -20,6 +20,7 @@
 
 package password.pwm.config;
 
+import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
@@ -49,8 +50,10 @@ import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmHashAlgorithm;
 
 import java.lang.reflect.InvocationTargetException;
+import java.security.MessageDigest;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collections;
@@ -74,12 +77,14 @@ public class StoredSettingReader implements SettingReader
     private final DomainID domainID;
 
     private final Map<ProfileDefinition, Map> profileCache;
+    private final String valueHash;
 
     public StoredSettingReader( final StoredConfiguration storedConfiguration, final String profileID, final DomainID domainID )
     {
         this.storedConfiguration = Objects.requireNonNull( storedConfiguration );
         this.profileID = profileID;
         this.domainID = Objects.requireNonNull( domainID );
+        this.valueHash = valueHash( storedConfiguration, domainID );
         this.profileCache = profileID == null
                 ? ProfileReader.makeCacheMap( storedConfiguration, domainID )
                 : Collections.emptyMap();
@@ -351,4 +356,25 @@ public class StoredSettingReader implements SettingReader
                 Map.Entry::getValue
         ) ) );
     }
+
+    @Override
+    public String getValueHash()
+    {
+        return valueHash;
+    }
+
+    private static String valueHash( final StoredConfiguration storedConfiguration, final DomainID domainID )
+    {
+        final MessageDigest messageDigest = PwmHashAlgorithm.SHA512.newMessageDigest();
+        messageDigest.update( domainID.stringValue().getBytes( PwmConstants.DEFAULT_CHARSET ) );
+
+        CollectionUtil.iteratorToStream( storedConfiguration.keys() )
+                .filter( key -> Objects.equals( key.getDomainID(), domainID ) )
+                .map( storedConfiguration::readStoredValue )
+                .flatMap( Optional::stream )
+                .map( StoredValue::valueHash )
+                .forEach( s -> messageDigest.update( s.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
+
+        return JavaHelper.binaryArrayToHex( messageDigest.digest() );
+    }
 }

+ 4 - 4
server/src/main/java/password/pwm/config/stored/ConfigurationReader.java → server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java

@@ -57,13 +57,13 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
- * Read the PWM configuration.
+ * Read and write the PWM configuration XML file from the filesystem.
  *
  * @author Jason D. Rivard
  */
-public class ConfigurationReader
+public class ConfigurationFileManager
 {
-    private static final PwmLogger LOGGER = PwmLogger.getLogger( ConfigurationReader.class.getName() );
+    private static final PwmLogger LOGGER = PwmLogger.getLogger( ConfigurationFileManager.class.getName() );
 
     private final File configFile;
     private final String configFileChecksum;
@@ -75,7 +75,7 @@ public class ConfigurationReader
 
     private volatile boolean saveInProgress;
 
-    public ConfigurationReader( final File configFile ) throws PwmUnrecoverableException
+    public ConfigurationFileManager( final File configFile ) throws PwmUnrecoverableException
     {
         this.configFile = configFile;
 

+ 1 - 1
server/src/main/java/password/pwm/health/HealthService.java

@@ -186,7 +186,7 @@ public class HealthService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         if ( executorService != null )
         {

+ 6 - 6
server/src/main/java/password/pwm/http/ContextManager.java

@@ -31,8 +31,8 @@ import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.ConfigurationProperty;
-import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationModifier;
@@ -93,7 +93,7 @@ public class ContextManager implements Serializable
     private transient ScheduledExecutorService taskMaster;
 
     private transient volatile PwmApplication pwmApplication;
-    private transient ConfigurationReader configReader;
+    private transient ConfigurationFileManager configReader;
     private ErrorInformation startupErrorInformation;
 
     private final AtomicInteger restartCount = new AtomicInteger( 0 );
@@ -246,7 +246,7 @@ public class ContextManager implements Serializable
         {
             configurationFile = locateConfigurationFile( applicationPath, PwmConstants.DEFAULT_CONFIG_FILE_FILENAME );
 
-            configReader = new ConfigurationReader( configurationFile );
+            configReader = new ConfigurationFileManager( configurationFile );
             appConfig = configReader.getConfiguration();
 
             mode = startupErrorInformation == null ? configReader.getConfigMode() : PwmApplicationMode.ERROR;
@@ -338,7 +338,7 @@ public class ContextManager implements Serializable
     }
 
     private void checkConfigForAutoImportLdapCerts(
-            final ConfigurationReader configReader
+            final ConfigurationFileManager configReader
     )
     {
         if ( configReader == null || configReader.getStoredConfiguration() == null )
@@ -418,7 +418,7 @@ public class ContextManager implements Serializable
         taskMaster.schedule( new RestartFlagWatcher(), 0, TimeUnit.MILLISECONDS );
     }
 
-    public ConfigurationReader getConfigReader( )
+    public ConfigurationFileManager getConfigReader( )
     {
         return configReader;
     }
@@ -533,7 +533,7 @@ public class ContextManager implements Serializable
 
                 try
                 {
-                        reInitialize();
+                    reInitialize();
                 }
                 catch ( final Exception e )
                 {

+ 2 - 2
server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java

@@ -23,7 +23,7 @@ package password.pwm.http.filter;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.PwmApplicationMode;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.ErrorInformation;
@@ -99,7 +99,7 @@ public class ConfigAccessFilter extends AbstractPwmFilter
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
         final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration();
 
         checkPreconditions( pwmRequest, storedConfig );

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -44,6 +44,7 @@ import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.intruder.IntruderServiceClient;
+import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.CaptchaUtility;
@@ -326,7 +327,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
 
         final MacroRequest macroRequest = MacroRequest.forUser( pwmDomain.getPwmApplication(), sessionLabel, userInfo, null );
 
-        pwmDomain.getPwmApplication().sendSmsUsingQueue( toNumber, smsMessage, sessionLabel, macroRequest );
+        SmsQueueService.sendSmsUsingQueue( pwmDomain.getPwmApplication(), toNumber, smsMessage, sessionLabel, macroRequest );
         return null;
     }
 

+ 3 - 1
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java

@@ -54,6 +54,7 @@ import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditServiceClient;
+import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.form.FormUtility;
@@ -285,7 +286,8 @@ class ActivateUserUtils
             return false;
         }
 
-        pwmRequest.getPwmApplication().sendSmsUsingQueue(
+        SmsQueueService.sendSmsUsingQueue(
+                pwmRequest.getPwmApplication(),
                 toSmsNumber,
                 message,
                 pwmRequest.getLabel(),

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java

@@ -33,7 +33,7 @@ import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.http.servlet.configeditor.function.UserMatchViewerFunction;
 import password.pwm.config.stored.ConfigurationProperty;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
@@ -106,7 +106,7 @@ public class ConfigGuideUtils
     )
             throws PwmOperationalException, PwmUnrecoverableException
     {
-        final ConfigurationReader configReader = contextManager.getConfigReader();
+        final ConfigurationFileManager configReader = contextManager.getConfigReader();
         final PwmApplication pwmApplication = contextManager.getPwmApplication();
 
         try

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java

@@ -30,7 +30,7 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.stored.ConfigurationProperty;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.ErrorInformation;
@@ -138,7 +138,7 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
             throws PwmUnrecoverableException, IOException, ServletException
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
         final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration();
 
         final String password = pwmRequest.readParameterAsString( "password" );
@@ -321,7 +321,7 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
             return;
         }
 
-        final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
         final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration();
 
         try

+ 5 - 5
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -28,7 +28,7 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.config.AppConfig;
 import password.pwm.config.stored.ConfigurationProperty;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
@@ -193,12 +193,12 @@ public class ConfigManagerServlet extends AbstractPwmServlet
     void initRequestAttributes( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        final ConfigurationReader configurationReader = pwmRequest.getContextManager().getConfigReader();
+        final ConfigurationFileManager configurationFileManager = pwmRequest.getContextManager().getConfigReader();
         pwmRequest.setAttribute( PwmRequestAttribute.PageTitle, LocaleHelper.getLocalizedMessage( Config.Title_ConfigManager, pwmRequest ) );
         pwmRequest.setAttribute( PwmRequestAttribute.ApplicationPath, pwmRequest.getPwmApplication().getPwmEnvironment().getApplicationPath().getAbsolutePath() );
-        pwmRequest.setAttribute( PwmRequestAttribute.ConfigFilename, configurationReader.getConfigFile().getAbsolutePath() );
+        pwmRequest.setAttribute( PwmRequestAttribute.ConfigFilename, configurationFileManager.getConfigFile().getAbsolutePath() );
         {
-            final Instant lastModifyTime = configurationReader.getStoredConfiguration().modifyTime();
+            final Instant lastModifyTime = configurationFileManager.getStoredConfiguration().modifyTime();
             final String output = lastModifyTime == null
                     ? LocaleHelper.getLocalizedMessage( Display.Value_NotApplicable, pwmRequest )
                     : StringUtil.toIsoDate( lastModifyTime );
@@ -208,7 +208,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet
         pwmRequest.setAttribute(
                 PwmRequestAttribute.ConfigHasPassword,
                 LocaleHelper.booleanString(
-                        StoredConfigurationUtil.hasPassword( configurationReader.getStoredConfiguration() ),
+                        StoredConfigurationUtil.hasPassword( configurationFileManager.getStoredConfiguration() ),
                         pwmRequest.getLocale(),
                         pwmRequest.getDomainConfig()
                 )

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchService.java

@@ -59,7 +59,7 @@ public class PeopleSearchService extends AbstractPwmService implements PwmServic
     }
 
     @Override
-    public void close()
+    public void shutdownImpl()
     {
         threadPoolExecutor.shutdown();
     }

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java

@@ -176,7 +176,7 @@ public class ResourceServletService extends AbstractPwmService implements PwmSer
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
     }

+ 1 - 1
server/src/main/java/password/pwm/http/state/SessionStateService.java

@@ -116,7 +116,7 @@ public class SessionStateService extends AbstractPwmService implements PwmServic
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
     }
 

+ 1 - 1
server/src/main/java/password/pwm/ldap/LdapConnectionService.java

@@ -171,7 +171,7 @@ public class LdapConnectionService extends AbstractPwmService implements PwmServ
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
         logDebugInfo();

+ 1 - 1
server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java

@@ -126,7 +126,7 @@ public class UserSearchEngine extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         if ( executor != null )
         {

+ 15 - 5
server/src/main/java/password/pwm/svc/AbstractPwmService.java

@@ -33,19 +33,18 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
 
 public abstract class AbstractPwmService implements PwmService
 {
     private PwmApplication pwmApplication;
-    private final AtomicReference<PwmService.STATUS> status = new AtomicReference<>( PwmService.STATUS.CLOSED );
+    private volatile PwmService.STATUS status = PwmService.STATUS.CLOSED;
     private ErrorInformation startupError;
     private DomainID domainID;
     private SessionLabel sessionLabel;
 
     public final PwmService.STATUS status()
     {
-        return status.get();
+        return status;
     }
 
     public final void init( final PwmApplication pwmApplication, final DomainID domainID )
@@ -53,7 +52,9 @@ public abstract class AbstractPwmService implements PwmService
     {
         this.pwmApplication = Objects.requireNonNull( pwmApplication );
         this.domainID = Objects.requireNonNull( domainID );
-        this.sessionLabel = SessionLabel.forPwmService( this, domainID );
+        this.sessionLabel = domainID.isSystem()
+                ? pwmApplication.getSessionLabel()
+                : pwmApplication.domains().get( domainID ).getSessionLabel();
 
         if ( pwmApplication.checkConditions( openConditions() ) )
         {
@@ -71,9 +72,18 @@ public abstract class AbstractPwmService implements PwmService
 
     protected void setStatus( final PwmService.STATUS status )
     {
-        this.status.set( status );
+        this.status = Objects.requireNonNull( status );
     }
 
+    @Override
+    public void shutdown()
+    {
+        this.status = STATUS.CLOSED;
+        shutdownImpl();
+    }
+
+    protected abstract void shutdownImpl();
+
     public DomainID getDomainID()
     {
         return domainID;

+ 1 - 1
server/src/main/java/password/pwm/svc/PwmService.java

@@ -53,7 +53,7 @@ public interface PwmService
 
     void init( PwmApplication pwmApplication, DomainID domainID ) throws PwmException;
 
-    void close( );
+    void shutdown( );
 
     List<HealthRecord> healthCheck( );
 

+ 1 - 1
server/src/main/java/password/pwm/svc/PwmServiceManager.java

@@ -205,7 +205,7 @@ public class PwmServiceManager
         try
         {
             final Instant startTime = Instant.now();
-            serviceInstance.close();
+            serviceInstance.shutdown();
             final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
             LOGGER.trace( () -> "successfully closed service " + pwmServiceEnum.serviceName( domainID ) + " (" + timeDuration.asCompactString() + ")" );
         }

+ 1 - 1
server/src/main/java/password/pwm/svc/cache/CacheService.java

@@ -81,7 +81,7 @@ public class CacheService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
     }

+ 1 - 1
server/src/main/java/password/pwm/svc/cr/CrService.java

@@ -105,7 +105,7 @@ public class CrService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         for ( final CrOperator operator : operatorMap.values() )
         {

+ 1 - 1
server/src/main/java/password/pwm/svc/db/DatabaseService.java

@@ -187,7 +187,7 @@ public class DatabaseService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
 

+ 1 - 1
server/src/main/java/password/pwm/svc/email/EmailService.java

@@ -140,7 +140,7 @@ public class EmailService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
         if ( workQueueProcessor != null )

+ 1 - 1
server/src/main/java/password/pwm/svc/event/AuditService.java

@@ -124,7 +124,7 @@ public class AuditService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         if ( syslogManager != null )
         {

+ 27 - 1
server/src/main/java/password/pwm/svc/event/CEFAuditFormatter.java

@@ -27,6 +27,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
+import password.pwm.config.PwmSetting;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
@@ -35,6 +36,7 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 
+import java.net.URI;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -59,6 +61,30 @@ public class CEFAuditFormatter implements AuditFormatter
         CEF_VALUE_ESCAPES = Collections.unmodifiableMap( map );
     }
 
+    private static Optional<String> deriveLocalServerHostname( final AppConfig appConfig )
+    {
+        if ( appConfig != null )
+        {
+            final String siteUrl = appConfig.readSettingAsString( PwmSetting.PWM_SITE_URL );
+            if ( StringUtil.notEmpty( siteUrl ) )
+            {
+                try
+                {
+                    final URI parsedUri = URI.create( siteUrl );
+                    {
+                        final String uriHost = parsedUri.getHost();
+                        return Optional.ofNullable( uriHost );
+                    }
+                }
+                catch ( final IllegalArgumentException e )
+                {
+                    LOGGER.trace( () -> " error parsing siteURL hostname: " + e.getMessage() );
+                }
+            }
+        }
+        return Optional.empty();
+    }
+
     enum CEFAuditField
     {
         cat( AuditField.type ),
@@ -100,7 +126,7 @@ public class CEFAuditFormatter implements AuditFormatter
         final String auditRecordAsJson = JsonFactory.get().serialize( auditRecord );
         final Map<String, Object> auditRecordMap = JsonFactory.get().deserializeMap( auditRecordAsJson, String.class, Object.class );
 
-        final Optional<String> srcHost = PwmApplication.deriveLocalServerHostname( pwmApplication.getConfig() );
+        final Optional<String> srcHost = deriveLocalServerHostname( pwmApplication.getConfig() );
 
         final StringBuilder cefOutput = new StringBuilder(  );
 

+ 1 - 1
server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java

@@ -95,7 +95,7 @@ public class HttpClientService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close()
+    public void shutdownImpl()
     {
         for ( final PwmHttpClient pwmHttpClient : new HashSet<>( issuedClients.keySet() ) )
         {

+ 2 - 2
server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java

@@ -121,7 +121,7 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "unexpected error starting intruder manager: " + e.getMessage() );
             LOGGER.error( errorInformation::toDebugStr );
             setStartupError( errorInformation );
-            close();
+            shutdown();
             return STATUS.CLOSED;
         }
 
@@ -193,7 +193,7 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
 
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
     }

+ 2 - 2
server/src/main/java/password/pwm/svc/intruder/IntruderSystemService.java

@@ -77,7 +77,7 @@ public class IntruderSystemService extends AbstractPwmService implements PwmServ
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "unexpected error starting intruder manager: " + e.getMessage() );
             LOGGER.error( errorInformation::toDebugStr );
             setStartupError( errorInformation );
-            close();
+            shutdown();
             return STATUS.CLOSED;
         }
 
@@ -85,7 +85,7 @@ public class IntruderSystemService extends AbstractPwmService implements PwmServ
     }
 
     @Override
-    public void close()
+    public void shutdownImpl()
     {
         setStatus( STATUS.CLOSED );
     }

+ 1 - 1
server/src/main/java/password/pwm/svc/node/NodeService.java

@@ -114,7 +114,7 @@ public class NodeService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         if ( nodeMachine != null )
         {

+ 1 - 1
server/src/main/java/password/pwm/svc/otp/OtpService.java

@@ -293,7 +293,7 @@ public class OtpService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         for ( final OtpOperator operator : operatorMap.values() )
         {

+ 1 - 1
server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java

@@ -209,7 +209,7 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
         JavaHelper.closeAndWaitExecutor( executorService, TimeDuration.of( 5, TimeDuration.Unit.SECONDS ) );

+ 1 - 1
server/src/main/java/password/pwm/svc/report/ReportRecordLocalDBStorageService.java

@@ -153,7 +153,7 @@ public class ReportRecordLocalDBStorageService extends AbstractPwmService implem
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
     }

+ 2 - 2
server/src/main/java/password/pwm/svc/report/ReportService.java

@@ -130,7 +130,7 @@ public class ReportService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
         cancelFlag.set( true );
@@ -139,7 +139,7 @@ public class ReportService extends AbstractPwmService implements PwmService
 
         if ( userCacheService != null )
         {
-            userCacheService.close();
+            userCacheService.shutdown();
         }
 
         executorService = null;

+ 1 - 1
server/src/main/java/password/pwm/svc/secure/AbstractSecureService.java

@@ -118,7 +118,7 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
             throws PwmException;
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
     }
 

+ 1 - 1
server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java

@@ -82,7 +82,7 @@ public class SessionTrackService extends AbstractPwmService implements PwmServic
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         pwmSessions.clear();
     }

+ 1 - 1
server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java

@@ -91,7 +91,7 @@ public class UrlShortenerService extends AbstractPwmService implements PwmServic
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( PwmService.STATUS.CLOSED );
     }

+ 33 - 1
server/src/main/java/password/pwm/svc/sms/SmsQueueService.java

@@ -57,6 +57,7 @@ import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBStoredQueue;
 import password.pwm.util.localdb.WorkQueueProcessor;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.secure.PwmRandom;
 
 import java.time.Instant;
@@ -75,6 +76,37 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( SmsQueueService.class );
 
+    public static void sendSmsUsingQueue(
+            final PwmApplication pwmApplication,
+            final String to,
+            final String message,
+            final SessionLabel sessionLabel,
+            final MacroRequest macroRequest
+    )
+    {
+        final SmsQueueService smsQueue = pwmApplication.getSmsQueue();
+        if ( smsQueue == null )
+        {
+            LOGGER.error( sessionLabel, () -> "SMS queue is unavailable, unable to send SMS to: " + to );
+            return;
+        }
+
+        final SmsItemBean smsItemBean = new SmsItemBean(
+                macroRequest.expandMacros( to ),
+                macroRequest.expandMacros( message ),
+                sessionLabel
+        );
+
+        try
+        {
+            smsQueue.addSmsToQueue( smsItemBean );
+        }
+        catch ( final PwmUnrecoverableException e )
+        {
+            LOGGER.warn( () -> "unable to add sms to queue: " + e.getMessage() );
+        }
+    }
+
     public enum SmsNumberFormat
     {
         RAW,
@@ -259,7 +291,7 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         if ( workQueueProcessor != null )
         {

+ 1 - 1
server/src/main/java/password/pwm/svc/stats/StatisticsService.java

@@ -317,7 +317,7 @@ public class StatisticsService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         try
         {

+ 1 - 1
server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java

@@ -222,7 +222,7 @@ public class TelemetryService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
 
     }

+ 3 - 2
server/src/main/java/password/pwm/svc/token/TokenService.java

@@ -61,6 +61,7 @@ import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.intruder.IntruderRecordType;
 import password.pwm.svc.intruder.IntruderServiceClient;
+import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.DataStore;
@@ -342,7 +343,7 @@ public class TokenService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
         if ( executorService != null )
@@ -806,7 +807,7 @@ public class TokenService extends AbstractPwmService implements PwmService
             final PwmDomain pwmDomain = tokenSendInfo.getPwmDomain();
             pwmDomain.getIntruderService().mark( IntruderRecordType.TOKEN_DEST, smsNumber, tokenSendInfo.getSessionLabel() );
 
-            pwmDomain.getPwmApplication().sendSmsUsingQueue( smsNumber, modifiedMessage, tokenSendInfo.getSessionLabel(), tokenSendInfo.getMacroRequest() );
+            SmsQueueService.sendSmsUsingQueue( pwmDomain.getPwmApplication(), smsNumber, modifiedMessage, tokenSendInfo.getSessionLabel(), tokenSendInfo.getMacroRequest() );
             LOGGER.debug( tokenSendInfo.getSessionLabel(), () -> "token SMS added to send queue for " + smsNumber );
             return true;
         }

+ 1 - 1
server/src/main/java/password/pwm/svc/userhistory/UserHistoryService.java

@@ -137,7 +137,7 @@ public class UserHistoryService extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
     }

+ 1 - 1
server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -292,7 +292,7 @@ abstract class AbstractWordlist extends AbstractPwmService implements Wordlist,
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         final TimeDuration closeWaitTime = TimeDuration.of( 1, TimeDuration.Unit.MINUTES );
 

+ 1 - 1
server/src/main/java/password/pwm/svc/wordlist/SharedHistoryService.java

@@ -89,7 +89,7 @@ public class SharedHistoryService extends AbstractPwmService implements PwmServi
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         setStatus( STATUS.CLOSED );
         if ( executorService != null )

+ 2 - 2
server/src/main/java/password/pwm/util/OnejarHelper.java

@@ -26,7 +26,7 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmEnvironment;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
 import password.pwm.util.java.StringUtil;
@@ -110,7 +110,7 @@ public class OnejarHelper
             throws Exception
     {
         final File configFile = new File( applicationPath + File.separator + PwmConstants.DEFAULT_CONFIG_FILE_FILENAME );
-        final ConfigurationReader configReader = new ConfigurationReader( configFile );
+        final ConfigurationFileManager configReader = new ConfigurationFileManager( configFile );
         final AppConfig config = configReader.getConfiguration();
         final PwmEnvironment pwmEnvironment = PwmEnvironment.builder()
                 .config( config )

+ 38 - 57
server/src/main/java/password/pwm/util/PwmScheduler.java

@@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.error.PwmError;
+import password.pwm.error.PwmInternalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.StringUtil;
@@ -33,6 +34,7 @@ import password.pwm.util.logging.PwmLogger;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.GregorianCalendar;
 import java.util.List;
 import java.util.Objects;
@@ -67,12 +69,12 @@ public class PwmScheduler
         applicationExecutorService.shutdown();
     }
 
-    public Future<?> immediateExecuteRunnableInNewThread(
+    public void immediateExecuteRunnableInNewThread(
             final Runnable runnable,
             final String threadName
     )
     {
-        return immediateExecuteCallableInNewThread( Executors.callable( runnable ), threadName );
+        immediateExecuteCallableInNewThread( Executors.callable( runnable ), threadName );
     }
 
     public <V> Future<V> immediateExecuteCallableInNewThread(
@@ -80,10 +82,7 @@ public class PwmScheduler
             final String threadName
     )
     {
-        if ( checkIfSchedulerClosed() )
-        {
-            return null;
-        }
+        checkIfSchedulerClosed();
 
         Objects.requireNonNull( callable );
 
@@ -129,10 +128,7 @@ public class PwmScheduler
             final TimeDuration delay
     )
     {
-        if ( checkIfSchedulerClosed() )
-        {
-            return null;
-        }
+        checkIfSchedulerClosed();
 
         Objects.requireNonNull( runnable );
         Objects.requireNonNull( executor );
@@ -143,45 +139,40 @@ public class PwmScheduler
             throw new IllegalStateException( "can not schedule job with shutdown scheduler" );
         }
 
-        final FutureRunner wrappedRunner = new FutureRunner( runnable, executor );
+        final FutureRunner<Object> wrappedRunner = new FutureRunner<>( runnable, executor );
         applicationExecutorService.schedule( wrappedRunner, delay.asMillis(), TimeUnit.MILLISECONDS );
         return wrappedRunner.getFuture();
     }
 
-    public void executeImmediateThreadPerJobAndAwaitCompletion(
-            final List<Callable<?>> runnableList,
+    public <T> List<T> executeImmediateThreadPerJobAndAwaitCompletion(
+            final List<Callable<T>> runnableList,
             final String threadNames
     )
             throws PwmUnrecoverableException
     {
-        if ( checkIfSchedulerClosed() )
-        {
-            return;
-        }
-
+        checkIfSchedulerClosed();
 
-        final List<Future<?>> futures = new ArrayList<>( runnableList.size() );
-        for ( final Callable<?> callable : runnableList )
+        final List<Future<T>> futures = new ArrayList<>( runnableList.size() );
+        for ( final Callable<T> callable : runnableList )
         {
-            futures.add( this.immediateExecuteCallableInNewThread( () ->
-            {
-                callable.call();
-                return null;
-            }, threadNames ) );
+            futures.add( this.immediateExecuteCallableInNewThread( callable, threadNames ) );
         }
 
-        for ( final Future<?> f : futures )
+        final List<T> results = new ArrayList<>();
+        for ( final Future<T> f : futures )
         {
-            awaitFutureCompletion( f );
+            results.add( awaitFutureCompletion( f ) );
         }
+
+        return Collections.unmodifiableList( results );
     }
 
-    private static void awaitFutureCompletion( final Future<?> future )
+    private static <T> T awaitFutureCompletion( final Future<T> future )
             throws PwmUnrecoverableException
     {
         try
         {
-            future.get();
+            return future.get();
         }
         catch ( final InterruptedException e )
         {
@@ -209,10 +200,7 @@ public class PwmScheduler
             final TimeDuration frequency
     )
     {
-        if ( checkIfSchedulerClosed() )
-        {
-            return;
-        }
+        checkIfSchedulerClosed();
 
 
         if ( initialDelay != null )
@@ -231,14 +219,9 @@ public class PwmScheduler
 
     public static ExecutorService makeBackgroundExecutor(
             final PwmApplication pwmApplication,
-            final Class clazz
+            final Class<?> clazz
     )
     {
-        if ( pwmApplication.getPwmScheduler().checkIfSchedulerClosed() )
-        {
-            return null;
-        }
-
         final ThreadPoolExecutor executor = new ThreadPoolExecutor(
                 1,
                 1,
@@ -253,7 +236,7 @@ public class PwmScheduler
     }
 
 
-    public static String makeThreadName( final PwmApplication pwmApplication, final Class theClass )
+    public static String makeThreadName( final PwmApplication pwmApplication, final Class<?> theClass )
     {
         String instanceName = "-";
         if ( pwmApplication != null )
@@ -264,7 +247,7 @@ public class PwmScheduler
         return makeThreadName( instanceName, theClass );
     }
 
-    public static String makeThreadName( final String instanceID, final Class theClass )
+    public static String makeThreadName( final String instanceID, final Class<?> theClass )
     {
         String instanceName = "-";
         if ( StringUtil.notEmpty( instanceID ) )
@@ -298,7 +281,7 @@ public class PwmScheduler
 
     public static ScheduledExecutorService makeSingleThreadExecutorService(
             final PwmApplication pwmApplication,
-            final Class theClass
+            final Class<?> theClass
     )
     {
         return makeSingleThreadExecutorService( pwmApplication.getInstanceID(), theClass );
@@ -306,7 +289,7 @@ public class PwmScheduler
 
     public static ScheduledExecutorService makeSingleThreadExecutorService(
             final String instanceID,
-            final Class theClass
+            final Class<?> theClass
     )
     {
         return Executors.newSingleThreadScheduledExecutor(
@@ -316,11 +299,11 @@ public class PwmScheduler
                 ) );
     }
 
-    private static class FutureRunner implements Runnable
+    private static class FutureRunner<T> implements Runnable
     {
         private final Runnable runnable;
         private final ExecutorService executor;
-        private volatile Future innerFuture;
+        private volatile Future<Object> innerFuture;
         private volatile boolean hasFailed;
 
         enum Flag
@@ -334,9 +317,9 @@ public class PwmScheduler
             this.executor = executor;
         }
 
-        Future getFuture()
+        Future<T> getFuture()
         {
-            return new Future()
+            return new Future<T>()
             {
                 @Override
                 public boolean cancel( final boolean mayInterruptIfRunning )
@@ -357,13 +340,13 @@ public class PwmScheduler
                 }
 
                 @Override
-                public Object get()
+                public T get()
                 {
                     return null;
                 }
 
                 @Override
-                public Object get( final long timeout, @NotNull final TimeUnit unit )
+                public T get( final long timeout, @NotNull final TimeUnit unit )
                 {
                     return null;
                 }
@@ -377,7 +360,7 @@ public class PwmScheduler
             {
                 if ( !executor.isShutdown() )
                 {
-                    innerFuture = executor.submit( runnable );
+                    innerFuture = executor.submit( Executors.callable( runnable ) );
                 }
                 else
                 {
@@ -392,14 +375,12 @@ public class PwmScheduler
         }
     }
 
-    private boolean checkIfSchedulerClosed()
+    private void checkIfSchedulerClosed()
     {
-        return false;
-        /*
-        return pwmApplication.getApplicationMode() == PwmApplicationMode.READ_ONLY
-                || pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
-                || applicationExecutorService.isShutdown();
-        */
+        if ( applicationExecutorService.isShutdown() )
+        {
+                throw new PwmInternalException( "scheduler is closed" );
+        }
     }
 
     public static Instant nextZuluZeroTime( )

+ 2 - 2
server/src/main/java/password/pwm/util/cli/CliEnvironment.java

@@ -24,7 +24,7 @@ import lombok.Builder;
 import lombok.Value;
 import password.pwm.PwmApplication;
 import password.pwm.config.AppConfig;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.util.localdb.LocalDB;
 
 import java.io.File;
@@ -35,7 +35,7 @@ import java.util.Map;
 @Builder( toBuilder = true )
 public class CliEnvironment
 {
-    final ConfigurationReader configurationReader;
+    final ConfigurationFileManager configurationFileManager;
     final File configurationFile;
     final AppConfig config;
     final File applicationPath;

+ 5 - 5
server/src/main/java/password/pwm/util/cli/MainClass.java

@@ -30,7 +30,7 @@ import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmEnvironment;
 import password.pwm.config.AppConfig;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -190,7 +190,7 @@ public class MainClass
 
         final File configurationFile = locateConfigurationFile( applicationPath );
 
-        final ConfigurationReader configReader = loadConfiguration( configurationFile );
+        final ConfigurationFileManager configReader = loadConfiguration( configurationFile );
         final AppConfig config = configReader.getConfiguration();
 
         final PwmApplication pwmApplication;
@@ -217,7 +217,7 @@ public class MainClass
 
         final Writer outputStream = new OutputStreamWriter( System.out, PwmConstants.DEFAULT_CHARSET );
         return CliEnvironment.builder()
-                .configurationReader( configReader )
+                .configurationFileManager( configReader )
                 .configurationFile( configurationFile )
                 .config( config )
                 .applicationPath( applicationPath )
@@ -441,9 +441,9 @@ public class MainClass
         return LocalDBFactory.getInstance( databaseDirectory, readonly, null, config );
     }
 
-    private static ConfigurationReader loadConfiguration( final File configurationFile ) throws Exception
+    private static ConfigurationFileManager loadConfiguration( final File configurationFile ) throws Exception
     {
-        final ConfigurationReader reader = new ConfigurationReader( configurationFile );
+        final ConfigurationFileManager reader = new ConfigurationFileManager( configurationFile );
 
         if ( reader.getConfigMode() == PwmApplicationMode.ERROR )
         {

+ 4 - 4
server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java

@@ -22,7 +22,7 @@ package password.pwm.util.cli.commands;
 
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.stored.ConfigurationProperty;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.util.cli.CliParameters;
@@ -35,8 +35,8 @@ public class ConfigLockCommand extends AbstractCliCommand
     public void doCommand( )
             throws Exception
     {
-        final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final ConfigurationFileManager configurationFileManager = cliEnvironment.getConfigurationFileManager();
+        final StoredConfiguration storedConfiguration = configurationFileManager.getStoredConfiguration();
         final Optional<String> configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE );
         if ( configIsEditable.isPresent() && !Boolean.parseBoolean( configIsEditable.get() ) )
         {
@@ -46,7 +46,7 @@ public class ConfigLockCommand extends AbstractCliCommand
 
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
         modifier.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) );
-        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        configurationFileManager.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: configuration has been locked" );
     }
 

+ 4 - 4
server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java

@@ -24,7 +24,7 @@ import password.pwm.bean.DomainID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationModifier;
@@ -51,8 +51,8 @@ public class ConfigResetHttpsCommand
             return;
         }
 
-        final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() );
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final ConfigurationFileManager configurationFileManager = new ConfigurationFileManager( cliEnvironment.getConfigurationFile() );
+        final StoredConfiguration storedConfiguration = configurationFileManager.getStoredConfiguration();
 
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
         for ( final PwmSetting setting : PwmSettingCategory.HTTPS_SERVER.getSettings() )
@@ -60,7 +60,7 @@ public class ConfigResetHttpsCommand
             final StoredConfigKey key = StoredConfigKey.forSetting( setting, null, DomainID.systemId() );
             modifier.resetSetting( key, null );
         }
-        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        configurationFileManager.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success" );
     }
 

+ 4 - 4
server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java

@@ -21,7 +21,7 @@
 package password.pwm.util.cli.commands;
 
 import password.pwm.bean.SessionLabel;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
@@ -36,12 +36,12 @@ public class ConfigSetPasswordCommand extends AbstractCliCommand
     public void doCommand( )
             throws Exception
     {
-        final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final ConfigurationFileManager configurationFileManager = cliEnvironment.getConfigurationFileManager();
+        final StoredConfiguration storedConfiguration = configurationFileManager.getStoredConfiguration();
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
         final String password = getOptionalPassword();
         StoredConfigurationUtil.setPassword( modifier, password );
-        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        configurationFileManager.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: new password has been set" );
     }
 

+ 4 - 4
server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java

@@ -22,7 +22,7 @@ package password.pwm.util.cli.commands;
 
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.stored.ConfigurationProperty;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.util.cli.CliParameters;
@@ -35,8 +35,8 @@ public class ConfigUnlockCommand extends AbstractCliCommand
     public void doCommand( )
             throws Exception
     {
-        final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final ConfigurationFileManager configurationFileManager = cliEnvironment.getConfigurationFileManager();
+        final StoredConfiguration storedConfiguration = configurationFileManager.getStoredConfiguration();
 
         final Optional<String> configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE );
         if ( configIsEditable.isPresent() && Boolean.parseBoolean( configIsEditable.get() ) )
@@ -47,7 +47,7 @@ public class ConfigUnlockCommand extends AbstractCliCommand
 
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
         modifier.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) );
-        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        configurationFileManager.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: configuration has been unlocked" );
     }
 

+ 4 - 4
server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java

@@ -21,7 +21,7 @@
 package password.pwm.util.cli.commands;
 
 import password.pwm.bean.SessionLabel;
-import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationFileManager;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.util.PasswordData;
@@ -64,8 +64,8 @@ public class ImportHttpsKeyStoreCommand extends AbstractCliCommand
         final String keyStorePassword = getOptionalPassword();
         final String inputAliasName = ( String ) cliEnvironment.getOptions().get( ALIAS_OPTIONNAME );
 
-        final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() );
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final ConfigurationFileManager configurationFileManager = new ConfigurationFileManager( cliEnvironment.getConfigurationFile() );
+        final StoredConfiguration storedConfiguration = configurationFileManager.getStoredConfiguration();
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
 
         try ( FileInputStream fileInputStream = new FileInputStream( inputFile ) )
@@ -84,7 +84,7 @@ public class ImportHttpsKeyStoreCommand extends AbstractCliCommand
             return;
         }
 
-        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        configurationFileManager.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: keystore has been imported" );
     }
 

+ 1 - 1
server/src/main/java/password/pwm/util/localdb/LocalDBService.java

@@ -72,7 +72,7 @@ public class LocalDBService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdown( )
     {
         //no-op
     }

+ 1 - 1
server/src/main/java/password/pwm/util/logging/LocalDBLogger.java

@@ -239,7 +239,7 @@ public class LocalDBLogger extends AbstractPwmService implements PwmService
     }
 
     @Override
-    public void close( )
+    public void shutdownImpl( )
     {
         final Instant startTime = Instant.now();
         int flushedEvents = 0;

+ 2 - 1
server/src/main/java/password/pwm/util/password/PasswordUtility.java

@@ -79,6 +79,7 @@ import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.event.HelpdeskAuditRecord;
+import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.AvgStatistic;
 import password.pwm.svc.stats.EpsStatistic;
 import password.pwm.svc.stats.Statistic;
@@ -178,7 +179,7 @@ public class PasswordUtility
 
         message = message.replace( "%TOKEN%", newPassword.getStringValue() );
 
-        pwmDomain.getPwmApplication().sendSmsUsingQueue( toNumber, message, null, macroRequest );
+        SmsQueueService.sendSmsUsingQueue( pwmDomain.getPwmApplication(), toNumber, message, null, macroRequest );
         LOGGER.debug( () -> String.format( "password SMS added to send queue for %s", toNumber ) );
         return null;
     }

+ 1 - 1
server/src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -352,7 +352,7 @@ Setting_Description_display.updateAttributes.agreement=<p>Specify a message to d
 Setting_Description_domain.list=List of domains supported by this application instance.  Domain order is unimportant.  The value of the domain(s) may be used in public URLs and parameters.<p>Domains provide a way for multiple systems/sites/tenants/customers to use a single instance of this @PwmAppName@ application.  Typically only a single instance is required.  If multiple domains are listed, the configuration editor will allow per-domain configuration of many settings.  Other settings are system-level and apply to the entire application instance.</p><p>Saving the configuration after increasing or decreasing the number of domains beyond a single domain may cause application URLs to change, and this configuration editor will change to allow editing of multiple domain configurations</p>
 Setting_Description_domain.hosts=Domain Hostnames
 Setting_Description_domain.system.adminDomain=Administrative Domain
-Setting_Description_domain.system.domainPaths=If enabled, domain IDs will be added to the path, and all access will required the domainID in the path.
+Setting_Description_domain.system.domainPaths=If enabled, domain IDs will be added to the path, and URL paths will required the inclusion of the domainID in the path.  Example: "/pwm/private/login" will become "/pwm/default/private/login".  Regardless of this setting, the domain is always accessible if the host header (the browser url) is matched by the setting in  
 Setting_Description_email.activation=Define this template to send an email to users after a successful activation.
 Setting_Description_email.activation.token=Define this template to send an email during the activation verification process. You can use %TOKEN% to insert the token value into the email.
 Setting_Description_email.adminAlert.toAddress=Define this template to send an email when System Audit events occur to the defined email addresses.