Explorar el Código

configeditor refactoring and menu performance

Jason Rivard hace 4 años
padre
commit
e92331865f
Se han modificado 17 ficheros con 1125 adiciones y 1025 borrados
  1. 2 2
      server/src/main/java/password/pwm/PwmApplication.java
  2. 217 205
      server/src/main/java/password/pwm/config/PwmSetting.java
  3. 68 21
      server/src/main/java/password/pwm/config/PwmSettingCategory.java
  4. 38 150
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  5. 55 76
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java
  6. 0 359
      server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeHelper.java
  7. 0 127
      server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeItem.java
  8. 23 36
      server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java
  9. 15 18
      server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java
  10. 45 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/LocaleInfo.java
  11. 428 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java
  12. 51 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeItem.java
  13. 15 6
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java
  14. 41 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingData.java
  15. 100 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java
  16. 24 23
      server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingInfo.java
  17. 3 2
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

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

@@ -35,7 +35,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthMonitor;
-import password.pwm.http.servlet.configeditor.ConfigEditorServletUtils;
+import password.pwm.http.servlet.configeditor.data.SettingDataMaker;
 import password.pwm.http.servlet.peoplesearch.PeopleSearchService;
 import password.pwm.http.servlet.resource.ResourceServletService;
 import password.pwm.http.state.SessionStateService;
@@ -392,7 +392,7 @@ public class PwmApplication
 
         try
         {
-            ConfigEditorServletUtils.generateSettingData( this, this.getConfig().getStoredConfiguration(), null, PwmConstants.DEFAULT_LOCALE );
+            SettingDataMaker.generateSettingData( this.getConfig().getStoredConfiguration(), null, PwmConstants.DEFAULT_LOCALE );
         }
         catch ( final Exception e )
         {

+ 217 - 205
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -1310,7 +1310,7 @@ public enum PwmSetting
     private final Supplier<List<TemplateSetReference<StoredValue>>> defaultValues = new LazySupplier<>( () -> PwmSettingReader.readDefaultValue( PwmSetting.this ) );
     private final Supplier<List<TemplateSetReference<String>>> examples = new LazySupplier<>( () -> PwmSettingReader.readExamples( PwmSetting.this ) );
     private final Supplier<Map<String, String>> options = new LazySupplier<>( () -> PwmSettingReader.readOptions( PwmSetting.this ) );
-    private final Supplier<Collection<PwmSettingFlag>> flags = new LazySupplier<>( () -> PwmSettingReader.readFlags( PwmSetting.this ) );
+    private final Supplier<Set<PwmSettingFlag>> flags = new LazySupplier<>( () -> PwmSettingReader.readFlags( PwmSetting.this ) );
     private final Supplier<Map<PwmSettingProperty, String>> properties = new LazySupplier<>( () -> PwmSettingReader.readProperties( PwmSetting.this ) );
     private final Supplier<Collection<LDAPPermissionInfo>> ldapPermissionInfo = new LazySupplier<>( () -> PwmSettingReader.readLdapPermissionInfo( PwmSetting.this ) );
     private final Supplier<Boolean> required = new LazySupplier<>( () -> PwmSettingReader.readRequired( PwmSetting.this ) );
@@ -1318,6 +1318,7 @@ public enum PwmSetting
     private final Supplier<Integer> level = new LazySupplier<>( () -> PwmSettingReader.readLevel( PwmSetting.this ) );
     private final Supplier<Pattern> pattern = new LazySupplier<>( () -> PwmSettingReader.readPattern( PwmSetting.this ) );
     private final Supplier<String> defaultLocaleLabel = new LazySupplier<>( () -> PwmSettingReader.readLabel( this, PwmConstants.DEFAULT_LOCALE ) );
+    private final Supplier<String> defaultLocaleDescription = new LazySupplier<>( () -> PwmSettingReader.readDescription( this, PwmConstants.DEFAULT_LOCALE ) );
     private final Supplier<String> defaultMenuLocation = new LazySupplier<>( () -> PwmSettingReader.readMenuLocationDebugDefault( this ) );
 
 
@@ -1381,7 +1382,7 @@ public enum PwmSetting
         return properties.get();
     }
 
-    public Collection<PwmSettingFlag> getFlags( )
+    public Set<PwmSettingFlag> getFlags( )
     {
         return flags.get();
     }
@@ -1404,10 +1405,12 @@ public enum PwmSetting
 
     public String getDescription( final Locale locale )
     {
-        final String propertyKey = password.pwm.i18n.PwmSetting.SETTING_DESCRIPTION_PREFIX + this.getKey();
-        final String storedText = LocaleHelper.getLocalizedMessage( locale, propertyKey, null, password.pwm.i18n.PwmSetting.class );
-        final MacroRequest macroRequest = MacroRequest.forStatic();
-        return macroRequest.expandMacros( storedText );
+        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) )
+        {
+            return defaultLocaleDescription.get();
+        }
+
+        return PwmSettingReader.readDescription( this, locale );
     }
 
     public String getExample( final PwmSettingTemplateSet template )
@@ -1445,16 +1448,14 @@ public enum PwmSetting
             final Locale locale
     )
     {
-        final String output;
         if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) && StringUtil.isEmpty( profileID ) )
         {
-            output = defaultMenuLocation.get();
+            return defaultMenuLocation.get();
         }
         else
         {
-            output = PwmSettingReader.readMenuLocationDebug( this, profileID, locale );
+            return PwmSettingReader.readMenuLocationDebug( this, profileID, locale );
         }
-        return output;
     }
 
     public Collection<LDAPPermissionInfo> getLDAPPermissionInfo()
@@ -1462,12 +1463,12 @@ public enum PwmSetting
         return ldapPermissionInfo.get();
     }
 
-public enum SettingStat
-{
-    Total,
-    hasProfile,
-    syntaxCounts,
-}
+    public enum SettingStat
+    {
+        Total,
+        hasProfile,
+        syntaxCounts,
+    }
 
     public static Map<SettingStat, Object> getStats( )
     {
@@ -1501,44 +1502,44 @@ public enum SettingStat
         return returnObj;
     }
 
-@Value
-static class TemplateSetReference<T>
-{
-    private final T reference;
-    private final Set<PwmSettingTemplate> settingTemplates;
-
-    private static <T> T referenceForTempleSet(
-            final List<TemplateSetReference<T>> templateSetReferences,
-            final PwmSettingTemplateSet pwmSettingTemplate
-    )
+    @Value
+    static class TemplateSetReference<T>
     {
-        if ( templateSetReferences == null || templateSetReferences.isEmpty() )
-        {
-            throw new IllegalStateException( "templateSetReferences can not be null" );
-        }
+        private final T reference;
+        private final Set<PwmSettingTemplate> settingTemplates;
 
-        if ( templateSetReferences.size() == 1 )
+        private static <T> T referenceForTempleSet(
+                final List<TemplateSetReference<T>> templateSetReferences,
+                final PwmSettingTemplateSet pwmSettingTemplate
+        )
         {
-            return templateSetReferences.iterator().next().getReference();
-        }
+            if ( templateSetReferences == null || templateSetReferences.isEmpty() )
+            {
+                throw new IllegalStateException( "templateSetReferences can not be null" );
+            }
 
-        for ( int matchCountExamSize = templateSetReferences.size(); matchCountExamSize > 0; matchCountExamSize-- )
-        {
-            for ( final TemplateSetReference<T> templateSetReference : templateSetReferences )
+            if ( templateSetReferences.size() == 1 )
+            {
+                return templateSetReferences.iterator().next().getReference();
+            }
+
+            for ( int matchCountExamSize = templateSetReferences.size(); matchCountExamSize > 0; matchCountExamSize-- )
             {
-                final Set<PwmSettingTemplate> temporarySet = JavaHelper.copiedEnumSet( templateSetReference.getSettingTemplates(), PwmSettingTemplate.class );
-                temporarySet.retainAll( pwmSettingTemplate.getTemplates() );
-                final int matchCount = temporarySet.size();
-                if ( matchCount == matchCountExamSize )
+                for ( final TemplateSetReference<T> templateSetReference : templateSetReferences )
                 {
-                    return templateSetReference.getReference();
+                    final Set<PwmSettingTemplate> temporarySet = JavaHelper.copiedEnumSet( templateSetReference.getSettingTemplates(), PwmSettingTemplate.class );
+                    temporarySet.retainAll( pwmSettingTemplate.getTemplates() );
+                    final int matchCount = temporarySet.size();
+                    if ( matchCount == matchCountExamSize )
+                    {
+                        return templateSetReference.getReference();
+                    }
                 }
             }
-        }
 
-        return templateSetReferences.iterator().next().getReference();
+            return templateSetReferences.iterator().next().getReference();
+        }
     }
-}
 
     public static Set<PwmSetting> sortedByMenuLocation( final Locale locale )
     {
@@ -1557,216 +1558,227 @@ static class TemplateSetReference<T>
         };
     }
 
-static class PwmSettingReader
-{
-    private static Collection<PwmSettingFlag> readFlags( final PwmSetting pwmSetting )
+    static class PwmSettingReader
     {
-        final Collection<PwmSettingFlag> returnObj = EnumSet.noneOf( PwmSettingFlag.class );
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final List<XmlElement> flagElements = settingElement.getChildren( "flag" );
-        for ( final XmlElement flagElement : flagElements )
+        private static Set<PwmSettingFlag> readFlags( final PwmSetting pwmSetting )
         {
-            final String value = flagElement.getTextTrim();
-
-            try
-            {
-                final PwmSettingFlag flag = PwmSettingFlag.valueOf( value );
-                returnObj.add( flag );
-            }
-            catch ( final IllegalArgumentException e )
+            final Set<PwmSettingFlag> returnObj = EnumSet.noneOf( PwmSettingFlag.class );
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final List<XmlElement> flagElements = settingElement.getChildren( "flag" );
+            for ( final XmlElement flagElement : flagElements )
             {
-                LOGGER.error( () -> "unknown flag for setting " + pwmSetting.getKey() + ", error: unknown flag value: " + value );
-            }
+                final String value = flagElement.getTextTrim();
+
+                try
+                {
+                    final PwmSettingFlag flag = PwmSettingFlag.valueOf( value );
+                    returnObj.add( flag );
+                }
+                catch ( final IllegalArgumentException e )
+                {
+                    LOGGER.error( () -> "unknown flag for setting " + pwmSetting.getKey() + ", error: unknown flag value: " + value );
+                }
 
+            }
+            return Collections.unmodifiableSet( returnObj );
         }
-        return Collections.unmodifiableCollection( returnObj );
-    }
 
