ソースを参照

improve restart speeds

jrivard@gmail.com 6 年 前
コミット
160dc6a6b2
25 ファイル変更440 行追加366 行削除
  1. 3 3
      docker/pom.xml
  2. 3 3
      onejar/pom.xml
  3. 47 31
      onejar/src/main/java/password/pwm/onejar/ArgumentParser.java
  4. 12 61
      onejar/src/main/java/password/pwm/onejar/OnejarConfig.java
  5. 2 2
      onejar/src/main/java/password/pwm/onejar/OnejarException.java
  6. 83 0
      onejar/src/main/java/password/pwm/onejar/OnejarMain.java
  7. 34 70
      onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java
  8. 2 0
      server/src/main/java/password/pwm/AppProperty.java
  9. 1 1
      server/src/main/java/password/pwm/PwmApplication.java
  10. 14 6
      server/src/main/java/password/pwm/config/Configuration.java
  11. 5 5
      server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
  12. 1 1
      server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  13. 41 32
      server/src/main/java/password/pwm/http/ContextManager.java
  14. 1 0
      server/src/main/java/password/pwm/http/PwmRequestAttribute.java
  15. 20 8
      server/src/main/java/password/pwm/http/PwmSession.java
  16. 12 1
      server/src/main/java/password/pwm/svc/PwmServiceManager.java
  17. 1 1
      server/src/main/java/password/pwm/svc/cache/CacheService.java
  18. 2 2
      server/src/main/java/password/pwm/util/java/JavaHelper.java
  19. 1 1
      server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java
  20. 19 9
      server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java
  21. 10 0
      server/src/main/java/password/pwm/util/secure/SecureService.java
  22. 3 1
      server/src/main/resources/password/pwm/AppProperty.properties
  23. 2 0
      webapp/src/main/webapp/META-INF/context.xml
  24. 112 126
      webapp/src/main/webapp/private/index.jsp
  25. 9 2
      webapp/src/main/webapp/public/resources/js/configeditor.js

+ 3 - 3
docker/pom.xml

@@ -25,7 +25,7 @@
             <plugin>
                 <groupId>com.google.cloud.tools</groupId>
                 <artifactId>jib-maven-plugin</artifactId>
-                <version>0.9.9</version>
+                <version>0.9.10</version>
                 <executions>
                     <execution>
                         <id>make-docker-image</id>
@@ -42,7 +42,7 @@
                                     <jvmFlag>-Xms1g</jvmFlag>
                                     <jvmFlag>-Xmx1g</jvmFlag>
                                 </jvmFlags>
-                                <mainClass>password.pwm.onejar.TomcatOneJarMain</mainClass>
+                                <mainClass>password.pwm.onejar.TomcatOnejarRunner</mainClass>
                                 <args>
                                     <arg>-applicationPath</arg>
                                     <arg>/config</arg>
@@ -50,7 +50,7 @@
                                 <format>docker</format>
                                 <ports>8443</ports>
                             </container>
-                            <useCurrentTimestamp>true</useCurrentTimestamp>
+                            <!--<useCurrentTimestamp>true</useCurrentTimestamp>-->
                             <allowInsecureRegistries>true</allowInsecureRegistries>
                         </configuration>
                     </execution>

+ 3 - 3
onejar/pom.xml

@@ -17,7 +17,7 @@
 
     <properties>
         <project.root.basedir>${project.basedir}/..</project.root.basedir>
-        <tomcat.version>9.0.11</tomcat.version>
+        <tomcat.version>9.0.12</tomcat.version>
         <jetty-version>9.4.11.v20180605</jetty-version>
     </properties>
 
@@ -50,7 +50,7 @@
                     </descriptors>
                     <archive>
                         <manifestEntries>
-                            <Main-Class>password.pwm.onejar.TomcatOneJarMain</Main-Class>
+                            <Main-Class>password.pwm.onejar.OnejarMain</Main-Class>
                             <Implementation-Title>${project.name}</Implementation-Title>
                             <Implementation-Version>${project.version}</Implementation-Version>
                             <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
@@ -107,4 +107,4 @@
             <version>1.4</version>
         </dependency>
     </dependencies>
-</project>
+</project>

+ 47 - 31
onejar/src/main/java/password/pwm/onejar/ArgumentParser.java

@@ -46,7 +46,7 @@ public class ArgumentParser
     private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
 
     public OnejarConfig parseArguments( final String[] args )
-            throws ArgumentParserException, TomcatOneJarException
+            throws ArgumentParserException, OnejarException
     {
         if ( args == null || args.length == 0 )
         {
@@ -67,7 +67,7 @@ public class ArgumentParser
 
             if ( commandLine.hasOption( Argument.version.name() ) )
             {
-                TomcatOneJarMain.out( TomcatOneJarMain.getVersion() );
+                OnejarMain.output( TomcatOnejarRunner.getVersion() );
                 return null;
             }
             else if ( commandLine.hasOption( Argument.help.name() ) )
@@ -154,13 +154,15 @@ public class ArgumentParser
     }
 
 
-    private OnejarConfig makeTomcatConfig( final Map<Argument, String> argumentMap ) throws IOException, ArgumentParserException
+    private OnejarConfig makeTomcatConfig( final Map<Argument, String> argumentMap )
+            throws IOException, ArgumentParserException
     {
-        final OnejarConfig onejarConfig = new OnejarConfig();
-        onejarConfig.setKeystorePass( genRandomString( 32 ) );
-        onejarConfig.setApplicationPath( parseFileOption( argumentMap, Argument.applicationPath ) );
+        final OnejarConfig.OnejarConfigBuilder onejarConfig = OnejarConfig.builder();
+        onejarConfig.keystorePass( genRandomString( 32 ) );
+        onejarConfig.applicationPath( parseFileOption( argumentMap, Argument.applicationPath ) );
 
-        onejarConfig.setContext( argumentMap.getOrDefault( Argument.context, Resource.defaultContext.getValue() ) );
+        final String context = argumentMap.getOrDefault( Argument.context, Resource.defaultContext.getValue() );
+        onejarConfig.context( context );
 
         if ( argumentMap.containsKey( Argument.war ) )
         {
@@ -171,33 +173,43 @@ public class ArgumentParser
                 System.out.println( msg );
                 throw new IllegalStateException( msg );
             }
-            onejarConfig.setWar( new FileInputStream( inputWarFile ) );
+            onejarConfig.war( new FileInputStream( inputWarFile ) );
         }
         else
         {
-            onejarConfig.setWar( getEmbeddedWar() );
+            onejarConfig.war( getEmbeddedWar() );
         }
 
-        onejarConfig.setPort( Integer.parseInt( Resource.defaultPort.getValue() ) );
-        if ( argumentMap.containsKey( Argument.port ) )
+        final int port;
         {
-            try
+            final int defaultPort = Integer.parseInt( Resource.defaultPort.getValue() );
+            if ( argumentMap.containsKey( Argument.port ) )
             {
-                onejarConfig.setPort( Integer.parseInt( argumentMap.get( Argument.port ) ) );
+                try
+                {
+                    port = Integer.parseInt( argumentMap.get( Argument.port ) );
+                    onejarConfig.port( port );
+                }
+                catch ( NumberFormatException e )
+                {
+                    final String msg = Argument.port.name() + " argument must be numeric";
+                    System.out.println( msg );
+                    throw new IllegalStateException( msg );
+                }
             }
-            catch ( NumberFormatException e )
+            else
             {
-                final String msg = Argument.port.name() + " argument must be numeric";
-                System.out.println( msg );
-                throw new IllegalStateException( msg );
+                port = defaultPort;
             }
         }
+        onejarConfig.port( port );
 
-        onejarConfig.setLocalAddress( argumentMap.getOrDefault( Argument.localAddress, Resource.defaultLocalAddress.getValue() ) );
+        final String localAddress = argumentMap.getOrDefault( Argument.localAddress, Resource.defaultLocalAddress.getValue() );
+        onejarConfig.localAddress( localAddress );
 
         try
         {
-            final ServerSocket socket = new ServerSocket( onejarConfig.getPort(), 100, InetAddress.getByName( onejarConfig.getLocalAddress() ) );
+            final ServerSocket socket = new ServerSocket( port, 100, InetAddress.getByName( localAddress ) );
             socket.close();
         }
         catch ( Exception e )
@@ -207,21 +219,21 @@ public class ArgumentParser
 
         if ( argumentMap.containsKey( Argument.workPath ) )
         {
-            onejarConfig.setWorkingPath( parseFileOption( argumentMap, Argument.workPath ) );
+            onejarConfig.workingPath( parseFileOption( argumentMap, Argument.workPath ) );
         }
         else
         {
-            onejarConfig.setWorkingPath( figureDefaultWorkPath( onejarConfig ) );
+            onejarConfig.workingPath( figureDefaultWorkPath( localAddress, context, port ) );
         }
 
-        return onejarConfig;
+        return onejarConfig.build();
     }
 
 
