Browse Source

refactor environment properties

Jason Rivard 2 years ago
parent
commit
2556e97e8f
21 changed files with 576 additions and 599 deletions
  1. 0 5
      onejar/src/main/java/password/pwm/onejar/OnejarConfig.java
  2. 8 23
      onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java
  3. 1 1
      onejar/src/main/resources/password/pwm/onejar/Resource.properties
  4. 240 0
      server/src/main/java/password/pwm/EnvironmentProperty.java
  5. 2 2
      server/src/main/java/password/pwm/FileLocker.java
  6. 0 2
      server/src/main/java/password/pwm/PwmAboutProperty.java
  7. 2 3
      server/src/main/java/password/pwm/PwmApplication.java
  8. 56 54
      server/src/main/java/password/pwm/PwmApplicationUtil.java
  9. 1 0
      server/src/main/java/password/pwm/PwmConstants.java
  10. 36 229
      server/src/main/java/password/pwm/PwmEnvironment.java
  11. 50 118
      server/src/main/java/password/pwm/http/ContextManager.java
  12. 57 66
      server/src/main/java/password/pwm/http/JspUtility.java
  13. 2 2
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java
  14. 0 16
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  15. 2 2
      server/src/main/java/password/pwm/svc/PwmServiceManager.java
  16. 0 2
      server/src/main/java/password/pwm/util/OnejarHelper.java
  17. 12 20
      server/src/main/java/password/pwm/util/cli/MainClass.java
  18. 1 31
      server/src/main/java/password/pwm/util/cli/MainOptions.java
  19. 1 0
      server/src/main/resources/password/pwm/PwmConstants.properties
  20. 64 0
      server/src/test/java/password/pwm/EnvironmentPropertyTest.java
  21. 41 23
      webapp/src/main/webapp/public/reference/environment.jsp

+ 0 - 5
onejar/src/main/java/password/pwm/onejar/OnejarConfig.java

@@ -49,9 +49,4 @@ class OnejarConfig
     {
         return new File( this.getWorkingPath().getAbsoluteFile() + File.separator + "keystore" );
     }
-
-    File getPwmAppPropertiesFile( )
-    {
-        return new File( this.getWorkingPath().getAbsoluteFile() + File.separator + "application.properties" );
-    }
 }

+ 8 - 23
onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java

@@ -29,12 +29,9 @@ import org.apache.coyote.http2.Http2Protocol;
 import javax.servlet.ServletException;
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URL;
@@ -81,8 +78,6 @@ public class TomcatOnejarRunner
             throw new OnejarException( "error generating keystore: " + e.getMessage() );
         }
 
-        outputPwmAppProperties( onejarConfig );
-
         setupEnv( onejarConfig );
 
         tomcat = new Tomcat();
@@ -164,7 +159,6 @@ public class TomcatOnejarRunner
         connector.setScheme( "https" );
         connector.addUpgradeProtocol( new Http2Protocol() );
         connector.setProperty( "SSLEnabled", "true" );
-       // connector.setAttribute( "truststoreType", "PKCS12" );
         connector.setProperty( "keystoreFile", onejarConfig.getKeystoreFile().getAbsolutePath() );
         connector.setProperty( "keystorePass", onejarConfig.getKeystorePass() );
         connector.setProperty( "keyAlias", OnejarMain.KEYSTORE_ALIAS );
@@ -253,26 +247,17 @@ public class TomcatOnejarRunner
 
     private void setupEnv( final OnejarConfig onejarConfig )
     {
-        final String envVarPrefix = Resource.envVarPrefix.getValue();
-        System.setProperty( envVarPrefix + "_APPLICATIONPATH", onejarConfig.getApplicationPath().getAbsolutePath() );
-        System.setProperty( envVarPrefix + "_APPLICATIONFLAGS", "[\"ManageHttps\",\"Onejar\"]" );
-        System.setProperty( envVarPrefix + "_APPLICATIONPARAMFILE", onejarConfig.getPwmAppPropertiesFile().getAbsolutePath() );
-        System.setProperty( "ONEJAR_ENV", "TRUE" );
-    }
+        final String envVarPrefix = Resource.envVarPrefix.getValue() + ".";
+        System.setProperty( envVarPrefix + "applicationPath", onejarConfig.getApplicationPath().getAbsolutePath() );
+        System.setProperty( envVarPrefix + "ManageHttps", Boolean.TRUE.toString() );
+        System.setProperty( envVarPrefix + "OnejarInstance", Boolean.TRUE.toString() );
+        System.setProperty( envVarPrefix + "AutoExportHttpsKeyStoreFile", onejarConfig.getKeystoreFile().getAbsolutePath() );
+        System.setProperty( envVarPrefix + "AutoExportHttpsKeyStorePassword", onejarConfig.getKeystorePass() );
+        System.setProperty( envVarPrefix + "AutoExportHttpsKeyStoreAlias", OnejarMain.KEYSTORE_ALIAS );
 
-    private void outputPwmAppProperties( final OnejarConfig onejarConfig ) throws IOException
-    {
-        final Properties properties = new Properties();
-        properties.setProperty( "AutoExportHttpsKeyStoreFile", onejarConfig.getKeystoreFile().getAbsolutePath() );
-        properties.setProperty( "AutoExportHttpsKeyStorePassword", onejarConfig.getKeystorePass() );
-        properties.setProperty( "AutoExportHttpsKeyStoreAlias", OnejarMain.KEYSTORE_ALIAS );
-        final File propFile = onejarConfig.getPwmAppPropertiesFile( );
-        try ( Writer writer = new OutputStreamWriter( new FileOutputStream( propFile ), StandardCharsets.UTF_8 ) )
-        {
-            properties.store( writer, "auto-generated file" );
-        }
     }
 
