Преглед изворни кода

configreader performance improvements

Jason Rivard пре 5 година
родитељ
комит
12643801f1
18 измењених фајлова са 767 додато и 550 уклоњено
  1. 7 0
      server/src/main/java/password/pwm/config/PwmSetting.java
  2. 94 9
      server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
  3. 36 40
      server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
  4. 2 2
      server/src/main/java/password/pwm/config/stored/StoredConfigData.java
  5. 9 4
      server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java
  6. 144 176
      server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java
  7. 27 25
      server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  8. 31 1
      server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java
  9. 63 83
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  10. 26 0
      server/src/main/java/password/pwm/config/value/ValueTypeConverter.java
  11. 5 1
      server/src/main/java/password/pwm/config/value/X509CertificateValue.java
  12. 87 142
      server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java
  13. 10 1
      server/src/main/java/password/pwm/util/java/LazySoftReference.java
  14. 7 0
      server/src/main/java/password/pwm/util/java/LazySupplier.java
  15. 7 3
      server/src/main/java/password/pwm/util/java/TimeDuration.java
  16. 27 3
      server/src/main/java/password/pwm/util/java/XmlDocument.java
  17. 173 56
      server/src/main/java/password/pwm/util/java/XmlElement.java
  18. 12 4
      server/src/main/java/password/pwm/util/java/XmlFactory.java

+ 7 - 0
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -1310,6 +1310,7 @@ public enum PwmSetting
     private final Supplier<Boolean> hidden = new LazySupplier<>( () -> PwmSettingReader.readHidden( PwmSetting.this ) );
     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<Pattern> defaultLocaleLabel = new LazySupplier<>( () -> PwmSettingReader.readPattern( PwmSetting.this ) );
 
     PwmSetting(
             final String key,
@@ -1725,5 +1726,11 @@ public enum PwmSetting
             }
             return Pattern.compile( ".*", Pattern.DOTALL );
         }
+
+        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 );
+        }
     }
 }

+ 94 - 9
server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java

@@ -24,15 +24,16 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.value.StoredValue;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.option.RecoveryMinLifetimeOption;
 import password.pwm.config.option.WebServiceUsage;
 import password.pwm.config.value.OptionListValue;
+import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.ValueTypeConverter;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.PwmExceptionLoggingConsumer;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Arrays;
@@ -46,17 +47,14 @@ class ConfigurationCleaner
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigurationCleaner.class );
 
-
-
-
     private static final List<PwmExceptionLoggingConsumer<StoredConfigurationModifier>> STORED_CONFIG_POST_PROCESSORS = Collections.unmodifiableList( Arrays.asList(
             new UpdateDeprecatedAdComplexitySettings(),
             new UpdateDeprecatedMinPwdLifetimeSetting(),
-            new UpdateDeprecatedPublicHealthSetting()
+            new UpdateDeprecatedPublicHealthSetting(),
+            new ProfileNonProfiledSettings(),
+            new CheckForSuperfluousProfileSettings()
     ) );
 
-
-
     static void postProcessStoredConfig(
             final StoredConfigurationModifier storedConfiguration
     )
@@ -64,8 +62,6 @@ class ConfigurationCleaner
         STORED_CONFIG_POST_PROCESSORS.forEach( aClass -> PwmExceptionLoggingConsumer.wrapConsumer( aClass ).accept( storedConfiguration ) );
     }
 
-
-
     private static class UpdateDeprecatedAdComplexitySettings implements PwmExceptionLoggingConsumer<StoredConfigurationModifier>
     {
         @Override
@@ -158,5 +154,94 @@ class ConfigurationCleaner
         }
     }
 
+    private static class ProfileNonProfiledSettings implements PwmExceptionLoggingConsumer<StoredConfigurationModifier>
+    {
+        @Override
+        public void accept( final StoredConfigurationModifier modifier )
+                throws PwmUnrecoverableException
+        {
+            final StoredConfiguration inputConfig = modifier.newStoredConfiguration();
+            inputConfig.modifiedItems()
+                    .parallelStream()
+                    .filter( ( key ) -> StoredConfigItemKey.RecordType.SETTING.equals( key.getRecordType() ) )
+                    .filter( ( key ) -> key.toPwmSetting().getCategory().hasProfiles() )
+                    .filter( ( key ) -> StringUtil.isEmpty( key.getProfileID() ) )
+                    .forEach( ( key ) -> convertSetting( inputConfig, modifier, key ) );
+        }
+
+        private void convertSetting(
+                final StoredConfiguration inputConfig,
+                final StoredConfigurationModifier modifier,
+                final StoredConfigItemKey key )
+        {
+            final PwmSetting pwmSetting = key.toPwmSetting();
 
+            final List<String> targetProfiles = inputConfig.profilesForSetting( pwmSetting );
+            final StoredValue value = inputConfig.readSetting( pwmSetting, null );
+            final Optional<ValueMetaData> valueMetaData = inputConfig.readMetaData( key );
+
+            for ( final String destProfile : targetProfiles )
+            {
+                LOGGER.info( () -> "moving setting " + key.toString() + " without profile attribute to profile \"" + destProfile + "\"." );
+                {
+                    try
+                    {
+                        modifier.writeSettingAndMetaData( pwmSetting, destProfile, value, valueMetaData.orElse( null ) );
+                    }
+                    catch ( final PwmUnrecoverableException e )
+                    {
+                        LOGGER.warn( () -> "error moving setting " + pwmSetting.getKey() + " without profile attribute to profile \"" + destProfile
+                                + "\", error: " + e.getMessage() );
+                    }
+                }
+            }
+
+            try
+            {
+                LOGGER.info( () -> "removing setting " + key.toString() + " without profile" );
+                modifier.deleteKey( key );
+            }
+            catch ( final PwmUnrecoverableException e )
+            {
+                LOGGER.warn( () -> "error deleting setting " + pwmSetting.getKey() + " after adding profile settings: " + e.getMessage() );
+            }
+        }
+    }
+
+    private static class CheckForSuperfluousProfileSettings implements PwmExceptionLoggingConsumer<StoredConfigurationModifier>
+    {
+        @Override
+        public void accept( final StoredConfigurationModifier modifier )
+                throws PwmUnrecoverableException
+        {
+            final StoredConfiguration inputConfig = modifier.newStoredConfiguration();
+            inputConfig.modifiedItems()
+                    .stream()
+                    .filter( ( key ) -> StoredConfigItemKey.RecordType.SETTING.equals( key.getRecordType() ) )
+                    .filter( ( key ) -> key.toPwmSetting().getCategory().hasProfiles() )
+                    .filter( ( key ) -> verifyProfileIsValid( key, inputConfig ) )
+                    .forEach( ( key ) -> removeSuperfluousProfile( key, modifier ) );
+        }
+
+        boolean verifyProfileIsValid( final StoredConfigItemKey key, final StoredConfiguration inputConfig )
+        {
+            final PwmSetting pwmSetting = key.toPwmSetting();
+            final String recordID = key.getProfileID();
+            final List<String> profiles = inputConfig.profilesForSetting( pwmSetting );
+            return !profiles.contains( recordID );
+        }
+
+        void removeSuperfluousProfile( final StoredConfigItemKey key, final StoredConfigurationModifier modifier )
+        {
+            try
+            {
+                LOGGER.info( () -> "removing setting " + key.toString() + " with non-existing profileID" );
+                modifier.deleteKey( key );
+            }
+            catch ( final PwmUnrecoverableException e )
+            {
+                LOGGER.warn( () -> "error deleting setting " + key.toString() + " with non-existing profileID: " + e.getMessage() );
+            }
+        }
+    }
 }

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

@@ -41,6 +41,7 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
+import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -68,7 +69,6 @@ public class ConfigurationReader
     private StoredConfiguration storedConfiguration;
     private ErrorInformation configFileError;
 
-
     private PwmApplicationMode configMode = PwmApplicationMode.NEW;
 
     private volatile boolean saveInProgress;
@@ -129,12 +129,40 @@ public class ConfigurationReader
             return null;
         }
 
+        final StoredConfiguration storedConfiguration;
         final Instant startTime = Instant.now();
 
-        final InputStream theFileData;
-        try
+        try ( InputStream theFileData = new BufferedInputStream( Files.newInputStream( configFile.toPath() ), 1024_1024 ) )
         {
-            theFileData = Files.newInputStream( configFile.toPath() );
+            try
+            {
+                storedConfiguration = StoredConfigurationFactory.input( theFileData );
+            }
+            catch ( final Exception e )
+            {
+                final String errorMsg = "unable to parse configuration file: " + e.getMessage();
+                final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
+                        {
+                                errorMsg,
+                                }
+                );
+                this.configMode = PwmApplicationMode.ERROR;
+                e.printStackTrace();
+                throw new PwmUnrecoverableException( errorInformation, e );
+            }
+
+            final List<String> validationErrorMsgs = StoredConfigurationUtil.validateValues( storedConfiguration );
+            if ( !JavaHelper.isEmpty( validationErrorMsgs ) )
+            {
+                final String errorMsg = "value error in config file, please investigate: " + validationErrorMsgs.get( 0 );
+                final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
+                        {
+                                errorMsg,
+                                }
+                );
+                this.configMode = PwmApplicationMode.ERROR;
+                throw new PwmUnrecoverableException( errorInformation );
+            }
         }
         catch ( final Exception e )
         {
@@ -142,42 +170,16 @@ public class ConfigurationReader
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
                     {
                             errorMsg,
-                    }
+                            }
             );
             this.configMode = PwmApplicationMode.ERROR;
             throw new PwmUnrecoverableException( errorInformation );
         }
 
-        final StoredConfiguration storedConfiguration;
-        try
-        {
-            storedConfiguration = StoredConfigurationFactory.input( theFileData );
-        }
-        catch ( final Exception e )
-        {
-            final String errorMsg = "unable to parse configuration file: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
-                    {
-                            errorMsg,
-                    }
-            );
-            this.configMode = PwmApplicationMode.ERROR;
-            e.printStackTrace(  );
-            throw new PwmUnrecoverableException( errorInformation );
-        }
+        final String fileSize = StringUtil.formatDiskSize( configFile.length() );
+        final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+        LOGGER.debug( () -> "configuration reading/parsing of " + fileSize + " complete in " + timeDuration.asLongString() );
 