-    private static void outputHelp( ) throws TomcatOneJarException
+    private static void outputHelp( ) throws OnejarException
     {
         final HelpFormatter formatter = new HelpFormatter();
-        System.out.println( TomcatOneJarMain.getVersion() );
+        System.out.println( TomcatOnejarRunner.getVersion() );
         System.out.println( "usage:" );
         formatter.printOptions(
                 System.console().writer(),
@@ -250,7 +262,11 @@ public class ArgumentParser
         return file;
     }
 
-    private static File figureDefaultWorkPath( final OnejarConfig onejarConfig )
+    private static File figureDefaultWorkPath(
+            final String localAddress,
+            final String context,
+            final int port
+    )
             throws ArgumentParserException, IOException
     {
         final String userHomePath = System.getProperty( "user.home" );
@@ -265,20 +281,20 @@ public class ArgumentParser
             {
                 String workPathStr = basePath.getPath() + File.separator + "work"
                         + "-"
-                        + escapeFilename( onejarConfig.getContext() )
+                        + escapeFilename( context )
                         + "-"
-                        + escapeFilename( Integer.toString( onejarConfig.getPort() ) );
+                        + escapeFilename( Integer.toString( port ) );
 
-                if ( onejarConfig.getLocalAddress() != null && !onejarConfig.getLocalAddress().isEmpty() )
+                if ( localAddress != null && !localAddress.isEmpty() )
                 {
-                    workPathStr += "-" + escapeFilename( onejarConfig.getLocalAddress() );
+                    workPathStr += "-" + escapeFilename( localAddress );
 
                 }
                 workPath = workPathStr;
             }
             final File workFile = new File( workPath );
             mkdirs( workFile );
-            TomcatOneJarMain.out( "using work directory: " + workPath );
+            OnejarMain.output( "using work directory: " + workPath );
             return workFile;
         }
 
@@ -287,7 +303,7 @@ public class ArgumentParser
 
     private static InputStream getEmbeddedWar( ) throws IOException, ArgumentParserException
     {
-        final Class clazz = TomcatOneJarMain.class;
+        final Class clazz = TomcatOnejarRunner.class;
         final String className = clazz.getSimpleName() + ".class";
         final String classPath = clazz.getResource( className ).toString();
         if ( !classPath.startsWith( "jar" ) )

+ 12 - 61
onejar/src/main/java/password/pwm/onejar/OnejarConfig.java

@@ -22,9 +22,15 @@
 
 package password.pwm.onejar;
 
+import lombok.Builder;
+import lombok.Value;
+
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 
+@Value
+@Builder
 class OnejarConfig
 {
     private int port;
@@ -35,73 +41,18 @@ class OnejarConfig
     private String localAddress;
     private String keystorePass;
 
-    public int getPort( )
-    {
-        return port;
-    }
-
-    public void setPort( final int port )
-    {
-        this.port = port;
-    }
-
-    public File getApplicationPath( )
-    {
-        return applicationPath;
-    }
-
-    public void setApplicationPath( final File applicationPath )
-    {
-        this.applicationPath = applicationPath;
-    }
-
-    public File getWorkingPath( )
-    {
-        return workingPath;
-    }
-
-    public void setWorkingPath( final File workingPath )
-    {
-        this.workingPath = workingPath;
-    }
-
-    public InputStream getWar( )
-    {
-        return war;
-    }
-
-    public void setWar( final InputStream war )
-    {
-        this.war = war;
-    }
-
-    public String getContext( )
-    {
-        return context;
-    }
-
-    public void setContext( final String context )
-    {
-        this.context = context;
-    }
-
-    public String getLocalAddress( )
-    {
-        return localAddress;
-    }
-
-    public void setLocalAddress( final String localAddress )
+    File getWarFolder( ) throws IOException
     {
-        this.localAddress = localAddress;
+        return new File( this.getWorkingPath().getAbsoluteFile() + File.separator + "war" );
     }
 
-    public String getKeystorePass( )
+    File getKeystoreFile( )
     {
-        return keystorePass;
+        return new File( this.getWorkingPath().getAbsoluteFile() + File.separator + "keystore" );
     }
 
-    public void setKeystorePass( final String keystorePass )
+    File getPwmAppPropertiesFile( )
     {
-        this.keystorePass = keystorePass;
+        return new File( this.getWorkingPath().getAbsoluteFile() + File.separator + "application.properties" );
     }
 }

+ 2 - 2
onejar/src/main/java/password/pwm/onejar/TomcatOneJarException.java → onejar/src/main/java/password/pwm/onejar/OnejarException.java

@@ -22,9 +22,9 @@
 
 package password.pwm.onejar;
 
-public class TomcatOneJarException extends Exception
+public class OnejarException extends Exception
 {
-    public TomcatOneJarException( final String msg )
+    public OnejarException( final String msg )
     {
         super( msg );
     }

+ 83 - 0
onejar/src/main/java/password/pwm/onejar/OnejarMain.java

@@ -0,0 +1,83 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.onejar;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+
+public class OnejarMain
+{
+    //private static final String TEMP_WAR_FILE_NAME = "embed.war";
+    static final String KEYSTORE_ALIAS = "https";
+
+
+    public static void main( final String[] args )
+    {
+        final ArgumentParser argumentParser = new ArgumentParser();
+        OnejarConfig onejarConfig = null;
+        try
+        {
+            onejarConfig = argumentParser.parseArguments( args );
+        }
+        catch ( ArgumentParserException | OnejarException e )
+        {
+            output( "error parsing command line: " + e.getMessage() );
+        }
+
+        final OnejarMain onejarMain = new OnejarMain();
+        onejarMain.run( onejarConfig );
+    }
+
+    void run( final OnejarConfig onejarConfig )
+    {
+        final TomcatOnejarRunner runner = new TomcatOnejarRunner( this );
+        final Instant startTime = Instant.now();
+
+        if ( onejarConfig != null )
+        {
+            try
+            {
+                runner.startTomcat( onejarConfig );
+            }
+            catch ( OnejarException | ServletException | IOException e )
+            {
+                out( "error starting tomcat: " + e.getMessage() );
+            }
+        }
+
+        final Duration duration = Duration.between( startTime, Instant.now() );
+        out( "exiting after " + duration.toString() );
+    }
+
+    void out( final String output )
+    {
+        output( output );
+    }
+
+    static void output( final String output )
+    {
+        System.out.println( output );
+    }
+}

+ 34 - 70
onejar/src/main/java/password/pwm/onejar/TomcatOneJarMain.java → onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java

@@ -57,57 +57,20 @@ import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
-public class TomcatOneJarMain
+public class TomcatOnejarRunner
 {
-    //private static final String TEMP_WAR_FILE_NAME = "embed.war";
-    private static final String KEYSTORE_ALIAS = "https";
+    final OnejarMain onejarMain;
 
-    public static void main( final String[] args )
+    public TomcatOnejarRunner( final OnejarMain onejarMain )
     {
-        final ArgumentParser argumentParser = new ArgumentParser();
-        OnejarConfig onejarConfig = null;
-        try
-        {
-            onejarConfig = argumentParser.parseArguments( args );
-        }
-        catch ( ArgumentParserException | TomcatOneJarException e )
-        {
-            out( "error parsing command line: " + e.getMessage() );
-        }
-        if ( onejarConfig != null )
-        {
-            try
-            {
-                startTomcat( onejarConfig );
-            }
-            catch ( TomcatOneJarException | ServletException | IOException e )
-            {
-                out( "error starting tomcat: " + e.getMessage() );
-            }
-        }
+        this.onejarMain = onejarMain;
     }
 
-    private static File getWarFolder( final OnejarConfig onejarConfig ) throws IOException
-    {
-        return new File( onejarConfig.getWorkingPath().getAbsoluteFile() + File.separator + "war" );
-    }
-
-    private static File getKeystoreFile( final OnejarConfig onejarConfig )
-    {
-        return new File( onejarConfig.getWorkingPath().getAbsoluteFile() + File.separator + "keystore" );
-    }
-
-    private static File getPwmAppPropertiesFile( final OnejarConfig onejarConfig )
-    {
-        return new File( onejarConfig.getWorkingPath().getAbsoluteFile() + File.separator + "application.properties" );
-    }
-
-
-    private static void explodeWar( final OnejarConfig onejarConfig ) throws IOException
+    private void explodeWar( final OnejarConfig onejarConfig ) throws IOException
     {
         final InputStream warSource = onejarConfig.getWar();
         final ZipInputStream zipInputStream = new ZipInputStream( warSource );
-        final File outputFolder = getWarFolder( onejarConfig );
+        final File outputFolder = onejarConfig.getWarFolder( );
 
         ArgumentParser.mkdirs( outputFolder );
 
@@ -128,7 +91,8 @@ public class TomcatOneJarMain
 
     }
 
-    private static void startTomcat( final OnejarConfig onejarConfig ) throws ServletException, IOException, TomcatOneJarException
+    void startTomcat( final OnejarConfig onejarConfig )
+            throws ServletException, IOException, OnejarException
     {
         final Instant startTime = Instant.now();
 
@@ -144,7 +108,7 @@ public class TomcatOneJarMain
         }
         catch ( Exception e )
         {
-            throw new TomcatOneJarException( "error generating keystore: " + e.getMessage() );
+            throw new OnejarException( "error generating keystore: " + e.getMessage() );
         }
 
         outputPwmAppProperties( onejarConfig );
@@ -175,7 +139,7 @@ public class TomcatOneJarMain
 
         deployRedirectConnector( tomcat, onejarConfig );
 
-        final String warPath = getWarFolder( onejarConfig ).getAbsolutePath();
+        final String warPath = onejarConfig.getWarFolder().getAbsolutePath();
         tomcat.addWebapp( "/" + onejarConfig.getContext(), warPath );
 
 
@@ -189,14 +153,14 @@ public class TomcatOneJarMain
         }
         catch ( LifecycleException e )
         {
-            throw new TomcatOneJarException( "unable to start tomcat: " + e.getMessage() );
+            throw new OnejarException( "unable to start tomcat: " + e.getMessage() );
         }
         tomcat.getServer().await();
 
-        System.out.println( "\n" );
+        System.out.println( "\nexiting..." );
     }
 
-    private static void deployRedirectConnector( final Tomcat tomcat, final OnejarConfig onejarConfig )
+    private void deployRedirectConnector( final Tomcat tomcat, final OnejarConfig onejarConfig )
             throws IOException, ServletException
     {
         final String srcRootWebXml = "ROOT-redirect-webapp/WEB-INF/web.xml";
@@ -220,7 +184,7 @@ public class TomcatOneJarMain
     }
 
 
-    private static Connector makeConnector( final OnejarConfig onejarConfig )
+    private Connector makeConnector( final OnejarConfig onejarConfig )
     {
         final Connector connector = new Connector( "HTTP/1.1" );
         connector.setPort( onejarConfig.getPort() );
@@ -232,19 +196,19 @@ public class TomcatOneJarMain
         connector.setSecure( true );
         connector.setScheme( "https" );
         connector.setAttribute( "SSLEnabled", "true" );
-        connector.setAttribute( "keystoreFile", getKeystoreFile( onejarConfig ).getAbsolutePath() );
+        connector.setAttribute( "keystoreFile", onejarConfig.getKeystoreFile().getAbsolutePath() );
         connector.setAttribute( "keystorePass", onejarConfig.getKeystorePass() );
-        connector.setAttribute( "keyAlias", KEYSTORE_ALIAS );
+        connector.setAttribute( "keyAlias", OnejarMain.KEYSTORE_ALIAS );
         connector.setAttribute( "clientAuth", "false" );
 
         return connector;
     }
 
-    static String getVersion( ) throws TomcatOneJarException
+     static String getVersion( ) throws OnejarException
     {
         try
         {
-            final Class clazz = TomcatOneJarMain.class;
+            final Class clazz = TomcatOnejarRunner.class;
             final String className = clazz.getSimpleName() + ".class";
             final String classPath = clazz.getResource( className ).toString();
             if ( !classPath.startsWith( "jar" ) )
@@ -261,11 +225,11 @@ public class TomcatOneJarMain
         }
         catch ( IOException e )
         {
-            throw new TomcatOneJarException( "error reading internal version info: " + e.getMessage() );
+            throw new OnejarException( "error reading internal version info: " + e.getMessage() );
         }
     }
 
-    private static void purgeDirectory( final Path rootPath )
+    private void purgeDirectory( final Path rootPath )
             throws IOException
     {
         System.out.println( "purging work directory: " + rootPath );
@@ -277,17 +241,17 @@ public class TomcatOneJarMain
     }
 
 
-    static void out( final String output )
+    void out( final String output )
     {
-        System.out.println( output );
+        onejarMain.out( output );
     }
 
 
-    static void generatePwmKeystore( final OnejarConfig onejarConfig )
+    void generatePwmKeystore( final OnejarConfig onejarConfig )
             throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException
     {
-        final File warPath = getWarFolder( onejarConfig );
-        final String keystoreFile = getKeystoreFile( onejarConfig ).getAbsolutePath();
+        final File warPath = onejarConfig.getWarFolder();
+        final String keystoreFile = onejarConfig.getKeystoreFile().getAbsolutePath();
         final File webInfPath = new File( warPath.getAbsolutePath() + File.separator + "WEB-INF" + File.separator + "lib" );
         final File[] jarFiles = webInfPath.listFiles();
         final List<URL> jarURLList = new ArrayList<>();
@@ -305,7 +269,7 @@ public class TomcatOneJarMain
                 "-applicationPath=" + onejarConfig.getApplicationPath().getAbsolutePath(),
                 "ExportHttpsKeyStore",
                 keystoreFile,
-                KEYSTORE_ALIAS,
+                OnejarMain.KEYSTORE_ALIAS,
                 onejarConfig.getKeystorePass(),
         };
 
@@ -313,35 +277,35 @@ public class TomcatOneJarMain
         classLoader.close();
     }
 
-    static void setupEnv( final OnejarConfig onejarConfig )
+    void setupEnv( final OnejarConfig onejarConfig )
     {
         final String envVarPrefix = Resource.envVarPrefix.getValue();
         System.setProperty( envVarPrefix + "_APPLICATIONPATH", onejarConfig.getApplicationPath().getAbsolutePath() );
         System.setProperty( envVarPrefix + "_APPLICATIONFLAGS", "ManageHttps" );
-        System.setProperty( envVarPrefix + "_APPLICATIONPARAMFILE", getPwmAppPropertiesFile( onejarConfig ).getAbsolutePath() );
+        System.setProperty( envVarPrefix + "_APPLICATIONPARAMFILE", onejarConfig.getPwmAppPropertiesFile().getAbsolutePath() );
     }
 
-    static void outputPwmAppProperties( final OnejarConfig onejarConfig ) throws IOException
+    void outputPwmAppProperties( final OnejarConfig onejarConfig ) throws IOException
     {
         final Properties properties = new Properties();
-        properties.setProperty( "AutoExportHttpsKeyStoreFile", getKeystoreFile( onejarConfig ).getAbsolutePath() );
+        properties.setProperty( "AutoExportHttpsKeyStoreFile", onejarConfig.getKeystoreFile().getAbsolutePath() );
         properties.setProperty( "AutoExportHttpsKeyStorePassword", onejarConfig.getKeystorePass() );
-        properties.setProperty( "AutoExportHttpsKeyStoreAlias", KEYSTORE_ALIAS );
-        final File propFile = getPwmAppPropertiesFile( onejarConfig );
+        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" );
         }
     }
 