+
     private void copyFileAndReplace(
             final String srcPath,
             final String destPath,

+ 1 - 1
onejar/src/main/resources/password/pwm/onejar/Resource.properties

@@ -19,7 +19,7 @@
 #
 
 
-envVarPrefix=PWM
+envVarPrefix=pwm
 defaultContext=pwm
 defaultWorkPathName=.pwm-workpath
 defaultPort=8443

+ 240 - 0
server/src/main/java/password/pwm/EnvironmentProperty.java

@@ -0,0 +1,240 @@
+/*
+ * 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.http.ContextManager;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.servlet.ServletContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public enum EnvironmentProperty
+{
+    applicationPath,
+    AutoExportHttpsKeyStoreFile,
+    AutoExportHttpsKeyStorePassword,
+    AutoExportHttpsKeyStoreAlias,
+    AutoWriteTomcatConfSourceFile,
+    AutoWriteTomcatConfOutputFile,
+    AppliancePort,
+    ApplianceHostnameFile,
+    ApplianceTokenFile,
+    InstanceID,
+    InitConsoleLogLevel,
+    ManageHttps,
+    NoFileLock,
+    CommandLineInstance,
+    OnejarInstance,;
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass( EnvironmentProperty.class );
+
+    public String conicalJavaOptionSystemName( final String contextName )
+    {
+        return effectiveContextName( contextName ) + "." + this.name();
+    }
+
+    public String conicalEnvironmentSystemName( final String contextName )
+    {
+        return ( effectiveContextName( contextName ) + "_" + this.name() ).toUpperCase();
+    }
+
+    public List<String> possibleNames( final String contextName )
+    {
+        return List.of(
+                conicalEnvironmentSystemName( contextName ),
+                conicalEnvironmentSystemName( contextName ).toLowerCase(),
+                conicalEnvironmentSystemName( contextName ).toUpperCase(),
+                conicalJavaOptionSystemName( contextName ),
+                conicalJavaOptionSystemName( contextName ).toLowerCase(),
+                conicalJavaOptionSystemName( contextName ).toUpperCase() );
+    }
+
+    private static String effectiveContextName( final String contextName )
+    {
+        return StringUtil.isEmpty( contextName ) ? PwmConstants.PWM_APP_NAME.toLowerCase() : contextName;
+    }
+
+    public static Optional<Path> readApplicationPath(
+            final ServletContext servletContext
+    )
+    {
+        final String effectiveContext = servletContext == null
+                ? PwmConstants.PWM_APP_NAME.toLowerCase()
+                : servletContext.getContextPath().replace( "/", "" );
+        final Map<EnvironmentProperty, String> tempMap = new EnumMap<>( EnvironmentProperty.class );
+        tempMap.putAll( ParameterReader.readEnvironmentFromSystemEnv( effectiveContext ) );
+        tempMap.putAll( ParameterReader.readEnvironmentFromSystemProperties( effectiveContext ) );
+        tempMap.putAll( ParameterReader.readEnvironmentFromContext( servletContext ) );
+
+        final String value = tempMap.get( EnvironmentProperty.applicationPath );
+        if ( !StringUtil.isTrimEmpty( value ) )
+        {
+            return Optional.of( Path.of( value ) );
+        }
+
+        return Optional.empty();
+    }
+
+    public static Map<EnvironmentProperty, String> readApplicationParams(
+            final Path applicationPath,
+            final ServletContext servletContext
+    )
+    {
+        final String effectiveContextName = servletContext == null
+                ? PwmConstants.PWM_APP_NAME.toLowerCase()
+                : servletContext.getContextPath().replace( "/", "" );
+
+        final Map<EnvironmentProperty, String> resultMap = new EnumMap<>( EnvironmentProperty.class );
+        resultMap.putAll( ParameterReader.readEnvironmentFromContext( servletContext ) );
+        resultMap.putAll( ParameterReader.readEnvironmentFromSystemEnv( effectiveContextName ) );
+        resultMap.putAll( ParameterReader.readEnvironmentFromSystemProperties( effectiveContextName ) );
+        resultMap.putAll( ParameterReader.readEnvironmentFromEnvPropFile( applicationPath ) );
+        return Collections.unmodifiableMap( resultMap );
+    }
+
+    private static class ParameterReader
+    {
+        private static Map<EnvironmentProperty, String> readEnvironmentFromContext( final ServletContext servletContext )
+        {
+            if ( servletContext == null )
+            {
+                return Collections.emptyMap();
+            }
+
+            final Map<String, String> stringMap = Collections.list( servletContext.getInitParameterNames() )
+                    .stream()
+                    .collect( Collectors.toMap(
+                            Function.identity(),
+                            servletContext::getInitParameter ) );
+
+            return readEnvironmentFromStringMap( stringMap, servletContext.getContextPath() );
+        }
+
+        private static Map<EnvironmentProperty, String> readEnvironmentFromSystemEnv( final String context )
+        {
+            if ( context == null )
+            {
+                return Collections.emptyMap();
+            }
+
+            return readEnvironmentFromStringMap( System.getenv(), context );
+        }
+
+        private static Map<EnvironmentProperty, String> readEnvironmentFromEnvPropFile( final Path appPath )
+        {
+            if ( appPath == null )
+            {
+                return Collections.emptyMap();
+            }
+
+            final Path propFilePath = Path.of( appPath.toString(), PwmConstants.DEFAULT_ENVIRONMENT_PROPERTIES_FILENAME );
+
+            if ( !Files.exists( propFilePath ) )
+            {
+                return Collections.emptyMap();
+            }
+
+            try
+            {
+                try ( InputStream inputStream = Files.newInputStream( propFilePath ) )
+                {
+                    final Properties properties = new Properties();
+                    properties.load( inputStream );
+                    final Map<String, String> stringMap = properties.entrySet().stream()
+                            .filter( entry -> entry.getKey() != null && entry.getValue() != null )
+                            .collect( Collectors.toMap(
+                                    entry -> entry.getKey().toString(),
+                                    entry -> entry.getValue().toString() ) );
+
+
+                    return readEnvironmentFromStringMap( stringMap, null );
+                }
+            }
+            catch ( final IOException e )
+            {
+                LOGGER.warn( () -> "unexpected error while reading " + PwmConstants.DEFAULT_ENVIRONMENT_PROPERTIES_FILENAME + ", error: " + e.getMessage(), e );
+                return Collections.emptyMap();
+            }
+
+        }
+
+        private static Map<EnvironmentProperty, String> readEnvironmentFromSystemProperties( final String context )
+        {
+            final Map<String, String> stringMap = System.getProperties().entrySet()
+                    .stream()
+                    .collect( Collectors.toMap(
+                            entry -> entry.getKey().toString(),
+                            entry -> entry.getValue().toString() ) );
+
+            return readEnvironmentFromStringMap( stringMap, context );
+        }
+
+        private static Map<EnvironmentProperty, String> readEnvironmentFromStringMap(
+                final Map<String, String> input,
+                final String context
+        )
+        {
+
+            final String normalizedContext = context == null ? null : context.replace( "/", "" );
+
+            final Map<EnvironmentProperty, String> returnObj = new EnumMap<>( EnvironmentProperty.class );
+
+            for ( final EnvironmentProperty environmentParameter : EnvironmentProperty.values() )
+            {
+                if ( context == null )
+                {
+                    final String value = input.get( environmentParameter.name() );
+                    if ( !StringUtil.isTrimEmpty( value ) && !ContextManager.UNSPECIFIED_VALUE.equalsIgnoreCase( value ) )
+                    {
+                        returnObj.put( environmentParameter, value );
+                    }
+                }
+                else
+                {
+                    possibleNameLoop:
+                    for ( final String possibleName : environmentParameter.possibleNames( normalizedContext ) )
+                    {
+                        final String value = input.get( possibleName );
+                        if ( !StringUtil.isTrimEmpty( value ) && !ContextManager.UNSPECIFIED_VALUE.equalsIgnoreCase( value ) )
+                        {
+                            returnObj.put( environmentParameter, value );
+                            break possibleNameLoop;
+                        }
+                    }
+                }
+            }
+
+            return Collections.unmodifiableMap( returnObj );
+        }
+    }
+}

+ 2 - 2
server/src/main/java/password/pwm/FileLocker.java

@@ -54,7 +54,7 @@ class FileLocker
 
     private boolean lockingAllowed( )
     {
-        return !pwmEnvironment.isInternalRuntimeInstance() && !pwmEnvironment.getFlags().contains( PwmEnvironment.ApplicationFlag.NoFileLock );
+        return !pwmEnvironment.isInternalRuntimeInstance() && !pwmEnvironment.readPropertyAsBoolean( EnvironmentProperty.NoFileLock );
     }
 
     public boolean isLocked( )
@@ -129,7 +129,7 @@ class FileLocker
             throws PwmUnrecoverableException
     {
         final AppConfig domainConfig = pwmEnvironment.getConfig();
-        final int maxWaitSeconds = pwmEnvironment.getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance )
+        final int maxWaitSeconds = pwmEnvironment.readPropertyAsBoolean( EnvironmentProperty.CommandLineInstance )
                 ? 1
                 : Integer.parseInt( domainConfig.readAppProperty( AppProperty.APPLICATION_FILELOCK_WAIT_SECONDS ) );
         final Instant startTime = Instant.now();

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

@@ -52,9 +52,7 @@ public enum PwmAboutProperty
     app_instanceID( "App InstanceID", PwmApplication::getInstanceID ),
     app_trialMode( null, pwmApplication -> Boolean.toString( PwmConstants.TRIAL_MODE ) ),
     app_deployment_type( null, pwmApplication -> pwmApplication.getPwmEnvironment().getDeploymentPlatform().name() ),
-    app_mode_manageHttps( null, pwmApplication -> Boolean.toString( pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.ManageHttps ) ) ),
     app_applicationPath( null, pwmApplication -> pwmApplication.getPwmEnvironment().getApplicationPath().getAbsolutePath() ),
-    app_environmentFlags( null, pwmApplication -> StringUtil.collectionToString( pwmApplication.getPwmEnvironment().getFlags() ) ),
     app_wordlistSize( null, pwmApplication -> Long.toString( pwmApplication.getWordlistService().size() ) ),
     app_sharedHistorySize( null, pwmApplication -> Long.toString( pwmApplication.getSharedHistoryManager().size() ) ),
     app_sharedHistoryOldestTime( null, pwmApplication -> format( pwmApplication.getSharedHistoryManager().getOldestEntryTime() ) ),

+ 2 - 3
server/src/main/java/password/pwm/PwmApplication.java

@@ -216,7 +216,7 @@ public class PwmApplication
         PwmDomainUtil.initDomains( this, domains().values() );
 
         final boolean skipPostInit = pwmEnvironment.isInternalRuntimeInstance()
-                || pwmEnvironment.getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance );
+                || pwmEnvironment.readPropertyAsBoolean( EnvironmentProperty.CommandLineInstance );
 
         if ( !skipPostInit )
         {
@@ -273,9 +273,8 @@ public class PwmApplication
         PwmApplicationUtil.outputKeystore( this );
         PwmApplicationUtil.outputTomcatConf( this );
 
-        LOGGER.debug( sessionLabel, () -> "application environment flags: " + StringUtil.collectionToString( pwmEnvironment.getFlags() ) );
         LOGGER.debug( sessionLabel, () -> "application environment parameters: "
-                + StringUtil.mapToString( pwmEnvironment.getParameters() ) );
+                + StringUtil.mapToString( pwmEnvironment.readProperties() ) );
 
         PwmApplicationUtil.outputApplicationInfoToLog( this );
         PwmApplicationUtil.outputConfigurationToLog( this, DomainID.systemId() );

+ 56 - 54
server/src/main/java/password/pwm/PwmApplicationUtil.java

@@ -101,7 +101,7 @@ class PwmApplicationUtil
     {
         final PwmEnvironment pwmEnvironment = pwmApplication.getPwmEnvironment();
 
-        if ( pwmEnvironment.isInternalRuntimeInstance() || pwmEnvironment.getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance ) )
+        if ( pwmEnvironment.isInternalRuntimeInstance() || pwmEnvironment.readPropertyAsBoolean( EnvironmentProperty.CommandLineInstance ) )
         {
             return;
         }
@@ -133,11 +133,10 @@ class PwmApplicationUtil
     )
     {
         {
-            final String newInstanceID = pwmApplication.getPwmEnvironment().getParameters().get( PwmEnvironment.ApplicationParameter.InstanceID );
-
-            if ( !StringUtil.isTrimEmpty( newInstanceID ) )
+            final Optional<String> newInstanceID = pwmApplication.getPwmEnvironment().readProperty( EnvironmentProperty.InstanceID );
+            if ( newInstanceID.isPresent() )
             {
-                return newInstanceID;
+                return newInstanceID.get();
             }
         }
 
@@ -175,17 +174,18 @@ class PwmApplicationUtil
     {
         try
         {
-
-            final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = pwmApplication.getPwmEnvironment().getParameters();
-            final String keystoreFileString = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStoreFile );
-            if ( StringUtil.isEmpty( keystoreFileString ) )
+            final PwmEnvironment pwmEnvironment = pwmApplication.getPwmEnvironment();
+            final Optional<String> keystoreFileString = pwmEnvironment.readProperty( EnvironmentProperty.AutoExportHttpsKeyStoreFile );
+            if ( keystoreFileString.isEmpty() )
             {
                 return;
             }
 
-            final File keyStoreFile = new File( keystoreFileString );
-            final String password = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStorePassword );
-            final String alias = applicationParams.get( PwmEnvironment.ApplicationParameter.AutoExportHttpsKeyStoreAlias );
+            final File keyStoreFile = new File( keystoreFileString.get() );
+            final String password = pwmEnvironment.readProperty( EnvironmentProperty.AutoExportHttpsKeyStorePassword )
+                    .orElseThrow( () -> new IllegalArgumentException( "keystore export property is configured, but keystore password is not specified " ) );
+            final String alias = pwmEnvironment.readProperty( EnvironmentProperty.AutoExportHttpsKeyStoreAlias )
+                    .orElseThrow( () -> new IllegalArgumentException( "keystore export property is configured, but keystore alias is not specified " ) );
             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() );
@@ -198,65 +198,67 @@ class PwmApplicationUtil
 
     static void outputTomcatConf( final PwmApplication pwmApplication )
     {
+        final PwmEnvironment pwmEnvironment = pwmApplication.getPwmEnvironment();
+        final Optional<String> tomcatOutputFileStr = pwmEnvironment.readProperty( EnvironmentProperty.AutoWriteTomcatConfOutputFile );
+        if ( tomcatOutputFileStr.isEmpty() )
+        {
+            return;
+        }
+
         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.get() );
+            final File tomcatSourceFile;
             {
-                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 Optional<String> tomcatSourceFileStr = pwmEnvironment.readProperty( EnvironmentProperty.AutoWriteTomcatConfSourceFile );
+                if ( tomcatSourceFileStr.isPresent() )
                 {
-                    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
+                    tomcatSourceFile = new File( tomcatSourceFileStr.get() );
+                    if ( !tomcatSourceFile.exists() )
                     {
                         LOGGER.error( pwmApplication.getSessionLabel(),
-                                () -> "can not output tomcat configuration file, source file parameter '"
-                                        + PwmEnvironment.ApplicationParameter.AutoWriteTomcatConfSourceFile + "' is not specified." );
+                                () -> "can not output tomcat configuration file, source file does not exist: " + tomcatSourceFile.getAbsolutePath() );
                         return;
                     }
                 }
-
-                try ( ByteArrayOutputStream outputContents = new ByteArrayOutputStream() )
+                else
                 {
-                    try ( InputStream fileInputStream = Files.newInputStream( tomcatOutputFile.toPath() ) )
-                    {
-                        ExportHttpsTomcatConfigCommand.TomcatConfigWriter.writeOutputFile(
-                                pwmApplication.getConfig(),
-                                fileInputStream,
-                                outputContents
-                        );
-                    }
+                    LOGGER.error( pwmApplication.getSessionLabel(),
+                            () -> "can not output tomcat configuration file, source file parameter '"
+                                    + EnvironmentProperty.AutoWriteTomcatConfSourceFile + "' is not specified." );
+                    return;
+                }
+            }
 
-                    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 ( ByteArrayOutputStream outputContents = new ByteArrayOutputStream() )
+            {
+                try ( InputStream fileInputStream = Files.newInputStream( tomcatOutputFile.toPath() ) )
+                {
+                    ExportHttpsTomcatConfigCommand.TomcatConfigWriter.writeOutputFile(
+                            pwmApplication.getConfig(),
+                            fileInputStream,
+                            outputContents
+                    );
+                }
 
-                    try ( OutputStream fileOutputStream = Files.newOutputStream( tomcatOutputFile.toPath() ) )
+                if ( tomcatOutputFile.exists() )
+                {
+                    LOGGER.trace( pwmApplication.getSessionLabel(), () -> "deleting existing tomcat configuration file " + tomcatOutputFile.getAbsolutePath() );
+                    if ( tomcatOutputFile.delete() )
                     {
-                        fileOutputStream.write( outputContents.toByteArray() );
+                        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "deleted existing tomcat configuration file: " + tomcatOutputFile.getAbsolutePath() );
                     }
                 }
 
-                LOGGER.info( pwmApplication.getSessionLabel(), () -> "successfully wrote tomcat configuration to 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 )
         {

+ 1 - 0
server/src/main/java/password/pwm/PwmConstants.java

@@ -60,6 +60,7 @@ public abstract class PwmConstants
     public static final String CHAI_API_VERSION = com.novell.ldapchai.ChaiConstant.CHAI_API_VERSION;
 
     public static final String DEFAULT_CONFIG_FILE_FILENAME = readPwmConstantsBundle( "defaultConfigFilename" );
+    public static final String DEFAULT_ENVIRONMENT_PROPERTIES_FILENAME = readPwmConstantsBundle( "defaultEnvironmentPropertiesFilename" );
     public static final String DEFAULT_PROPERTIES_CONFIG_FILE_FILENAME = readPwmConstantsBundle( "defaultPropertiesConfigFilename" );
 
     public static final String PWM_APP_NAME = readPwmConstantsBundle( "pwm.appName" );

+ 36 - 229
server/src/main/java/password/pwm/PwmEnvironment.java

@@ -22,7 +22,6 @@ package password.pwm;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import lombok.Builder;
-import lombok.Singular;
 import lombok.Value;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
@@ -30,26 +29,16 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
-import password.pwm.util.java.CollectionUtil;
-import password.pwm.util.java.EnumUtil;
 import password.pwm.util.java.LazySupplier;
-import password.pwm.util.java.StringUtil;
-import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 
+import javax.servlet.ServletContext;
 import java.io.File;
-import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Properties;
-import java.util.Set;
+import java.util.function.Supplier;
 
 @Value
 @Builder( toBuilder = true )
@@ -68,44 +57,11 @@ public class PwmEnvironment
     private File configurationFile;
     private ContextManager contextManager;
 
-    @Singular
-    private Set<ApplicationFlag> flags;
+    private final Supplier<Map<EnvironmentProperty, String>> parameters = LazySupplier.create( this::readApplicationParams );
 
-    @Singular
-    private Map<ApplicationParameter, String> parameters;
+    private final LazySupplier<DeploymentPlatform> deploymentPlatformLazySupplier
+            = LazySupplier.create( this::determineDeploymentPlatform );
 
-    private final LazySupplier<DeploymentPlatform> deploymentPlatformLazySupplier = LazySupplier.create( this::determineDeploymentPlatform );
-
-    public enum ApplicationParameter
-    {
-        AutoExportHttpsKeyStoreFile,
-        AutoExportHttpsKeyStorePassword,
-        AutoExportHttpsKeyStoreAlias,
-        AutoWriteTomcatConfSourceFile,
-        AutoWriteTomcatConfOutputFile,
-        AppliancePort,
-        ApplianceHostnameFile,
-        ApplianceTokenFile,
-        InstanceID,
-        InitConsoleLogLevel,;
-
-        public static Optional<ApplicationParameter> forString( final String input )
-        {
-            return EnumUtil.readEnumFromString( ApplicationParameter.class, input );
-        }
-    }
-
-    public enum ApplicationFlag
-    {
-        ManageHttps,
-        NoFileLock,
-        CommandLineInstance,;
-
-        public static Optional<ApplicationFlag> forString( final String input )
-        {
-            return EnumUtil.readEnumFromString( ApplicationFlag.class, input );
-        }
-    }
 
     public enum DeploymentPlatform
     {
@@ -115,55 +71,8 @@ public class PwmEnvironment
         Appliance,;
     }
 
-    public enum EnvironmentParameter
-    {
-        applicationPath,
-        applicationFlags,
-        applicationParamFile,;
-
-        public String conicalJavaOptionSystemName( )
-        {
-            return PwmConstants.PWM_APP_NAME.toLowerCase() + "." + this;
-        }
-
-        public String conicalEnvironmentSystemName( )
-        {
-            return ( PwmConstants.PWM_APP_NAME.toLowerCase() + "_" + this ).toUpperCase();
-        }
-
-        public List<String> possibleNames( final String contextName )
-        {
-            final List<String> returnValues = new ArrayList<>();
-            if ( contextName != null )
-            {
-                // java property format <app>.<context>.<paramName> like pwm.pwm.applicationFlag
-                final String value = PwmConstants.PWM_APP_NAME.toLowerCase()
-                        + "."
-                        + contextName
-                        + "."
-                        + this;
-                returnValues.add( value );
-                returnValues.add( value.toUpperCase() );
-                returnValues.add( value.replace( '.', '_' ) );
-                returnValues.add( value.toUpperCase().replace( '.', '_' ) );
-            }
-            {
-                // java property format <app>.<paramName> like pwm.applicationFlag
-                final String value = PwmConstants.PWM_APP_NAME.toLowerCase()
-                        + "."
-                        + this;
-                returnValues.add( value );
-                returnValues.add( value.toUpperCase() );
-                returnValues.add( value.replace( '.', '_' ) );
-                returnValues.add( value.toUpperCase().replace( '.', '_' ) );
-            }
-
-            return Collections.unmodifiableList( returnValues );
-        }
-    }
-
 
-    public void verifyIfApplicationPathIsSetProperly( )
+    public void verifyIfApplicationPathIsSetProperly()
             throws PwmUnrecoverableException
     {
         final File applicationPath = this.getApplicationPath();
@@ -185,6 +94,21 @@ public class PwmEnvironment
         }
     }
 
+    public Map<EnvironmentProperty, String> readProperties()
+    {
+        return parameters.get();
+    }
+
+    public Optional<String> readProperty( final EnvironmentProperty environmentParameter )
+    {
+        return Optional.ofNullable( parameters.get().get( environmentParameter ) );
+    }
+
+    public boolean readPropertyAsBoolean( final EnvironmentProperty environmentParameter )
+    {
+        return Boolean.parseBoolean( parameters.get().get( environmentParameter ) );
+    }
+
     public PwmEnvironment makeRuntimeInstance(
             final AppConfig appConfig
     )
@@ -198,7 +122,8 @@ public class PwmEnvironment
     }
 
 
-    public static void verifyApplicationPath( final File applicationPath ) throws PwmUnrecoverableException
+    public static void verifyApplicationPath( final File applicationPath )
+            throws PwmUnrecoverableException
     {
 
         if ( applicationPath == null )
@@ -248,132 +173,6 @@ public class PwmEnvironment
 
     }
 
-    public static class ParseHelper
-    {
-        public static Set<ApplicationFlag> readApplicationFlagsFromSystem( final String contextName )
-        {
-            final Optional<String> rawValue = readValueFromSystem( EnvironmentParameter.applicationFlags, contextName );
-            if ( rawValue.isPresent() )
-            {
-                return parseApplicationFlagValueParameter( rawValue.get() );
-            }
-            return Collections.emptySet();
-        }
-
-        public static Map<ApplicationParameter, String> readApplicationParmsFromSystem( final String contextName )
-        {
-            final Optional<String> rawValue = readValueFromSystem( EnvironmentParameter.applicationParamFile, contextName );
-            if ( rawValue.isPresent() )
-            {
-                return readAppParametersFromPath( rawValue.get() );
-            }
-            return Collections.emptyMap();
-        }
-
-        public static Optional<String> readValueFromSystem( final PwmEnvironment.EnvironmentParameter parameter, final String contextName )
-        {
-            final List<String> namePossibilities = parameter.possibleNames( contextName );
-
-            for ( final String propertyName : namePossibilities )
-            {
-                final String propValue = System.getProperty( propertyName );
-                if ( StringUtil.notEmpty( propValue ) )
-                {
-                    return Optional.of( propValue );
-                }
-            }
-
-            for ( final String propertyName : namePossibilities )
-            {
-                final String propValue = System.getenv( propertyName );
-                if ( StringUtil.notEmpty( propValue ) )
-                {
-                    return Optional.of( propValue );
-                }
-            }
-
-            return Optional.empty();
-        }
-
-        public static Set<ApplicationFlag> parseApplicationFlagValueParameter( final String input )
-        {
-            if ( input == null )
-            {
-                return Collections.emptySet();
-            }
-
-            try
-            {
-                final List<String> jsonValues = JsonFactory.get().deserializeStringList( input );
-                final Set<ApplicationFlag> returnFlags = CollectionUtil.readEnumSetFromStringCollection( ApplicationFlag.class, jsonValues );
-                return Collections.unmodifiableSet( returnFlags );
-            }
-            catch ( final Exception e )
-            {
-                //
-            }
-
-            final Set<ApplicationFlag> returnFlags = EnumSet.noneOf( ApplicationFlag.class );
-            for ( final String value : input.split( "," ) )
-            {
-                final Optional<ApplicationFlag> flag = ApplicationFlag.forString( value );
-                if ( flag.isPresent() )
-                {
-                    returnFlags.add( flag.get() );
-                }
-                else
-                {
-                    LOGGER.warn( SESSION_LABEL, () -> "unknown " + EnvironmentParameter.applicationFlags + " value: " + input );
-                }
-            }
-            return returnFlags;
-        }
-
-        public static Map<ApplicationParameter, String> readAppParametersFromPath( final String input )
-        {
-            if ( input == null )
-            {
-                return Collections.emptyMap();
-            }
-
-            final Properties propValues = new Properties();
-            try ( InputStream fileInputStream = Files.newInputStream( Path.of( input ) ) )
-            {
-                propValues.load( fileInputStream );
-            }
-            catch ( final Exception e )
-            {
-                LOGGER.warn( SESSION_LABEL, () -> "error reading properties file '" + input + "' specified by environment setting "
-                        + EnvironmentParameter.applicationParamFile + ", error: " + e.getMessage() );
-            }
-
-            try
-            {
-                final Map<ApplicationParameter, String> returnParams = new EnumMap<>( ApplicationParameter.class );
-                for ( final Object key : propValues.keySet() )
-                {
-                    final String keyString = key.toString();
-                    final Optional<ApplicationParameter> param = ApplicationParameter.forString( keyString );
-                    if ( param.isPresent() )
-                    {
-                        returnParams.put( param.get(), propValues.getProperty( keyString ) );
-                    }
-                    else
-                    {
-                        LOGGER.warn( SESSION_LABEL, () -> "unknown " + EnvironmentParameter.applicationParamFile + " value: " + input );
-                    }
-                }
-                return Collections.unmodifiableMap( returnParams );
-            }
-            catch ( final Exception e )
-            {
-                LOGGER.warn( SESSION_LABEL, () -> "unable to parse jason value of " + EnvironmentParameter.applicationParamFile + ", error: " + e.getMessage() );
-            }
-
-            return Collections.emptyMap();
-        }
-    }
-
     public static PwmApplicationMode checkForTrial( final PwmApplicationMode mode )
     {
         if ( PwmConstants.TRIAL_MODE && mode == PwmApplicationMode.RUNNING )
@@ -393,18 +192,26 @@ public class PwmEnvironment
     @SuppressFBWarnings( "DMI_HARDCODED_ABSOLUTE_FILENAME" )
     private DeploymentPlatform determineDeploymentPlatform()
     {
-        final File dockerEnvFile = new File( "/.dockerenv" );
-
-        if ( dockerEnvFile.exists() )
+        if ( Files.exists( Path.of( "/.dockerenv" ) ) )
         {
             return DeploymentPlatform.Docker;
         }
 
-        final String envValue = System.getProperty( "ONEJAR_ENV", "FALSE" );
-        if ( Boolean.getBoolean( envValue ) )
+        if ( readPropertyAsBoolean( EnvironmentProperty.OnejarInstance ) )
         {
             return DeploymentPlatform.Onejar;
         }
         return DeploymentPlatform.War;
     }
+
+    private Map<EnvironmentProperty, String> readApplicationParams()
+    {
+        final Path applicationPath = this.applicationPath == null ? null : this.applicationPath.toPath();
+        final ServletContext effectiveContext = this.contextManager == null
+                ? null
+                : this.contextManager.getServletContext();
+
+        return EnvironmentProperty.readApplicationParams( applicationPath, effectiveContext );
+    }
 }
+

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

@@ -22,6 +22,7 @@ package password.pwm.http;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import password.pwm.AppProperty;
+import password.pwm.EnvironmentProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
@@ -63,11 +64,8 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Executors;
@@ -100,7 +98,7 @@ public class ContextManager implements Serializable
 
     private File applicationPath;
 
-    private static final String UNSPECIFIED_VALUE = "unspecified";
+    public static final String UNSPECIFIED_VALUE = "unspecified";
 
     public ContextManager( final ServletContext servletContext )
     {
@@ -193,35 +191,18 @@ public class ContextManager implements Serializable
         throw new PwmUnrecoverableException( errorInformation );
     }
 
-    @SuppressFBWarnings( "MDM_SETDEFAULTLOCALE" )
     public void initialize( )
     {
         final Instant startTime = Instant.now();
 
-        try
-        {
-            Locale.setDefault( PwmConstants.DEFAULT_LOCALE );
-        }
-        catch ( final Exception e )
-        {
-            outputError( "unable to set default locale as Java machine default locale: " + e.getMessage() );
-        }
+        initializeSystemLocale();
 
         AppConfig appConfig = null;
         PwmApplicationMode mode = PwmApplicationMode.ERROR;
 
-        final ParameterReader parameterReader = new ParameterReader( servletContext );
+        if ( initializeApplicationPath() == ProcessStatus.Halt )
         {
-            final Optional<String> applicationPathStr = parameterReader.readApplicationPath();
-            if ( applicationPathStr.isEmpty() )
-            {
-                startupErrorInformation = new ErrorInformation( PwmError.ERROR_ENVIRONMENT_ERROR, "application path is not specified" );
-                return;
-            }
-            else
-            {
-                applicationPath = new File( applicationPathStr.get() );
-            }
+            return;
         }
 
         File configurationFile = null;
@@ -254,9 +235,6 @@ public class ContextManager implements Serializable
             LOGGER.debug( SESSION_LABEL, () -> "configuration file was loaded from " + ( filename ) );
         }
 
-        final Collection<PwmEnvironment.ApplicationFlag> applicationFlags = parameterReader.readApplicationFlags();
-        final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = parameterReader.readApplicationParams( applicationPath );
-
         mode = PwmEnvironment.checkForTrial( mode );
 
         try
@@ -267,8 +245,6 @@ public class ContextManager implements Serializable
                     .applicationMode( mode )
                     .configurationFile( configurationFile )
                     .contextManager( this )
-                    .flags( applicationFlags )
-                    .parameters( applicationParams )
                     .build();
 
             if ( pwmApplication == null )
@@ -290,6 +266,45 @@ public class ContextManager implements Serializable
                         PwmScheduler.makeThreadName( SESSION_LABEL, pwmApplication, this.getClass() ) + "-"
                 ) );
 
+        initializeFileWatchers();
+
+        LOGGER.trace( SESSION_LABEL, () -> "initialization complete (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
+    }
+
+    @SuppressFBWarnings( "MDM_SETDEFAULTLOCALE" )
+    private static void initializeSystemLocale()
+    {
+        try
+        {
+            Locale.setDefault( PwmConstants.DEFAULT_LOCALE );
+        }
+        catch ( final Exception e )
+        {
+            outputError( "unable to set default locale as Java machine default locale: " + e.getMessage() );
+        }
+    }
+
+    private ProcessStatus initializeApplicationPath()
+    {
+        EnvironmentProperty.readApplicationPath( this.servletContext ).ifPresent( appPath -> this.applicationPath = appPath.toFile() );
+
+        if ( this.applicationPath == null )
+        {
+            startupErrorInformation = new ErrorInformation( PwmError.ERROR_ENVIRONMENT_ERROR, "application path is not specified" );
+            return ProcessStatus.Halt;
+        }
+
+        if ( !this.applicationPath.exists() )
+        {
+            startupErrorInformation = new ErrorInformation( PwmError.ERROR_ENVIRONMENT_ERROR, "specified application path does not exist" );
+            return ProcessStatus.Halt;
+        }
+
+        return ProcessStatus.Continue;
+    }
+
+    private void initializeFileWatchers()
+    {
         boolean reloadOnChange = true;
         long fileScanFrequencyMs = 5000;
         {
@@ -315,10 +330,8 @@ public class ContextManager implements Serializable
         {
             taskMaster.scheduleWithFixedDelay( new SilentPropertiesFileWatcher(), fileScanFrequencyMs, fileScanFrequencyMs, TimeUnit.MILLISECONDS );
         }
-
-        LOGGER.trace( SESSION_LABEL, () -> "initialization complete (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
     }
-
+    
     private void checkConfigForAutoImportLdapCerts(
             final ConfigurationFileManager configReader
     )
@@ -565,6 +578,11 @@ public class ContextManager implements Serializable
         return restartCount.get();
     }
 
+    public ServletContext getServletContext()
+    {
+        return servletContext;
+    }
+
     private File locateConfigurationFile( final File applicationPath, final String filename )
     {
         return new File( applicationPath.getAbsolutePath() + File.separator + filename );
@@ -598,92 +616,6 @@ public class ContextManager implements Serializable
         return servletContext.getResourceAsStream( path );
     }
 
-    private static class ParameterReader
-    {
-        private final ServletContext servletContext;
-
-
-        ParameterReader( final ServletContext servletContext )
-        {
-            this.servletContext = servletContext;
-        }
-
-        Optional<String> readApplicationPath( )
-        {
-            final Optional<String> contextAppPathSetting = readEnvironmentParameter( PwmEnvironment.EnvironmentParameter.applicationPath );
-            if ( contextAppPathSetting.isPresent() )
-            {
-                return contextAppPathSetting;
-            }
-
-            final String contextPath = servletContext.getContextPath().replace( "/", "" );
-            return PwmEnvironment.ParseHelper.readValueFromSystem(
-                    PwmEnvironment.EnvironmentParameter.applicationPath,
-                    contextPath
-            );
-        }
-
-        Set<PwmEnvironment.ApplicationFlag> readApplicationFlags( )
-        {
-            final Optional<String> contextAppFlagsValue = readEnvironmentParameter( PwmEnvironment.EnvironmentParameter.applicationFlags );
-            if ( contextAppFlagsValue.isPresent() )
-            {
-                return PwmEnvironment.ParseHelper.parseApplicationFlagValueParameter( contextAppFlagsValue.get() );
-            }
-
-            final String contextPath = servletContext.getContextPath().replace( "/", "" );
-            return PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem( contextPath );
-        }
-
-        Map<PwmEnvironment.ApplicationParameter, String> readApplicationParams( final File applicationPath  )
-        {
-            // attempt to read app params file from specified env param file value
-            {
-                final Optional<String> contextAppParamsValue = readEnvironmentParameter( PwmEnvironment.EnvironmentParameter.applicationParamFile );
-                if ( contextAppParamsValue.isPresent() )
-                {
-                    return PwmEnvironment.ParseHelper.readAppParametersFromPath( contextAppParamsValue.get() );
-                }
-            }
-
-            // attempt to read app params file from specified system file value
-            {
-                final String contextPath = servletContext.getContextPath().replace( "/", "" );
-                final Map<PwmEnvironment.ApplicationParameter, String> results = PwmEnvironment.ParseHelper.readApplicationParmsFromSystem( contextPath );
-                if ( !results.isEmpty() )
-                {
-                    return results;
-                }
-            }
-
-            // attempt to read via application.properties in applicationPath
-            if ( applicationPath != null && applicationPath.exists() )
-            {
-                final File appPropertiesFile = new File( applicationPath.getPath() + File.separator + "application.properties" );
-                if ( appPropertiesFile.exists() )
-                {
-                    return PwmEnvironment.ParseHelper.readAppParametersFromPath( appPropertiesFile.getPath() );
-                }
-            }
-
-            return Collections.emptyMap();
-        }
-
-
-        private Optional<String> readEnvironmentParameter( final PwmEnvironment.EnvironmentParameter environmentParameter )
-        {
-            final String value = servletContext.getInitParameter( environmentParameter.toString() );
-            if ( StringUtil.notEmpty( value ) )
-            {
-                if ( !UNSPECIFIED_VALUE.equalsIgnoreCase( value ) )
-                {
-                    return Optional.of( value );
-                }
-            }
-            return Optional.empty();
-        }
-    }
-
     public String getServerInfo( )
     {
         return servletContext.getServerInfo();

+ 57 - 66
server/src/main/java/password/pwm/http/JspUtility.java

@@ -32,11 +32,11 @@ import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import javax.servlet.jsp.PageContext;
 import java.text.NumberFormat;
 import java.time.Instant;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.function.Supplier;
 
 public abstract class JspUtility
@@ -44,43 +44,40 @@ public abstract class JspUtility
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( JspUtility.class );
 
-    private static PwmRequest forRequest(
+    private static Optional<PwmRequest> forRequest(
             final ServletRequest request
     )
     {
-        final PwmRequest pwmRequest = ( PwmRequest ) request.getAttribute( PwmRequestAttribute.PwmRequest.toString() );
-        if ( pwmRequest == null )
-        {
-            LOGGER.warn( () -> "unable to load pwmRequest object during jsp execution" );
-        }
-        return pwmRequest;
+        return Optional.of( ( PwmRequest ) request.getAttribute( PwmRequestAttribute.PwmRequest.toString() ) );
+    }
+
+    private static PwmUnrecoverableException makeMissingRequestException()
+    {
+        final String msg = "unable to load pwmRequest object during jsp execution";
+        return PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, msg );
     }
 
     public static <E extends PwmSessionBean> E getSessionBean( final PageContext pageContext, final Class<E> theClass )
             throws PwmUnrecoverableException
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        try
-        {
-            return pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, theClass );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            final String msg = "unable to load pwmRequest object during jsp execution: " + e.getMessage();
-            LOGGER.warn( () -> msg );
-            throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, msg );
-        }
+        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() )
+                .orElseThrow( JspUtility::makeMissingRequestException );
+        return pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, theClass );
     }
 
     public static Object getAttribute( final PageContext pageContext, final PwmRequestAttribute requestAttr )
+            throws PwmUnrecoverableException
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
+        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() )
+                .orElseThrow( JspUtility::makeMissingRequestException );
         return pwmRequest.getAttribute( requestAttr );
     }
 
     public static boolean getBooleanAttribute( final PageContext pageContext, final PwmRequestAttribute requestAttr )
+            throws PwmUnrecoverableException
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
+        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() )
+                .orElseThrow( JspUtility::makeMissingRequestException );
         final Object value = pwmRequest.getAttribute( requestAttr );
         return value != null && Boolean.parseBoolean( value.toString() );
     }
@@ -92,44 +89,30 @@ public abstract class JspUtility
 
     public static void setFlag( final PageContext pageContext, final PwmRequestFlag flag, final boolean value )
     {
-        final PwmRequest pwmRequest;
-        try
-        {
-            pwmRequest = PwmRequest.forRequest(
-                    ( HttpServletRequest ) pageContext.getRequest(),
-                    ( HttpServletResponse ) pageContext.getResponse()
-            );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.warn( () -> "unable to load pwmRequest object during jsp execution: " + e.getMessage() );
-            return;
-        }
-        if ( pwmRequest != null )
+        forRequest( pageContext.getRequest() ).ifPresent( pwmRequest ->
         {
             pwmRequest.setFlag( flag, value );
-        }
+        } );
     }
 
     public static boolean isFlag( final HttpServletRequest request, final PwmRequestFlag flag )
+            throws PwmUnrecoverableException
     {
-        final PwmRequest pwmRequest = forRequest( request );
-        return pwmRequest != null && pwmRequest.isFlag( flag );
+        final PwmRequest pwmRequest = forRequest( request )
+                .orElseThrow( JspUtility::makeMissingRequestException );
+        return pwmRequest.isFlag( flag );
     }
 
     public static Locale locale( final HttpServletRequest request )
     {
-        final PwmRequest pwmRequest = forRequest( request );
-        if ( pwmRequest != null )
-        {
-            return pwmRequest.getLocale();
-        }
-        return PwmConstants.DEFAULT_LOCALE;
+        return forRequest( request ).map( PwmRequest::getLocale ).orElse( PwmConstants.DEFAULT_LOCALE );
     }
 
     public static long numberSetting( final HttpServletRequest request, final PwmSetting pwmSetting, final long defaultValue )
+            throws PwmUnrecoverableException
     {
-        final PwmRequest pwmRequest = forRequest( request );
+        final PwmRequest pwmRequest = forRequest( request )
+                .orElseThrow( JspUtility::makeMissingRequestException );
         if ( pwmRequest != null )
         {
             try
@@ -146,39 +129,48 @@ public abstract class JspUtility
 
     public static void logError( final PageContext pageContext, final String message )
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        final PwmLogger logger = PwmLogger.getLogger( "jsp:" + pageContext.getPage().getClass() );
-        logger.error( pwmRequest, () -> message );
+        forRequest( pageContext.getRequest() ).ifPresent( pwmRequest ->
+        {
+            final PwmLogger logger = PwmLogger.getLogger( "jsp:" + pageContext.getPage().getClass() );
+            logger.error( pwmRequest, () -> message );
+        } );
     }
 
     public static String getMessage( final PageContext pageContext, final PwmDisplayBundle key )
+            throws PwmUnrecoverableException
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
+        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() )
+                .orElseThrow( JspUtility::makeMissingRequestException );
         return LocaleHelper.getLocalizedMessage( key, pwmRequest );
     }
 
     public static PwmSession getPwmSession( final PageContext pageContext )
+            throws PwmUnrecoverableException
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
+        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() )
+                .orElseThrow( JspUtility::makeMissingRequestException );
         return pwmRequest.getPwmSession();
     }
 
     public static PwmRequest getPwmRequest( final PageContext pageContext )
+            throws PwmUnrecoverableException
     {
-        return forRequest( pageContext.getRequest() );
+        return forRequest( pageContext.getRequest() )
+                .orElseThrow( JspUtility::makeMissingRequestException );
     }
 
     public static String friendlyWrite( final PageContext pageContext, final boolean value )
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        return LocaleHelper.valueBoolean( pwmRequest.getLocale(), value );
+        return forRequest( pageContext.getRequest() )
+                .map( pwmRequest -> LocaleHelper.valueBoolean( pwmRequest.getLocale(), value ) )
+                .orElse( "" );
     }
 
     public static String friendlyWrite( final PageContext pageContext, final long value )
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        final NumberFormat numberFormat = NumberFormat.getInstance( pwmRequest.getLocale() );
-        return numberFormat.format( value );
+        return forRequest( pageContext.getRequest() )
+                .map( pwmRequest -> NumberFormat.getInstance( pwmRequest.getLocale() ).format( value ) )
+                .orElse( "" );
     }
 
     public static String friendlyWrite( final PageContext pageContext, final String input )
@@ -212,24 +204,23 @@ public abstract class JspUtility
 
     public static String friendlyWriteNotApplicable( final PageContext pageContext )
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        return LocaleHelper.valueNotApplicable( pwmRequest.getLocale() );
+        return forRequest( pageContext.getRequest() )
+                .map( pwmRequest -> LocaleHelper.valueNotApplicable( pwmRequest.getLocale() ) )
+                .orElse( "" );
     }
 
     public static String friendlyWrite( final PageContext pageContext, final Instant instant )
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        if ( instant == null )
-        {
-            return LocaleHelper.valueNotApplicable( pwmRequest.getLocale() );
-        }
-        return "<span class=\"timestamp\">" + instant + "</span>";
+        return forRequest( pageContext.getRequest() )
+                .map( pwmRequest -> "<span class=\"timestamp\">" + instant + "</span>" )
+                .orElse( "" );
     }
 
     public static String localizedString( final PageContext pageContext, final String key, final Class<? extends PwmDisplayBundle> bundleClass, final String... values )
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        return LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), key, pwmRequest.getDomainConfig(), bundleClass, values );
+        return forRequest( pageContext.getRequest() )
+                .map( pwmRequest -> LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), key, pwmRequest.getDomainConfig(), bundleClass, values ) )
+                .orElse( "" );
     }
 }
 

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java

@@ -22,8 +22,8 @@ package password.pwm.http.servlet.configeditor.data;
 
 import lombok.Builder;
 import lombok.Value;
+import password.pwm.EnvironmentProperty;
 import password.pwm.PwmConstants;
-import password.pwm.PwmEnvironment;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
@@ -67,7 +67,7 @@ public class NavTreeSettings implements Serializable
         final int level = ( int ) ( ( double ) inputParameters.get( "level" ) );
         final String filterText = ( String ) inputParameters.get( "text" );
         final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest );
-        final boolean manageHttps = pwmRequest.getPwmApplication().getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.ManageHttps );
+        final boolean manageHttps = pwmRequest.getPwmApplication().getPwmEnvironment().readPropertyAsBoolean( EnvironmentProperty.ManageHttps );
 
         return NavTreeSettings.builder()
                 .modifiedSettingsOnly( modifiedSettingsOnly )

+ 0 - 16
server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -451,22 +451,6 @@ public enum PwmIfTest
         }
     }
 
-    private static class EnvironmentFlagTest implements Test
-    {
-        private final PwmEnvironment.ApplicationFlag flag;
-
-        EnvironmentFlagTest( final PwmEnvironment.ApplicationFlag flag )
-        {
-            this.flag = flag;
-        }
-
-        @Override
-        public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            return pwmRequest.getPwmApplication().getPwmEnvironment().getFlags().contains( flag );
-        }
-    }
-
     private static class EndUserFunctionalityTest implements Test
     {
         @Override

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

@@ -20,9 +20,9 @@
 
 package password.pwm.svc;
 
+import password.pwm.EnvironmentProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.PwmEnvironment;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.error.ErrorInformation;
@@ -84,7 +84,7 @@ public class PwmServiceManager
         final Instant startTime = Instant.now();
 
         final boolean internalRuntimeInstance = pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
-                || pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance );
+                || pwmApplication.getPwmEnvironment().readPropertyAsBoolean( EnvironmentProperty.CommandLineInstance );
 
         final String logVerb = initialized ? "restart" : "start";
         final StatisticCounterBundle<InitializationStats> statCounter = new StatisticCounterBundle<>( InitializationStats.class );

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

@@ -37,7 +37,6 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStream;
 import java.security.KeyStore;
-import java.util.Collections;
 import java.util.Properties;
 
 public class OnejarHelper
@@ -119,7 +118,6 @@ public class OnejarHelper
                 .applicationMode( PwmApplicationMode.READ_ONLY )
                 .configurationFile( configFile )
                 .internalRuntimeInstance( true )
-                .flags( Collections.singleton( PwmEnvironment.ApplicationFlag.CommandLineInstance ) )
                 .build();
 
         return PwmApplication.createPwmApplication( pwmEnvironment );

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

@@ -21,6 +21,7 @@
 package password.pwm.util.cli;
 
 import password.pwm.AppProperty;
+import password.pwm.EnvironmentProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
@@ -67,13 +68,12 @@ import password.pwm.util.localdb.LocalDBFactory;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
-import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.EnumSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -193,7 +193,7 @@ public class MainClass
 
         if ( parameters.needsPwmApplication )
         {
-            pwmApplication = loadPwmApplication( applicationPath, mainOptions.getApplicationFlags(), config, configurationFile, parameters.readOnly );
+            pwmApplication = loadPwmApplication( applicationPath, config, configurationFile, parameters.readOnly );
             localDB = pwmApplication.getLocalDB();
         }
         else if ( parameters.needsLocalDB )
@@ -428,7 +428,6 @@ public class MainClass
 
     private static PwmApplication loadPwmApplication(
             final File applicationPath,
-            final Collection<PwmEnvironment.ApplicationFlag> flags,
             final AppConfig config,
             final File configurationFile,
             final boolean readonly
@@ -436,22 +435,15 @@ public class MainClass
             throws PwmUnrecoverableException
     {
         final PwmApplicationMode mode = readonly ? PwmApplicationMode.READ_ONLY : PwmApplicationMode.RUNNING;
-        final Collection<PwmEnvironment.ApplicationFlag> applicationFlags = EnumSet.noneOf( PwmEnvironment.ApplicationFlag.class  );
-        if ( flags == null )
-        {
-            applicationFlags.addAll( PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem( null ) );
-        }
-        else
-        {
-            applicationFlags.addAll( flags );
-        }
-        applicationFlags.add( PwmEnvironment.ApplicationFlag.CommandLineInstance );
+        System.setProperty(
+                PwmConstants.PWM_APP_NAME.toLowerCase() + "." + EnvironmentProperty.CommandLineInstance.name(),
+                Boolean.TRUE.toString() );
+
         final PwmEnvironment pwmEnvironment = PwmEnvironment.builder()
                 .config( config )
                 .applicationPath( applicationPath )
                 .applicationMode( mode )
                 .configurationFile( configurationFile )
-                .flags( applicationFlags )
                 .build();
 
         final PwmApplication pwmApplication = PwmApplication.createPwmApplication( pwmEnvironment );
@@ -475,7 +467,7 @@ public class MainClass
         System.out.println( txt );
     }
 
-    private static File figureApplicationPath( final MainOptions mainOptions ) throws IOException, PwmUnrecoverableException
+    private static File figureApplicationPath( final MainOptions mainOptions ) throws PwmUnrecoverableException
     {
         final File applicationPath;
         if ( mainOptions != null && mainOptions.getApplicationPath() != null )
@@ -484,17 +476,17 @@ public class MainClass
         }
         else
         {
-            final Optional<String> appPathStr = PwmEnvironment.ParseHelper.readValueFromSystem( PwmEnvironment.EnvironmentParameter.applicationPath, null );
+            final Optional<Path> appPathStr = EnvironmentProperty.readApplicationPath( null );
             if ( appPathStr.isPresent() )
             {
-                applicationPath = new File( appPathStr.get() );
+                applicationPath = appPathStr.get().toFile();
             }
             else
             {
                 final String errorMsg = "unable to locate applicationPath.  Specify using -applicationPath option, java option "
-                        + "\"" + PwmEnvironment.EnvironmentParameter.applicationPath.conicalJavaOptionSystemName() + "\""
+                        + "\"" + EnvironmentProperty.applicationPath.conicalJavaOptionSystemName( null ) + "\""
                         + ", or system environment setting "
-                        + "\"" + PwmEnvironment.EnvironmentParameter.applicationPath.conicalEnvironmentSystemName() + "\"";
+                        + "\"" + EnvironmentProperty.applicationPath.conicalEnvironmentSystemName( null ) + "\"";
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_STARTUP_ERROR, errorMsg ) );
             }
         }

+ 1 - 31
server/src/main/java/password/pwm/util/cli/MainOptions.java

@@ -20,7 +20,6 @@
 
 package password.pwm.util.cli;
 
-import password.pwm.PwmEnvironment;
 import password.pwm.util.logging.PwmLogLevel;
 
 import java.io.File;
@@ -28,29 +27,24 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.io.Writer;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 
 public class MainOptions implements Serializable
 {
     private static final String OPT_DEBUG_LEVEL = "-debugLevel";
     private static final String OPT_APP_PATH = "-applicationPath";
-    private static final String OPT_APP_FLAGS = "-applicationFlags";
     private static final String OPT_FORCE = "-force";
 
 
     private PwmLogLevel pwmLogLevel;
     private File applicationPath;
     private boolean forceFlag;
-    private Collection<PwmEnvironment.ApplicationFlag> applicationFlags;
     private List<String> remainingArguments;
 
     MainOptions(
             final PwmLogLevel pwmLogLevel,
             final File applicationPath,
             final boolean forceFlag,
-            final Collection<PwmEnvironment.ApplicationFlag> applicationFlags,
             final List<String> remainingArguments
 
     )
@@ -58,9 +52,7 @@ public class MainOptions implements Serializable
         this.pwmLogLevel = pwmLogLevel;
         this.applicationPath = applicationPath;
         this.forceFlag = forceFlag;
-        this.applicationFlags = applicationFlags;
         this.remainingArguments = remainingArguments;
-
     }
 
     public PwmLogLevel getPwmLogLevel( )
@@ -78,11 +70,6 @@ public class MainOptions implements Serializable
         return forceFlag;
     }
 
-    public Collection<PwmEnvironment.ApplicationFlag> getApplicationFlags( )
-    {
-        return applicationFlags;
-    }
-
     public List<String> getRemainingArguments( )
     {
         return remainingArguments;
@@ -93,12 +80,9 @@ public class MainOptions implements Serializable
             final Writer debugWriter
     )
     {
-
-
         PwmLogLevel pwmLogLevel = null;
         File applicationPath = null;
         boolean forceFlag = false;
-        Collection<PwmEnvironment.ApplicationFlag> applicationFlags = Collections.emptyList();
         final List<String> remainingArguments;
 
         final List<String> outputArgs = new ArrayList<>();
@@ -146,20 +130,6 @@ public class MainOptions implements Serializable
                     {
                         forceFlag = true;
                     }
-                    else if ( arg.startsWith( OPT_APP_FLAGS ) )
-                    {
-                        if ( arg.length() < OPT_APP_FLAGS.length() + 2 )
-                        {
-                            out( debugWriter, OPT_APP_FLAGS + " option must include value (example: " + OPT_APP_FLAGS + "=Flag1,Flag2)" );
-                            System.exit( -1 );
-                        }
-                        else
-                        {
-                            final String flagStr = arg.substring( OPT_APP_PATH.length() + 1 );
-                            applicationFlags = PwmEnvironment.ParseHelper.parseApplicationFlagValueParameter( flagStr );
-                        }
-                        outputArgs.add( arg );
-                    }
                     else
                     {
                         outputArgs.add( arg );
@@ -169,7 +139,7 @@ public class MainOptions implements Serializable
         }
 
         remainingArguments = new ArrayList<>( outputArgs );
-        return new MainOptions( pwmLogLevel, applicationPath, forceFlag, applicationFlags, remainingArguments );
+        return new MainOptions( pwmLogLevel, applicationPath, forceFlag, remainingArguments );
     }
 
     static void out( final Writer debugWriter, final CharSequence out )

+ 1 - 0
server/src/main/resources/password/pwm/PwmConstants.properties

@@ -36,6 +36,7 @@ trial=false
 url.pwm-home=https://github.com/pwm-project/pwm
 paramName.token=token
 defaultConfigFilename=PwmConfiguration.xml
+defaultEnvironmentPropertiesFilename=application.properties
 defaultPropertiesConfigFilename=silent.properties
 enableEulaDisplay=false
 applicationPathInfoFile=applicationPath.properties

+ 64 - 0
server/src/test/java/password/pwm/EnvironmentPropertyTest.java

@@ -0,0 +1,64 @@
+/*
+ * 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 org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Properties;
+
+class EnvironmentPropertyTest
+{
+    @TempDir
+    private Path temporaryPath;
+
+    @Test
+    public void testEnvironmentSystemProperties()
+    {
+        System.setProperty( EnvironmentProperty.applicationPath.conicalJavaOptionSystemName( null ), "/tmp/example" );
+        final Map<EnvironmentProperty, String> map = EnvironmentProperty.readApplicationParams( null, null );
+        Assertions.assertEquals( "/tmp/example", map.get( EnvironmentProperty.applicationPath ) );
+    }
+
+    @Test
+    public void testAppPropertyFileProperties()
+            throws Exception
+    {
+        {
+            final Path propFilePath = Path.of( temporaryPath.toString(), PwmConstants.DEFAULT_ENVIRONMENT_PROPERTIES_FILENAME );
+            final Properties outputProps = new Properties();
+            outputProps.setProperty( EnvironmentProperty.InstanceID.name(), "TEST-VALUE-22" );
+            try ( OutputStream outputStream = Files.newOutputStream( propFilePath ) )
+            {
+                outputProps.store( outputStream, "test" );
+            }
+        }
+
+        final Map<EnvironmentProperty, String> props = EnvironmentProperty.readApplicationParams( temporaryPath, null );
+
+        Assertions.assertEquals( "TEST-VALUE-22", props.get( EnvironmentProperty.InstanceID ) );
+    }
+}

+ 41 - 23
webapp/src/main/webapp/public/reference/environment.jsp

@@ -57,18 +57,27 @@
     <div id="centerbody">
         <%@ include file="reference-nav.jsp"%>
         <h1><%=PwmConstants.PWM_APP_NAME%> Environment</h1>
-        <h2><code>ApplicationPath</code></h2>
-        <p>The application path setting is required.  Configure the application path to use a directory on the local filesystem.  <%=PwmConstants.PWM_APP_NAME%> will store it's
-            operating files in this directory.  Specifically, the following contents will be stored in the application path:</p>
+        <h2><code>Application Path</code></h2>
+        <p>
+            The application path is the primary configuration and working directory for an instance of <%=PwmConstants.PWM_APP_NAME%>.
+            Each instance of <%=PwmConstants.PWM_APP_NAME%> must have its own unique application path.
+            The following contents are stored in the application path directory:
+        </p>
         <ul>
-            <li>Configuration</li>
+            <li>configuration</li>
+            <li>log files</li>
+            <li>backup data</li>
             <li>LocalDB database</li>
-            <li>Log files</li>
-            <li>Backup data</li>
+            <ul>
+                <li>temporary working files</li>
+                <li>caches</li>
+                <li>server statistics</li>
+            </ul>
         </ul>
         <p>
-            The path value is given as an absolute directory path on the local file system.  The <%=PwmConstants.PWM_APP_NAME%> application must have full permissions to create, modify and delete folders in this directory.  The directory must already be exist when <%=PwmConstants.PWM_APP_NAME%> is started, it will not be
-            automatically created.
+            The path value is given as an absolute directory path on the local file system.
+            The <%=PwmConstants.PWM_APP_NAME%> application must have full permissions to create, modify and delete folders in this directory.
+            The directory must already be exist when <%=PwmConstants.PWM_APP_NAME%> is started, it will not be automatically created.
         </p>
         <p>
             Older versions of <%=PwmConstants.PWM_APP_NAME%> did not require the application path to be set and would automatically use the exploded war directory's
@@ -83,8 +92,14 @@
             <li><a href="#webxml">Servlet <code>web.xml</code></a></li>
         </ul>
         <h3><a id="envvar">Environment Variable (Recommended)</a></h3>
-        <p>The application will read the <b><%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</b> variable to determine the location of the application path.  Relative paths are not permitted.</p>
-        <p>Because you set this value at the OS level, it will make maintaining and updating the application easier because you will not need to change anything other than applying a new <code>war</code> file.</p>
+        <p>
+            The application will read the <b><%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</b> variable to determine the location of the application path.
+            Relative paths are not permitted.
+        </p>
+        <p>
+            Because you set this value at the OS level, it will make maintaining and updating the application easier because you will not need to change anything other than
+            applying a new <code>war</code> file.
+        </p>
         <h3>Linux Example</h3>
         <div class="codeExample">
             <code>export <%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH='/home/user/<%=PwmConstants.PWM_APP_NAME.toLowerCase()%>-data'</code>
@@ -109,8 +124,9 @@
             <code>-D<%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.applicationPath="c:\<%=PwmConstants.PWM_APP_NAME.toLowerCase()%>-data"</code>
         </div>
         <h3><a id="webxml">Servlet web.xml</a></h3>
-        Modify the servlet <code>WEB-INF/web.xml</code> file.  You must modify the application war file to accomplish this method.  This method accommodates multiple
-        applications on a single application server.  File paths must be absolute.
+        <p>Modify the servlet <code>WEB-INF/web.xml</code> file.  You must modify the application war file to accomplish this method.  This method accommodates multiple
+            applications on a single application server.  File paths must be absolute.</p><p>This method is not recommended because updates to the WAR will
+        overwrite the web.xml and you will need to re-apply any changes.</p>
         <h4>Linux Example</h4>
         <div class="codeExample">
             <code>
@@ -136,26 +152,25 @@
             <code>export <%=PwmConstants.PWM_APP_NAME%>_APPLICATIONFLAGS='ManageHttps,NoFileLock'</code>
         </div>
         <br/>
-        <h1>Environment Variable Names and Servlet Context</h1>
+        <h1>Property Names and Servlet Context</h1>
         <p>
-            The environment variables listed above, such as <code><%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</code> and <code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.applicationPath</code> assume the default context name of
-            <code>/<%=PwmConstants.PWM_APP_NAME.toLowerCase()%>/</code> is used.  The context path will default to the war file name on most systems.
+            The environment variables and system properties listed above, such as <code><%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</code> and <code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.applicationPath</code> assume the default context name of
+            <code>/<%=PwmConstants.PWM_APP_NAME.toLowerCase()%></code> is used.
         </p>
         <p>
-            For non-default context names, or when there are multiple <%=PwmConstants.PWM_APP_NAME%> applications deployed on the same server, the environment variable must include the context.  For example:
         </p>
         <table>
             <tr>
-                <td><h3>Context</h3></td><td><h3>Environment Property Name</h3></td><td><h3>Environment Variable Name</h3></td>
+                <td><h3>Context</h3></td><td><h3>Java System Property Name</h3></td><td><h3>Environment Variable Name</h3></td>
             </tr>
             <tr>
-                <td><code>/<%=PwmConstants.PWM_APP_NAME.toLowerCase()%>/</code></td><td><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.applicationPath</code><br/>or<br/><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.<%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.applicationPath</code></td><td><code><%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</code><br/>or<br/><code><%=PwmConstants.PWM_APP_NAME%>_<%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</code></td>
+                <td><code>/<%=PwmConstants.PWM_APP_NAME.toLowerCase()%></code></td><td><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.applicationPath</code><br/>or<br/><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.<%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.applicationPath</code></td><td><code><%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</code><br/>or<br/><code><%=PwmConstants.PWM_APP_NAME%>_<%=PwmConstants.PWM_APP_NAME%>_APPLICATIONPATH</code></td>
             </tr>
             <tr>
-                <td><code>/acme/</code></td><td><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.acme.applicationPath</code></td><td><code><%=PwmConstants.PWM_APP_NAME%>_ACME_APPLICATIONPATH</code></td>
+                <td><code>/acme</code></td><td><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.acme.applicationPath</code></td><td><code><%=PwmConstants.PWM_APP_NAME%>_ACME_APPLICATIONPATH</code></td>
             </tr>
             <tr>
-                <td><code>/acme2/</code></td><td><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.acme2.applicationPath</code></td><td><code><%=PwmConstants.PWM_APP_NAME%>_ACME2_APPLICATIONPATH</code></td>
+                <td><code>/acme2</code></td><td><code><%=PwmConstants.PWM_APP_NAME.toLowerCase()%>.acme2.applicationPath</code></td><td><code><%=PwmConstants.PWM_APP_NAME%>_ACME2_APPLICATIONPATH</code></td>
             </tr>
         </table>
         <p>In case of conflict, the application path parameters are evaluated in the following order.</p>
@@ -164,13 +179,16 @@
             <li><a href="#property">Java System Property</a></li>
             <li><a href="#envvar">Environment Variable (Recommended)</a></li>
         </ol>
-        <h2><code>ApplicationFlags</code></h2>
-        <p>Application flags can be set to enable or disable behaviors in <%=PwmConstants.PWM_APP_NAME%>.   By default, no flags are set.  Setting flags is optional.  Flags are specified as a comma seperated
-            list of values.  Values are case sensitive.  In most cases, you will not need to set an application flag.
+        <h2><code>Other <%=PwmConstants.PWM_APP_NAME%> Environment Properties</code></h2>
+        <p>The <code>applicationPath</code> property is just one of many that can be set.  The following properties also exist, and are set the same way as the <code>applicationPath</code>
+            describe above.  Additionally, properties can also be set using a <i>application.properties</i> file in the root of the
+            <code>applicationPath</code>directory, though in that case the keys do not need a context prefix.
         <table>
             <tr><td><h3>Flag</h3></td><td><h3>Behavior</h3></td></tr>
+            <tr><td>applicationPath</td><td>Root of the application path directory as discussed above.</td></tr>
             <tr><td>ManageHttps</td><td>Enable the setting category <code><%=PwmSettingCategory.HTTPS_SERVER.getLabel(PwmConstants.DEFAULT_LOCALE)%></code></td></tr>
             <tr><td>NoFileLock</td><td>Disable the file lock in the application path directory.</td></tr>
+            <tr><td>InstanceID</td><td>Override the default random-generated InstanceID value</td></tr>
         </table>
     </div>
 </div>