-        final List<String> validationErrorMsgs = StoredConfigurationUtil.validateValues( storedConfiguration );
-        if ( !JavaHelper.isEmpty( validationErrorMsgs ) )
-        {
-            final String errorMsg = "value error in config file, please investigate: " + validationErrorMsgs.get( 0 );
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
-                    {
-                            errorMsg,
-                    }
-            );
-            this.configMode = PwmApplicationMode.ERROR;
-            throw new PwmUnrecoverableException( errorInformation );
-        }
 
         final Optional<String> configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE );
         if ( PwmConstants.TRIAL_MODE || ( configIsEditable.isPresent() && "true".equalsIgnoreCase( configIsEditable.get() ) ) )
@@ -189,12 +191,6 @@ public class ConfigurationReader
             this.configMode = PwmApplicationMode.RUNNING;
         }
 
-        final String fileSize = StringUtil.formatDiskSize( configFile.length() );
-        final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
-        LOGGER.debug( () -> "configuration reading/parsing of " + fileSize + " complete in " + timeDuration.asLongString() );
-
-
-
         return storedConfiguration;
     }
 

+ 2 - 2
server/src/main/java/password/pwm/config/stored/StoredConfigData.java

@@ -56,14 +56,14 @@ class StoredConfigData
 
     static Map<StoredConfigItemKey, ValueMetaData> carrierAsMetaDataMap( final Collection<ValueAndMetaCarrier> input )
     {
-        return input.stream()
+        return input.parallelStream()
                 .filter( ( t ) -> t.getKey() != null && t.getMetaData() != null )
                 .collect( Collectors.toMap( StoredConfigData.ValueAndMetaCarrier::getKey, StoredConfigData.ValueAndMetaCarrier::getMetaData ) );
     }
 
     static Map<StoredConfigItemKey, StoredValue> carrierAsStoredValueMap( final Collection<ValueAndMetaCarrier> input )
     {
-        return input.stream()
+        return input.parallelStream()
                 .filter( ( t ) -> t.getKey() != null && t.getValue() != null )
                 .collect( Collectors.toMap( StoredConfigData.ValueAndMetaCarrier::getKey, StoredConfigData.ValueAndMetaCarrier::getValue ) );
     }

+ 9 - 4
server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java

@@ -27,11 +27,13 @@ import password.pwm.i18n.Config;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.function.Supplier;
 
 public class StoredConfigItemKey implements Serializable, Comparable<StoredConfigItemKey>
 {
@@ -58,6 +60,10 @@ public class StoredConfigItemKey implements Serializable, Comparable<StoredConfi
     private final String recordID;
     private final String profileID;
 
+    private final transient Supplier<String> toStringSupplier = new LazySupplier<>( () -> this.getLabel( PwmConstants.DEFAULT_LOCALE ) );
+
+    private static final long serialVersionUID = 1L;
+
     private StoredConfigItemKey( final RecordType recordType, final String recordID, final String profileID )
     {
         Objects.requireNonNull( recordType, "recordType can not be null" );
@@ -217,21 +223,20 @@ public class StoredConfigItemKey implements Serializable, Comparable<StoredConfi
     @Override
     public boolean equals( final Object anotherObject )
     {
-        return anotherObject != null
-                && anotherObject instanceof StoredConfigItemKey
+        return anotherObject instanceof StoredConfigItemKey
                 && toString().equals( anotherObject.toString() );
     }
 
     @Override
     public String toString()
     {
-        return getLabel( PwmConstants.DEFAULT_LOCALE );
+        return toStringSupplier.get();
     }
 
     @Override
     public int compareTo( final StoredConfigItemKey o )
     {
-        return getLabel( PwmConstants.DEFAULT_LOCALE ).compareTo( o.toString() );
+        return toString().compareTo( o.toString() );
     }
 
     public PwmSettingSyntax getSyntax()

+ 144 - 176
server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java

@@ -23,8 +23,8 @@ package password.pwm.config.stored;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingFlag;
-import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.value.LocalizedStringValue;
@@ -34,13 +34,13 @@ import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.config.value.ValueTypeConverter;
-import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.PwmExceptionLoggingConsumer;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.XmlDocument;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlFactory;
@@ -55,33 +55,48 @@ import java.time.Instant;
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Queue;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class StoredConfigXmlSerializer implements StoredConfigSerializer
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigXmlSerializer.class );
-
     private static final String XML_FORMAT_VERSION = "5";
+    private static final boolean ENABLE_PERF_LOGGING = false;
 
     @Override
     public StoredConfiguration readInput( final InputStream inputStream )
             throws PwmUnrecoverableException
     {
+        final Instant startTime = Instant.now();
         final XmlFactory xmlFactory = XmlFactory.getFactory();
         final XmlDocument xmlDocument = xmlFactory.parseXml( inputStream );
+        perfLog( "parseXml", startTime );
+
+        final Instant startPreProcessXml = Instant.now();
         XmlCleaner.preProcessXml( xmlDocument );
+        perfLog( "startPreProcessXml", startPreProcessXml );
+
 
         final XmlInputDocumentReader xmlInputDocumentReader = new XmlInputDocumentReader( xmlDocument );
         final StoredConfigData storedConfigData = xmlInputDocumentReader.getStoredConfigData();
-        return new StoredConfigurationImpl( storedConfigData );
+        final StoredConfiguration storedConfiguration = new StoredConfigurationImpl( storedConfigData );
+        perfLog( "readInputTotal", startTime );
+        return storedConfiguration;
     }
 
     @Override
@@ -100,13 +115,28 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
         xmlFactory.outputDocument( xmlDocument, outputStream );
     }
 
+    private static void perfLog( final CharSequence msg, final Instant startTimestamp )
+    {
+        if ( ENABLE_PERF_LOGGING )
+        {
+            final String output = msg + "::" + TimeDuration.compactFromCurrent( startTimestamp );
+            LOGGER.trace( () -> output );
+            System.out.println( output );
+        }
+    }
+
     static class XmlInputDocumentReader
     {
+        private final EnumMap<PwmSettingCategory, List<String>> cachedProfiles = new EnumMap<>( PwmSettingCategory.class );
+
         private final XmlDocument document;
+        private final PwmSecurityKey pwmSecurityKey;
 
         XmlInputDocumentReader( final XmlDocument document )
+                throws PwmUnrecoverableException
         {
             this.document = document;
+            this.pwmSecurityKey = new PwmSecurityKey( readCreateTime() + "StoredConfiguration" );
         }
 
         StoredConfigData getStoredConfigData()
@@ -114,20 +144,30 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
             final String createTime = readCreateTime();
             final Instant modifyTime = readModifyTime();
 
-            final List<StoredConfigData.ValueAndMetaCarrier> values = new ArrayList<>();
-            values.addAll( readProperties() );
-            values.addAll( readSettings() );
-            values.addAll( readLocaleBundles() );
-            return StoredConfigData.builder()
+            // define the parallelized the readers
+            final List<Supplier<List<StoredConfigData.ValueAndMetaCarrier>>> suppliers = new ArrayList<>();
+            suppliers.add( this::readProperties );
+            suppliers.add( this::readSettings );
+            suppliers.add( this::readLocaleBundles );
+
+            // execute the readers and put results in the queue
+            final Queue<StoredConfigData.ValueAndMetaCarrier> values = new ConcurrentLinkedQueue<>();
+            suppliers.parallelStream().forEach( ( supplier ) -> values.addAll( supplier.get() ) );
+
+            final Instant startStoredConfigDataBuild = Instant.now();
+            final StoredConfigData storedConfigData = StoredConfigData.builder()
                     .createTime( createTime )
                     .modifyTime( modifyTime )
                     .metaDatas( StoredConfigData.carrierAsMetaDataMap( values ) )
                     .storedValues( StoredConfigData.carrierAsStoredValueMap( values ) )
                     .build();
+            perfLog( "startStoredConfigDataBuild", startStoredConfigDataBuild );
+            return storedConfigData;
         }
 
-        private Collection<StoredConfigData.ValueAndMetaCarrier> readProperties()
+        private List<StoredConfigData.ValueAndMetaCarrier> readProperties()
         {
+            final Instant startReadProperties = Instant.now();
             final List<StoredConfigData.ValueAndMetaCarrier> valueAndMetaWrapper = new ArrayList<>();
             for ( final ConfigurationProperty configurationProperty : ConfigurationProperty.values() )
             {
@@ -140,104 +180,62 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                     valueAndMetaWrapper.add( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) );
                 }
             }
+            perfLog( "startReadProperties", startReadProperties );
             return valueAndMetaWrapper;
         }
 
-        private Collection<StoredConfigData.ValueAndMetaCarrier> readSettings()
+        private List<StoredConfigData.ValueAndMetaCarrier> readSettings( )
         {
-            final List<StoredConfigData.ValueAndMetaCarrier> returnList = new ArrayList<>();
-            for ( final PwmSetting pwmSetting : PwmSetting.values() )
+            final Instant startReadSettings = Instant.now();
+            final Function<XmlElement, Stream<StoredConfigData.ValueAndMetaCarrier>> readSettingForXmlElement = xmlElement ->
             {
-                if ( !pwmSetting.getCategory().hasProfiles() )
-                {
-                    readSetting( pwmSetting, null ).ifPresent( returnList::add );
-                }
-            }
+                final Optional<StoredConfigData.ValueAndMetaCarrier> valueAndMetaCarrier = readSetting( xmlElement );
+                return valueAndMetaCarrier.map( Stream::of ).orElseGet( Stream::empty );
+            };
 