-    static void copyFileAndReplace(
+    void copyFileAndReplace(
             final String srcPath,
             final String destPath,
             final String rootcontext
     )
             throws IOException
     {
-        try ( InputStream inputStream = TomcatOneJarMain.class.getClassLoader().getResourceAsStream( srcPath ) )
+        try ( InputStream inputStream = TomcatOnejarRunner.class.getClassLoader().getResourceAsStream( srcPath ) )
         {
             try ( BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream, "UTF8" ) ) )
             {

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

@@ -117,6 +117,8 @@ public enum AppProperty
     HTTP_COOKIE_CAPTCHA_SKIP_NAME                   ( "http.cookie.captchaSkip.name" ),
     HTTP_COOKIE_CAPTCHA_SKIP_AGE                    ( "http.cookie.captchaSkip.age" ),
     HTTP_COOKIE_LOGIN_NAME                          ( "http.cookie.login.name" ),
+    HTTP_COOKIE_NONCE_NAME                          ( "http.cookie.nonce.name" ),
+    HTTP_COOKIE_NONCE_LENGTH                        ( "http.cookie.nonce.length" ),
     HTTP_BASIC_AUTH_CHARSET                         ( "http.basicAuth.charset" ),
     HTTP_BODY_MAXREAD_LENGTH                        ( "http.body.maxReadLength" ),
     HTTP_CLIENT_ALWAYS_LOG_ENTITIES                 ( "http.client.alwaysLogEntities" ),

+ 1 - 1
server/src/main/java/password/pwm/PwmApplication.java

@@ -296,7 +296,7 @@ public class PwmApplication
     {
         final Instant startTime = Instant.now();
 
-        LOGGER.debug( "loaded configuration: " + pwmEnvironment.getConfig().toDebugString() );
+        pwmEnvironment.getConfig().outputToLog();
 
         // detect if config has been modified since previous startup
         try

+ 14 - 6
server/src/main/java/password/pwm/config/Configuration.java

@@ -72,7 +72,6 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.PasswordData;
-import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
@@ -124,12 +123,21 @@ public class Configuration implements SettingReader
         }
     }
 
-    public String toDebugString( )
+    public void outputToLog( )
     {
-        final StringBuilder outputText = new StringBuilder();
-        outputText.append( "  " );
-        outputText.append( JsonUtil.serialize( StoredConfigurationUtil.toJsonDebugObject( storedConfiguration ), JsonUtil.Flag.PrettyPrint ) );
-        return outputText.toString().replaceAll( "\n", "\n  " );
+        final Map<String, String> debugStrings = storedConfiguration.getModifiedSettingDebugValues( PwmConstants.DEFAULT_LOCALE, true );
+        final List<String> outputStrings = new ArrayList<>();
+
+        for ( final Map.Entry<String, String> entry : debugStrings.entrySet() )
+        {
+            final String spacedValue = entry.getValue().replace( "\n", "\n   " );
+            final String output = " " + entry.getKey() + "\n   " + spacedValue + "\n";
+            outputStrings.add( output );
+        }
+
+        LOGGER.trace( "--begin current configuration output--" );
+        outputStrings.forEach( LOGGER::trace );
+        LOGGER.trace( "--end current configuration output--" );
     }
 
     public List<FormConfiguration> readSettingAsForm( final PwmSetting setting )

+ 5 - 5
server/src/main/java/password/pwm/config/stored/ConfigurationReader.java

@@ -22,7 +22,6 @@
 
 package password.pwm.config.stored;
 
-import org.apache.commons.io.FileUtils;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
@@ -35,10 +34,10 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -137,8 +136,7 @@ public class ConfigurationReader
         final InputStream theFileData;
         try
         {
-            final byte[] contents = FileUtils.readFileToByteArray( configFile );
-            theFileData = new ByteArrayInputStream( contents );
+            theFileData = Files.newInputStream( configFile.toPath() );
         }
         catch ( Exception e )
         {
@@ -193,7 +191,9 @@ public class ConfigurationReader
             this.configMode = PwmApplicationMode.RUNNING;
         }
 
-        LOGGER.debug( "configuration reading/parsing complete in " + TimeDuration.fromCurrent( startTime ).asLongString() );
+        final String fileSize = StringUtil.formatDiskSize( configFile.length() );
+        final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+        LOGGER.debug( "configuration reading/parsing of " + fileSize + " complete in " + timeDuration.asLongString() );
 
         return storedConfiguration;
     }