-    private static Map<String, String> readOptions( final PwmSetting pwmSetting )
-    {
-        final Map<String, String> returnList = new LinkedHashMap<>();
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final Optional<XmlElement> optionsElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_OPTIONS );
-        if ( optionsElement.isPresent() )
+        private static Map<String, String> readOptions( final PwmSetting pwmSetting )
         {
-            final List<XmlElement> optionElements = optionsElement.get().getChildren( PwmSettingXml.XML_ELEMENT_OPTION );
-            if ( optionElements != null )
+            final Map<String, String> returnList = new LinkedHashMap<>();
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final Optional<XmlElement> optionsElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_OPTIONS );
+            if ( optionsElement.isPresent() )
             {
-                for ( final XmlElement optionElement : optionElements )
+                final List<XmlElement> optionElements = optionsElement.get().getChildren( PwmSettingXml.XML_ELEMENT_OPTION );
+                if ( optionElements != null )
                 {
-                    if ( optionElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_VALUE ) == null )
+                    for ( final XmlElement optionElement : optionElements )
                     {
-                        throw new IllegalStateException( "option element is missing 'value' attribute for key " + pwmSetting.getKey() );
+                        if ( optionElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_VALUE ) == null )
+                        {
+                            throw new IllegalStateException( "option element is missing 'value' attribute for key " + pwmSetting.getKey() );
+                        }
+                        returnList.put( optionElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_VALUE ), optionElement.getText() );
                     }
-                    returnList.put( optionElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_VALUE ), optionElement.getText() );
                 }
             }
+            final Map<String, String> finalList = Collections.unmodifiableMap( returnList );
+            return Collections.unmodifiableMap( finalList );
         }
-        final Map<String, String> finalList = Collections.unmodifiableMap( returnList );
-        return Collections.unmodifiableMap( finalList );
-    }
 
-    private static Collection<LDAPPermissionInfo> readLdapPermissionInfo( final PwmSetting pwmSetting )
-    {
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final List<XmlElement> permissionElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_LDAP_PERMISSION );
-        final List<LDAPPermissionInfo> returnObj = new ArrayList<>();
-        if ( permissionElements != null )
+        private static Collection<LDAPPermissionInfo> readLdapPermissionInfo( final PwmSetting pwmSetting )
         {
-            for ( final XmlElement permissionElement : permissionElements )
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final List<XmlElement> permissionElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_LDAP_PERMISSION );
+            final List<LDAPPermissionInfo> returnObj = new ArrayList<>();
+            if ( permissionElements != null )
             {
-                final Optional<LDAPPermissionInfo.Actor> actor = JavaHelper.readEnumFromString(
-                        LDAPPermissionInfo.Actor.class,
-                        permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACTOR )
-                );
-                final Optional<LDAPPermissionInfo.Access> type = JavaHelper.readEnumFromString(
-                        LDAPPermissionInfo.Access.class,
-                        permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACCESS )
-                );
-                if ( actor.isPresent() && type.isPresent() )
+                for ( final XmlElement permissionElement : permissionElements )
                 {
-                    final LDAPPermissionInfo permissionInfo = new LDAPPermissionInfo( type.get(), actor.get() );
-                    returnObj.add( permissionInfo );
+                    final Optional<LDAPPermissionInfo.Actor> actor = JavaHelper.readEnumFromString(
+                            LDAPPermissionInfo.Actor.class,
+                            permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACTOR )
+                    );
+                    final Optional<LDAPPermissionInfo.Access> type = JavaHelper.readEnumFromString(
+                            LDAPPermissionInfo.Access.class,
+                            permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACCESS )
+                    );
+                    if ( actor.isPresent() && type.isPresent() )
+                    {
+                        final LDAPPermissionInfo permissionInfo = new LDAPPermissionInfo( type.get(), actor.get() );
+                        returnObj.add( permissionInfo );
+                    }
                 }
             }
+            return Collections.unmodifiableList( returnObj );
         }
-        return Collections.unmodifiableList( returnObj );
-    }
 
-    private static List<TemplateSetReference<String>> readExamples( final PwmSetting pwmSetting )
-    {
-        final List<TemplateSetReference<String>> returnObj = new ArrayList<>();
-        final MacroRequest macroRequest = MacroRequest.forStatic();
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final List<XmlElement> exampleElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_EXAMPLE );
-        for ( final XmlElement exampleElement : exampleElements )
-        {
-            final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( exampleElement );
-            final String exampleString = macroRequest.expandMacros( exampleElement.getText() );
-            returnObj.add( new TemplateSetReference<>( exampleString, Collections.unmodifiableSet( definedTemplates ) ) );
-        }
-        if ( returnObj.isEmpty() )
+        private static List<TemplateSetReference<String>> readExamples( final PwmSetting pwmSetting )
         {
-            returnObj.add( new TemplateSetReference<>( "", Collections.emptySet() ) );
+            final List<TemplateSetReference<String>> returnObj = new ArrayList<>();
+            final MacroRequest macroRequest = MacroRequest.forStatic();
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final List<XmlElement> exampleElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_EXAMPLE );
+            for ( final XmlElement exampleElement : exampleElements )
+            {
+                final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( exampleElement );
+                final String exampleString = macroRequest.expandMacros( exampleElement.getText() );
+                returnObj.add( new TemplateSetReference<>( exampleString, Collections.unmodifiableSet( definedTemplates ) ) );
+            }
+            if ( returnObj.isEmpty() )
+            {
+                returnObj.add( new TemplateSetReference<>( "", Collections.emptySet() ) );
+            }
+            return Collections.unmodifiableList( returnObj );
         }
-        return Collections.unmodifiableList( returnObj );
-    }
 
-    private static Map<PwmSettingProperty, String> readProperties( final PwmSetting pwmSetting )
-    {
-        final Map<PwmSettingProperty, String> newProps = new EnumMap<>( PwmSettingProperty.class );
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final Optional<XmlElement> propertiesElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_PROPERTIES );
-        if ( propertiesElement.isPresent() )
+        private static Map<PwmSettingProperty, String> readProperties( final PwmSetting pwmSetting )
         {
-            final List<XmlElement> propertyElements = propertiesElement.get().getChildren( PwmSettingXml.XML_ELEMENT_PROPERTY );
-            if ( propertyElements != null )
+            final Map<PwmSettingProperty, String> newProps = new EnumMap<>( PwmSettingProperty.class );
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final Optional<XmlElement> propertiesElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_PROPERTIES );
+            if ( propertiesElement.isPresent() )
             {
-                for ( final XmlElement propertyElement : propertyElements )
+                final List<XmlElement> propertyElements = propertiesElement.get().getChildren( PwmSettingXml.XML_ELEMENT_PROPERTY );
+                if ( propertyElements != null )
                 {
-                    if ( propertyElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_KEY ) == null )
+                    for ( final XmlElement propertyElement : propertyElements )
                     {
-                        throw new IllegalStateException( "property element is missing 'key' attribute for value " + pwmSetting.getKey() );
+                        if ( propertyElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_KEY ) == null )
+                        {
+                            throw new IllegalStateException( "property element is missing 'key' attribute for value " + pwmSetting.getKey() );
+                        }
+                        final PwmSettingProperty property = JavaHelper.readEnumFromString(
+                                PwmSettingProperty.class,
+                                null,
+                                propertyElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_KEY ) );
+                        if ( property == null )
+                        {
+                            throw new IllegalStateException( "property element has unknown 'key' attribute for value " + pwmSetting.getKey() );
+                        }
+                        newProps.put( property, propertyElement.getText() );
                     }
-                    final PwmSettingProperty property = JavaHelper.readEnumFromString(
-                            PwmSettingProperty.class,
-                            null,
-                            propertyElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_KEY ) );
-                    if ( property == null )
-                    {
-                        throw new IllegalStateException( "property element has unknown 'key' attribute for value " + pwmSetting.getKey() );
-                    }
-                    newProps.put( property, propertyElement.getText() );
                 }
             }
+            return Collections.unmodifiableMap( newProps );
         }
-        return Collections.unmodifiableMap( newProps );
-    }
 
-    private static List<TemplateSetReference<StoredValue>> readDefaultValue( final PwmSetting pwmSetting )
-    {
-        final List<TemplateSetReference<StoredValue>> returnObj = new ArrayList<>();
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final List<XmlElement> defaultElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_DEFAULT );
-        if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
-        {
-            returnObj.add( new TemplateSetReference<>( new PasswordValue( null ), Collections.emptySet() ) );
-        }
-        else
+        private static List<TemplateSetReference<StoredValue>> readDefaultValue( final PwmSetting pwmSetting )
         {
-            for ( final XmlElement defaultElement : defaultElements )
+            final List<TemplateSetReference<StoredValue>> returnObj = new ArrayList<>();
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final List<XmlElement> defaultElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_DEFAULT );
+            if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
             {
-                final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement );
-                final StoredValue storedValue = ValueFactory.fromXmlValues( pwmSetting, defaultElement, null );
-                returnObj.add( new TemplateSetReference<>( storedValue, definedTemplates ) );
+                returnObj.add( new TemplateSetReference<>( new PasswordValue( null ), Collections.emptySet() ) );
             }
+            else
+            {
+                for ( final XmlElement defaultElement : defaultElements )
+                {
+                    final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement );
+                    final StoredValue storedValue = ValueFactory.fromXmlValues( pwmSetting, defaultElement, null );
+                    returnObj.add( new TemplateSetReference<>( storedValue, definedTemplates ) );
+                }
+            }
+            if ( returnObj.isEmpty() )
+            {
+                throw new IllegalStateException( "no default value for setting " + pwmSetting.getKey() );
+            }
+            return Collections.unmodifiableList( returnObj );
         }
-        if ( returnObj.isEmpty() )
-        {
-            throw new IllegalStateException( "no default value for setting " + pwmSetting.getKey() );
-        }
-        return Collections.unmodifiableList( returnObj );
-    }
 
 
-    private static boolean readRequired( final PwmSetting pwmSetting )
-    {
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final String requiredAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_REQUIRED );
-        return "true".equalsIgnoreCase( requiredAttribute );
-    }
+        private static boolean readRequired( final PwmSetting pwmSetting )
+        {
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final String requiredAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_REQUIRED );
+            return "true".equalsIgnoreCase( requiredAttribute );
+        }
 
-    private static boolean readHidden( final PwmSetting pwmSetting )
-    {
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final String requiredAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_HIDDEN );
-        return "true".equalsIgnoreCase( requiredAttribute ) || pwmSetting.getCategory().isHidden();
-    }
+        private static boolean readHidden( final PwmSetting pwmSetting )
+        {
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final String requiredAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_HIDDEN );
+            return "true".equalsIgnoreCase( requiredAttribute ) || pwmSetting.getCategory().isHidden();
+        }
 
-    private static int readLevel( final PwmSetting pwmSetting )
-    {
-        final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-        final String levelAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_LEVEL );
-        return JavaHelper.silentParseInt( levelAttribute, 0 );
-    }
+        private static int readLevel( final PwmSetting pwmSetting )
+        {
+            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
+            final String levelAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_LEVEL );
+            return JavaHelper.silentParseInt( levelAttribute, 0 );
+        }
 