-            for ( final PwmSetting pwmSetting : PwmSetting.values() )
-            {
-                if ( pwmSetting.getCategory().hasProfiles() )
-                {
-                    final List<String> profileIDs = profilesForSetting( pwmSetting );
-                    for ( final String profileID : profileIDs )
-                    {
-                        readSetting( pwmSetting, profileID ).ifPresent( returnList::add );
-                    }
-                }
-            }
-            return returnList;
+            final List<XmlElement> settingElements = xpathForAllSetting();
+            final List<StoredConfigData.ValueAndMetaCarrier> results = settingElements
+                    .parallelStream()
+                    .flatMap( readSettingForXmlElement )
+                    .collect( Collectors.toList() );
+            perfLog( "startReadSettings", startReadSettings );
+            return results;
         }
 
         Optional<StoredConfigData.ValueAndMetaCarrier> readSetting( final PwmSetting setting, final String profileID )
         {
             final Optional<XmlElement> settingElement = xpathForSetting( setting, profileID );
 
-            if ( !settingElement.isPresent() )
-            {
-                return Optional.empty();
-            }
-
-            if ( settingElement.get().getChild( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT ).isPresent() )
-            {
-                return Optional.empty();
-            }
-
-            try
-            {
-                final StoredValue storedValue = ValueFactory.fromXmlValues( setting, settingElement.get(), getKey() );
-                final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
-                final ValueMetaData metaData = readMetaDataFromXmlElement( key, settingElement.get() ).orElse( null );
-                return Optional.of( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) );
-            }
-            catch ( final PwmException e )
-            {
-                final String errorMsg = "unexpected error reading setting '" + setting.getKey() + "' profile '" + profileID + "', error: " + e.getMessage();
-                throw new IllegalStateException( errorMsg );
-            }
+            return settingElement.isPresent()
+                    ? readSetting( settingElement.get() )
+                    : Optional.empty();
         }
 
-        List<String> profilesForSetting( final PwmSetting pwmSetting )
+        Optional<StoredConfigData.ValueAndMetaCarrier> readSetting( final XmlElement settingElement )
         {
-            if ( !pwmSetting.getCategory().hasProfiles() && pwmSetting.getSyntax() != PwmSettingSyntax.PROFILE )
+            final String settingKey = settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY );
+            final String profileID = settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE );
+            final Optional<PwmSetting> optionalPwmSetting = PwmSetting.forKey( settingKey );
+            if ( optionalPwmSetting.isPresent() )
             {
-                return Collections.emptyList();
-            }
+                final PwmSetting pwmSetting = optionalPwmSetting.get();
+                final boolean defaultValueSaved = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT ).isPresent();
+                final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( pwmSetting, profileID );
+                final ValueMetaData metaData = readMetaDataFromXmlElement( key, settingElement ).orElse( null );
 
-            final PwmSetting profileSetting;
-            if ( pwmSetting.getSyntax() == PwmSettingSyntax.PROFILE )
-            {
-                profileSetting = pwmSetting;
-            }
-            else
-            {
-                profileSetting = pwmSetting.getCategory().getProfileSetting().orElseThrow( IllegalStateException::new );
-            }
+                final StoredValue storedValue = defaultValueSaved
+                        ? null
+                        : ValueFactory.fromXmlValues( pwmSetting, settingElement, pwmSecurityKey );
 
-            final StoredValue effectiveValue;
-            {
-                final Optional<StoredConfigData.ValueAndMetaCarrier> configuredValue = readSetting( profileSetting, null );
-                if ( configuredValue.isPresent() )
-                {
-                    effectiveValue = configuredValue.get().getValue();
-                }
-                else
-                {
-                    effectiveValue = profileSetting.getDefaultValue( templateSetSupplier.get() );
-                }
+                return Optional.of( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) );
             }
 
-            final List<String> settingValues = ValueTypeConverter.valueToStringArray( effectiveValue );
-            final List<String> profiles = new ArrayList<>( settingValues );
-            profiles.removeIf( StringUtil::isEmpty );
-            return Collections.unmodifiableList( profiles );
+            return Optional.empty();
         }
 
-
-        public PwmSecurityKey getKey() throws PwmUnrecoverableException
+        public PwmSecurityKey getKey()
         {
-            final XmlElement rootElement = document.getRootElement();
-            final String createTimeString = rootElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_CREATE_TIME );
-            return new PwmSecurityKey( createTimeString + "StoredConfiguration" );
+            return this.pwmSecurityKey;
         }
 
         String readCreateTime()
@@ -300,21 +298,20 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
             return null;
         }
 
-        private Collection<StoredConfigData.ValueAndMetaCarrier> readLocaleBundles()
+        private List<StoredConfigData.ValueAndMetaCarrier> readLocaleBundles()
         {
-            final List<StoredConfigData.ValueAndMetaCarrier> returnWrapper = new ArrayList<>();
-
-            for ( final XmlElement localeBundleElement : xpathForLocaleBundles() )
+            final Instant startReadLocaleBundles = Instant.now();
+            final Function<XmlElement, Stream<StoredConfigData.ValueAndMetaCarrier>> xmlToLocaleBundleReader = xmlElement ->
             {
-                final String bundleName = localeBundleElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_BUNDLE );
+                final String bundleName = xmlElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_BUNDLE );
                 final Optional<PwmLocaleBundle> pwmLocaleBundle = PwmLocaleBundle.forKey( bundleName );
-                pwmLocaleBundle.ifPresent( ( bundle ) ->
+                if ( pwmLocaleBundle.isPresent() )
                 {
-                    final String key = localeBundleElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY );
-                    if ( bundle.getDisplayKeys().contains( key ) )
+                    final String key = xmlElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY );
+                    if ( pwmLocaleBundle.get().getDisplayKeys().contains( key ) )
                     {
                         final Map<String, String> bundleMap = new LinkedHashMap<>();
-                        for ( final XmlElement valueElement : localeBundleElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ) )
+                        for ( final XmlElement valueElement : xmlElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ) )
                         {
                             final String localeStrValue = valueElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE );
                             bundleMap.put( localeStrValue == null ? "" : localeStrValue, valueElement.getText() );
@@ -323,13 +320,21 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                         {
                             final StoredConfigItemKey storedConfigItemKey = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle.get(), key );
                             final StoredValue storedValue = new LocalizedStringValue( bundleMap );
-                            final ValueMetaData metaData = readMetaDataFromXmlElement( storedConfigItemKey, localeBundleElement ).orElse( null );
-                            returnWrapper.add( new StoredConfigData.ValueAndMetaCarrier( storedConfigItemKey, storedValue, metaData ) );
+                            final ValueMetaData metaData = readMetaDataFromXmlElement( storedConfigItemKey, xmlElement ).orElse( null );
+                            return Stream.of( new StoredConfigData.ValueAndMetaCarrier( storedConfigItemKey, storedValue, metaData ) );
                         }
                     }
-                } );
-            }
-            return Collections.unmodifiableList( returnWrapper );
+                }
+
+                return Stream.empty();
+            };
+
+            final List<StoredConfigData.ValueAndMetaCarrier> results = xpathForLocaleBundles()
+                    .parallelStream()
+                    .flatMap( xmlToLocaleBundleReader )
+                    .collect( Collectors.toList() );
+            perfLog( "startReadLocaleBundles", startReadLocaleBundles );
+            return results;
         }
 
         private Optional<ValueMetaData> readMetaDataFromXmlElement( final StoredConfigItemKey key, final XmlElement xmlElement )
@@ -386,6 +391,12 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                     .orElseThrow( () -> new IllegalStateException( "configuration xml document missing 'settings' element" ) );
         }
 
+        List<XmlElement> xpathForAllSetting()
+        {
+            final String xpathString = "//" + StoredConfigXmlConstants.XML_ELEMENT_SETTING;
+            return document.evaluateXpathToElements( xpathString );
+        }
+
         Optional<XmlElement> xpathForSetting( final PwmSetting setting, final String profileID )
         {
             final String xpathString;
@@ -430,9 +441,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
             decorateRootElement( rootElement, storedConfiguration );
 
             rootElement.addContent( makePropertiesElement( storedConfiguration ) );
-
             rootElement.addContent( makeSettingsXmlElement( storedConfiguration, outputSettings ) );
-
             rootElement.addContent( XmlOutputHandler.makeLocaleBundleXmlElements( storedConfiguration ) );
         }
 
@@ -440,7 +449,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
         {
             rootElement.setComment( Collections.singletonList( generateCommentText() ) );
             rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PWM_VERSION, PwmConstants.BUILD_VERSION );
-            rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRRIBUTE_PWM_BUILD, PwmConstants.BUILD_NUMBER );
+            rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PWM_BUILD, PwmConstants.BUILD_NUMBER );
             rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_XML_VERSION, XML_FORMAT_VERSION );
 
             rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_CREATE_TIME, storedConfiguration.createTime() );
@@ -463,29 +472,22 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                     .storedValueEncoderMode( figureEncoderMode( storedConfiguration, outputSettings ) )
                     .build();
 
-            for ( final PwmSetting pwmSetting : PwmSetting.sortedByMenuLocation( PwmConstants.DEFAULT_LOCALE ) )
+            final Consumer<StoredConfigItemKey> xmlSettingWriter = storedConfigItemKey ->
             {
-                if ( !pwmSetting.getFlags().contains( PwmSettingFlag.Deprecated ) )
-                {
-                    if ( pwmSetting.getCategory().hasProfiles() )
-                    {
-                        for ( final String profileID : storedConfiguration.profilesForSetting( pwmSetting ) )
-                        {
-                            final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, profileID );
-                            final XmlElement settingElement = makeSettingXmlElement( storedConfiguration, pwmSetting, profileID, storedValue, xmlOutputProcessData );
-                            decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromSetting( pwmSetting, profileID ), settingElement );
-                            settingsElement.addContent( settingElement );
-                        }
-                    }
-                    else
-                    {
-                        final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, null );
-                        final XmlElement settingElement = makeSettingXmlElement( storedConfiguration, pwmSetting, null, storedValue, xmlOutputProcessData );
-                        decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromSetting( pwmSetting, null ), settingElement );
-                        settingsElement.addContent( settingElement );
-                    }
-                }
-            }
+                final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
+                final String profileID = storedConfigItemKey.getProfileID();
+                final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, profileID );
+                final XmlElement settingElement = makeSettingXmlElement( storedConfiguration, pwmSetting, profileID, storedValue, xmlOutputProcessData );
+                decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromSetting( pwmSetting, profileID ), settingElement );
+                settingsElement.addContent( settingElement );
+            };
+
+            StoredConfigurationUtil.allPossibleSettingKeysForConfiguration( storedConfiguration )
+                    .parallelStream()
+                    .filter( ( key ) -> StoredConfigItemKey.RecordType.SETTING.equals( key.getRecordType() ) )
+                    .filter( ( key ) -> !key.toPwmSetting().getFlags().contains( PwmSettingFlag.Deprecated ) )
+                    .sorted()
+                    .forEachOrdered( xmlSettingWriter );
 
             return settingsElement;
         }