+ 1 - 1
server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -455,7 +455,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
 
     public Map<String, String> getModifiedSettingDebugValues( final Locale locale, final boolean prettyPrint )
     {
-        final Map<String, String> returnObj = new LinkedHashMap<>();
+        final Map<String, String> returnObj = new TreeMap<>();
         for ( final SettingValueRecord record : this.modifiedSettings() )
         {
             final String label = record.getSetting().toMenuLocationDebug( record.getProfile(), locale );

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

@@ -36,6 +36,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
 
@@ -45,12 +46,16 @@ import javax.servlet.http.HttpSession;
 import java.io.File;
 import java.io.InputStream;
 import java.io.Serializable;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Timer;
 import java.util.TimerTask;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class ContextManager implements Serializable
 {
@@ -58,14 +63,13 @@ public class ContextManager implements Serializable
     private static final PwmLogger LOGGER = PwmLogger.forClass( ContextManager.class );
 
     private transient ServletContext servletContext;
-    private transient Timer taskMaster;
+    private transient ScheduledExecutorService taskMaster;
 
     private transient PwmApplication pwmApplication;
     private transient ConfigurationReader configReader;
     private ErrorInformation startupErrorInformation;
 
-    private volatile boolean restartRequestedFlag = false;
-    private int restartCount = 0;
+    private AtomicInteger restartCount = new AtomicInteger( 0 );
     private final String instanceGuid;
 
     private String contextPath;
@@ -215,9 +219,11 @@ public class ContextManager implements Serializable
             handleStartupError( "unable to initialize application: ", e );
         }
 
-        final String threadName = JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + " timer";
-        taskMaster = new Timer( threadName, true );
-        taskMaster.schedule( new RestartFlagWatcher(), 1031, 1031 );
+        taskMaster = Executors.newSingleThreadScheduledExecutor(
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + "-",
+                        true
+                ) );
 
         boolean reloadOnChange = true;
         long fileScanFrequencyMs = 5000;
@@ -229,7 +235,7 @@ public class ContextManager implements Serializable
             }
             if ( reloadOnChange )
             {
-                taskMaster.schedule( new ConfigFileWatcher(), fileScanFrequencyMs, fileScanFrequencyMs );
+                taskMaster.scheduleWithFixedDelay( new ConfigFileWatcher(), fileScanFrequencyMs, fileScanFrequencyMs, TimeUnit.MILLISECONDS );
             }
 
             checkConfigForSaveOnRestart( configReader, pwmApplication );