-    private static Pattern readPattern( final PwmSetting pwmSetting )
-    {
-        final XmlElement settingNode = PwmSettingXml.readSettingXml( pwmSetting );
-        final Optional<XmlElement> regexNode = settingNode.getChild( PwmSettingXml.XML_ELEMENT_REGEX );
-        if ( regexNode.isPresent() )
+        private static Pattern readPattern( final PwmSetting pwmSetting )
         {
-            try
-            {
-                return Pattern.compile( regexNode.get().getText() );
-            }
-            catch ( final PatternSyntaxException e )
+            final XmlElement settingNode = PwmSettingXml.readSettingXml( pwmSetting );
+            final Optional<XmlElement> regexNode = settingNode.getChild( PwmSettingXml.XML_ELEMENT_REGEX );
+            if ( regexNode.isPresent() )
             {
-                final String errorMsg = "error compiling regex constraints for setting " + pwmSetting.toString() + ", error: " + e.getMessage();
-                LOGGER.error( () -> errorMsg, e );
-                throw new IllegalStateException( errorMsg, e );
+                try
+                {
+                    return Pattern.compile( regexNode.get().getText() );
+                }
+                catch ( final PatternSyntaxException e )
+                {
+                    final String errorMsg = "error compiling regex constraints for setting " + pwmSetting.toString() + ", error: " + e.getMessage();
+                    LOGGER.error( () -> errorMsg, e );
+                    throw new IllegalStateException( errorMsg, e );
+                }
             }
+            return Pattern.compile( ".*", Pattern.DOTALL );
         }
-        return Pattern.compile( ".*", Pattern.DOTALL );
-    }
 
-    private static String readMenuLocationDebugDefault( final PwmSetting pwmSetting )
-    {
-        final Locale locale = PwmConstants.DEFAULT_LOCALE;
-        final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
-        return pwmSetting.getCategory().toMenuLocationDebug( null, locale ) + separator + pwmSetting.getLabel( locale );
-    }
+        private static String readMenuLocationDebugDefault( final PwmSetting pwmSetting )
+        {
+            final Locale locale = PwmConstants.DEFAULT_LOCALE;
+            final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
+            return pwmSetting.getCategory().toMenuLocationDebug( null, locale ) + separator + pwmSetting.getLabel( locale );
+        }
 
-    private static String readMenuLocationDebug( final PwmSetting pwmSetting, final String profileID, final Locale locale )
-    {
-        final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
-        return pwmSetting.getCategory().toMenuLocationDebug( profileID, locale ) + separator + pwmSetting.getLabel( locale );
-    }
+        private static String readMenuLocationDebug( final PwmSetting pwmSetting, final String profileID, final Locale locale )
+        {
+            final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
+            return pwmSetting.getCategory().toMenuLocationDebug( profileID, locale ) + separator + pwmSetting.getLabel( locale );
+        }
 
-    private static String readLabel( final PwmSetting pwmSetting, final Locale locale )
-    {
-        final String propertyKey = password.pwm.i18n.PwmSetting.SETTING_LABEL_PREFIX + pwmSetting.getKey();
-        return LocaleHelper.getLocalizedMessage( locale, propertyKey, null, password.pwm.i18n.PwmSetting.class );
+        private static String readLabel( final PwmSetting pwmSetting, final Locale locale )
+        {
+            return readStringProperty( password.pwm.i18n.PwmSetting.SETTING_LABEL_PREFIX + pwmSetting.getKey(), locale );
+        }
+
+        private static String readDescription( final PwmSetting pwmSetting, final Locale locale )
+        {
+            return readStringProperty( password.pwm.i18n.PwmSetting.SETTING_DESCRIPTION_PREFIX + pwmSetting.getKey(), locale );
+        }
+
+        private static String readStringProperty( final String key, final Locale locale )
+        {
+            final String storedText = LocaleHelper.getLocalizedMessage( locale, key, null, password.pwm.i18n.PwmSetting.class );
+            final MacroRequest macroRequest = MacroRequest.forStatic();
+            return macroRequest.expandMacros( storedText );
+        }
     }
 }
-}

+ 68 - 21
server/src/main/java/password/pwm/config/PwmSettingCategory.java

@@ -20,21 +20,22 @@
 
 package password.pwm.config;
 
+import password.pwm.PwmConstants;
 import password.pwm.i18n.Config;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.XmlElement;
+import password.pwm.util.macro.MacroRequest;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Optional;
-import java.util.TreeMap;
 import java.util.function.Supplier;
 
 public enum PwmSettingCategory
@@ -197,10 +198,12 @@ public enum PwmSettingCategory
 
     private final PwmSettingCategory parent;
 
-    private transient Supplier<Optional<PwmSetting>> profileSetting = new LazySupplier<>( () -> XmlReader.readProfileSettingFromXml( this, true ) );
-    private transient Supplier<Integer> level = new LazySupplier<>( () -> XmlReader.readLevel( this ) );
-    private transient Supplier<Boolean> hidden = new LazySupplier<>( () -> XmlReader.readHidden( this ) );
-    private transient Supplier<Boolean> isTopLevelProfile = new LazySupplier<>( () -> XmlReader.readIsTopLevelProfile( this ) );
+    private final Supplier<Optional<PwmSetting>> profileSetting = new LazySupplier<>( () -> XmlReader.readProfileSettingFromXml( this, true ) );
+    private final Supplier<Integer> level = new LazySupplier<>( () -> XmlReader.readLevel( this ) );
+    private final Supplier<Boolean> hidden = new LazySupplier<>( () -> XmlReader.readHidden( this ) );
+    private final Supplier<Boolean> isTopLevelProfile = new LazySupplier<>( () -> XmlReader.readIsTopLevelProfile( this ) );
+    private final Supplier<String> defaultLocaleLabel = new LazySupplier<>( () -> XmlReader.readLabel( this, PwmConstants.DEFAULT_LOCALE ) );
+    private final Supplier<String> defaultLocaleDescription = new LazySupplier<>( () -> XmlReader.readDescription( this, PwmConstants.DEFAULT_LOCALE ) );
 
 
     PwmSettingCategory( final PwmSettingCategory parent )
@@ -235,14 +238,22 @@ public enum PwmSettingCategory
 
     public String getLabel( final Locale locale )
     {
-        final String key = password.pwm.i18n.PwmSetting.CATEGORY_LABEL_PREFIX + this.getKey();
-        return LocaleHelper.getLocalizedMessage( locale, key, null, password.pwm.i18n.PwmSetting.class );
+        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) )
+        {
+            return defaultLocaleLabel.get();
+        }
+
+        return XmlReader.readLabel( this, locale );
     }
 
     public String getDescription( final Locale locale )
     {
-        final String key = password.pwm.i18n.PwmSetting.CATEGORY_DESCRIPTION_PREFIX + this.getKey();
-        return LocaleHelper.getLocalizedMessage( locale, key, null, password.pwm.i18n.PwmSetting.class );
+        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) )
+        {
+            return defaultLocaleDescription.get();
+        }
+
+        return XmlReader.readDescription( this, locale );
     }
 
     public int getLevel( )
@@ -313,7 +324,6 @@ public enum PwmSettingCategory
             final Locale locale
     )
     {
-
         final String parentValue = category.getParent() == null
                 ? ""
                 : toMenuLocationDebug( category.getParent(), profileID, locale );
@@ -349,20 +359,23 @@ public enum PwmSettingCategory
     {
         if ( cachedSortedSettings == null )
         {
-            // prevents dupes from being eliminated;
-            int counter = 0;
-
-            final Map<String, PwmSettingCategory> sortedCategories = new TreeMap<>();
-            for ( final PwmSettingCategory category : PwmSettingCategory.values() )
-            {
-                final String sortValue = category.toMenuLocationDebug( null, locale ) + ( counter++ );
-                sortedCategories.put( sortValue, category );
-            }
-            cachedSortedSettings = Collections.unmodifiableList( new ArrayList<>( sortedCategories.values() ) );
+            final List<PwmSettingCategory> tempList = new ArrayList<>( Arrays.asList( PwmSettingCategory.values() ) );
+            tempList.sort( menuLocationComparator( locale ) );
+            cachedSortedSettings = Collections.unmodifiableList( tempList );
         }
         return cachedSortedSettings;
     }
 
+    private static Comparator<PwmSettingCategory> menuLocationComparator( final Locale locale )
+    {
+        return ( o1, o2 ) ->
+        {
+            final String selfValue = o1.toMenuLocationDebug( null, locale );
+            final String otherValue = o2.toMenuLocationDebug( null, locale );
+            return selfValue.compareTo( otherValue );
+        };
+    }
+
     public static List<PwmSettingCategory> valuesForReferenceDoc( final Locale locale )
     {
         final List<PwmSettingCategory> values = new ArrayList<>( sortedValues( locale ) );
@@ -405,6 +418,23 @@ public enum PwmSettingCategory
                 .findFirst();
     }
 
+    public static Optional<PwmSettingCategory> forProfileSetting( final PwmSetting setting )
+    {
+        for ( final PwmSettingCategory loopCategory : PwmSettingCategory.values() )
+        {
+            if ( loopCategory.hasProfiles() )
+            {
+                final Optional<PwmSetting> profileSetting = loopCategory.getProfileSetting();
+                if ( profileSetting.isPresent() && profileSetting.get() == setting )
+                {
+                    return Optional.of( loopCategory );
+                }
+            }
+        }
+
+        return Optional.empty();
+    }
+
     private static class XmlReader
     {
 
@@ -468,5 +498,22 @@ public enum PwmSettingCategory
         {
             return readProfileSettingFromXml( category, false ).isPresent();
         }
+
+        private static String readLabel( final PwmSettingCategory category, final Locale locale )
+        {
+            return readStringProperty( password.pwm.i18n.PwmSetting.CATEGORY_LABEL_PREFIX + category.getKey(), locale );
+        }
+
+        private static String readDescription( final PwmSettingCategory category, final Locale locale )
+        {
+            return readStringProperty( password.pwm.i18n.PwmSetting.CATEGORY_DESCRIPTION_PREFIX + category.getKey(), locale );
+        }
+
+        private static String readStringProperty( final String key, final Locale locale )
+        {
+            final String storedText = LocaleHelper.getLocalizedMessage( locale, key, null, password.pwm.i18n.PwmSetting.class );
+            final MacroRequest macroRequest = MacroRequest.forStatic();
+            return macroRequest.expandMacros( storedText );
+        }
     }
 }

+ 38 - 150
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -60,6 +60,11 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.ConfigManagerBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
+import password.pwm.http.servlet.configeditor.data.NavTreeDataMaker;
+import password.pwm.http.servlet.configeditor.data.NavTreeItem;
+import password.pwm.http.servlet.configeditor.data.NavTreeSettings;
+import password.pwm.http.servlet.configeditor.data.SettingData;
+import password.pwm.http.servlet.configeditor.data.SettingDataMaker;
 import password.pwm.http.servlet.configmanager.ConfigManagerServlet;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.Message;
@@ -78,7 +83,6 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.queue.SmsQueueManager;
-import password.pwm.util.secure.HttpsServerCertificateManager;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestRandomPasswordServer;
 import password.pwm.ws.server.rest.bean.HealthData;
@@ -86,7 +90,6 @@ import password.pwm.ws.server.rest.bean.HealthData;
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.Serializable;
 import java.time.Instant;
 import java.util.ArrayList;