@@ -563,7 +565,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                 final XmlElement xmlElement
         )
         {
-            final Optional<ValueMetaData> valueMetaData = ( ( StoredConfiguration ) storedConfiguration ).readMetaData( key );
+            final Optional<ValueMetaData> valueMetaData = storedConfiguration.readMetaData( key );
 
             if ( valueMetaData.isPresent() )
             {
@@ -612,7 +614,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                     final Map<String, String> localeBundle = storedConfiguration.readLocaleBundleMap( pwmLocaleBundle, key );
                     if ( !JavaHelper.isEmpty( localeBundle ) )
                     {
-                        final XmlElement localeBundleElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_LOCALEBUNDLE );
+                        final XmlElement localeBundleElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_LOCALE_BUNDLE );
                         localeBundleElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_BUNDLE, pwmLocaleBundle.getKey() );
                         localeBundleElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY, key );
 
@@ -652,7 +654,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
         public static final String XML_ELEMENT_SETTINGS = "settings";
         public static final String XML_ELEMENT_SETTING = "setting";
         public static final String XML_ELEMENT_DEFAULT = "default";
-        public static final String XML_ELEMENT_LOCALEBUNDLE = "localeBundle";
+        public static final String XML_ELEMENT_LOCALE_BUNDLE = "localeBundle";
         public static final String XML_ELEMENT_LABEL = "label";
         public static final String XML_ELEMENT_VALUE = "value";
 
@@ -667,7 +669,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
         public static final String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion";
         public static final String XML_ATTRIBUTE_BUNDLE = "bundle";
         public static final String XML_ATTRIBUTE_XML_VERSION = "xmlVersion";
-        public static final String XML_ATTRRIBUTE_PWM_BUILD = "pwmBuild";
+        public static final String XML_ATTRIBUTE_PWM_BUILD = "pwmBuild";
         public static final String XML_ATTRIBUTE_PWM_VERSION = "pwmVersion";
         public static final String XML_ATTRIBUTE_LOCALE = "locale";
     }
@@ -678,7 +680,6 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                 new MigratePreValueXmlElements(),
                 new MigrateOldPropertyFormat(),
                 new AppPropertyOverrideMigration(),
-                new ProfileNonProfiledSettings(),
                 new MigrateDeprecatedProperties(),
                 new UpdatePropertiesWithoutType()
         ) );
@@ -687,7 +688,12 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                 final XmlDocument document
         )
         {
-            XML_PRE_PROCESSORS.forEach( ( c ) -> PwmExceptionLoggingConsumer.wrapConsumer( c ).accept( document ) );
+            XML_PRE_PROCESSORS.forEach( ( c ) ->
+            {
+                final Instant startTime = Instant.now();
+                PwmExceptionLoggingConsumer.wrapConsumer( c ).accept( document );
+                perfLog( "preProcessor-" + c.getClass().getName(), startTime );
+            } );
         }
 
         private static class MigratePreValueXmlElements implements PwmExceptionLoggingConsumer<XmlDocument>
@@ -762,50 +768,6 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
             }
         }
 
-        static class ProfileNonProfiledSettings implements PwmExceptionLoggingConsumer<XmlDocument>
-        {
-            @Override
-            public void accept( final XmlDocument xmlDocument )
-            {
-                final StoredConfigXmlSerializer.XmlInputDocumentReader reader = new StoredConfigXmlSerializer.XmlInputDocumentReader( xmlDocument );
-                for ( final PwmSetting setting : PwmSetting.values() )
-                {
-                    if ( setting.getCategory().hasProfiles() )
-                    {
-                        reader.xpathForSetting( setting, null ).ifPresent( existingSettingElement ->
-                        {
-                            final List<String> profileStringDefinitions = new ArrayList<>();
-                            {
-                                final List<String> configuredProfiles = reader.profilesForSetting( setting );
-                                if ( !JavaHelper.isEmpty( configuredProfiles ) )
-                                {
-                                    profileStringDefinitions.addAll( configuredProfiles );
-                                }
-                            }
-
-                            if ( profileStringDefinitions.isEmpty() )
-                            {
-                                profileStringDefinitions.add( PwmConstants.PROFILE_ID_DEFAULT );
-                            }
-
-                            for ( final String destProfile : profileStringDefinitions )
-                            {
-                                LOGGER.info( () -> "moving setting " + setting.getKey() + " without profile attribute to profile \"" + destProfile + "\"." );
-                                {
-                                    //existingSettingElement.detach();
-                                    final XmlElement newSettingElement = existingSettingElement.copy();
-                                    newSettingElement.setAttribute( StoredConfigXmlSerializer.StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE, destProfile );
-
-                                    final XmlElement settingsElement = reader.xpathForSettings();
-                                    settingsElement.addContent( newSettingElement );
-                                }
-                            }
-                        } );
-                    }
-                }
-            }
-        }
-
         private static class MigrateDeprecatedProperties implements PwmExceptionLoggingConsumer<XmlDocument>
         {
             @Override
@@ -894,7 +856,13 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                             final List<String> existingValues = new ArrayList<>();
                             {
                                 final Optional<StoredConfigData.ValueAndMetaCarrier> valueAndMetaTuple =  documentReader.readSetting( PwmSetting.APP_PROPERTY_OVERRIDES, null );
-                                valueAndMetaTuple.ifPresent( ( t ) -> existingValues.addAll( ( List<String> ) t.getValue().toNativeObject() ) );
+                                valueAndMetaTuple.ifPresent( ( t ) ->
+                                {
+                                    if ( t.getValue() != null )
+                                    {
+                                        existingValues.addAll( ValueTypeConverter.valueToStringArray( t.getValue() ) );
+                                    }
+                                } );
                             }
                             existingValues.add( newValue );
                             rewriteAppPropertySettingElement( xmlDocument, existingValues );

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

@@ -23,9 +23,10 @@ package password.pwm.config.stored;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.PwmSettingTemplateSet;
-import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.LocalizedStringValue;
+import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.StringValue;
+import password.pwm.config.value.ValueTypeConverter;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.java.JavaHelper;
@@ -36,7 +37,6 @@ import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.SecureEngine;
 
 import java.time.Instant;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -44,8 +44,9 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.TreeMap;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * Immutable in-memory configuration.
@@ -68,11 +69,10 @@ public class StoredConfigurationImpl implements StoredConfiguration
     {
         this.createTime = storedConfigData.getCreateTime();
         this.modifyTime = storedConfigData.getModifyTime();
-        this.metaValues = Collections.unmodifiableMap(  new TreeMap<>( storedConfigData.getMetaDatas() ) );
+        this.metaValues = Collections.unmodifiableMap( storedConfigData.getMetaDatas() );
         this.templateSet = readTemplateSet( storedConfigData.getStoredValues() );
 
-        final Map<StoredConfigItemKey, StoredValue> tempMap = new TreeMap<>( storedConfigData.getStoredValues() );
-        removeAllDefaultValues( tempMap, templateSet );
+        final Map<StoredConfigItemKey, StoredValue> tempMap = removeDefaultSettingValues( storedConfigData.getStoredValues(), templateSet );
         this.storedValues = Collections.unmodifiableMap( tempMap );
     }
 
@@ -85,19 +85,28 @@ public class StoredConfigurationImpl implements StoredConfiguration
         this.templateSet = readTemplateSet( Collections.emptyMap() );
     }
 
-    private static void removeAllDefaultValues( final Map<StoredConfigItemKey, StoredValue> valueMap, final PwmSettingTemplateSet pwmSettingTemplateSet )
+    private static Map<StoredConfigItemKey, StoredValue> removeDefaultSettingValues(
+            final Map<StoredConfigItemKey, StoredValue> valueMap,
+            final PwmSettingTemplateSet pwmSettingTemplateSet
+    )
     {
-        valueMap.entrySet().removeIf( entry ->
+        final Predicate<Map.Entry<StoredConfigItemKey, StoredValue>> checkIfValueIsDefault = entry ->
         {
-            final StoredConfigItemKey key = entry.getKey();
-            if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
+            if ( StoredConfigItemKey.RecordType.SETTING.equals( entry.getKey().getRecordType() ) )
             {
-                final StoredValue loopValue = entry.getValue();
-                final StoredValue defaultValue = key.toPwmSetting().getDefaultValue( pwmSettingTemplateSet );
-                return Objects.equals( loopValue.valueHash(), defaultValue.valueHash() );
+                final String loopHash = entry.getValue().valueHash();
+                final String defaultHash = entry.getKey().toPwmSetting().getDefaultValue( pwmSettingTemplateSet ).valueHash();
+                return !Objects.equals( loopHash, defaultHash );
             }
-            return false;
-        } );
+            return true;
+        };
+
+        final Map<StoredConfigItemKey, StoredValue> results = valueMap.entrySet()
+                .parallelStream()
+                .filter( checkIfValueIsDefault )
+                .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
+
+        return Collections.unmodifiableMap( results );
     }
 
 