@@ -261,7 +267,7 @@ public class ContextManager implements Serializable
             final StoredConfigurationImpl newConfig = StoredConfigurationImpl.copy( configReader.getStoredConfiguration() );
             newConfig.writeConfigProperty( ConfigurationProperty.CONFIG_ON_START, "false" );
             configReader.saveConfiguration( newConfig, pwmApplication, null );
-            restartRequestedFlag = true;
+            requestPwmApplicationRestart();
         }
         catch ( Exception e )
         {
@@ -315,7 +321,7 @@ public class ContextManager implements Serializable
                 LOGGER.error( "unexpected error attempting to close application: " + e.getMessage() );
             }
         }
-        taskMaster.cancel();
+        taskMaster.shutdown();
 
 
         this.pwmApplication = null;
@@ -324,15 +330,8 @@ public class ContextManager implements Serializable
 
     public void requestPwmApplicationRestart( )
     {
-        restartRequestedFlag = true;
-        try
-        {
-            taskMaster.schedule( new ConfigFileWatcher(), 0 );
-        }
-        catch ( IllegalStateException e )
-        {
-            LOGGER.debug( "could not schedule config file watcher, timer is in illegal state: " + e.getMessage() );
-        }
+        LOGGER.debug( "immediate restart requested" );
+        taskMaster.schedule( new RestartFlagWatcher(), 0, TimeUnit.MILLISECONDS );
     }
 
     public ConfigurationReader getConfigReader( )
@@ -350,32 +349,37 @@ public class ContextManager implements Serializable
                 if ( configReader.modifiedSinceLoad() )
                 {
                     LOGGER.info( "configuration file modification has been detected" );
-                    restartRequestedFlag = true;
+                    requestPwmApplicationRestart();
                 }
             }
         }
     }
 
-    private class RestartFlagWatcher extends TimerTask
+    private class RestartFlagWatcher implements Runnable
     {
 
         public void run( )
         {
-            if ( restartRequestedFlag )
-            {
-                doReinitialize();
-            }
+            doReinitialize();
         }
 
         private void doReinitialize( )
         {
+            final Instant startTime = Instant.now();
+
             if ( configReader != null && configReader.isSaveInProgress() )
             {
-                LOGGER.info( "delaying restart request due to in progress file save" );
+                final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+                LOGGER.info( "delaying restart request due to in progress file save (" + timeDuration.asCompactString() + ")" );
+                taskMaster.schedule( new RestartFlagWatcher(), 1, TimeUnit.SECONDS );
                 return;
             }
 
-            LOGGER.info( "beginning application restart" );
+            {
+                final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+                LOGGER.info( "beginning application restart (" + timeDuration.asCompactString() + "), restart count=" + restartCount.incrementAndGet() );
+            }
+
             try
             {
                 shutdown();
@@ -385,12 +389,17 @@ public class ContextManager implements Serializable
                 LOGGER.fatal( "unexpected error during shutdown: " + e.getMessage(), e );
             }
 
-            LOGGER.info( "application restart; shutdown completed, now starting new application instance" );
-            restartCount++;
+            {
+                final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+                LOGGER.info( "application restart; shutdown completed, now starting new application instance ("
+                        + timeDuration.asCompactString() + ")" );
+            }
             initialize();
 
-            LOGGER.info( "application restart completed" );
-            restartRequestedFlag = false;
+            {
+                final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+                LOGGER.info( "application restart completed (" + timeDuration.asCompactString() + ")" );
+            }
         }
     }
 
@@ -401,7 +410,7 @@ public class ContextManager implements Serializable
 
     public int getRestartCount( )
     {
-        return restartCount;
+        return restartCount.get();
     }
 
     public File locateConfigurationFile( final File applicationPath )

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

@@ -89,6 +89,7 @@ public enum PwmRequestAttribute
     NewUser_VisibleProfiles,
 
     CookieBeanStorage,
+    CookieNonce,
 
     ShortcutItems,
     NextUrl,

+ 20 - 8
server/src/main/java/password/pwm/http/PwmSession.java

@@ -75,7 +75,6 @@ public class PwmSession implements Serializable
     private static final Object CREATION_LOCK = new Object();
 
     private transient SessionManager sessionManager;
-    private transient PwmSecurityKey sessionSecurityKey;
 
     public static PwmSession createPwmSession( final PwmApplication pwmApplication )
             throws PwmUnrecoverableException
@@ -367,15 +366,28 @@ public class PwmSession implements Serializable
     synchronized PwmSecurityKey getSecurityKey( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        if ( sessionSecurityKey == null )
+        final int length = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_LENGTH ) );
+        final String cookieName =  pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_NAME );
+
+        String nonce = (String) pwmRequest.getAttribute( PwmRequestAttribute.CookieNonce );
+        if ( nonce == null || nonce.length() != length )
+        {
+            nonce = pwmRequest.readCookie( cookieName );
+        }
+
+        if ( nonce == null || nonce.length() != length )
         {
-            final PasswordData configSecret = pwmRequest.getConfig().readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY );
-            final String sessionKey = pwmRequest.getHttpServletRequest().getSession().getId();
-            final String concatValue = configSecret.getStringValue() + sessionKey;
-            final String hashValue = pwmRequest.getPwmApplication().getSecureService().hash( concatValue );
-            sessionSecurityKey = new PwmSecurityKey( hashValue );
+            nonce = pwmRequest.getPwmApplication().getSecureService().pwmRandom().alphaNumericString( length );
         }
 
-        return sessionSecurityKey;
+        final PasswordData configSecret = pwmRequest.getConfig().readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY );
+        final String concatValue = configSecret.getStringValue() + nonce;
+        final String hashValue = pwmRequest.getPwmApplication().getSecureService().hash( concatValue );
+        final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( hashValue );
+
+        pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, nonce );
+        pwmRequest.getPwmResponse().writeCookie( cookieName, nonce, -1, PwmHttpResponseWrapper.CookiePath.Application );
+
+        return pwmSecurityKey;
     }
 }

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

@@ -131,6 +131,10 @@ public class PwmServiceManager
             return;
         }
 
+        LOGGER.trace( "beginning to close all services" );
+        final Instant startTime = Instant.now();
+
+
         final List<Class<? extends PwmService>> reverseServiceList = new ArrayList<>( PwmServiceEnum.allClasses() );
         Collections.reverse( reverseServiceList );
         for ( final Class<? extends PwmService> serviceClass : reverseServiceList )
@@ -141,16 +145,23 @@ public class PwmServiceManager
             }
         }
         initialized = false;