@@ -101,7 +104,6 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
-import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -447,7 +449,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final StoredConfigurationModifier modifer = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
         {
             final String updateDescriptionTextCmd = pwmRequest.readParameterAsString( "updateNotesText" );
             if ( StringUtil.nullSafeEqualsIgnoreCase( "true", updateDescriptionTextCmd ) )
@@ -456,7 +458,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                 {
                     final String bodyString = pwmRequest.readRequestBodyAsString();
                     final String value = JsonUtil.deserialize( bodyString, String.class );
-                    modifer.writeConfigProperty( ConfigurationProperty.NOTES, value );
+                    modifier.writeConfigProperty( ConfigurationProperty.NOTES, value );
                     LOGGER.trace( () -> "updated notesText" );
                 }
                 catch ( final Exception e )
@@ -471,19 +473,19 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                     try
                     {
                         final PwmSettingTemplate template = PwmSettingTemplate.valueOf( requestedTemplate );
-                        modifer.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, template.toString() );
+                        modifier.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, template.toString() );
                         LOGGER.trace( () -> "setting template to: " + requestedTemplate );
                     }
                     catch ( final IllegalArgumentException e )
                     {
-                        modifer.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, PwmSettingTemplate.DEFAULT.toString() );
+                        modifier.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, PwmSettingTemplate.DEFAULT.toString() );
                         LOGGER.error( () -> "unknown template set request: " + requestedTemplate );
                     }
                 }
             }
         }
 
-        configManagerBean.setStoredConfiguration( modifer.newStoredConfiguration() );
+        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         return ProcessStatus.Halt;
     }
 
@@ -495,7 +497,6 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
 
-
         final Map<String, Object> returnObj = new ConcurrentHashMap<>();
         final ExecutorService executor = Executors.newFixedThreadPool( 3 );
         executor.execute( () -> ConfigEditorServletUtils.outputChangeLogData( pwmRequest, configManagerBean, returnObj ) );
@@ -531,34 +532,25 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             return ProcessStatus.Halt;
         }
 
-
         final Set<StoredConfigItemKey> searchResults = StoredConfigurationUtil.search( storedConfiguration, searchTerm, locale );
-        final ConcurrentHashMap<String, Map<String, SearchResultItem>> returnData = new ConcurrentHashMap<>();
+        final Map<String, Map<String, SearchResultItem>> returnData = new HashMap<>();
 
         searchResults
-                .parallelStream()
+                .stream()
                 .filter( key -> key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
                 .forEach( recordID ->
                 {
-                    final PwmSetting setting = recordID.toPwmSetting();
-                    final SearchResultItem item = new SearchResultItem(
-                            setting.getCategory().toString(),
-                            storedConfiguration.readSetting( setting, recordID.getProfileID() ).toDebugString( locale ),
-                            setting.getCategory().toMenuLocationDebug( recordID.getProfileID(), locale ),
-                            storedConfiguration.isDefaultValue( setting, recordID.getProfileID() ),
-                            recordID.getProfileID()
-                    );
+                    final SearchResultItem item = SearchResultItem.fromKey( recordID, storedConfiguration, locale );
                     final String returnCategory = item.getNavigation();
 
-
                     returnData.putIfAbsent( returnCategory, new ConcurrentHashMap<>() );
-                    returnData.get( returnCategory ).put( setting.getKey(), item );
+                    returnData.get( returnCategory ).put( recordID.getRecordID(), item );
                 } );
 
         final TreeMap<String, Map<String, SearchResultItem>> outputMap = new TreeMap<>();
-        for ( final String key : returnData.keySet() )
+        for ( final Map.Entry<String, Map<String, SearchResultItem>> entry : returnData.entrySet() )
         {
-            outputMap.put( key, new TreeMap<>( returnData.get( key ) ) );
+            outputMap.put( entry.getKey(), new TreeMap<>( entry.getValue() ) );
         }
 
         restResultBean = RestResultBean.withData( outputMap );
@@ -674,7 +666,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             final Optional<EmailServer> emailServer = EmailServerUtil.makeEmailServer( testConfiguration, emailServerProfile, null );
             if ( emailServer.isPresent() )
             {
-                final MacroRequest macroRequest = MacroRequest.forUser( pwmRequest, pwmRequest.getUserInfoIfLoggedIn() );
+                final MacroRequest macroRequest = SampleDataGenerator.sampleMacroRequest( pwmRequest.getPwmApplication() );
 
                 try
                 {
@@ -707,7 +699,6 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException, IOException, ServletException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
 
         final String key = pwmRequest.readParameterAsString( "key" );
         final PwmSetting setting = PwmSetting.forKey( key )
@@ -716,45 +707,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
 
         if ( setting == PwmSetting.HTTPS_CERT )
         {
-            try
-            {
-                final PasswordData passwordData = pwmRequest.readParameterAsPassword( "password" );
-                final String alias = pwmRequest.readParameterAsString( "alias" );
-                final HttpsServerCertificateManager.KeyStoreFormat keyStoreFormat;
-                try
-                {
-                    keyStoreFormat = HttpsServerCertificateManager.KeyStoreFormat.valueOf( pwmRequest.readParameterAsString( "format" ) );
-                }
-                catch ( final IllegalArgumentException e )
-                {
-                    throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "unknown format type: " + e.getMessage(), new String[]
-                            {
-                                    "format",
-                                    }
-                    ) );
-                }
-
-                final Map<String, PwmRequest.FileUploadItem> fileUploads = pwmRequest.readFileUploads( maxFileSize, 1 );
-                final InputStream fileIs = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).getContent().newByteArrayInputStream();
-
-                HttpsServerCertificateManager.importKey(
-                        modifier,
-                        keyStoreFormat,
-                        fileIs,
-                        passwordData,
-                        alias
-                );
-
-                configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
-                pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
-                return ProcessStatus.Halt;
-            }
-            catch ( final PwmException e )
-            {
-                LOGGER.error( pwmRequest, () -> "error during https certificate upload: " + e.getMessage() );
-                pwmRequest.respondWithError( e.getErrorInformation(), false );
-                return ProcessStatus.Halt;
-            }
+            ConfigEditorServletUtils.processHttpsCertificateUpload( pwmRequest, configManagerBean );
+            return ProcessStatus.Halt;
         }
 
         final FileValue fileValue = ConfigEditorServletUtils.readFileUploadToSettingValue( pwmRequest, maxFileSize );
@@ -764,11 +718,12 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                     ? pwmRequest.getPwmSession().getUserInfo().getUserIdentity()
                     : null;
 
+            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
             modifier.writeSetting( setting, null, fileValue, userIdentity );
+            configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
             pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         }
 
-        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         return ProcessStatus.Halt;
     }
 
@@ -779,77 +734,25 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
     {
         final Instant startTime = Instant.now();
-        final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-
-        final ArrayList<NavTreeItem> navigationData = new ArrayList<>();
-        final Map<String, Object> inputParameters = pwmRequest.readBodyAsJsonMap( PwmHttpRequestWrapper.Flag.BypassValidation );
-        final boolean modifiedSettingsOnly = ( boolean ) inputParameters.get( "modifiedSettingsOnly" );
-        final double level = ( double ) inputParameters.get( "level" );
-        final String filterText = ( String ) inputParameters.get( "text" );
 
-        {
-            // root node
-            final NavTreeItem categoryInfo = new NavTreeItem();
-            categoryInfo.setId( "ROOT" );
-            categoryInfo.setName( "ROOT" );
-            navigationData.add( categoryInfo );
-        }
-
-        {
-            final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
-            final List<PwmSettingCategory> categories = NavTreeHelper.filteredCategories(
-                    pwmRequest.getPwmApplication(),
-                    storedConfiguration,
-                    pwmRequest.getLocale(),
-                    modifiedSettingsOnly,
-                    level,
-                    filterText
-            );
-            navigationData.addAll( NavTreeHelper.makeSettingNavItems( categories, storedConfiguration, pwmRequest.getLocale() ) );
-        }
+        final Map<String, String> inputParameters = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation );
 
-        boolean includeDisplayText = true;
-        if ( level >= 1 )
-        {
-            for ( final PwmLocaleBundle localeBundle : PwmLocaleBundle.values() )
-            {
-                if ( !localeBundle.isAdminOnly() )
-                {
-                    final Set<String> modifiedKeys = new TreeSet<>();
-                    if ( modifiedSettingsOnly )
-                    {
-                        modifiedKeys.addAll( NavTreeHelper.determineModifiedKeysSettings( localeBundle, pwmRequest.getConfig(), configManagerBean.getStoredConfiguration() ) );
-                    }
-                    if ( !modifiedSettingsOnly || !modifiedKeys.isEmpty() )
-                    {
-                        final NavTreeItem categoryInfo = new NavTreeItem();
-                        categoryInfo.setId( localeBundle.toString() );
-                        categoryInfo.setName( localeBundle.getTheClass().getSimpleName() );
-                        categoryInfo.setParent( "DISPLAY_TEXT" );
-                        categoryInfo.setType( NavTreeHelper.NavItemType.displayText );
-                        categoryInfo.setKeys( new TreeSet<>( modifiedSettingsOnly ? modifiedKeys : localeBundle.getDisplayKeys() ) );
-                        navigationData.add( categoryInfo );
-                        includeDisplayText = true;
-                    }
-                }
-            }
-        }
+        final NavTreeSettings navTreeSettings = NavTreeSettings.builder()
+                .modifiedSettingsOnly( Boolean.parseBoolean( inputParameters.get( "modifiedSettingsOnly" ) ) )
+                .level( JavaHelper.silentParseInt( inputParameters.get( "level" ), 0 ) )
+                .filterText( inputParameters.get( "text" ) )
+                .locale( pwmRequest.getLocale() )
+                .build();
 
-        if ( includeDisplayText )
-        {
-            final NavTreeItem categoryInfo = new NavTreeItem();
-            categoryInfo.setId( "DISPLAY_TEXT" );
-            categoryInfo.setName( "Display Text" );
-            categoryInfo.setType( NavTreeHelper.NavItemType.navigation );
-            categoryInfo.setParent( "ROOT" );
-            navigationData.add( categoryInfo );
-        }
+        final StoredConfiguration storedConfiguration = getBean( pwmRequest ).getStoredConfiguration();
 
-        NavTreeHelper.moveNavItemToTopOfList( PwmSettingCategory.NOTES.toString(), navigationData );
-        NavTreeHelper.moveNavItemToTopOfList( PwmSettingCategory.TEMPLATES.toString(), navigationData );
+        final List<NavTreeItem> navigationData = NavTreeDataMaker.makeNavTreeData(
+                pwmRequest.getPwmApplication(),
+                storedConfiguration,
+                navTreeSettings );
 
         LOGGER.trace( pwmRequest, () -> "completed navigation tree data request in " + TimeDuration.fromCurrent( startTime ).asCompactString() );
-        pwmRequest.outputJsonResult( RestResultBean.withData( navigationData ) );
+        pwmRequest.outputJsonResult( RestResultBean.withData( new ArrayList<>( navigationData ) ) );
         return ProcessStatus.Halt;
     }
 