@@ -193,16 +202,9 @@ public class StoredConfigurationImpl implements StoredConfiguration
     @Override
     public List<String> profilesForSetting( final PwmSetting pwmSetting )
     {
-        final List<String> returnObj = new ArrayList<>();
-        for ( final StoredConfigItemKey storedConfigItemKey : storedValues.keySet() )
-        {
-            if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING
-                    && Objects.equals( storedConfigItemKey.getRecordID(), pwmSetting.getKey() ) )
-            {
-                returnObj.add( storedConfigItemKey.getProfileID() );
-            }
-        }
-        return Collections.unmodifiableList( returnObj );
+        final Optional<PwmSetting> profileSetting = pwmSetting.getCategory().getProfileSetting();
+        return profileSetting.map( setting -> ValueTypeConverter.valueToProfileID( setting, readSetting( setting, null ) ) )
+                .orElse( Collections.emptyList() );
     }
 
     public ValueMetaData readSettingMetadata( final PwmSetting setting, final String profileID )

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

@@ -71,6 +71,17 @@ public class StoredConfigurationModifier
             final UserIdentity userIdentity
     )
             throws PwmUnrecoverableException
+    {
+        writeSettingAndMetaData( setting, profileID, value, new ValueMetaData( Instant.now(), userIdentity ) );
+    }
+
+    void writeSettingAndMetaData(
+            final PwmSetting setting,
+            final String profileID,
+            final StoredValue value,
+            final ValueMetaData valueMetaData
+    )
+            throws PwmUnrecoverableException
     {
         Objects.requireNonNull( setting );
         Objects.requireNonNull( value );
@@ -91,7 +102,7 @@ public class StoredConfigurationModifier
 
             return storedConfigData.toBuilder()
                     .storedValue( key, value )
-                    .metaData( key, new ValueMetaData( Instant.now(), userIdentity ) )
+                    .metaData( key, valueMetaData )
                     .build();
         } );
     }
@@ -159,6 +170,25 @@ public class StoredConfigurationModifier
         } );
     }
 
+    public void deleteKey( final StoredConfigItemKey key )
+            throws PwmUnrecoverableException
+    {
+        update( ( storedConfigData ) ->
+        {
+            final Map<StoredConfigItemKey, StoredValue> existingStoredValues = new HashMap<>( storedConfigData.getStoredValues() );
+            final Map<StoredConfigItemKey, ValueMetaData> existingMetaValues = new HashMap<>( storedConfigData.getMetaDatas() );
+
+            existingStoredValues.remove( key );
+            existingMetaValues.remove( key );
+
+            return storedConfigData.toBuilder()
+                    .clearStoredValues()
+                    .storedValues( existingStoredValues )
+                    .metaDatas( existingMetaValues )
+                    .build();
+        } );
+    }
+
     public void writeLocaleBundleMap(
             final PwmLocaleBundle pwmLocaleBundle,
             final String keyName,

+ 63 - 83
server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java

@@ -29,11 +29,13 @@ import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.StoredValue;
+import password.pwm.config.value.ValueTypeConverter;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
+import password.pwm.util.java.PwmExceptionLoggingConsumer;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -52,17 +54,19 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public abstract class StoredConfigurationUtil
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationUtil.class );
 
-    public static List<String> profilesForSetting
-            (
-                    final PwmSetting pwmSetting,
-                    final StoredConfiguration storedConfiguration
-            )
+    public static List<String> profilesForSetting(
+            final PwmSetting pwmSetting,
+            final StoredConfiguration storedConfiguration
+    )
     {
         if ( !pwmSetting.getCategory().hasProfiles() && pwmSetting.getSyntax() != PwmSettingSyntax.PROFILE )
         {
@@ -88,7 +92,6 @@ public abstract class StoredConfigurationUtil
     )
     {
         final PwmSetting profileSetting = category.getProfileSetting().orElseThrow( IllegalStateException::new );
-
         return profilesForProfileSetting( profileSetting, storedConfiguration );
     }
 
@@ -97,67 +100,39 @@ public abstract class StoredConfigurationUtil
             final StoredConfiguration storedConfiguration
     )
     {
-        final Object nativeObject = storedConfiguration.readSetting( profileSetting, null ).toNativeObject();
-        final List<String> settingValues = ( List<String> ) nativeObject;
+        final StoredValue storedValue = storedConfiguration.readSetting( profileSetting, null );
+        final List<String> settingValues = ValueTypeConverter.valueToStringArray( storedValue );
         final List<String> profiles = new ArrayList<>( settingValues );
         profiles.removeIf( StringUtil::isEmpty );
         return Collections.unmodifiableList( profiles );
-
-    }
-
-    public static String changeLogAsDebugString(
-            final StoredConfiguration storedConfiguration,
-            final Set<StoredConfigItemKey> configChangeLog,
-            final Locale locale
-    )
-            throws PwmUnrecoverableException
-    {
-
-        final Map<String, String> outputMap = StoredConfigurationUtil.makeDebugMap( storedConfiguration, configChangeLog, locale );
-        final StringBuilder output = new StringBuilder();
-        if ( outputMap.isEmpty() )
-        {
-            output.append( "No setting changes." );
-        }
-        else
-        {
-            for ( final Map.Entry<String, String> entry : outputMap.entrySet() )
-            {
-                final String keyName = entry.getKey();
-                final String value = entry.getValue();
-                output.append( keyName );
-                output.append( "\n" );
-                output.append( " Value: " );
-                output.append( value );
-                output.append( "\n" );
-            }
-        }
-        return output.toString();
-
     }
 
-    public static StoredConfiguration copyConfigAndBlankAllPasswords( final StoredConfiguration input )
+    public static StoredConfiguration copyConfigAndBlankAllPasswords( final StoredConfiguration storedConfig )
             throws PwmUnrecoverableException
     {
-        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( input );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfig );
 
-        for ( final StoredConfigItemKey storedConfigItemKey : input.modifiedItems() )
+        final Consumer<StoredConfigItemKey> valueModifier = PwmExceptionLoggingConsumer.wrapConsumer( storedConfigItemKey ->
         {
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             {
                 final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
                 if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
                 {
-                    final ValueMetaData valueMetaData = input.readSettingMetadata( pwmSetting, storedConfigItemKey.getProfileID() );
+                    final ValueMetaData valueMetaData = storedConfig.readSettingMetadata( pwmSetting, storedConfigItemKey.getProfileID() );
                     final UserIdentity userIdentity = valueMetaData == null ? null : valueMetaData.getUserIdentity();
                     final PasswordValue passwordValue = new PasswordValue( new PasswordData( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ) );
                     modifier.writeSetting( pwmSetting, storedConfigItemKey.getProfileID(), passwordValue, userIdentity );
                 }
             }
-        }
+        } );
 
+        storedConfig.modifiedItems()
+                .parallelStream()
+                .filter( ( key ) -> StoredConfigItemKey.RecordType.SETTING.equals( key.getRecordType() ) )
+                .forEach( valueModifier );
 
-        final Optional<String> pwdHash = input.readConfigProperty( ConfigurationProperty.PASSWORD_HASH );
+        final Optional<String> pwdHash = storedConfig.readConfigProperty( ConfigurationProperty.PASSWORD_HASH );
         if ( pwdHash.isPresent() )
         {
             modifier.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT );
@@ -168,36 +143,39 @@ public abstract class StoredConfigurationUtil
 
     public static List<String> validateValues( final StoredConfiguration storedConfiguration )
     {
-        final Instant startTime = Instant.now();
-        final List<String> errorStrings = new ArrayList<>();
-
-        for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedItems() )
+        final Function<StoredConfigItemKey, Stream<String>> validateSettingFunction = storedConfigItemKey ->
         {
-            if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
-            {
-                final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
-                final String profileID = storedConfigItemKey.getProfileID();
-                final StoredValue loopValue = storedConfiguration.readSetting( pwmSetting, profileID );
+            final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
+            final String profileID = storedConfigItemKey.getProfileID();
+            final StoredValue loopValue = storedConfiguration.readSetting( pwmSetting, profileID );
 
-                try
-                {
-                    final List<String> errors = loopValue.validateValue( pwmSetting );
-                    for ( final String loopError : errors )
-                    {
-                        errorStrings.add( pwmSetting.toMenuLocationDebug( storedConfigItemKey.getProfileID(), PwmConstants.DEFAULT_LOCALE ) + " - " + loopError );
-                    }
-                }
-                catch ( final Exception e )
+            try
+            {
+                final List<String> errors = loopValue.validateValue( pwmSetting );
+                for ( final String loopError : errors )
                 {
-                    LOGGER.error( () -> "unexpected error during validate value for "
-                            + pwmSetting.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + ", error: "
-                            + e.getMessage(), e );
+                    return Stream.of( pwmSetting.toMenuLocationDebug( storedConfigItemKey.getProfileID(), PwmConstants.DEFAULT_LOCALE ) + " - " + loopError );
                 }
             }
-        }
+            catch ( final Exception e )
+            {
+                LOGGER.error( () -> "unexpected error during validate value for "
+                        + pwmSetting.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + ", error: "
+                        + e.getMessage(), e );
+            }
+            return Stream.empty();
+        };
+
+        final Instant startTime = Instant.now();
+        final List<String> errorStrings = storedConfiguration.modifiedItems()
+                .parallelStream()
+                .filter( key -> StoredConfigItemKey.RecordType.SETTING.equals( key.getRecordType() ) )
+                .flatMap( validateSettingFunction )
+                .collect( Collectors.toList() );
+
 
         LOGGER.trace( () -> "StoredConfiguration validator completed in " + TimeDuration.compactFromCurrent( startTime ) );
-        return errorStrings;
+        return Collections.unmodifiableList( errorStrings );
     }
 
     public static Set<StoredConfigItemKey> search( final StoredConfiguration storedConfiguration, final String searchTerm, final Locale locale )
@@ -354,7 +332,7 @@ public abstract class StoredConfigurationUtil
             throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
                     {
                             "can not set blank password",
-                    }
+                            }
             ) );
         }
         final String trimmedPassword = password.trim();