+
+        final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+        LOGGER.trace( "closed all services in " + timeDuration.asCompactString() );
+
     }
 
     private void shutDownService( final Class<? extends PwmService> serviceClass )
     {
+
         LOGGER.trace( "closing service " + serviceClass.getName() );
         final PwmService loopService = runningServices.get( serviceClass );
-        LOGGER.trace( "successfully closed service " + serviceClass.getName() );
         try
         {
+            final Instant startTime = Instant.now();
             loopService.close();
+            final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+            LOGGER.trace( "successfully closed service " + serviceClass.getName() + " (" + timeDuration.asCompactString() + ")" );
         }
         catch ( Exception e )
         {

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

@@ -180,7 +180,7 @@ public class CacheService implements PwmService
         if ( memoryCacheStore != null )
         {
             final CacheStoreInfo info = memoryCacheStore.getCacheStoreInfo();
-            traceOutput.append( ", memCache=" );
+            traceOutput.append( "memCache=" );
             traceOutput.append( JsonUtil.serialize( info ) );
             traceOutput.append( ", histogram=" );
             traceOutput.append( JsonUtil.serializeMap( memoryCacheStore.storedClassHistogram( "" ) ) );

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

@@ -159,7 +159,7 @@ public class JavaHelper
             }
             catch ( InterruptedException e )
             {
-                //who cares
+                // ignore
             }
         }
         while ( ( System.currentTimeMillis() - startTime ) < sleepTimeMS );
@@ -174,7 +174,7 @@ public class JavaHelper
     )
     {
         final long startTime = System.currentTimeMillis();
-        final long pauseTime = Math.max( sleepTimeMS, predicateCheckIntervalMS );
+        final long pauseTime = Math.min( sleepTimeMS, predicateCheckIntervalMS );
         while ( ( System.currentTimeMillis() - startTime ) < sleepTimeMS )
         {
             JavaHelper.pause( pauseTime );

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

@@ -1077,7 +1077,7 @@ LocalDBStoredQueue implements Queue<String>, Deque<String>
             final AtomicInteger examinedRecords = new AtomicInteger( 0 );
 
             final ConditionalTaskExecutor conditionalTaskExecutor = new ConditionalTaskExecutor(
-                    new Runnable()
+                     new Runnable()
                     {
                         @Override
                         public void run( )

+ 19 - 9
server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java

@@ -161,21 +161,28 @@ public final class WorkQueueProcessor<W extends Serializable>
         final WorkerThread localWorkerThread = workerThread;
         workerThread = null;
 
+        final Instant startTime = Instant.now();
+        logger.debug( "attempting to flush queue prior to shutdown, items in queue=" + queueSize() );
+
         localWorkerThread.flushQueueAndClose();
-        final Instant shutdownStartTime = Instant.now();
 
-        if ( queueSize() > 0 )
-        {
-            logger.debug( "attempting to flush queue prior to shutdown, items in queue=" + queueSize() );
-        }
-        while ( localWorkerThread.isRunning() && TimeDuration.fromCurrent( shutdownStartTime ).isLongerThan( settings.getMaxShutdownWaitTime() ) )
+        if ( localWorkerThread.isRunning() )
         {
-            JavaHelper.pause( CLOSE_RETRY_CYCLE_INTERVAL.getTotalMilliseconds() );
+            JavaHelper.pause(
+                    settings.getMaxShutdownWaitTime().getMilliseconds(),
+                    CLOSE_RETRY_CYCLE_INTERVAL.getTotalMilliseconds(),
+                    o -> !localWorkerThread.isRunning() );
         }
 
+        final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+        final String msg = "shutting down with " + queue.size() + " items remaining in work queue (" + timeDuration.asCompactString() + ")";
         if ( !queue.isEmpty() )
         {
-            logger.warn( "shutting down with " + queue.size() + " items remaining in work queue" );
+            logger.warn( msg );
+        }
+        else
+        {
+            logger.debug( msg );
         }
     }
 
@@ -352,7 +359,10 @@ public final class WorkQueueProcessor<W extends Serializable>
             notifyWorkPending();
 
             // rest until not running for up to 3 seconds....
-            JavaHelper.pause( 3000, 50, o -> !running.get() );
+            if ( running.get() )
+            {
+                JavaHelper.pause( 3000, 10, o -> !running.get() );
+            }
         }
 
         void notifyWorkPending( )

+ 10 - 0
server/src/main/java/password/pwm/util/secure/SecureService.java

@@ -46,6 +46,7 @@ public class SecureService implements PwmService
     private PwmSecurityKey pwmSecurityKey;
     private PwmBlockAlgorithm defaultBlockAlgorithm;
     private PwmHashAlgorithm defaultHashAlgorithm;
+    private PwmRandom pwmRandom;
 
     @Override
     public STATUS status( )
@@ -174,4 +175,13 @@ public class SecureService implements PwmService
     {
         return SecureEngine.hash( file, defaultHashAlgorithm );
     }
+
+    public PwmRandom pwmRandom()
+    {
+        if ( pwmRandom == null )
+        {
+            pwmRandom = PwmRandom.getInstance();
+        }
+        return pwmRandom;
+    }
 }

+ 3 - 1
server/src/main/resources/password/pwm/AppProperty.properties

@@ -142,6 +142,8 @@ http.cookie.maxReadLength=10240
 http.cookie.captchaSkip.name=captcha-key
 http.cookie.captchaSkip.age=86400
 http.cookie.login.name=SESSION
+http.cookie.nonce.name=ID
+http.cookie.nonce.length=32
 http.parameter.forward=forwardURL
 http.parameter.logout=logoutURL
 http.parameter.theme=theme
@@ -172,7 +174,7 @@ intruder.delayPerCountMS=200
 intruder.delayMaxJitterMS=2000
 ldap.resolveCanonicalDN=true
 ldap.cache.canonical.enable=true
-ldap.cache.canonical.seconds=60
+ldap.cache.canonical.seconds=600
 ldap.cache.userGuid.enable=true
 ldap.cache.userGuid.seconds=3600
 ldap.chaiSettings=

+ 2 - 0
webapp/src/main/webapp/META-INF/context.xml

@@ -20,6 +20,8 @@
   -->
 
 <Context tldValidation="false" unloadDelay="30000" useHttpOnly="true" mapperContextRootRedirectEnabled="true">
+  <!--
   <Manager pathname=""/>
+  -->
   <!--pathname param prevents session persistence across restarts -->
 </Context>

+ 112 - 126
webapp/src/main/webapp/private/index.jsp

@@ -38,174 +38,160 @@
     </jsp:include>
 
     <div id="centerbody" class="tile-centerbody">
-        <div>
-            <pwm:if test="<%=PwmIfTest.endUserFunctionalityAvailable%>" negate="true">
-                <p><pwm:display key="Warning_NoEndUserModules" bundle="Config"/></p>
-                <br/>
-            </pwm:if>
-            <pwm:if test="<%=PwmIfTest.endUserFunctionalityAvailable%>">
-                <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.CHANGE_PASSWORD%>">
-                    <a id="button_ChangePassword" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PrivateChangePassword.servletUrl()%>'/>">
-                        <div class="tile">
-                            <div class="tile-content">
-                                <div class="tile-image password-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_ChangePassword'/>"><pwm:display key="Title_ChangePassword"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_ChangePassword'/>"><pwm:display key="Long_Title_ChangePassword"/></div>
-                            </div>
+        <pwm:if test="<%=PwmIfTest.endUserFunctionalityAvailable%>" negate="true">
+            <p><pwm:display key="Warning_NoEndUserModules" bundle="Config"/></p>
+            <br/>
+        </pwm:if>
+        <pwm:if test="<%=PwmIfTest.endUserFunctionalityAvailable%>">
+            <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.CHANGE_PASSWORD%>">
+                <a id="button_ChangePassword" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PrivateChangePassword.servletUrl()%>'/>">
+                    <div class="tile">
+                        <div class="tile-content">
+                            <div class="tile-image password-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_ChangePassword'/>"><pwm:display key="Title_ChangePassword"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_ChangePassword'/>"><pwm:display key="Long_Title_ChangePassword"/></div>
                         </div>
-                    </a>
-                </pwm:if>
-
-                <pwm:if test="<%=PwmIfTest.peopleSearchEnabled%>">
-                    <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.PEOPLE_SEARCH%>">
-                        <a id="button_PeopleSearch" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PrivatePeopleSearch.servletUrl()%>'/>#/search">
-                            <div class="tile">
-                                <div class="tile-content">
-                                    <div class="tile-image search-image"></div>
-                                    <div class="tile-title" title="<pwm:display key='Title_PeopleSearch'/>"><pwm:display key="Title_PeopleSearch"/></div>
-                                    <div class="tile-subtitle" title="<pwm:display key='Long_Title_PeopleSearch'/>"><pwm:display key="Long_Title_PeopleSearch"/></div>
-                                </div>
-                            </div>
-                        </a>
-                    </pwm:if>
-                </pwm:if>
-
-                <pwm:if test="<%=PwmIfTest.orgChartEnabled%>">
-                    <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.PEOPLE_SEARCH%>">
-                        <a id="button_PeopleSearch" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PrivatePeopleSearch.servletUrl()%>'/>#/orgchart">
-                            <div class="tile">
-                                <div class="tile-content">
-                                    <div class="tile-image orgchart-image"></div>
-                                    <div class="tile-title" title="<pwm:display key='Title_OrgChart'/>"><pwm:display key="Title_OrgChart"/></div>
-                                    <div class="tile-subtitle" title="<pwm:display key='Title_OrgChart'/>"><pwm:display key="Title_OrgChart"/></div>
-                                </div>
-                            </div>
-                        </a>
-                    </pwm:if>
-                </pwm:if>
-
-                <pwm:if test="<%=PwmIfTest.setupChallengeEnabled%>">
-                    <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.SETUP_RESPONSE%>">
-                        <a id="button_SetupResponses" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SetupResponses.servletUrl()%>'/>">
-                            <div class="tile">
-                                <div class="tile-content">
-                                    <div class="tile-image security-image"></div>
-                                    <div class="tile-title" title="<pwm:display key='Title_SetupResponses'/>"><pwm:display key="Title_SetupResponses"/></div>
-                                    <div class="tile-subtitle" title="<pwm:display key='Long_Title_SetupResponses'/>"><pwm:display key="Long_Title_SetupResponses"/></div>
-                                </div>
-                            </div>
-                        </a>
-                    </pwm:if>
-                </pwm:if>
+                    </div>
+                </a>
+            </pwm:if>
 
-                <pwm:if test="<%=PwmIfTest.otpSetupEnabled%>">
-                    <a id="button_SetupOtpSecret" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SetupOtp.servletUrl()%>'/>">
+            <pwm:if test="<%=PwmIfTest.peopleSearchEnabled%>">
+                <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.PEOPLE_SEARCH%>">
+                    <a id="button_PeopleSearch" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PrivatePeopleSearch.servletUrl()%>'/>#/search">
                         <div class="tile">
                             <div class="tile-content">
-                                <div class="tile-image mobile-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_SetupOtpSecret'/>"><pwm:display key="Title_SetupOtpSecret"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_SetupOtpSecret'/>"><pwm:display key="Long_Title_SetupOtpSecret"/></div>
+                                <div class="tile-image search-image"></div>
+                                <div class="tile-title" title="<pwm:display key='Title_PeopleSearch'/>"><pwm:display key="Title_PeopleSearch"/></div>
+                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_PeopleSearch'/>"><pwm:display key="Long_Title_PeopleSearch"/></div>
                             </div>
                         </div>
                     </a>
                 </pwm:if>
+            </pwm:if>
 
-                <pwm:if test="<%=PwmIfTest.updateProfileAvailable%>">
-                    <a id="button_UpdateProfile" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.UpdateProfile.servletUrl()%>'/>">
+            <pwm:if test="<%=PwmIfTest.orgChartEnabled%>">
+                <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.PEOPLE_SEARCH%>">
+                    <a id="button_PeopleSearch" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PrivatePeopleSearch.servletUrl()%>'/>#/orgchart">
                         <div class="tile">
                             <div class="tile-content">
-                                <div class="tile-image profile-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_UpdateProfile'/>"><pwm:display key="Title_UpdateProfile"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_UpdateProfile'/>"><pwm:display key="Long_Title_UpdateProfile"/></div>
+                                <div class="tile-image orgchart-image"></div>
+                                <div class="tile-title" title="<pwm:display key='Title_OrgChart'/>"><pwm:display key="Title_OrgChart"/></div>
+                                <div class="tile-subtitle" title="<pwm:display key='Title_OrgChart'/>"><pwm:display key="Title_OrgChart"/></div>
                             </div>
                         </div>
                     </a>
                 </pwm:if>
+            </pwm:if>
 
-                <pwm:if test="<%=PwmIfTest.shortcutsEnabled%>">
-                    <a id="button_Shortcuts" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.Shortcuts.servletUrl()%>'/>">
+            <pwm:if test="<%=PwmIfTest.setupChallengeEnabled%>">
+                <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.SETUP_RESPONSE%>">
+                    <a id="button_SetupResponses" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SetupResponses.servletUrl()%>'/>">
                         <div class="tile">
                             <div class="tile-content">
-                                <div class="tile-image shortcut-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_Shortcuts'/>"><pwm:display key="Title_Shortcuts"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_Shortcuts'/>"><pwm:display key="Long_Title_Shortcuts"/></div>
+                                <div class="tile-image security-image"></div>
+                                <div class="tile-title" title="<pwm:display key='Title_SetupResponses'/>"><pwm:display key="Title_SetupResponses"/></div>
+                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_SetupResponses'/>"><pwm:display key="Long_Title_SetupResponses"/></div>
                             </div>
                         </div>
                     </a>
                 </pwm:if>
+            </pwm:if>
 
-                <pwm:if test="<%=PwmIfTest.accountInfoEnabled%>">
-                    <a id="button_UserInformation" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.AccountInformation.servletUrl()%>'/>">
-                        <div class="tile">
-                            <div class="tile-content">
-                                <div class="tile-image user-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_UserInformation'/>"><pwm:display key="Title_UserInformation"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_UserInformation'/>"><pwm:display key="Long_Title_UserInformation"/></div>
-                            </div>
+            <pwm:if test="<%=PwmIfTest.otpSetupEnabled%>">
+                <a id="button_SetupOtpSecret" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SetupOtp.servletUrl()%>'/>">
+                    <div class="tile">
+                        <div class="tile-content">
+                            <div class="tile-image mobile-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_SetupOtpSecret'/>"><pwm:display key="Title_SetupOtpSecret"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_SetupOtpSecret'/>"><pwm:display key="Long_Title_SetupOtpSecret"/></div>
                         </div>
-                    </a>
-                </pwm:if>
+                    </div>
+                </a>
+            </pwm:if>
 
-                <pwm:if test="<%=PwmIfTest.helpdeskAvailable%>">
-                    <a id="button_Helpdesk" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.Helpdesk.servletUrl()%>'/>">
-                        <div class="tile">
-                            <div class="tile-content">
-                                <div class="tile-image support-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_Helpdesk'/>"><pwm:display key="Title_Helpdesk"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_Helpdesk'/>"><pwm:display key="Long_Title_Helpdesk"/></div>
-                            </div>
+            <pwm:if test="<%=PwmIfTest.updateProfileAvailable%>">
+                <a id="button_UpdateProfile" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.UpdateProfile.servletUrl()%>'/>">
+                    <div class="tile">
+                        <div class="tile-content">
+                            <div class="tile-image profile-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_UpdateProfile'/>"><pwm:display key="Title_UpdateProfile"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_UpdateProfile'/>"><pwm:display key="Long_Title_UpdateProfile"/></div>
                         </div>
-                    </a>
-                </pwm:if>
+                    </div>
+                </a>
+            </pwm:if>
 
+            <pwm:if test="<%=PwmIfTest.shortcutsEnabled%>">
+                <a id="button_Shortcuts" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.Shortcuts.servletUrl()%>'/>">
+                    <div class="tile">
+                        <div class="tile-content">
+                            <div class="tile-image shortcut-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_Shortcuts'/>"><pwm:display key="Title_Shortcuts"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_Shortcuts'/>"><pwm:display key="Long_Title_Shortcuts"/></div>
+                        </div>
+                    </div>
+                </a>
+            </pwm:if>
 
-                <pwm:if test="<%=PwmIfTest.DeleteAccountAvailable%>">
-                    <a id="button_Helpdesk" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SelfDelete.servletUrl()%>'/>">
-                        <div class="tile">
-                            <div class="tile-content">
-                                <div class="tile-image selfdelete-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_DeleteAccount'/>"><pwm:display key="Title_DeleteAccount"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_DeleteAccount'/>"><pwm:display key="Long_Title_DeleteAccount"/></div>
-                            </div>
+            <pwm:if test="<%=PwmIfTest.accountInfoEnabled%>">
+                <a id="button_UserInformation" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.AccountInformation.servletUrl()%>'/>">
+                    <div class="tile">
+                        <div class="tile-content">
+                            <div class="tile-image user-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_UserInformation'/>"><pwm:display key="Title_UserInformation"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_UserInformation'/>"><pwm:display key="Long_Title_UserInformation"/></div>
                         </div>
-                    </a>
-                </pwm:if>
+                    </div>
+                </a>
+            </pwm:if>
 
-                <pwm:if test="<%=PwmIfTest.guestRegistrationAvailable%>">
-                    <a id="button_GuestRegistration" href="<pwm:url url='<%=PwmServletDefinition.GuestRegistration.servletUrl()%>' addContext="true"/>">
-                        <div class="tile">
-                            <div class="tile-content">
-                                <div class="tile-image guest-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_GuestRegistration'/>"><pwm:display key="Title_GuestRegistration"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_GuestRegistration'/>"><pwm:display key="Long_Title_GuestRegistration"/></div>
-                            </div>
+            <pwm:if test="<%=PwmIfTest.helpdeskAvailable%>">
+                <a id="button_Helpdesk" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.Helpdesk.servletUrl()%>'/>">
+                    <div class="tile">
+                        <div class="tile-content">
+                            <div class="tile-image support-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_Helpdesk'/>"><pwm:display key="Title_Helpdesk"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_Helpdesk'/>"><pwm:display key="Long_Title_Helpdesk"/></div>
                         </div>
-                    </a>
-                </pwm:if>
+                    </div>
+                </a>
             </pwm:if>
-        </div>
-        <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.PWMADMIN%>">
-            <div>
-                <h2 style="padding-top: 20px"><pwm:display key="Title_Admin"/></h2>
-                <a id="button_Admin_Dashboard" href="<pwm:url url='<%=PwmServletDefinition.Admin.servletUrl()%>' addContext="true"/> ">
+
+
+            <pwm:if test="<%=PwmIfTest.DeleteAccountAvailable%>">
+                <a id="button_Helpdesk" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SelfDelete.servletUrl()%>'/>">
                     <div class="tile">
                         <div class="tile-content">
-                            <div class="tile-image admin-image"></div>
-                            <div class="tile-title" title="<pwm:display key='Title_Admin'/>"><pwm:display key="Title_Admin"/></div>
-                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_Admin'/>"><pwm:display key="Long_Title_Admin"/></div>
+                            <div class="tile-image selfdelete-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_DeleteAccount'/>"><pwm:display key="Title_DeleteAccount"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_DeleteAccount'/>"><pwm:display key="Long_Title_DeleteAccount"/></div>
                         </div>
                     </div>
                 </a>
-                <a id="button_Admin" href="<pwm:url url='<%=PwmServletDefinition.Admin.servletUrl()%>' addContext="true"/> ">
+            </pwm:if>
+
+            <pwm:if test="<%=PwmIfTest.guestRegistrationAvailable%>">
+                <a id="button_GuestRegistration" href="<pwm:url url='<%=PwmServletDefinition.GuestRegistration.servletUrl()%>' addContext="true"/>">
                     <div class="tile">
                         <div class="tile-content">
-                            <div class="tile-image admin-image"></div>
-                            <div class="tile-title" title="<pwm:display key='Title_Admin'/>"><pwm:display key="Title_Admin"/></div>
-                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_Admin'/>"><pwm:display key="Long_Title_Admin"/></div>
+                            <div class="tile-image guest-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_GuestRegistration'/>"><pwm:display key="Title_GuestRegistration"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_GuestRegistration'/>"><pwm:display key="Long_Title_GuestRegistration"/></div>
                         </div>
                     </div>
                 </a>
-            </div>
+            </pwm:if>
+        </pwm:if>
+        <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.PWMADMIN%>">
+            <a id="button_Admin" href="<pwm:url url='<%=PwmServletDefinition.Admin.servletUrl()%>' addContext="true"/> ">
+                <div class="tile">
+                    <div class="tile-content">
+                        <div class="tile-image admin-image"></div>
+                        <div class="tile-title" title="<pwm:display key='Title_Admin'/>"><pwm:display key="Title_Admin"/></div>
+                        <div class="tile-subtitle" title="<pwm:display key='Long_Title_Admin'/>"><pwm:display key="Long_Title_Admin"/></div>
+                    </div>
+                </div>
+            </a>
         </pwm:if>
     </div>
     <div class="push"></div>

+ 9 - 2
webapp/src/main/webapp/public/resources/js/configeditor.js

@@ -1074,7 +1074,7 @@ PWM_CFGEDIT.drawNavigationMenu = function() {
                 // Create the Tree.
                 var tree = new Tree({
                     model: model,
-                    persist: true,
+                    persist: false,
                     getIconClass: function(/*dojo.store.Item*/ item, /*Boolean*/ opened){
                         return 'tree-noicon';
                     },
@@ -1083,17 +1083,24 @@ PWM_CFGEDIT.drawNavigationMenu = function() {
                     id: 'navigationTree',
                     onClick: function(item){
                         PWM_MAIN.Preferences.writeSessionStorage('configEditor-lastSelected',item);
+                        var path = tree.get('paths');
+                        PWM_MAIN.Preferences.writeSessionStorage('configEditor-path',JSON.stringify(path));
                         PWM_CFGEDIT.dispatchNavigationItem(item);
                     }
                 });
 
+                var storedPath = PWM_MAIN.Preferences.readSessionStorage('configEditor-path');
+                if (storedPath) {
+                    var path = JSON.parse(storedPath);
+                    tree.set('paths', path);
+                }
 
                 PWM_MAIN.getObject('navigationTree').innerHTML = '';
                 tree.placeAt(PWM_MAIN.getObject('navigationTree'));
                 tree.startup();
                 PWM_MAIN.setStyle('navigationTreeWrapper','display','inherit');
                 PWM_VAR['navigationTree'] = tree; // used for expand/collapse button events;
-                console.log('completed menu tree drawing')
+                console.log('completed menu tree drawing');
             }
         );
     };