@@ -858,8 +761,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final ConfigEditorServletUtils.SettingData settingData =  ConfigEditorServletUtils.generateSettingData(
-                pwmRequest.getPwmApplication(),
+        final SettingData settingData =  SettingDataMaker.generateSettingData(
                 configManagerBean.getStoredConfiguration(),
                 pwmRequest.getLabel(),
                 pwmRequest.getLocale()
@@ -945,27 +847,13 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final String settingKey = inputMap.get( "setting" );
         final PwmSetting setting = PwmSetting.forKey( settingKey )
                 .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
-        PwmSettingCategory category = null;
-        for ( final PwmSettingCategory loopCategory : PwmSettingCategory.values() )
-        {
-            if ( loopCategory.hasProfiles() )
-            {
-                final Optional<PwmSetting> profileSetting = loopCategory.getProfileSetting();
-                if ( profileSetting.isPresent() && profileSetting.get() == setting )
-                {
-                    category = loopCategory;
-                }
-            }
-        }
+
+        final PwmSettingCategory category = PwmSettingCategory.forProfileSetting( setting )
+                .orElseThrow( () -> new IllegalStateException( "specified key does not associated with a profile-enabled category" ) );
 
         final String sourceID = inputMap.get( "sourceID" );
         final String destinationID = inputMap.get( "destinationID" );
 
-        if ( category == null )
-        {
-            throw new IllegalStateException();
-        }
-
         try
         {
             modifier.copyProfileID( category, sourceID, destinationID, pwmRequest.getUserInfoIfLoggedIn() );

+ 55 - 76
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java

@@ -20,18 +20,14 @@
 
 package password.pwm.http.servlet.configeditor;
 
-import lombok.Builder;
-import lombok.Value;
-import password.pwm.PwmApplication;
+import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
-import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingSyntax;
-import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.ValueMetaData;
 import password.pwm.config.value.ActionValue;
@@ -47,17 +43,19 @@ import password.pwm.health.ConfigurationChecker;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.ConfigManagerBean;
+import password.pwm.i18n.Message;
 import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.PasswordData;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroRequest;
+import password.pwm.util.secure.HttpsServerCertificateManager;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.bean.HealthData;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
-import java.io.Serializable;
+import java.io.InputStream;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -78,7 +76,7 @@ public class ConfigEditorServletUtils
             final PwmRequest pwmRequest,
             final int maxFileSize
     )
-            throws PwmUnrecoverableException, IOException, ServletException
+            throws IOException
     {
 
         final Map<String, PwmRequest.FileUploadItem> fileUploads;
@@ -185,73 +183,6 @@ public class ConfigEditorServletUtils
         return HealthData.builder().build();
     }
 
-    @Value
-    @Builder
-    public static class SettingData implements Serializable
-    {
-        private final Map<String, Object> settings;
-        private final Map<String, Object> categories;
-        private final Map<String, Object> locales;
-        private final Object ldapProfileIds;
-        private final PwmSettingTemplateSet currentTemplate;
-        private final Map<String, Object> var;
-
-    }
-
-    public static SettingData generateSettingData(
-            final PwmApplication pwmApplication,
-            final StoredConfiguration storedConfiguration,
-            final SessionLabel sessionLabel,
-            final Locale locale
-
-    )
-            throws PwmUnrecoverableException
-    {
-        final Instant startTime = Instant.now();
-        final MacroRequest macroRequest = MacroRequest.forNonUserSpecific( pwmApplication, sessionLabel );
-        final PwmSettingTemplateSet template = storedConfiguration.getTemplateSet();
-        final SettingData.SettingDataBuilder builder = SettingData.builder();
-
-        {
-            final LinkedHashMap<String, Object> settingMap = new LinkedHashMap<>();
-            for ( final PwmSetting setting : PwmSetting.values() )
-            {
-
-                settingMap.put( setting.getKey(), SettingInfo.forSetting( setting, template, macroRequest, locale ) );
-            }
-            builder.settings( settingMap );
-        }
-        {
-            final LinkedHashMap<String, Object> categoryMap = new LinkedHashMap<>();
-            for ( final PwmSettingCategory category : PwmSettingCategory.values() )
-            {
-                categoryMap.put( category.getKey(), CategoryInfo.forCategory( category, macroRequest, locale ) );
-            }
-            builder.categories( categoryMap );
-        }
-        {
-            final LinkedHashMap<String, Object> labelMap = new LinkedHashMap<>();
-            for ( final PwmLocaleBundle localeBundle : PwmLocaleBundle.values() )
-            {
-                final LocaleInfo localeInfo = new LocaleInfo();
-                localeInfo.description = localeBundle.getTheClass().getSimpleName();
-                localeInfo.key = localeBundle.toString();
-                localeInfo.adminOnly = localeBundle.isAdminOnly();
-                labelMap.put( localeBundle.getTheClass().getSimpleName(), localeInfo );
-            }
-            builder.locales( labelMap );
-        }
-        {
-            final LinkedHashMap<String, Object> varMap = new LinkedHashMap<>();
-            varMap.put( "ldapProfileIds", storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST, null ).toNativeObject() );
-            varMap.put( "currentTemplate", storedConfiguration.getTemplateSet() );
-            builder.var( varMap );
-        }
-        LOGGER.trace( sessionLabel, () -> "generated settingData", () -> TimeDuration.fromCurrent( startTime ) );
-        return builder.build();
-
-    }
-
     static ConfigEditorServlet.ReadSettingResponse handleLocaleBundleReadSetting(
             final PwmRequest pwmRequest,
             final StoredConfiguration storedConfig,
@@ -369,4 +300,52 @@ public class ConfigEditorServletUtils
         builder.syntax( theSetting.getSyntax().toString() );
         return builder.build();
     }
+
+    static void processHttpsCertificateUpload(
+            final PwmRequest pwmRequest,
+            final ConfigManagerBean configManagerBean
+    )
+            throws IOException, ServletException
+    {
+        try
+        {
+            final PasswordData passwordData = pwmRequest.readParameterAsPassword( "password" );
+            final String alias = pwmRequest.readParameterAsString( "alias" );
+            final HttpsServerCertificateManager.KeyStoreFormat keyStoreFormat;
+            try
+            {
+                keyStoreFormat = HttpsServerCertificateManager.KeyStoreFormat.valueOf( pwmRequest.readParameterAsString( "format" ) );
+            }
+            catch ( final IllegalArgumentException e )
+            {
+                throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "unknown format type: " + e.getMessage(), new String[]
+                        {
+                                "format",
+                                }
+                ) );
+            }
+
+            final int maxFileSize = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.CONFIG_MAX_JDBC_JAR_SIZE ) );
+            final Map<String, PwmRequest.FileUploadItem> fileUploads = pwmRequest.readFileUploads( maxFileSize, 1 );
+            final InputStream fileIs = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).getContent().newByteArrayInputStream();
+
+            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
+
+            HttpsServerCertificateManager.importKey(
+                    modifier,
+                    keyStoreFormat,
+                    fileIs,
+                    passwordData,
+                    alias
+            );
+
+            configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
+            pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
+        }
+        catch ( final PwmException e )
+        {
+            LOGGER.error( pwmRequest, () -> "error during https certificate upload: " + e.getMessage() );
+            pwmRequest.respondWithError( e.getErrorInformation(), false );
+        }
+    }
 }

+ 0 - 359
server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeHelper.java