@@ -363,11 +341,10 @@ public abstract class StoredConfigurationUtil
             throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
                     {
                             "can not set blank password",
-                    }
+                            }
             ) );
         }
 
-
         final String passwordHash = BCrypt.hashPassword( password );
         storedConfiguration.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash );
     }
@@ -433,22 +410,26 @@ public abstract class StoredConfigurationUtil
             final StoredConfiguration storedConfiguration
     )
     {
-        final Set<StoredConfigItemKey> loopResults = new HashSet<>();
-        for ( final PwmSetting loopSetting : PwmSetting.values() )
+        final Function<PwmSetting, Stream<StoredConfigItemKey>> function = loopSetting ->
         {
             if ( loopSetting.getCategory().hasProfiles() )
             {
-                for ( final String profile : storedConfiguration.profilesForSetting( loopSetting ) )
-                {
-                    loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, profile ) );
-                }
+                return storedConfiguration.profilesForSetting( loopSetting )
+                        .stream()
+                        .map( profileId -> StoredConfigItemKey.fromSetting( loopSetting, profileId ) )
+                        .collect( Collectors.toList() )
+                        .stream();
             }
             else
             {
-                loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, null ) );
+                return Stream.of( StoredConfigItemKey.fromSetting( loopSetting, null ) );
             }
-        }
-        return Collections.unmodifiableSet( loopResults );
+        };
+
+        return Collections.unmodifiableSet( Stream.of( PwmSetting.values() )
+                .parallel()
+                .flatMap( function )
+                .collect( Collectors.collectingAndThen( Collectors.toSet(), Collections::unmodifiableSet ) ) );
     }
 
     public static Set<StoredConfigItemKey> changedValues (
@@ -458,8 +439,7 @@ public abstract class StoredConfigurationUtil
     {
         final Instant startTime = Instant.now();
 
-        final Set<StoredConfigItemKey> interestedReferences = new HashSet<>();
-        interestedReferences.addAll( originalConfiguration.modifiedItems() );
+        final Set<StoredConfigItemKey> interestedReferences = new HashSet<>( originalConfiguration.modifiedItems() );
         interestedReferences.addAll( modifiedConfiguration.modifiedItems() );
 
         final Set<StoredConfigItemKey> deltaReferences = interestedReferences

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

@@ -20,6 +20,7 @@
 
 package password.pwm.config.value;
 
+import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.value.data.ActionConfiguration;
@@ -30,6 +31,7 @@ import password.pwm.config.value.data.UserPermission;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import java.security.cert.X509Certificate;
@@ -41,6 +43,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 public final class ValueTypeConverter
 {
@@ -306,4 +309,27 @@ public final class ValueTypeConverter
         final Set<String> strValues = ( Set<String> ) value.toNativeObject();
         return JavaHelper.readEnumSetFromStringCollection( enumClass, strValues );
     }
+
+    public static List<String> valueToProfileID( final PwmSetting profileSetting, final StoredValue storedValue )
+    {
+        if ( PwmSettingSyntax.PROFILE != profileSetting.getSyntax() )
+        {
+            throw new IllegalArgumentException( "may not read profile value for setting: " + profileSetting.toString() );
+        }
+
+        final List<String> profiles = ValueTypeConverter.valueToStringArray( storedValue );
+
+        final List<String> returnSet = profiles
+                .stream()
+                .distinct()
+                .filter( ( profile ) -> !StringUtil.isEmpty( profile ) )
+                .collect( Collectors.toCollection( ArrayList::new ) );
+
+        if ( returnSet.isEmpty() )
+        {
+            returnSet.add( PwmConstants.PROFILE_ID_DEFAULT );
+        }
+
+        return Collections.unmodifiableList( returnSet );
+    }
 }

+ 5 - 1
server/src/main/java/password/pwm/config/value/X509CertificateValue.java

@@ -47,6 +47,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class X509CertificateValue extends AbstractValue implements StoredValue
 {
@@ -95,7 +96,10 @@ public class X509CertificateValue extends AbstractValue implements StoredValue
         {
             throw new NullPointerException( "certificates cannot be null" );
         }
-        this.b64certificates = Collections.unmodifiableList( b64certificates );
+        this.b64certificates = Collections.unmodifiableList(
+                b64certificates.stream()
+                .map( StringUtil::stripAllWhitespace )
+                .collect( Collectors.toList() ) );
         this.certs = new LazySupplier<>( () -> X509Utils.certificatesFromBase64s( b64certificates ) );
     }
 

+ 87 - 142
server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java

@@ -21,17 +21,21 @@
 package password.pwm.ldap;
 
 import com.novell.ldapchai.ChaiEntry;
+import com.novell.ldapchai.exception.ChaiException;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiConfiguration;
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.util.ChaiUtility;
+import lombok.Builder;
+import lombok.Value;
 import password.pwm.PwmApplication;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.error.PwmException;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.Serializable;
@@ -58,76 +62,104 @@ public class LdapDebugDataGenerator
         final List<LdapDebugInfo> returnList = new ArrayList<>();
         for ( final LdapProfile ldapProfile : configuration.getLdapProfiles().values() )
         {
-            final LdapDebugInfo ldapDebugInfo = new LdapDebugInfo();
-            ldapDebugInfo.setProfileName( ldapProfile.getIdentifier() );
-            ldapDebugInfo.setDisplayName( ldapProfile.getDisplayName( locale ) );
+            final List<LdapDebugServerInfo> ldapDebugServerInfos = new ArrayList<>();
+
             try
             {
-                final ChaiProvider chaiProvider = LdapOperationsHelper.createChaiProvider(
-                        pwmApplication,
-                        null,
-                        ldapProfile,
-                        configuration,
-                        ldapProfile.readSettingAsString( PwmSetting.LDAP_PROXY_USER_DN ),
-                        ldapProfile.readSettingAsPassword( PwmSetting.LDAP_PROXY_USER_PASSWORD )
-                );
-                final Collection<ChaiConfiguration> chaiConfigurations = ChaiUtility.splitConfigurationPerReplica( chaiProvider.getChaiConfiguration(), null );
-                final List<LdapDebugServerInfo> ldapDebugServerInfos = new ArrayList<>();
+                final ChaiConfiguration profileChaiConf = LdapOperationsHelper.createChaiConfiguration( configuration, ldapProfile );
+                final Collection<ChaiConfiguration> chaiConfigurations = ChaiUtility.splitConfigurationPerReplica( profileChaiConf, null );
+
                 for ( final ChaiConfiguration chaiConfiguration : chaiConfigurations )
                 {
-                    final LdapDebugServerInfo ldapDebugServerInfo = new LdapDebugServerInfo();
-                    ldapDebugServerInfo.setLdapServerlUrl( chaiConfiguration.getSetting( ChaiSetting.BIND_URLS ) );
-                    final ChaiProvider loopProvider = chaiProvider.getProviderFactory().newProvider( chaiConfiguration );
-
+                    try
                     {
-                        final ChaiEntry rootDSEentry = ChaiUtility.getRootDSE( loopProvider );
-                        final Map<String, List<String>> rootDSEdata = LdapOperationsHelper.readAllEntryAttributeValues( rootDSEentry );
-                        ldapDebugServerInfo.setRootDseAttributes( rootDSEdata );
+                        final ChaiProvider chaiProvider = LdapOperationsHelper.createChaiProvider(
+                                pwmApplication,
+                                sessionLabel,
+                                ldapProfile,
+                                configuration,
+                                ldapProfile.readSettingAsString( PwmSetting.LDAP_PROXY_USER_DN ),
+                                ldapProfile.readSettingAsPassword( PwmSetting.LDAP_PROXY_USER_PASSWORD )
+                        );
+
+                        final LdapDebugServerInfo ldapDebugServerInfo = makeLdapDebugServerInfo( chaiConfiguration, chaiProvider, ldapProfile );
+                        ldapDebugServerInfos.add( ldapDebugServerInfo );
                     }
-
+                    catch ( final PwmException | ChaiException e )
                     {
-                        final String proxyUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_PROXY_USER_DN );
-                        if ( proxyUserDN != null )
-                        {
-                            ldapDebugServerInfo.setProxyDN( proxyUserDN );
-                            final ChaiEntry proxyUserEntry = chaiProvider.getEntryFactory().newChaiEntry( proxyUserDN );
-                            if ( proxyUserEntry.exists() )
-                            {
-                                final Map<String, List<String>> proxyUserData = LdapOperationsHelper.readAllEntryAttributeValues( proxyUserEntry );
-                                ldapDebugServerInfo.setProxyUserAttributes( proxyUserData );
-                            }
-                        }
+                        LOGGER.error( () -> "error during output of ldap profile debug data profile: "
+                                + ldapProfile + ", error: " + e.getMessage() );
                     }
+                }
 
-                    {
-
-                        final String testUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN );
-                        if ( testUserDN != null )
-                        {
-                            ldapDebugServerInfo.setTestUserDN( testUserDN );
-                            final ChaiEntry testUserEntry = chaiProvider.getEntryFactory().newChaiEntry( testUserDN );
-                            if ( testUserEntry.exists() )
-                            {
-                                final Map<String, List<String>> testUserdata = LdapOperationsHelper.readAllEntryAttributeValues( testUserEntry );
-                                ldapDebugServerInfo.setTestUserAttributes( testUserdata );
-                            }
-                        }
-                    }
+                final LdapDebugInfo ldapDebugInfo = LdapDebugInfo.builder()
+                        .profileName( ldapProfile.getIdentifier() )
+                        .displayName( ldapProfile.getDisplayName( locale ) )
+                        .serverInfo( ldapDebugServerInfos )
+                        .build();
 
-                    ldapDebugServerInfos.add( ldapDebugServerInfo );
-                }
-                ldapDebugInfo.setServerInfo( ldapDebugServerInfos );
                 returnList.add( ldapDebugInfo );
 
             }
-            catch ( final Exception e )
+            catch ( final PwmException e )
             {
-                LOGGER.error( () -> "error during output of ldap profile debug data profile: " + ldapProfile + ", error: " + e.getMessage() );
+                LOGGER.error( () -> "error during output of ldap profile debug data profile: "
+                        + ldapProfile + ", error: " + e.getMessage() );
             }
         }
         return returnList;
     }
 
+    private static LdapDebugDataGenerator.LdapDebugServerInfo makeLdapDebugServerInfo(
+            final ChaiConfiguration chaiConfiguration,
+            final ChaiProvider chaiProvider,
+            final LdapProfile ldapProfile
+    )
+            throws ChaiUnavailableException, ChaiOperationException
+    {
+        final LdapDebugServerInfo.LdapDebugServerInfoBuilder builder = LdapDebugServerInfo.builder();
+
+        builder.ldapServerlUrl( chaiConfiguration.getSetting( ChaiSetting.BIND_URLS ) );
+        final ChaiProvider loopProvider = chaiProvider.getProviderFactory().newProvider( chaiConfiguration );
+
+        {
+            final ChaiEntry rootDSEentry = ChaiUtility.getRootDSE( loopProvider );
+            final Map<String, List<String>> rootDSEdata = LdapOperationsHelper.readAllEntryAttributeValues( rootDSEentry );
+            builder.rootDseAttributes( rootDSEdata );
+        }
+
+        {
+            final String proxyUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_PROXY_USER_DN );
+            if ( proxyUserDN != null )
+            {
+                builder.proxyDN( proxyUserDN );
+                final ChaiEntry proxyUserEntry = chaiProvider.getEntryFactory().newChaiEntry( proxyUserDN );
+                if ( proxyUserEntry.exists() )
+                {
+                    final Map<String, List<String>> proxyUserData = LdapOperationsHelper.readAllEntryAttributeValues( proxyUserEntry );
+                    builder.proxyUserAttributes( proxyUserData );
+                }
+            }
+        }
+
+        {
+
+            final String testUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN );
+            if ( testUserDN != null )
+            {
+                builder.testUserDN( testUserDN );
+                final ChaiEntry testUserEntry = chaiProvider.getEntryFactory().newChaiEntry( testUserDN );
+                if ( testUserEntry.exists() )
+                {
+                    final Map<String, List<String>> testUserdata = LdapOperationsHelper.readAllEntryAttributeValues( testUserEntry );
+                    builder.testUserAttributes( testUserdata );
+                }
+            }
+        }
+
+        return builder.build();
+    }
+
     private Map<String, List<String>> readUserAttributeData( final ChaiProvider chaiProvider, final String userDN )
             throws ChaiUnavailableException, ChaiOperationException
     {
@@ -142,44 +174,17 @@ public class LdapDebugDataGenerator
         return null;
     }
 
-
+    @Value
+    @Builder
     public static class LdapDebugInfo implements Serializable
     {
         private String profileName;
         private String displayName;
         private List<LdapDebugServerInfo> serverInfo;
-
-        public String getProfileName( )
-        {
-            return profileName;
-        }
-
-        public void setProfileName( final String profileName )
-        {
-            this.profileName = profileName;
-        }
-
-        public String getDisplayName( )
-        {
-            return displayName;
-        }
-
-        public void setDisplayName( final String displayName )
-        {
-            this.displayName = displayName;
-        }
-
-        public List<LdapDebugServerInfo> getServerInfo( )
-        {
-            return serverInfo;
-        }
-
-        public void setServerInfo( final List<LdapDebugServerInfo> serverInfo )
-        {
-            this.serverInfo = serverInfo;
-        }
     }
 
+    @Value
+    @Builder
     public static class LdapDebugServerInfo implements Serializable
     {
         private String ldapServerlUrl;
@@ -188,65 +193,5 @@ public class LdapDebugDataGenerator
         private String proxyDN;
         private Map<String, List<String>> proxyUserAttributes;
         private Map<String, List<String>> rootDseAttributes;
-
-        public String getLdapServerlUrl( )
-        {
-            return ldapServerlUrl;
-        }
-
-        public void setLdapServerlUrl( final String ldapServerlUrl )
-        {
-            this.ldapServerlUrl = ldapServerlUrl;
-        }
-
-        public String getTestUserDN( )
-        {
-            return testUserDN;
-        }
-
-        public void setTestUserDN( final String testUserDN )
-        {
-            this.testUserDN = testUserDN;
-        }
-
-        public Map<String, List<String>> getTestUserAttributes( )
-        {
-            return testUserAttributes;
-        }
-
-        public void setTestUserAttributes( final Map<String, List<String>> testUserAttributes )
-        {
-            this.testUserAttributes = testUserAttributes;
-        }
-
-        public String getProxyDN( )
-        {
-            return proxyDN;
-        }
-
-        public void setProxyDN( final String proxyDN )
-        {
-            this.proxyDN = proxyDN;
-        }
-
-        public Map<String, List<String>> getProxyUserAttributes( )
-        {
-            return proxyUserAttributes;
-        }
-
-        public void setProxyUserAttributes( final Map<String, List<String>> proxyUserAttributes )
-        {
-            this.proxyUserAttributes = proxyUserAttributes;
-        }
-
-        public Map<String, List<String>> getRootDseAttributes( )
-        {
-            return rootDseAttributes;
-        }
-
-        public void setRootDseAttributes( final Map<String, List<String>> rootDseAttributes )
-        {
-            this.rootDseAttributes = rootDseAttributes;
-        }
     }
 }

+ 10 - 1
server/src/main/java/password/pwm/util/java/LazySoftReference.java

@@ -23,6 +23,15 @@ package password.pwm.util.java;
 import java.lang.ref.SoftReference;
 import java.util.function.Supplier;
 
+/**
+ * A lazy soft reference holder.  This reference will be built lazy and held softly
+ * (according to the semantics of {@link SoftReference}).  This class is not thread
+ * safe, and the GC may delete the reference at any time, so the {@link Supplier}
+ * given to the constructor may be executed multiple times over the lifetime of
+ * the reference.
+ *
+ * @param <E> type of object to hold
+ */
 public class LazySoftReference<E>
 {
     private volatile SoftReference<E> reference = new SoftReference<>( null );
@@ -33,7 +42,7 @@ public class LazySoftReference<E>
         this.supplier = supplier;
     }
 