@@ -1,359 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2020 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.http.servlet.configeditor;
-
-import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
-import password.pwm.PwmEnvironment;
-import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
-import password.pwm.config.value.StoredValue;
-import password.pwm.config.stored.StoredConfiguration;
-import password.pwm.config.stored.StoredConfigurationUtil;
-import password.pwm.i18n.Config;
-import password.pwm.i18n.PwmLocaleBundle;
-import password.pwm.util.i18n.LocaleHelper;
-import password.pwm.util.java.StringUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.ResourceBundle;
-import java.util.Set;
-import java.util.TreeSet;
-
-class NavTreeHelper
-{
-    static Set<String> determineModifiedKeysSettings(
-            final PwmLocaleBundle bundle,
-            final Configuration config,
-            final StoredConfiguration storedConfiguration
-    )
-    {
-        final Set<String> modifiedKeys = new TreeSet<>();
-        for ( final String key : bundle.getDisplayKeys() )
-        {
-            final Map<String, String> storedBundle = storedConfiguration.readLocaleBundleMap( bundle, key );
-            if ( !storedBundle.isEmpty() )
-            {
-                for ( final Locale locale : config.getKnownLocales() )
-                {
-                    final ResourceBundle defaultBundle = ResourceBundle.getBundle( bundle.getTheClass().getName(), locale );
-                    final String localeKeyString = PwmConstants.DEFAULT_LOCALE.toString().equals( locale.toString() ) ? "" : locale.toString();
-                    if ( storedBundle.containsKey( localeKeyString ) )
-                    {
-                        final String value = storedBundle.get( localeKeyString );
-                        if ( value != null && !value.equals( defaultBundle.getString( key ) ) )
-                        {
-                            modifiedKeys.add( key );
-                        }
-                    }
-                }
-            }
-        }
-        return modifiedKeys;
-    }
-
-    static boolean categoryMatcher(
-            final PwmApplication pwmApplication,
-            final PwmSettingCategory category,
-            final StoredConfiguration storedConfiguration,
-            final boolean modifiedOnly,
-            final int minLevel,
-            final String text
-    )
-    {
-        if ( category.isHidden() )
-        {
-            return false;
-        }
-
-        if ( category == PwmSettingCategory.HTTPS_SERVER )
-        {
-            if ( !pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.ManageHttps ) )
-            {
-                return false;
-            }
-        }
-
-        for ( final PwmSettingCategory childCategory : category.getChildCategories() )
-        {
-            if ( categoryMatcher( pwmApplication, childCategory, storedConfiguration, modifiedOnly, minLevel, text ) )
-            {
-                return true;
-            }
-        }
-
-        if ( category.hasProfiles() )
-        {
-            final List<String> profileIDs = storedConfiguration.profilesForSetting(
-                    category.getProfileSetting().orElseThrow( IllegalStateException::new ) );
-
-            if ( profileIDs == null || profileIDs.isEmpty() )
-            {
-                return true;
-            }
-
-            for ( final String profileID : profileIDs )
-            {
-                for ( final PwmSetting setting : category.getSettings() )
-                {
-                    if ( settingMatches( storedConfiguration, setting, profileID, modifiedOnly, minLevel, text ) )
-                    {
-                        return true;
-                    }
-                }
-            }
-
-        }
-        else
-        {
-            for ( final PwmSetting setting : category.getSettings() )
-            {
-                if ( settingMatches( storedConfiguration, setting, null, modifiedOnly, minLevel, text ) )
-                {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    static List<PwmSettingCategory> filteredCategories(
-            final PwmApplication pwmApplication,
-            final StoredConfiguration storedConfiguration,
-            final Locale locale,
-            final boolean modifiedSettingsOnly,
-            final double level,
-            final String filterText
-    )
-    {
-        final List<PwmSettingCategory> returnList = new ArrayList<>();
-
-        for ( final PwmSettingCategory loopCategory : PwmSettingCategory.sortedValues( locale ) )
-        {
-            if ( NavTreeHelper.categoryMatcher(
-                    pwmApplication,
-                    loopCategory,
-                    storedConfiguration,
-                    modifiedSettingsOnly,
-                    ( int ) level,
-                    filterText
-            ) )
-            {
-                returnList.add( loopCategory );
-            }
-        }
-
-        return returnList;
-    }
-
-    /**
-     * Produces a collection of {@code NavTreeItem}.
-     */
-    static List<NavTreeItem> makeSettingNavItems(
-            final List<PwmSettingCategory> categories,
-            final StoredConfiguration storedConfiguration,
-            final Locale locale
-    )
-    {
-        final List<NavTreeItem> navigationData = new ArrayList<>();
-
-        for ( final PwmSettingCategory loopCategory : categories )
-        {
-            if ( !loopCategory.hasProfiles() )
-            {
-                // regular category, so output a standard nav tree item
-                navigationData.add( navTreeItemForCategory( loopCategory, locale, null ) );
-            }
-            else
-            {
-                final List<String> profiles = StoredConfigurationUtil.profilesForCategory( loopCategory, storedConfiguration );
-                final boolean topLevelProfileParent = loopCategory.isTopLevelProfile();
-
-                if ( topLevelProfileParent )
-                {
-                    // edit profile option
-                    navigationData.add( navTreeItemForCategory( loopCategory, locale, null ) );
-
-                    {
-                        final NavTreeItem profileEditorInfo = new NavTreeItem();
-                        profileEditorInfo.setId( loopCategory.getKey() + "-EDITOR" );
-                        final String editItemName = LocaleHelper.getLocalizedMessage( locale, Config.Label_ProfileListEditMenuItem, null );
-                        profileEditorInfo.setName( editItemName );
-                        profileEditorInfo.setType( NavTreeHelper.NavItemType.profileDefinition );
-                        final PwmSetting profileSetting = loopCategory.getProfileSetting().orElseThrow( IllegalStateException::new );
-                        profileEditorInfo.setProfileSetting( profileSetting.getKey() );
-                        profileEditorInfo.setParent( loopCategory.getKey() );
-                        navigationData.add( profileEditorInfo );
-                    }
-
-                    for ( final String profileId : profiles )
-                    {
-                        final NavTreeItem profileInfo = navTreeItemForCategory( loopCategory, locale, profileId );
-                        profileInfo.setName( profileId.isEmpty() ? "Default" : profileId );
-                        profileInfo.setId( "profile-" + loopCategory.getKey() + "-" + profileId );
-                        profileInfo.setParent( loopCategory.getKey() );
-                        if ( loopCategory.getChildCategories().isEmpty() )
-                        {
-                            profileInfo.setType( NavItemType.category );
-                        }
-                        else
-                        {
-                            profileInfo.setType( NavItemType.navigation );
-                        }
-
-                        navigationData.add( profileInfo );
-                    }
-                }
-                else
-                {
-                    for ( final String profileId : profiles )
-                    {
-                        navigationData.add( navTreeItemForCategory( loopCategory, locale, profileId ) );
-                    }
-                }
-            }
-        }
-
-        return navigationData;
-    }
-
-    private static NavTreeItem navTreeItemForCategory(
-            final PwmSettingCategory category,
-            final Locale locale,
-            final String profileId
-    )
-    {
-        final NavTreeItem categoryItem = new NavTreeItem();
-        categoryItem.setId( category.getKey() + ( profileId != null ? "-" + profileId : "" ) );
-        categoryItem.setName( category.getLabel( locale ) );
-        categoryItem.setCategory( category.getKey() );
-        if ( category.getParent() != null )
-        {
-            if ( profileId != null )
-            {
-                categoryItem.setParent( "profile-" + category.getParent().getKey() + "-" + profileId );
-            }
-            else
-            {
-                categoryItem.setParent( category.getParent().getKey() );
-            }
-        }
-        else
-        {
-            categoryItem.setParent( "ROOT" );
-        }
-        if ( category.getChildCategories().isEmpty() && !category.isTopLevelProfile() )
-        {
-            categoryItem.setType( NavTreeHelper.NavItemType.category );
-        }
-        else
-        {
-            categoryItem.setType( NavTreeHelper.NavItemType.navigation );
-        }
-        if ( profileId != null )
-        {
-            categoryItem.setProfile( profileId );
-        }
-        categoryItem.setMenuLocation( category.toMenuLocationDebug( profileId, locale ) );
-        return categoryItem;
-    }
-
-    private static boolean settingMatches(
-            final StoredConfiguration storedConfiguration,
-            final PwmSetting setting,
-            final String profileID,
-            final boolean modifiedOnly,
-            final int level,
-            final String text
-    )
-    {
-        if ( setting.isHidden() )
-        {
-            return false;
-        }
-
-        if ( modifiedOnly )
-        {
-            if ( storedConfiguration.isDefaultValue( setting, profileID ) )
-            {
-                return false;
-            }
-        }
-
-        if ( level > 0 && setting.getLevel() > level )
-        {
-            return false;
-        }
-
-
-        if ( text == null || text.isEmpty() )
-        {
-            return true;
-        }
-        else
-        {
-            final StoredValue storedValue = storedConfiguration.readSetting( setting, profileID );
-            for ( final String term : StringUtil.whitespaceSplit( text ) )
-            {
-                if ( StoredConfigurationUtil.matchSetting( storedConfiguration, setting, storedValue, term, PwmConstants.DEFAULT_LOCALE ) )
-                {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    static void moveNavItemToTopOfList( final String categoryID, final List<NavTreeItem> navigationData )
-    {
-        {
-            // put templates on top
-            NavTreeItem templateEntry = null;
-            for ( final NavTreeItem entry : navigationData )
-            {
-                if ( categoryID.equals( entry.getId() ) )
-                {
-                    templateEntry = entry;
-                }
-            }
-            if ( templateEntry != null )
-            {
-                navigationData.remove( templateEntry );
-                navigationData.add( 0, templateEntry );
-            }
-        }
-    }
-
-    enum NavItemType
-    {
-        category,
-        navigation,
-        displayText,
-        profile,
-        profileDefinition,
-    }
-
-}

+ 0 - 127
server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeItem.java

@@ -1,127 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2020 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.http.servlet.configeditor;
-
-import java.io.Serializable;
-import java.util.Set;
-
-class NavTreeItem implements Serializable
-{
-    private String id;
-    private String name;
-    private String parent;
-    private String category;
-    private NavTreeHelper.NavItemType type;
-    private String profileSetting;
-    private String menuLocation;
-    private Set<String> keys;
-    private String profile;
-
-    public String getId( )
-    {
-        return id;
-    }
-
-    public void setId( final String id )
-    {
-        this.id = id;
-    }
-
-    public String getName( )
-    {
-        return name;
-    }
-
-    public void setName( final String name )
-    {
-        this.name = name;
-    }
-
-    public String getParent( )
-    {
-        return parent;
-    }
-
-    public void setParent( final String parent )
-    {
-        this.parent = parent;
-    }
-
-    public String getCategory( )
-    {
-        return category;
-    }
-
-    public void setCategory( final String category )
-    {
-        this.category = category;
-    }
-
-    public NavTreeHelper.NavItemType getType( )
-    {
-        return type;
-    }
-
-    public void setType( final NavTreeHelper.NavItemType type )
-    {
-        this.type = type;
-    }
-
-    public String getProfileSetting( )
-    {
-        return profileSetting;
-    }
-
-    public void setProfileSetting( final String profileSetting )
-    {
-        this.profileSetting = profileSetting;
-    }
-
-    public String getMenuLocation( )
-    {
-        return menuLocation;
-    }
-
-    public void setMenuLocation( final String menuLocation )
-    {
-        this.menuLocation = menuLocation;
-    }
-
-    public Set<String> getKeys( )
-    {
-        return keys;
-    }
-
-    public void setKeys( final Set<String> keys )
-    {
-        this.keys = keys;
-    }
-
-    public String getProfile( )
-    {
-        return profile;
-    }
-
-    public void setProfile( final String profile )
-    {
-        this.profile = profile;
-    }
-}

+ 23 - 36
server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java

@@ -21,50 +21,37 @@
 package password.pwm.http.servlet.configeditor;
 
 import com.google.gson.annotations.SerializedName;
+import lombok.Value;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.StoredConfigItemKey;
+import password.pwm.config.stored.StoredConfiguration;
 
 import java.io.Serializable;
+import java.util.Locale;
 
+@Value
 class SearchResultItem implements Serializable
 {
-    private String category;
-    private String value;
-    private String navigation;
+    private final String category;
+    private final String value;
+    private final String navigation;
 
     @SerializedName( "default" )
-    private boolean defaultValue;
-    private String profile;
+    private final boolean defaultValue;
+    private final String profile;
 
-    SearchResultItem( final String category, final String value, final String navigation, final boolean defaultValue, final String profile )
+    static SearchResultItem fromKey(
+            final StoredConfigItemKey key,
+            final StoredConfiguration storedConfiguration,
+            final Locale locale )
     {
-        this.category = category;
-        this.value = value;
-        this.navigation = navigation;
-        this.defaultValue = defaultValue;
-        this.profile = profile;
-    }
-
-    public String getCategory( )
-    {
-        return category;
-    }
-
-    public String getValue( )
-    {
-        return value;
-    }
-
-    public String getNavigation( )
-    {
-        return navigation;
-    }
-
-    public boolean isDefaultValue( )
-    {
-        return defaultValue;
-    }
-
-    public String getProfile( )
-    {
-        return profile;
+        final PwmSetting setting = key.toPwmSetting();
+        return new SearchResultItem(
+                setting.getCategory().toString(),
+                storedConfiguration.readSetting( setting, key.getProfileID() ).toDebugString( locale ),
+                setting.getCategory().toMenuLocationDebug( key.getProfileID(), locale ),
+                storedConfiguration.isDefaultValue( setting, key.getProfileID() ),
+                key.getProfileID()
+        );
     }
 }

+ 15 - 18
server/src/main/java/password/pwm/http/servlet/configeditor/CategoryInfo.java → server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java

@@ -18,16 +18,17 @@
  * limitations under the License.
  */
 
-package password.pwm.http.servlet.configeditor;
+package password.pwm.http.servlet.configeditor.data;
 
-import lombok.Data;
+import lombok.Builder;
+import lombok.Value;
 import password.pwm.config.PwmSettingCategory;
-import password.pwm.util.macro.MacroRequest;
 
 import java.io.Serializable;
 import java.util.Locale;
 
-@Data
+@Value
+@Builder
 public class CategoryInfo implements Serializable
 {
     private int level;
@@ -42,21 +43,17 @@ public class CategoryInfo implements Serializable
 
     public static CategoryInfo forCategory(
             final PwmSettingCategory category,
-            final MacroRequest macroRequest,
             final Locale locale )
     {
-        final CategoryInfo categoryInfo = new CategoryInfo();
-        categoryInfo.key = category.getKey();
-        categoryInfo.level = category.getLevel();
-        categoryInfo.description = macroRequest.expandMacros( category.getDescription( locale ) );
-        categoryInfo.label = category.getLabel( locale );
-        categoryInfo.hidden = category.isHidden();
-        if ( category.getParent() != null )
-        {
-            categoryInfo.parent = category.getParent().getKey();
-        }
-        categoryInfo.profiles = category.hasProfiles();
-        categoryInfo.menuLocation = category.toMenuLocationDebug( "PROFILE", locale );
-        return categoryInfo;
+        return CategoryInfo.builder()
+                .key( category.getKey() )
+                .level( category.getLevel() )
+                .description( category.getDescription( locale ) )
+                .label( category.getLabel( locale ) )
+                .hidden( category.isHidden() )
+                .parent( category.getParent() != null ? category.getParent().getKey() : null )
+                .profiles( category.hasProfiles() )
+                .menuLocation( category.toMenuLocationDebug( "PROFILE", locale ) )
+                .build();
     }
 }

+ 45 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/data/LocaleInfo.java

@@ -0,0 +1,45 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 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.http.servlet.configeditor.data;
+
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.i18n.PwmLocaleBundle;
+
+import java.io.Serializable;
+
+@Value
+@Builder
+public class LocaleInfo implements Serializable
+{
+    public String description;
+    public String key;
+    public boolean adminOnly;
+
+    public static LocaleInfo forBundle( final PwmLocaleBundle pwmLocaleBundle )
+    {
+        return LocaleInfo.builder()
+                    .description( pwmLocaleBundle.getTheClass().getSimpleName() )
+                    .key( pwmLocaleBundle.toString() )
+                    .adminOnly( pwmLocaleBundle.isAdminOnly() )
+                    .build();
+    }
+}

+ 428 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java

@@ -0,0 +1,428 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 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.http.servlet.configeditor.data;
+
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.PwmEnvironment;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationUtil;
+import password.pwm.config.value.StoredValue;
+import password.pwm.i18n.Config;
+import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class NavTreeDataMaker
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( NavTreeDataMaker.class );
+
+    private static final String ROOT_ITEM_ID = "ROOT";
+    private static final String DISPLAY_TEXT_ITEM_ID = "DISPLAY_TEXT";
+
+    private static final Supplier<NavTreeItem> DISPLAY_TEXT_ITEM = () -> NavTreeItem.builder()
+            .id( DISPLAY_TEXT_ITEM_ID )
+            .name( "Display Text" )
+            .type( NavTreeItem.NavItemType.navigation )
+            .parent( ROOT_ITEM_ID )
+            .build();
+
+    private static final Supplier<NavTreeItem> ROOT_ITEM = () -> NavTreeItem.builder()
+            .id( ROOT_ITEM_ID )
+            .name( ROOT_ITEM_ID )
+            .build();
+
+    public static List<NavTreeItem> makeNavTreeData(
+            final PwmApplication pwmApplication,
+            final StoredConfiguration storedConfiguration,
+            final NavTreeSettings navTreeSettings
+    )
+    {
+        final Instant startTime = Instant.now();
+        final List<NavTreeItem> navigationData = new ArrayList<>();
+
+        // root node
+        navigationData.add( ROOT_ITEM.get() );
+
+        // settings
+        navigationData.addAll( NavTreeDataMaker.makeSettingNavItems( pwmApplication, navTreeSettings, storedConfiguration ) );
+
+        boolean includeDisplayText = false;
+        if ( navTreeSettings.getLevel() >= 1 )
+        {
+            final List<NavTreeItem> displayTextItems = makeDisplayBundleNavItems( navTreeSettings, storedConfiguration );
+            if ( displayTextItems.isEmpty() )
+            {
+                includeDisplayText = true;
+            }
+
+            navigationData.addAll( displayTextItems );
+        }
+
+        if ( includeDisplayText )
+        {
+
+            navigationData.add( DISPLAY_TEXT_ITEM.get() );
+        }
+
+        NavTreeDataMaker.moveNavItemToTopOfList( PwmSettingCategory.NOTES.toString(), navigationData );
+        NavTreeDataMaker.moveNavItemToTopOfList( PwmSettingCategory.TEMPLATES.toString(), navigationData );
+
+        LOGGER.trace( () -> "completed navigation tree data request for " + navigationData.size()  + " items", () -> TimeDuration.fromCurrent( startTime ) );
+        return Collections.unmodifiableList( navigationData );
+    }
+
+    private static List<NavTreeItem> makeDisplayBundleNavItems(
+            final NavTreeSettings navTreeSettings,
+            final StoredConfiguration storedConfiguration
+    )
+    {
+        final List<NavTreeItem> navigationData = new ArrayList<>();
+        for ( final PwmLocaleBundle localeBundle : PwmLocaleBundle.values() )
+        {
+            if ( !localeBundle.isAdminOnly() )
+            {
+                final Set<String> modifiedKeys = new TreeSet<>();
+                if ( navTreeSettings.isModifiedSettingsOnly() )
+                {
+                    modifiedKeys.addAll( NavTreeDataMaker.determineModifiedDisplayBundleSettings( localeBundle, storedConfiguration ) );
+                }
+
+                if ( !navTreeSettings.isModifiedSettingsOnly() || !modifiedKeys.isEmpty() )
+                {
+                    final NavTreeItem categoryInfo = NavTreeItem.builder()
+                            .id( localeBundle.toString() )
+                            .name( localeBundle.getTheClass().getSimpleName() )
+                            .parent( DISPLAY_TEXT_ITEM_ID )
+                            .type( NavTreeItem.NavItemType.displayText )
+                            .keys( new TreeSet<>( navTreeSettings.isModifiedSettingsOnly() ? modifiedKeys : localeBundle.getDisplayKeys() ) )
+                            .build();
+                    navigationData.add( categoryInfo );
+                }
+            }
+        }
+        return Collections.unmodifiableList( navigationData );
+    }
+
+    private static Set<String> determineModifiedDisplayBundleSettings(
+            final PwmLocaleBundle bundle,
+            final StoredConfiguration storedConfiguration
+    )
+    {
+        final List<Locale> knownLocales = new Configuration( storedConfiguration ).getKnownLocales();
+        final Set<String> modifiedKeys = new TreeSet<>();
+        for ( final String key : bundle.getDisplayKeys() )
+        {
+            final Map<String, String> storedBundle = storedConfiguration.readLocaleBundleMap( bundle, key );
+            if ( !storedBundle.isEmpty() )
+            {
+                for ( final Locale locale : knownLocales )
+                {
+                    final String localeKeyString = PwmConstants.DEFAULT_LOCALE.toString().equals( locale.toString() ) ? "" : locale.toString();
+                    if ( storedBundle.containsKey( localeKeyString ) )
+                    {
+                        final String value = storedBundle.get( localeKeyString );
+                        if ( value != null )
+                        {
+                            final ResourceBundle defaultBundle = ResourceBundle.getBundle( bundle.getTheClass().getName(), locale );
+                            if ( !value.equals( defaultBundle.getString( key ) ) )
+                            {
+                                modifiedKeys.add( key );
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return modifiedKeys;
+    }
+
+    private static boolean categoryMatcher(
+            final PwmApplication pwmApplication,
+            final PwmSettingCategory category,
+            final StoredConfiguration storedConfiguration,
+            final NavTreeSettings navTreeSettings
+    )
+    {
+        if ( category.isHidden() )
+        {
+            return false;
+        }
+
+        if ( category == PwmSettingCategory.HTTPS_SERVER )
+        {
+            if ( !pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.ManageHttps ) )
+            {
+                return false;
+            }
+        }
+
+        for ( final PwmSettingCategory childCategory : category.getChildCategories() )
+        {
+            if ( categoryMatcher( pwmApplication, childCategory, storedConfiguration, navTreeSettings ) )
+            {
+                return true;
+            }
+        }
+
+        if ( category.hasProfiles() )
+        {
+            final List<String> profileIDs = storedConfiguration.profilesForSetting(
+                    category.getProfileSetting().orElseThrow( IllegalStateException::new ) );
+
+            if ( profileIDs == null || profileIDs.isEmpty() )
+            {
+                return true;
+            }
+
+            for ( final String profileID : profileIDs )
+            {
+                for ( final PwmSetting setting : category.getSettings() )
+                {
+                    if ( settingMatches( storedConfiguration, setting, profileID, navTreeSettings ) )
+                    {
+                        return true;
+                    }
+                }
+            }
+
+        }
+        else
+        {
+            for ( final PwmSetting setting : category.getSettings() )
+            {
+                if ( settingMatches( storedConfiguration, setting, null, navTreeSettings ) )
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static List<PwmSettingCategory> filteredCategories(
+            final PwmApplication pwmApplication,
+            final StoredConfiguration storedConfiguration,
+            final NavTreeSettings navTreeSettings
+    )
+    {
+        return Collections.unmodifiableList( PwmSettingCategory.sortedValues( navTreeSettings.getLocale() )
+                .stream()
+                .filter( pwmSettingCategory -> NavTreeDataMaker.categoryMatcher(
+                        pwmApplication,
+                        pwmSettingCategory,
+                        storedConfiguration,
+                        navTreeSettings
+                ) )
+                .collect( Collectors.toList() ) );
+    }
+
+    /**
+     * Produces a collection of {@code NavTreeItem}.
+     */
+    private static List<NavTreeItem> makeSettingNavItems(
+            final PwmApplication pwmApplication,
+            final NavTreeSettings navTreeSettings,
+            final StoredConfiguration storedConfiguration
+    )
+    {
+        final List<PwmSettingCategory> categories = NavTreeDataMaker.filteredCategories(
+                pwmApplication,
+                storedConfiguration,
+                navTreeSettings
+        );
+        final Locale locale = navTreeSettings.getLocale();
+        final List<NavTreeItem> navigationData = new ArrayList<>();
+
+        for ( final PwmSettingCategory loopCategory : categories )
+        {
+            if ( !loopCategory.hasProfiles() )
+            {
+                // regular category, so output a standard nav tree item
+                navigationData.add( navTreeItemForCategory( loopCategory, locale, null ) );
+            }
+            else
+            {
+                final List<String> profiles = StoredConfigurationUtil.profilesForCategory( loopCategory, storedConfiguration );
+                final boolean topLevelProfileParent = loopCategory.isTopLevelProfile();
+
+                if ( topLevelProfileParent )
+                {
+                    // edit profile option
+                    navigationData.add( navTreeItemForCategory( loopCategory, locale, null ) );
+
+                    {
+                        final PwmSetting profileSetting = loopCategory.getProfileSetting().orElseThrow( IllegalStateException::new );
+                        final NavTreeItem profileEditorInfo = NavTreeItem.builder()
+                                .id( loopCategory.getKey() + "-EDITOR" )
+                                .name( LocaleHelper.getLocalizedMessage( locale, Config.Label_ProfileListEditMenuItem, null ) )
+                                .type( NavTreeItem.NavItemType.profileDefinition )
+                                .profileSetting( profileSetting.getKey() )
+                                .parent( loopCategory.getKey() )
+                                .build();
+                        navigationData.add( profileEditorInfo );
+                    }
+
+                    for ( final String profileId : profiles )
+                    {
+                        final NavTreeItem profileInfo = navTreeItemForCategory( loopCategory, locale, profileId )
+                                .toBuilder()
+                                .name( profileId.isEmpty() ? "Default" : profileId )
+                                .id( "profile-" + loopCategory.getKey() + "-" + profileId )
+                                .parent( loopCategory.getKey() )
+                                .type( loopCategory.getChildCategories().isEmpty() ? NavTreeItem.NavItemType.category :  NavTreeItem.NavItemType.navigation )
+                                .build();
+                        navigationData.add( profileInfo );
+                    }
+                }
+                else
+                {
+                    for ( final String profileId : profiles )
+                    {
+                        navigationData.add( navTreeItemForCategory( loopCategory, locale, profileId ) );
+                    }
+                }
+            }
+        }
+
+        return navigationData;
+    }
+
+    private static NavTreeItem navTreeItemForCategory(
+            final PwmSettingCategory category,
+            final Locale locale,
+            final String profileId
+    )
+    {
+        final NavTreeItem.NavTreeItemBuilder categoryItem = NavTreeItem.builder();
+        categoryItem.id( category.getKey() + ( profileId != null ? "-" + profileId : "" ) );
+        categoryItem.name( category.getLabel( locale ) );
+        categoryItem.category( category.getKey() );
+        if ( category.getParent() != null )
+        {
+            if ( profileId != null )
+            {
+                categoryItem.parent( "profile-" + category.getParent().getKey() + "-" + profileId );
+            }
+            else
+            {
+                categoryItem.parent( category.getParent().getKey() );
+            }
+        }
+        else
+        {
+            categoryItem.parent( ROOT_ITEM_ID );
+        }
+        if ( category.getChildCategories().isEmpty() && !category.isTopLevelProfile() )
+        {
+            categoryItem.type( NavTreeItem.NavItemType.category );
+        }
+        else
+        {
+            categoryItem.type( NavTreeItem.NavItemType.navigation );
+        }
+        if ( profileId != null )
+        {
+            categoryItem.profile( profileId );
+        }
+        categoryItem.menuLocation( category.toMenuLocationDebug( profileId, locale ) );
+        return categoryItem.build();
+    }
+
+    private static boolean settingMatches(
+            final StoredConfiguration storedConfiguration,
+            final PwmSetting setting,
+            final String profileID,
+            final NavTreeSettings navTreeSettings
+    )
+    {
+        if ( setting.isHidden() )
+        {
+            return false;
+        }
+
+        if ( navTreeSettings.isModifiedSettingsOnly() )
+        {
+            if ( storedConfiguration.isDefaultValue( setting, profileID ) )
+            {
+                return false;
+            }
+        }
+
+        if ( navTreeSettings.getLevel() > 0 && setting.getLevel() > navTreeSettings.getLevel() )
+        {
+            return false;
+        }
+
+        final String text = navTreeSettings.getFilterText();
+        if ( StringUtil.isEmpty( text ) )
+        {
+            return true;
+        }
+        else
+        {
+            final StoredValue storedValue = storedConfiguration.readSetting( setting, profileID );
+            for ( final String term : StringUtil.whitespaceSplit( text ) )
+            {
+                if ( StoredConfigurationUtil.matchSetting( storedConfiguration, setting, storedValue, term, PwmConstants.DEFAULT_LOCALE ) )
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static void moveNavItemToTopOfList( final String categoryID, final List<NavTreeItem> navigationData )
+    {
+        Objects.requireNonNull( categoryID );
+
+        final Optional<NavTreeItem> optionalMatchItem = navigationData.stream()
+                .filter( navTreeItem -> categoryID.equals( navTreeItem.getId() ) )
+                .findAny();
+
+        optionalMatchItem.ifPresent( navTreeItem ->
+        {
+            navigationData.remove( navTreeItem );
+            navigationData.add( 0, navTreeItem );
+        } );
+    }
+}

+ 51 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeItem.java

@@ -0,0 +1,51 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 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.http.servlet.configeditor.data;
+
+import lombok.Builder;
+import lombok.Value;
+
+import java.io.Serializable;
+import java.util.Set;
+
+@Value
+@Builder( toBuilder = true )
+public class NavTreeItem implements Serializable
+{
+    private final String id;
+    private final String name;
+    private final String parent;
+    private final String category;
+    private final NavItemType type;
+    private final String profileSetting;
+    private final String menuLocation;
+    private final Set<String> keys;
+    private final String profile;
+
+    public enum NavItemType
+    {
+        category,
+        navigation,
+        displayText,
+        profile,
+        profileDefinition,
+    }
+}

+ 15 - 6
server/src/main/java/password/pwm/http/servlet/configeditor/LocaleInfo.java → server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java

@@ -18,13 +18,22 @@
  * limitations under the License.
  */
 
-package password.pwm.http.servlet.configeditor;
+package password.pwm.http.servlet.configeditor.data;
 
-import java.io.Serializable;
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.PwmConstants;
 
-public class LocaleInfo implements Serializable
+import java.util.Locale;
+
+@Value
+@Builder
+public class NavTreeSettings
 {
-    public String description;
-    public String key;
-    public boolean adminOnly;
+    private final boolean modifiedSettingsOnly;
+    private final int level;
+    private final String filterText;
+
+    @Builder.Default
+    private final Locale locale = PwmConstants.DEFAULT_LOCALE;
 }

+ 41 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingData.java

@@ -0,0 +1,41 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 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.http.servlet.configeditor.data;
+
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.config.PwmSettingTemplateSet;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Value
+@Builder
+public class SettingData implements Serializable
+{
+    private final Map<String, SettingInfo> settings;
+    private final Map<String, CategoryInfo> categories;
+    private final Map<String, LocaleInfo> locales;
+    private final Object ldapProfileIds;
+    private final PwmSettingTemplateSet currentTemplate;
+    private final SettingDataMaker.VarData var;
+
+}

+ 100 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java

@@ -0,0 +1,100 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 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.http.servlet.configeditor.data;
+
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.bean.SessionLabel;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.PwmSettingTemplateSet;
+import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.value.ValueTypeConverter;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class SettingDataMaker
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( SettingDataMaker.class );
+
+    public static SettingData generateSettingData(
+            final StoredConfiguration storedConfiguration,
+            final SessionLabel sessionLabel,
+            final Locale locale
+
+    )
+            throws PwmUnrecoverableException
+    {
+        final Instant startGenerateTime = Instant.now();
+        final PwmSettingTemplateSet templateSet = storedConfiguration.getTemplateSet();
+
+        final Map<String, SettingInfo> settingMap = Collections.unmodifiableMap( Arrays.stream( PwmSetting.values() )
+                .collect( Collectors.toMap(
+                        PwmSetting::getKey,
+                        pwmSetting -> SettingInfo.forSetting( pwmSetting, templateSet, locale ) ) ) );
+
+        final Map<String, CategoryInfo> categoryInfoMap = Collections.unmodifiableMap( Arrays.stream( PwmSettingCategory.values() )
+                .collect( Collectors.toMap(
+                        PwmSettingCategory::getKey,
+                        pwmSettingCategory -> CategoryInfo.forCategory( pwmSettingCategory, locale ) ) ) );
+
+        final Map<String, LocaleInfo> labelMap = Collections.unmodifiableMap( Arrays.stream( PwmLocaleBundle.values() )
+                .collect( Collectors.toMap(
+                        pwmLocaleBundle ->  pwmLocaleBundle.getTheClass().getSimpleName(),
+                        LocaleInfo::forBundle ) ) );
+
+        final VarData varMap = VarData.builder()
+                .ldapProfileIds( ValueTypeConverter.valueToStringArray( storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST, null ) ) )
+                .currentTemplate( templateSet )
+                .build();
+
+        final SettingData settingData = SettingData.builder()
+                .settings( settingMap )
+                .categories( categoryInfoMap )
+                .locales( labelMap )
+                .var( varMap )
+                .build();
+
+        LOGGER.trace( sessionLabel, () -> "generated settingData with "
+                + settingData.getSettings().size() + " settings and "
+                + settingData.getCategories().size() + " categories", () -> TimeDuration.fromCurrent( startGenerateTime ) );
+
+        return settingData;
+    }
+
+    @Value
+    @Builder
+    static class VarData
+    {
+        private final List<String> ldapProfileIds;
+        private final PwmSettingTemplateSet currentTemplate;
+    }
+}

+ 24 - 23
server/src/main/java/password/pwm/http/servlet/configeditor/SettingInfo.java → server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingInfo.java

@@ -18,24 +18,26 @@
  * limitations under the License.
  */
 
-package password.pwm.http.servlet.configeditor;
+package password.pwm.http.servlet.configeditor.data;
 
-import lombok.Data;
+import lombok.Builder;
+import lombok.Value;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingFlag;
 import password.pwm.config.PwmSettingProperty;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.PwmSettingTemplateSet;
-import password.pwm.util.macro.MacroRequest;
+import password.pwm.util.java.JavaHelper;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
-@Data
+@Value
+@Builder
 public class SettingInfo implements Serializable
 {
     private String key;
@@ -50,29 +52,28 @@ public class SettingInfo implements Serializable
     private String pattern;
     private String placeholder;
     private int level;
-    private List<PwmSettingFlag> flags;
+    private Set<PwmSettingFlag> flags;
 
     static SettingInfo forSetting(
             final PwmSetting setting,
             final PwmSettingTemplateSet template,
-            final MacroRequest macroRequest,
             final Locale locale
     )
     {
-        final SettingInfo settingInfo = new SettingInfo();
-        settingInfo.key = setting.getKey();
-        settingInfo.description = macroRequest.expandMacros( setting.getDescription( locale ) );
-        settingInfo.level = setting.getLevel();
-        settingInfo.label = setting.getLabel( locale );
-        settingInfo.syntax = setting.getSyntax();
-        settingInfo.category = setting.getCategory();
-        settingInfo.properties = setting.getProperties();
-        settingInfo.required = setting.isRequired();
-        settingInfo.hidden = setting.isHidden();
-        settingInfo.options = setting.getOptions();
-        settingInfo.pattern = setting.getRegExPattern().toString();
-        settingInfo.placeholder = setting.getExample( template );
-        settingInfo.flags = new ArrayList<>( setting.getFlags() );
-        return settingInfo;
+        return SettingInfo.builder()
+                .key( setting.getKey() )
+                .description( setting.getDescription( locale ) )
+                .level( setting.getLevel() )
+                .label( setting.getLabel( locale ) )
+                .syntax( setting.getSyntax() )
+                .category( setting.getCategory() )
+                .properties( setting.getProperties() )
+                .required( setting.isRequired() )
+                .hidden( setting.isHidden() )
+                .options( setting.getOptions() )
+                .pattern( setting.getRegExPattern().toString() )
+                .placeholder( setting.getExample( template ) )
+                .flags( Collections.unmodifiableSet( JavaHelper.copiedEnumSet( setting.getFlags(), PwmSettingFlag.class ) ) )
+                .build();
     }
 }

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

@@ -59,6 +59,8 @@ import password.pwm.http.bean.ConfigGuideBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.configeditor.ConfigEditorServletUtils;
+import password.pwm.http.servlet.configeditor.data.SettingData;
+import password.pwm.http.servlet.configeditor.data.SettingDataMaker;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapBrowser;
 import password.pwm.ldap.schema.SchemaOperationResult;
@@ -610,8 +612,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         final ConfigGuideBean configGuideBean = getBean( pwmRequest );
         final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
 
-        final ConfigEditorServletUtils.SettingData settingData = ConfigEditorServletUtils.generateSettingData(
-                pwmRequest.getPwmApplication(),
+        final SettingData settingData = SettingDataMaker.generateSettingData(
                 storedConfiguration,
                 pwmRequest.getLabel(),
                 pwmRequest.getLocale()