-    public synchronized E get()
+    public E get()
     {
         E localValue = reference.get();
         if ( localValue == null )

+ 7 - 0
server/src/main/java/password/pwm/util/java/LazySupplier.java

@@ -22,6 +22,13 @@ package password.pwm.util.java;
 
 import java.util.function.Supplier;
 
+/**
+ * Supplier implementation that will cache the value.   Note this implementation
+ * is NOT thread safe, it is entirely possible that the underlying {@link Supplier}
+ * will be invoked multiple times.
+ *
+ * @param <T> the type of object being supplied.
+ */
 public class LazySupplier<T> implements Supplier<T>
 {
     private boolean supplied = false;

+ 7 - 3
server/src/main/java/password/pwm/util/java/TimeDuration.java

@@ -360,17 +360,17 @@ public class TimeDuration implements Comparable<TimeDuration>, Serializable
             final StringBuilder sb = new StringBuilder();
             if ( sb.length() == 0 )
             {
-                if ( ms < 5000 )
+                if ( ms < 10_000 )
                 {
                     final BigDecimal msDecimal = new BigDecimal( ms ).movePointLeft( 3 );
 
                     final DecimalFormat formatter;
 
-                    if ( ms > 2000 )
+                    if ( ms > 5000 )
                     {
                         formatter = new DecimalFormat( "#.#" );
                     }
-                    else if ( ms > 1000 )
+                    else if ( ms > 2000 )
                     {
                         formatter = new DecimalFormat( "#.##" );
                     }
@@ -390,11 +390,15 @@ public class TimeDuration implements Comparable<TimeDuration>, Serializable
             {
                 sb.append( fractionalTimeDetail.seconds );
             }
+
             sb.append( " " );
             sb.append( ms == 1000
                     ? LocaleHelper.getLocalizedMessage( locale, Display.Display_Second, null )
                     : LocaleHelper.getLocalizedMessage( locale, Display.Display_Seconds, null )
             );
+
+
+
             segments.add( sb.toString() );
         }
 

+ 27 - 3
server/src/main/java/password/pwm/util/java/XmlDocument.java

@@ -34,6 +34,8 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 public interface XmlDocument
 {
@@ -99,6 +101,7 @@ public interface XmlDocument
     class XmlDocumentW3c implements XmlDocument
     {
         final org.w3c.dom.Document document;
+        final Lock lock = new ReentrantLock();
 
         public XmlDocumentW3c( final org.w3c.dom.Document document )
         {
@@ -108,7 +111,15 @@ public interface XmlDocument
         @Override
         public XmlElement getRootElement()
         {
-            return new XmlElement.XmlElementW3c( document.getDocumentElement() );
+            lock.lock();
+            try
+            {
+                return new XmlElement.XmlElementW3c( document.getDocumentElement(), lock );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
@@ -129,23 +140,36 @@ public interface XmlDocument
                 final String xpathExpression
         )
         {
+            lock.lock();
             try
             {
                 final XPath xPath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
                 final javax.xml.xpath.XPathExpression expression = xPath.compile( xpathExpression );
                 final NodeList nodeList = (NodeList) expression.evaluate( document, XPathConstants.NODESET );
-                return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList );
+                return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList, lock );
             }
             catch ( final XPathExpressionException e )
             {
                 throw new IllegalStateException( "error evaluating xpath expression: " + e.getMessage() );
             }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public XmlDocument copy()
         {
-            return new XmlDocumentW3c( ( org.w3c.dom.Document) document.cloneNode( true ) );
+            lock.lock();
+            try
+            {
+                return new XmlDocumentW3c( ( org.w3c.dom.Document ) document.cloneNode( true ) );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
     }
 }

+ 173 - 56
server/src/main/java/password/pwm/util/java/XmlElement.java

@@ -32,6 +32,8 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 public interface XmlElement
 {
@@ -228,16 +230,26 @@ public interface XmlElement
     class XmlElementW3c implements XmlElement
     {
         private final org.w3c.dom.Element element;
+        private volatile Lock lock;
 
-        XmlElementW3c( final org.w3c.dom.Element element )
+        XmlElementW3c( final org.w3c.dom.Element element, final Lock lock )
         {
             this.element = element;
+            this.lock = lock;
         }
 
         @Override
         public String getName()
         {
-            return element.getTagName();
+            lock.lock();
+            try
+            {
+                return element.getTagName();
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
@@ -254,36 +266,76 @@ public interface XmlElement
         @Override
         public String getAttributeValue( final String attribute )
         {
-            final String attrValue = element.getAttribute( attribute );
-            return StringUtil.isEmpty( attrValue ) ? null : attrValue;
+            lock.lock();
+            try
+            {
+                final String attrValue = element.getAttribute( attribute );
+                return StringUtil.isEmpty( attrValue ) ? null : attrValue;
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public List<XmlElement> getChildren()
         {
-            final NodeList nodeList = element.getChildNodes();
-            return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList );
+            lock.lock();
+            try
+            {
+                final NodeList nodeList = element.getChildNodes();
+                return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList, lock );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public List<XmlElement> getChildren( final String elementName )
         {
-            final NodeList nodeList = element.getElementsByTagName( elementName );
-            return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList );
+            lock.lock();
+            try
+            {
+                final NodeList nodeList = element.getElementsByTagName( elementName );
+                return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList, lock );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public String getText()
         {
-            final String value = element.getTextContent();
-            return value == null ? "" : value;
+            lock.lock();
+            try
+            {
+                final String value = element.getTextContent();
+                return value == null ? "" : value;
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public String getTextTrim()
         {
-            final String result = element.getTextContent();
-            return result == null ? null : result.trim();
+            lock.lock();
+            try
+            {
+                final String result = element.getTextContent();
+                return result == null ? null : result.trim();
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
@@ -296,106 +348,171 @@ public interface XmlElement
         @Override
         public void setAttribute( final String name, final String value )
         {
-            element.setAttribute( name, value );
+            lock.lock();
+            try
+            {
+                element.setAttribute( name, value );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public void detach()
         {
-            element.getParentNode().removeChild( element );
+            lock.lock();
+            try
+            {
+                element.getParentNode().removeChild( element );
+            }
+            finally
+            {
+                lock.unlock();
+            }
+            lock = new ReentrantLock();
         }
 
         @Override
         public void removeContent()
         {
-            final NodeList nodeList = element.getChildNodes();
-            for ( final XmlElement child : XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList ) )
+            lock.lock();
+            try
             {
-                element.removeChild( ( (XmlElementW3c) child ).element );
+                final NodeList nodeList = element.getChildNodes();
+                for ( final XmlElement child : XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList, lock ) )
+                {
+                    element.removeChild( ( (XmlElementW3c) child ).element );
+                    ( ( XmlElementW3c ) child ).lock = new ReentrantLock();
+                }
+            }
+            finally
+            {
+                lock.unlock();
             }
         }
 
         @Override
         public void removeAttribute( final String attributeName )
         {
-            element.removeAttribute( attributeName );
+            lock.lock();
+            try
+            {
+                element.removeAttribute( attributeName );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public void addContent( final XmlElement element )
         {
-            final org.w3c.dom.Element w3cElement = ( ( XmlElementW3c ) element ).element;
-            this.element.getOwnerDocument().adoptNode( w3cElement );
-            this.element.appendChild( w3cElement );
+            addContent( Collections.singletonList( element ) );
         }
 
         public void addContent( final List<XmlElement> elements )
         {
-            for ( final XmlElement element : elements )
+            lock.lock();
+            try
             {
-                final org.w3c.dom.Element w3cElement = ( ( XmlElementW3c ) element ).element;
-                this.element.getOwnerDocument().adoptNode( w3cElement );
-                this.element.appendChild( w3cElement );
+                for ( final XmlElement element : elements )
+                {
+                    final org.w3c.dom.Element w3cElement = ( ( XmlElementW3c ) element ).element;
+                    this.element.getOwnerDocument().adoptNode( w3cElement );
+                    this.element.appendChild( w3cElement );
+                    ( ( XmlElementW3c ) element ).lock = lock;
+                }
+            }
+            finally
+            {
+                lock.unlock();
             }
         }
 
         @Override
         public void addText( final String text )
         {
-            final DocumentBuilder documentBuilder = XmlFactory.XmlFactoryW3c.getBuilder();
-            final org.w3c.dom.Document document = documentBuilder.newDocument();
-            final org.w3c.dom.Text textNode = document.createTextNode( text );
-            this.element.getOwnerDocument().adoptNode( textNode );
-            element.appendChild( textNode );
+            lock.lock();
+            try
+            {
+                final DocumentBuilder documentBuilder = XmlFactory.XmlFactoryW3c.getBuilder();
+                final org.w3c.dom.Document document = documentBuilder.newDocument();
+                final org.w3c.dom.Text textNode = document.createTextNode( text );
+                this.element.getOwnerDocument().adoptNode( textNode );
+                element.appendChild( textNode );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public void setComment( final List<String> textLines )
         {
-            final NodeList nodeList = element.getChildNodes();
-            for ( int i = 0; i < nodeList.getLength(); i++ )
+            lock.lock();
+            try
             {
-                final Node node = nodeList.item( i );
-                if ( node.getNodeType() == Node.COMMENT_NODE )
+                final NodeList nodeList = element.getChildNodes();
+                for ( int i = 0; i < nodeList.getLength(); i++ )
                 {
-                    element.removeChild( node );
+                    final Node node = nodeList.item( i );
+                    if ( node.getNodeType() == Node.COMMENT_NODE )
+                    {
+                        element.removeChild( node );
+                    }
                 }
-            }
-
-            final DocumentBuilder documentBuilder = XmlFactory.XmlFactoryW3c.getBuilder();
-            final org.w3c.dom.Document document = documentBuilder.newDocument();
 
-            final List<String> reversedList = new ArrayList<>( textLines );
-            Collections.reverse( reversedList );
-            for ( final String text : reversedList )
-            {
-                final org.w3c.dom.Comment textNode = document.createComment( text );
-                this.element.getOwnerDocument().adoptNode( textNode );
+                final DocumentBuilder documentBuilder = XmlFactory.XmlFactoryW3c.getBuilder();
+                final org.w3c.dom.Document document = documentBuilder.newDocument();
 
-                if ( element.hasChildNodes() )
-                {
-                    element.insertBefore( textNode, element.getFirstChild() );
-                }
-                else
+                final List<String> reversedList = new ArrayList<>( textLines );
+                Collections.reverse( reversedList );
+                for ( final String text : reversedList )
                 {
-                    element.appendChild( textNode );
-                }
+                    final org.w3c.dom.Comment textNode = document.createComment( text );
+                    this.element.getOwnerDocument().adoptNode( textNode );
+
+                    if ( element.hasChildNodes() )
+                    {
+                        element.insertBefore( textNode, element.getFirstChild() );
+                    }
+                    else
+                    {
+                        element.appendChild( textNode );
+                    }
 
+                }
+            }
+            finally
+            {
+                lock.unlock();
             }
         }
 
         @Override
         public XmlElement copy()
         {
-            final Node newNode = this.element.cloneNode( true );
-            this.element.getOwnerDocument().adoptNode( newNode );
-            return new XmlElementW3c( (org.w3c.dom.Element ) newNode );
+            lock.lock();
+            try
+            {
+                final Node newNode = this.element.cloneNode( true );
+                this.element.getOwnerDocument().adoptNode( newNode );
+                return new XmlElementW3c( (org.w3c.dom.Element ) newNode, lock );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         @Override
         public XmlElement parent()
         {
-            return new XmlElementW3c( ( org.w3c.dom.Element ) this.element.getParentNode() );
+            return new XmlElementW3c( ( org.w3c.dom.Element ) this.element.getParentNode(), lock );
         }
     }
 }

+ 12 - 4
server/src/main/java/password/pwm/util/java/XmlFactory.java

@@ -49,6 +49,8 @@ import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 public interface XmlFactory
 {
@@ -226,6 +228,9 @@ public interface XmlFactory
         public void outputDocument( final XmlDocument document, final OutputStream outputStream )
                 throws IOException
         {
+            final Lock lock = ( ( XmlDocument.XmlDocumentW3c ) document ).lock;
+
+            lock.lock();
             try
             {
                 final Transformer tr = TransformerFactory.newInstance().newTransformer();
@@ -238,9 +243,13 @@ public interface XmlFactory
             {
                 throw new IOException( "error loading xml transformer: " + e.getMessage() );
             }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
-        static List<XmlElement> nodeListToElementList( final NodeList nodeList )
+        static List<XmlElement> nodeListToElementList( final NodeList nodeList, final Lock lock )
         {
             final List<XmlElement> returnList = new ArrayList<>();
             if ( nodeList != null )
@@ -250,7 +259,7 @@ public interface XmlFactory
                     final Node node = nodeList.item( i );
                     if ( node.getNodeType() == Node.ELEMENT_NODE )
                     {
-                        returnList.add( new XmlElement.XmlElementW3c( ( org.w3c.dom.Element ) node ) );
+                        returnList.add( new XmlElement.XmlElementW3c( ( org.w3c.dom.Element ) node, lock ) );
                     }
                 }
                 return returnList;
@@ -274,8 +283,7 @@ public interface XmlFactory
             final DocumentBuilder documentBuilder = getBuilder();
             final org.w3c.dom.Document document = documentBuilder.newDocument();
             final org.w3c.dom.Element element = document.createElement( name );
-            return new XmlElement.XmlElementW3c( element );
+            return new XmlElement.XmlElementW3c( element, new ReentrantLock() );
         }
-
     }
 }