浏览代码

tests, errorprone fixes and refactorings

Jason Rivard 5 年之前
父节点
当前提交
a6a1a97af1
共有 33 个文件被更改,包括 365 次插入236 次删除
  1. 4 6
      data-service/src/main/java/password/pwm/receiver/SummaryBean.java
  2. 1 0
      server/src/main/java/password/pwm/config/Configuration.java
  3. 57 57
      server/src/main/java/password/pwm/config/PwmSetting.java
  4. 3 4
      server/src/main/java/password/pwm/config/PwmSettingCategory.java
  5. 23 3
      server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java
  6. 2 1
      server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java
  7. 4 4
      server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java
  8. 2 3
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  9. 4 44
      server/src/main/java/password/pwm/config/value/ValueTypeConverter.java
  10. 14 9
      server/src/main/java/password/pwm/health/CertificateChecker.java
  11. 33 1
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  12. 1 0
      server/src/main/java/password/pwm/health/HealthMessage.java
  13. 13 6
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  14. 5 2
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  15. 1 1
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java
  16. 4 3
      server/src/main/java/password/pwm/svc/event/AuditEvent.java
  17. 8 6
      server/src/main/java/password/pwm/svc/event/LdapXmlUserHistory.java
  18. 7 6
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java
  19. 5 15
      server/src/main/java/password/pwm/svc/stats/Statistic.java
  20. 6 6
      server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java
  21. 1 1
      server/src/main/java/password/pwm/util/cli/MainClass.java
  22. 5 5
      server/src/main/java/password/pwm/util/db/DBConfiguration.java
  23. 2 1
      server/src/main/java/password/pwm/util/db/JDBCDriverLoader.java
  24. 9 13
      server/src/main/java/password/pwm/util/java/AverageTracker.java
  25. 11 5
      server/src/main/java/password/pwm/util/java/JavaHelper.java
  26. 7 8
      server/src/main/java/password/pwm/util/macro/InternalMacros.java
  27. 1 1
      server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java
  28. 8 0
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  29. 1 0
      server/src/main/resources/password/pwm/i18n/Health.properties
  30. 50 1
      server/src/test/java/password/pwm/config/PwmSettingPropertyTest.java
  31. 5 22
      server/src/test/java/password/pwm/config/PwmSettingTest.java
  32. 3 2
      server/src/test/java/password/pwm/config/PwmSettingXmlTest.java
  33. 65 0
      server/src/test/java/password/pwm/util/java/AverageTrackerTest.java

+ 4 - 6
data-service/src/main/java/password/pwm/receiver/SummaryBean.java

@@ -110,22 +110,20 @@ public class SummaryBean
 
                 for ( final String settingKey : bean.getConfiguredSettings() )
                 {
-                    final PwmSetting setting = PwmSetting.forKey( settingKey );
-                    if ( setting != null )
+                    PwmSetting.forKey( settingKey ).ifPresent( ( setting ) ->
                     {
                         final String description = setting.toMenuLocationDebug( null, null );
                         incrementCounterMap( settingCount, description );
-                    }
+                    } );
                 }
 
                 for ( final String statKey : bean.getStatistics().keySet() )
                 {
-                    final Statistic statistic = Statistic.forKey( statKey );
-                    if ( statistic != null )
+                    Statistic.forKey( statKey ).ifPresent( ( statistic ->
                     {
                         final int count = Integer.parseInt( bean.getStatistics().get( statKey ) );
                         incrementCounterMap( statCount, statistic.getLabel( null ), count );
-                    }
+                    } ) );
                 }
             }
         }

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

@@ -155,6 +155,7 @@ public class Configuration implements SettingReader
     {
         final StoredValue value = readStoredValue( setting );
         final E returnValue = ValueTypeConverter.valueToEnum( setting, value, enumClass );
+
         if ( MessageSendMethod.class.equals( enumClass ) )
         {
             deprecatedSettingException( setting, null, ( MessageSendMethod ) returnValue );

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

@@ -37,7 +37,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.EnumMap;
+import java.util.EnumSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -1299,8 +1300,8 @@ public enum PwmSetting
     private final PwmSettingCategory category;
 
     // cached values read from XML file
-    private final Supplier<List<TemplateSetAssociation>> defaultValues = new LazySupplier<>( () -> PwmSettingReader.readDefaultValue( PwmSetting.this ) );
-    private final Supplier<List<TemplateSetAssociation>> examples = new LazySupplier<>( () -> PwmSettingReader.readExamples( PwmSetting.this ) );
+    private final Supplier<List<TemplateSetReference<StoredValue>>> defaultValues = new LazySupplier<>( () -> PwmSettingReader.readDefaultValue( PwmSetting.this ) );
+    private final Supplier<List<TemplateSetReference<String>>> examples = new LazySupplier<>( () -> PwmSettingReader.readExamples( PwmSetting.this ) );
     private final Supplier<Map<String, String>> options = new LazySupplier<>( () -> PwmSettingReader.readOptions( PwmSetting.this ) );
     private final Supplier<Collection<PwmSettingFlag>> flags = new LazySupplier<>( () -> PwmSettingReader.readFlags( PwmSetting.this ) );
     private final Supplier<Map<PwmSettingProperty, String>> properties = new LazySupplier<>( () -> PwmSettingReader.readProperties( PwmSetting.this ) );
@@ -1341,25 +1342,25 @@ public enum PwmSetting
         return syntax;
     }
 
-    private List<TemplateSetAssociation> getDefaultValue()
+    private List<TemplateSetReference<StoredValue>> getDefaultValue()
     {
         return defaultValues.get();
     }
 
     public StoredValue getDefaultValue( final PwmSettingTemplateSet templateSet )
     {
-        final List<TemplateSetAssociation> defaultValues = getDefaultValue();
-        return ( StoredValue ) associationForTempleSet( defaultValues, templateSet ).getObject();
+        final List<TemplateSetReference<StoredValue>> defaultValues = getDefaultValue();
+        return TemplateSetReference.referenceForTempleSet( defaultValues, templateSet );
     }
 
     public Map<String, String> getDefaultValueDebugStrings( final Locale locale )
     {
         final Map<String, String> returnObj = new LinkedHashMap<>();
-        for ( final TemplateSetAssociation templateSetAssociation : this.getDefaultValue() )
+        for ( final TemplateSetReference<StoredValue> templateSetReference : this.getDefaultValue() )
         {
             returnObj.put(
-                    StringUtil.join( templateSetAssociation.getSettingTemplates(), "," ),
-                    ( ( StoredValue ) templateSetAssociation.getObject() ).toDebugString( locale )
+                    StringUtil.join( templateSetReference.getSettingTemplates(), "," ),
+                    ( templateSetReference.getReference() ).toDebugString( locale )
             );
         }
         return Collections.unmodifiableMap( returnObj );
@@ -1397,7 +1398,7 @@ public enum PwmSetting
 
     public String getExample( final PwmSettingTemplateSet template )
     {
-        return ( String ) associationForTempleSet( examples.get(), template ).getObject();
+        return TemplateSetReference.referenceForTempleSet( examples.get(), template );
     }
 
     public boolean isRequired( )
@@ -1420,12 +1421,11 @@ public enum PwmSetting
         return pattern.get();
     }
 
-    public static PwmSetting forKey( final String key )
+    public static Optional<PwmSetting> forKey( final String key )
     {
         return Arrays.stream( values() )
                 .filter( loopValue -> loopValue.getKey().equals( key ) )
-                .findFirst()
-                .orElse( null );
+                .findFirst();
     }
 
     public String toMenuLocationDebug(
@@ -1482,52 +1482,52 @@ public enum PwmSetting
     }
 
     @Value
-    public static class TemplateSetAssociation
+    static class TemplateSetReference<T>
     {
-        private final Object object;
+        private final T reference;
         private final Set<PwmSettingTemplate> settingTemplates;
-    }
 
-    public static Set<PwmSetting> sortedByMenuLocation( final Locale locale )
-    {
-        final TreeMap<String, PwmSetting> treeMap = new TreeMap<>();
-        for ( final PwmSetting pwmSetting : PwmSetting.values() )
+        private static <T> T referenceForTempleSet(
+                final List<TemplateSetReference<T>> templateSetReferences,
+                final PwmSettingTemplateSet pwmSettingTemplate
+        )
         {
-            treeMap.put( pwmSetting.toMenuLocationDebug( null, locale ), pwmSetting );
-        }
-        return Collections.unmodifiableSet( new LinkedHashSet<>( treeMap.values() ) );
-    }
-
-    private static TemplateSetAssociation associationForTempleSet(
-            final List<TemplateSetAssociation> associationSets,
-            final PwmSettingTemplateSet pwmSettingTemplate
-    )
-    {
-        if ( associationSets == null || associationSets.isEmpty() )
-        {
-            return null;
-        }
+            if ( templateSetReferences == null || templateSetReferences.isEmpty() )
+            {
+                throw new IllegalStateException( "templateSetReferences can not be null" );
+            }
 
-        if ( associationSets.size() == 1 )
-        {
-            return associationSets.iterator().next();
-        }
+            if ( templateSetReferences.size() == 1 )
+            {
+                return templateSetReferences.iterator().next().getReference();
+            }
 
-        for ( int matchCountExamSize = associationSets.size(); matchCountExamSize > 0; matchCountExamSize-- )
-        {
-            for ( final TemplateSetAssociation associationSet : associationSets )
+            for ( int matchCountExamSize = templateSetReferences.size(); matchCountExamSize > 0; matchCountExamSize-- )
             {
-                final Set<PwmSettingTemplate> temporarySet = new HashSet<>( associationSet.getSettingTemplates() );
-                temporarySet.retainAll( pwmSettingTemplate.getTemplates() );
-                final int matchCount = temporarySet.size();
-                if ( matchCount == matchCountExamSize )
+                for ( final TemplateSetReference<T> templateSetReference : templateSetReferences )
                 {
-                    return associationSet;
+                    final Set<PwmSettingTemplate> temporarySet = JavaHelper.copiedEnumSet( templateSetReference.getSettingTemplates(), PwmSettingTemplate.class );
+                    temporarySet.retainAll( pwmSettingTemplate.getTemplates() );
+                    final int matchCount = temporarySet.size();
+                    if ( matchCount == matchCountExamSize )
+                    {
+                        return templateSetReference.getReference();
+                    }
                 }
             }
+
+            return templateSetReferences.iterator().next().getReference();
         }
+    }
 
-        return associationSets.iterator().next();
+    public static Set<PwmSetting> sortedByMenuLocation( final Locale locale )
+    {
+        final TreeMap<String, PwmSetting> treeMap = new TreeMap<>();
+        for ( final PwmSetting pwmSetting : PwmSetting.values() )
+        {
+            treeMap.put( pwmSetting.toMenuLocationDebug( null, locale ), pwmSetting );
+        }
+        return Collections.unmodifiableSet( new LinkedHashSet<>( treeMap.values() ) );
     }
 
     static class PwmSettingReader
@@ -1535,7 +1535,7 @@ public enum PwmSetting
 
         private static Collection<PwmSettingFlag> readFlags( final PwmSetting pwmSetting )
         {
-            final Collection<PwmSettingFlag> returnObj = new ArrayList<>();
+            final Collection<PwmSettingFlag> returnObj = EnumSet.noneOf( PwmSettingFlag.class );
             final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
             final List<XmlElement> flagElements = settingElement.getChildren( "flag" );
             for ( final XmlElement flagElement : flagElements )
@@ -1609,9 +1609,9 @@ public enum PwmSetting
             return Collections.unmodifiableList( returnObj );
         }
 
-        private static List<TemplateSetAssociation> readExamples( final PwmSetting pwmSetting )
+        private static List<TemplateSetReference<String>> readExamples( final PwmSetting pwmSetting )
         {
-            final List<TemplateSetAssociation> returnObj = new ArrayList<>();
+            final List<TemplateSetReference<String>> returnObj = new ArrayList<>();
             final MacroMachine macroMachine = MacroMachine.forStatic();
             final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
             final List<XmlElement> exampleElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_EXAMPLE );
@@ -1619,18 +1619,18 @@ public enum PwmSetting
             {
                 final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( exampleElement );
                 final String exampleString = macroMachine.expandMacros( exampleElement.getText() );
-                returnObj.add( new TemplateSetAssociation( exampleString, Collections.unmodifiableSet( definedTemplates ) ) );
+                returnObj.add( new TemplateSetReference<>( exampleString, Collections.unmodifiableSet( definedTemplates ) ) );
             }
             if ( returnObj.isEmpty() )
             {
-                returnObj.add( new TemplateSetAssociation( "", Collections.emptySet() ) );
+                returnObj.add( new TemplateSetReference<>( "", Collections.emptySet() ) );
             }
             return Collections.unmodifiableList( returnObj );
         }
 
         private static Map<PwmSettingProperty, String> readProperties( final PwmSetting pwmSetting )
         {
-            final Map<PwmSettingProperty, String> newProps = new LinkedHashMap<>();
+            final Map<PwmSettingProperty, String> newProps = new EnumMap<>( PwmSettingProperty.class );
             final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
             final Optional<XmlElement> propertiesElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_PROPERTIES );
             if ( propertiesElement.isPresent() )
@@ -1659,14 +1659,14 @@ public enum PwmSetting
             return Collections.unmodifiableMap( newProps );
         }
 
-        private static List<TemplateSetAssociation> readDefaultValue( final PwmSetting pwmSetting )
+        private static List<TemplateSetReference<StoredValue>> readDefaultValue( final PwmSetting pwmSetting )
         {
-            final List<TemplateSetAssociation> returnObj = new ArrayList<>();
+            final List<TemplateSetReference<StoredValue>> returnObj = new ArrayList<>();
             final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
             final List<XmlElement> defaultElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_DEFAULT );
             if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
             {
-                returnObj.add( new TemplateSetAssociation( new PasswordValue( null ), Collections.emptySet() ) );
+                returnObj.add( new TemplateSetReference<>( new PasswordValue( null ), Collections.emptySet() ) );
             }
             else
             {
@@ -1674,7 +1674,7 @@ public enum PwmSetting
                 {
                     final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement );
                     final StoredValue storedValue = ValueFactory.fromXmlValues( pwmSetting, defaultElement, null );
-                    returnObj.add( new TemplateSetAssociation( storedValue, definedTemplates ) );
+                    returnObj.add( new TemplateSetReference<>( storedValue, definedTemplates ) );
                 }
             }
             if ( returnObj.isEmpty() )

+ 3 - 4
server/src/main/java/password/pwm/config/PwmSettingCategory.java

@@ -398,12 +398,11 @@ public enum PwmSettingCategory
         return Collections.unmodifiableCollection( returnValues );
     }
 
-    public static PwmSettingCategory forKey( final String key )
+    public static Optional<PwmSettingCategory> forKey( final String key )
     {
         return Arrays.stream( values() )
                 .filter( loopValue -> loopValue.getKey().equals( key ) )
-                .findFirst()
-                .orElse( null );
+                .findFirst();
     }
 
     private static class XmlReader
@@ -421,7 +420,7 @@ public enum PwmSettingCategory
                     final String settingKey = profileElement.get().getAttributeValue( "setting" );
                     if ( settingKey != null )
                     {
-                        return Optional.of( PwmSetting.forKey( settingKey ) );
+                        return PwmSetting.forKey( settingKey );
                     }
                 }
                 if ( nested )

+ 23 - 3
server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java

@@ -21,8 +21,10 @@
 package password.pwm.config;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.List;
 import java.util.Set;
 
 public class PwmSettingTemplateSet implements Serializable
@@ -31,7 +33,7 @@ public class PwmSettingTemplateSet implements Serializable
 
     public PwmSettingTemplateSet( final Set<PwmSettingTemplate> templates )
     {
-        final Set<PwmSettingTemplate> workingSet = new HashSet<>();
+        final Set<PwmSettingTemplate> workingSet = EnumSet.noneOf( PwmSettingTemplate.class );
 
         if ( templates != null )
         {
@@ -44,7 +46,7 @@ public class PwmSettingTemplateSet implements Serializable
             }
         }
 
-        final Set<PwmSettingTemplate.Type> seenTypes = new HashSet<>();
+        final Set<PwmSettingTemplate.Type> seenTypes = EnumSet.noneOf( PwmSettingTemplate.Type.class );
         for ( final PwmSettingTemplate template : workingSet )
         {
             seenTypes.add( template.getType() );
@@ -70,4 +72,22 @@ public class PwmSettingTemplateSet implements Serializable
     {
         return new PwmSettingTemplateSet( null );
     }
+
+    /**
+     * Get all possible templateSets, useful for testing.
+     * @return
+     */
+    public static List<PwmSettingTemplateSet> allValues()
+    {
+        final List<PwmSettingTemplateSet> templateSets = new ArrayList<>();
+
+        for ( final PwmSettingTemplate template : PwmSettingTemplate.values() )
+        {
+            final PwmSettingTemplateSet templateSet = new PwmSettingTemplateSet( Collections.singleton( template ) );
+            templateSets.add( templateSet );
+        }
+
+        templateSets.add( getDefault() );
+        return Collections.unmodifiableList( templateSets );
+    }
 }

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

@@ -183,7 +183,8 @@ public class StoredConfigItemKey implements Serializable, Comparable<StoredConfi
             throw new IllegalStateException( "attempt to read pwmSetting key for non-setting ConfigItemKey" );
         }
 
-        return PwmSetting.forKey( this.recordID );
+        return PwmSetting.forKey( this.recordID ).orElseThrow( () -> new IllegalStateException(
+                "attempt to read ConfigItemKey with unknown setting key '" + getRecordID() + "'" ) );
     }
 
     public PwmLocaleBundle toLocaleBundle()

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

@@ -27,12 +27,13 @@ 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.StoredValue;
 import password.pwm.config.value.LocalizedStringValue;
+import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.StoredValueEncoder;
 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;
@@ -58,7 +59,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -226,8 +226,8 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                 }
             }
 
-            final List<String> settingValues = ( List<String> ) effectiveValue.toNativeObject();
-            final LinkedList<String> profiles = new LinkedList<>( settingValues );
+            final List<String> settingValues = ValueTypeConverter.valueToStringArray( effectiveValue );
+            final List<String> profiles = new ArrayList<>( settingValues );
             profiles.removeIf( StringUtil::isEmpty );
             return Collections.unmodifiableList( profiles );
         }

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

@@ -27,8 +27,8 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.PwmSettingTemplateSet;
-import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.PasswordValue;
+import password.pwm.config.value.StoredValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -45,7 +45,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -100,7 +99,7 @@ public abstract class StoredConfigurationUtil
     {
         final Object nativeObject = storedConfiguration.readSetting( profileSetting, null ).toNativeObject();
         final List<String> settingValues = ( List<String> ) nativeObject;
-        final LinkedList<String> profiles = new LinkedList<>( settingValues );
+        final List<String> profiles = new ArrayList<>( settingValues );
         profiles.removeIf( StringUtil::isEmpty );
         return Collections.unmodifiableList( profiles );
 

+ 4 - 44
server/src/main/java/password/pwm/config/value/ValueTypeConverter.java

@@ -29,15 +29,14 @@ import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 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.logging.PwmLogger;
 
-import java.lang.reflect.InvocationTargetException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -278,27 +277,8 @@ public final class ValueTypeConverter
             throw new IllegalArgumentException( "may not read SELECT enum value for setting: " + setting.toString() );
         }
 
-        if ( value != null )
-        {
-            final String strValue = ( String ) value.toNativeObject();
-            try
-            {
-                return ( E ) enumClass.getMethod( "valueOf", String.class ).invoke( null, strValue );
-            }
-            catch ( final InvocationTargetException e1 )
-            {
-                if ( e1.getCause() instanceof IllegalArgumentException )
-                {
-                    LOGGER.error( () -> "illegal setting value for option '" + strValue + "' for setting key '" + setting.getKey() + "' is not recognized, will use default" );
-                }
-            }
-            catch ( final Exception e1 )
-            {
-                LOGGER.error( () -> "unexpected error", e1 );
-            }
-        }
-
-        return null;
+        final String strValue = ( String ) value.toNativeObject();
+        return JavaHelper.readEnumFromString( enumClass, strValue ).orElse( null );
     }
 
     public static Map<FileValue.FileInformation, FileValue.FileContent> valueToFile( final PwmSetting setting, final StoredValue storedValue )
@@ -323,27 +303,7 @@ public final class ValueTypeConverter
             throw new IllegalArgumentException( "may not read optionlist value for setting: " + setting.toString() );
         }
 
-        final Set<E> returnSet = new LinkedHashSet<>();
         final Set<String> strValues = ( Set<String> ) value.toNativeObject();
-        for ( final String strValue : strValues )
-        {
-            try
-            {
-                returnSet.add( ( E ) enumClass.getMethod( "valueOf", String.class ).invoke( null, strValue ) );
-            }
-            catch ( final InvocationTargetException e1 )
-            {
-                if ( e1.getCause() instanceof IllegalArgumentException )
-                {
-                    LOGGER.error( () -> "illegal setting value for option '" + strValue + "' is not recognized, will use default" );
-                }
-            }
-            catch ( final Exception e1 )
-            {
-                LOGGER.error( () -> "unexpected error", e1 );
-            }
-        }
-
-        return Collections.unmodifiableSet( returnSet );
+        return JavaHelper.readEnumSetFromStringCollection( enumClass, strValues );
     }
 }

+ 14 - 9
server/src/main/java/password/pwm/health/CertificateChecker.java

@@ -45,6 +45,7 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 public class CertificateChecker implements HealthChecker
@@ -99,19 +100,23 @@ public class CertificateChecker implements HealthChecker
         {
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             {
-                final PwmSetting pwmSetting = PwmSetting.forKey( storedConfigItemKey.getRecordID() );
-                if ( pwmSetting != null && pwmSetting.getSyntax() == PwmSettingSyntax.ACTION )
+                final Optional<PwmSetting> optionalPwmSetting = PwmSetting.forKey( storedConfigItemKey.getRecordID() );
+                optionalPwmSetting.ifPresent( pwmSetting ->
                 {
-                    final ActionValue value = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, storedConfigItemKey.getProfileID() );
-                    for ( final ActionConfiguration actionConfiguration : value.toNativeObject() )
+                    if ( pwmSetting.getSyntax() == PwmSettingSyntax.ACTION )
                     {
-                        for ( final ActionConfiguration.WebAction webAction : actionConfiguration.getWebActions()  )
+                        final ActionValue value = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, storedConfigItemKey.getProfileID() );
+                        for ( final ActionConfiguration actionConfiguration : value.toNativeObject() )
                         {
-                            final List<X509Certificate> certificates = webAction.getCertificates();
-                            returnList.addAll( doHealthCheck( configuration, pwmSetting, storedConfigItemKey.getProfileID(), certificates ) );
+                            for ( final ActionConfiguration.WebAction webAction : actionConfiguration.getWebActions() )
+                            {
+                                final List<X509Certificate> certificates = webAction.getCertificates();
+                                returnList.addAll( doHealthCheck( configuration, pwmSetting, storedConfigItemKey.getProfileID(), certificates ) );
+                            }
                         }
                     }
                 }
+                );
             }
         }
         return Collections.unmodifiableList( returnList );
@@ -173,7 +178,7 @@ public class CertificateChecker implements HealthChecker
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, errorMsg.toString(), new String[]
                     {
                             errorMsg.toString(),
-                    }
+                            }
             );
             throw new PwmOperationalException( errorInformation );
         }
@@ -191,7 +196,7 @@ public class CertificateChecker implements HealthChecker
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, errorMsg.toString(), new String[]
                     {
                             errorMsg.toString(),
-                    }
+                            }
             );
             throw new PwmOperationalException( errorInformation );
         }

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

@@ -27,6 +27,7 @@ import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
+import password.pwm.config.profile.ChangePasswordProfile;
 import password.pwm.config.value.StoredValue;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.MessageSendMethod;
@@ -139,7 +140,8 @@ public class ConfigurationChecker implements HealthChecker
             VerifyDbConfiguredIfNeeded.class,
             VerifyIfDeprecatedSendMethodValuesUsed.class,
             VerifyIfDeprecatedJsFormOptionUsed.class,
-            VerifyNewUserLdapProfile.class
+            VerifyNewUserLdapProfile.class,
+            VerifyPasswordWaitTimes.class
     ) );
 
     static class VerifyBasicConfigs implements ConfigHealthCheck
@@ -543,6 +545,36 @@ public class ConfigurationChecker implements HealthChecker
         }
     }
 
+    static class VerifyPasswordWaitTimes implements ConfigHealthCheck
+    {
+        @Override
+        public List<HealthRecord> healthCheck( final Configuration config, final Locale locale )
+        {
+            final List<HealthRecord> records = new ArrayList<>();
+
+            for ( final ChangePasswordProfile changePasswordProfile : config.getChangePasswordProfile().values() )
+            {
+                final long minValue = changePasswordProfile.readSettingAsLong( PwmSetting.PASSWORD_SYNC_MIN_WAIT_TIME );
+                final long maxValue = changePasswordProfile.readSettingAsLong( PwmSetting.PASSWORD_SYNC_MAX_WAIT_TIME );
+                if ( maxValue > 0 && minValue > maxValue )
+                {
+                    final String profileID = changePasswordProfile.getIdentifier();
+                    final String detailMsg = " (" + minValue + ")"
+                            + " > "
+                            + " (" + maxValue + ")";
+                    records.add( HealthRecord.forMessage(
+                            HealthMessage.Config_ValueConflict,
+                            PwmSetting.PASSWORD_SYNC_MAX_WAIT_TIME.toMenuLocationDebug( profileID, locale ),
+                            PwmSetting.PASSWORD_SYNC_MIN_WAIT_TIME.toMenuLocationDebug( profileID, locale ),
+                            detailMsg
+                    ) );
+                }
+            }
+
+
+            return records;
+        }
+    }
 
     interface ConfigHealthCheck
     {

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

@@ -73,6 +73,7 @@ public enum HealthMessage
     Config_DeprecatedJSForm( HealthStatus.CONFIG, HealthTopic.Configuration ),
     Config_InvalidLdapProfile( HealthStatus.CONFIG, HealthTopic.Configuration ),
     Config_NoLdapProfiles( HealthStatus.CONFIG, HealthTopic.Configuration ),
+    Config_ValueConflict( HealthStatus.CONFIG, HealthTopic.Configuration ),
 
     LDAP_VendorsNotSame( HealthStatus.CONFIG, HealthTopic.LDAP ),
     LDAP_OK( HealthStatus.GOOD, HealthTopic.LDAP ),

+ 13 - 6
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -212,7 +212,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final String bodyString = pwmRequest.readRequestBodyAsString();
         final Map<String, String> requestMap = JsonUtil.deserializeStringMap( bodyString );
-        final PwmSetting pwmSetting = PwmSetting.forKey( requestMap.get( "setting" ) );
+        final PwmSetting pwmSetting = PwmSetting.forKey( requestMap.get( "setting" ) )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
         final String functionName = requestMap.get( "function" );
         final String profileID = pwmSetting.getCategory().hasProfiles() ? pwmRequest.readParameterAsString( "profile" ) : null;
         final String extraData = requestMap.get( "extraData" );
@@ -257,7 +258,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final String key = pwmRequest.readParameterAsString( "key" );
         final Object returnValue;
         final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>();
-        final PwmSetting theSetting = PwmSetting.forKey( key );
+        final PwmSetting theSetting = PwmSetting.forKey( key )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
 
         if ( key.startsWith( "localeBundle" ) )
         {
@@ -378,7 +380,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
         final String key = pwmRequest.readParameterAsString( "key" );
         final String bodyString = pwmRequest.readRequestBodyAsString();
-        final PwmSetting setting = PwmSetting.forKey( key );
+        final PwmSetting setting = PwmSetting.forKey( key )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
         final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>();
         final UserIdentity loggedInUser = pwmRequest.getPwmSession().isAuthenticated()
                 ? pwmRequest.getPwmSession().getUserInfo().getUserIdentity()
@@ -440,7 +443,9 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
         final UserIdentity loggedInUser = pwmRequest.getUserInfoIfLoggedIn();
         final String key = pwmRequest.readParameterAsString( "key" );
-        final PwmSetting setting = PwmSetting.forKey( key );
+        final PwmSetting setting = PwmSetting.forKey( key )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
+
 
         if ( key.startsWith( "localeBundle" ) )
         {
@@ -815,7 +820,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
 
         final String key = pwmRequest.readParameterAsString( "key" );
-        final PwmSetting setting = PwmSetting.forKey( key );
+        final PwmSetting setting = PwmSetting.forKey( key )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
         final int maxFileSize = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.CONFIG_MAX_JDBC_JAR_SIZE ) );
 
         if ( setting == PwmSetting.HTTPS_CERT )
@@ -1117,7 +1123,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation );
 
         final String settingKey = inputMap.get( "setting" );
-        final PwmSetting setting = PwmSetting.forKey( settingKey );
+        final PwmSetting setting = PwmSetting.forKey( settingKey )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
         PwmSettingCategory category = null;
         for ( final PwmSettingCategory loopCategory : PwmSettingCategory.values() )
         {

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

@@ -601,7 +601,8 @@ public class ConfigGuideServlet extends ControlledPwmServlet
 
         final String key = pwmRequest.readParameterAsString( "key" );
         final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>();
-        final PwmSetting theSetting = PwmSetting.forKey( key );
+        final PwmSetting theSetting = PwmSetting.forKey( key )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
 
         final Object returnValue;
         returnValue = storedConfiguration.readSetting( theSetting, profileID ).toNativeObject();
@@ -623,7 +624,9 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         final String profileID = "default";
         final String key = pwmRequest.readParameterAsString( "key" );
         final String bodyString = pwmRequest.readRequestBodyAsString();
-        final PwmSetting setting = PwmSetting.forKey( key );
+        final PwmSetting setting = PwmSetting.forKey( key )
+                .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
+
         final ConfigGuideBean configGuideBean = getBean( pwmRequest );
         final StoredConfiguration storedConfigurationImpl = ConfigGuideForm.generateStoredConfig( configGuideBean );
 

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java

@@ -123,7 +123,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
         {
             if ( ref.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             {
-                final PwmSetting pwmSetting = PwmSetting.forKey( ref.getRecordID() );
+                final PwmSetting pwmSetting = ref.toPwmSetting();
                 if ( pwmSetting.getSyntax() == PwmSettingSyntax.X509CERT )
                 {
                     final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, ref.getProfileID() );

+ 4 - 3
server/src/main/java/password/pwm/svc/event/AuditEvent.java

@@ -28,6 +28,7 @@ import password.pwm.util.java.JsonUtil;
 
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.ResourceBundle;
 
 public enum AuditEvent
@@ -102,7 +103,7 @@ public enum AuditEvent
         return message;
     }
 
-    public static AuditEvent forKey( final String key )
+    public static Optional<AuditEvent> forKey( final String key )
     {
         for ( final AuditEvent loopEvent : AuditEvent.values() )
         {
@@ -112,12 +113,12 @@ public enum AuditEvent
                 final String resourceKey = message.getKey();
                 if ( resourceKey.equals( key ) )
                 {
-                    return loopEvent;
+                    return Optional.of( loopEvent );
                 }
             }
         }
 
-        return null;
+        return Optional.empty();
     }
 
     public String getLocalizedString( final Configuration config, final Locale locale )

+ 8 - 6
server/src/main/java/password/pwm/svc/event/LdapXmlUserHistory.java

@@ -299,12 +299,14 @@ class LdapXmlUserHistory implements UserHistoryStore
                 {
                     final long timeStamp = hrElement.getAttribute( XML_ATTR_TIMESTAMP ).getLongValue();
                     final String transactionCode = hrElement.getAttribute( XML_ATTR_TRANSACTION ).getValue();
-                    final AuditEvent eventCode = AuditEvent.forKey( transactionCode );
-                    final String srcAddr = hrElement.getAttribute( XML_ATTR_SRC_IP ) != null ? hrElement.getAttribute( XML_ATTR_SRC_IP ).getValue() : "";
-                    final String srcHost = hrElement.getAttribute( XML_ATTR_SRC_HOST ) != null ? hrElement.getAttribute( XML_ATTR_SRC_HOST ).getValue() : "";
-                    final String message = hrElement.getText();
-                    final StoredEvent storedEvent = new StoredEvent( eventCode, timeStamp, message, srcAddr, srcHost );
-                    returnHistory.addEvent( storedEvent );
+                    AuditEvent.forKey( transactionCode ).ifPresent( ( eventCode ) ->
+                    {
+                        final String srcAddr = hrElement.getAttribute( XML_ATTR_SRC_IP ) != null ? hrElement.getAttribute( XML_ATTR_SRC_IP ).getValue() : "";
+                        final String srcHost = hrElement.getAttribute( XML_ATTR_SRC_HOST ) != null ? hrElement.getAttribute( XML_ATTR_SRC_HOST ).getValue() : "";
+                        final String message = hrElement.getText();
+                        final StoredEvent storedEvent = new StoredEvent( eventCode, timeStamp, message, srcAddr, srcHost );
+                        returnHistory.addEvent( storedEvent );
+                    } );
                 }
             }
             catch ( final JDOMException | IOException e )

+ 7 - 6
server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java

@@ -93,6 +93,7 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class PwmHttpClient implements AutoCloseable
@@ -357,9 +358,9 @@ public class PwmHttpClient implements AutoCloseable
         final PwmHttpClientResponse.PwmHttpClientResponseBuilder httpClientResponseBuilder = PwmHttpClientResponse.builder();
         httpClientResponseBuilder.requestID( clientRequest.getRequestID() );
 
-        final HttpContentType httpContentType = contentTypeForEntity( httpResponse.getEntity() );
+        final Optional<HttpContentType> optionalHttpContentType = contentTypeForEntity( httpResponse.getEntity() );
 
-        if ( httpContentType != null && httpContentType.getDataType() ==  HttpEntityDataType.ByteArray )
+        if ( optionalHttpContentType.isPresent() && optionalHttpContentType.get().getDataType() ==  HttpEntityDataType.ByteArray )
         {
             httpClientResponseBuilder.binaryBody( readBinaryEntityBody( httpResponse.getEntity() ) );
             httpClientResponseBuilder.dataType( HttpEntityDataType.ByteArray );
@@ -378,7 +379,7 @@ public class PwmHttpClient implements AutoCloseable
 
         final PwmHttpClientResponse httpClientResponse = httpClientResponseBuilder
                 .statusCode( httpResponse.getStatusLine().getStatusCode() )
-                .contentType( httpContentType )
+                .contentType( optionalHttpContentType.orElse( HttpContentType.plain ) )
                 .statusPhrase( httpResponse.getStatusLine().getReasonPhrase() )
                 .headers( Collections.unmodifiableMap( responseHeaders ) )
                 .build();
@@ -517,7 +518,7 @@ public class PwmHttpClient implements AutoCloseable
         }
     }
 
-    private static HttpContentType contentTypeForEntity( final HttpEntity httpEntity )
+    private static Optional<HttpContentType> contentTypeForEntity( final HttpEntity httpEntity )
     {
         if ( httpEntity != null )
         {
@@ -537,7 +538,7 @@ public class PwmHttpClient implements AutoCloseable
                                 final HttpContentType httpContentType = HttpContentType.fromContentTypeHeader( name, null );
                                 if ( httpContentType != null )
                                 {
-                                    return httpContentType;
+                                    return Optional.of( httpContentType );
                                 }
                             }
                         }
@@ -546,7 +547,7 @@ public class PwmHttpClient implements AutoCloseable
             }
         }
 
-        return null;
+        return Optional.empty();
     }
 
     private ImmutableByteArray readBinaryEntityBody( final HttpEntity httpEntity )

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

@@ -29,6 +29,7 @@ import password.pwm.util.java.TimeDuration;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -197,21 +198,10 @@ public enum Statistic
         }
     }
 
-    public static Statistic forKey( final String key )
+    public static Optional<Statistic> forKey( final String key )
     {
-        if ( key == null )
-        {
-            return null;
-        }
-
-        for ( final Statistic stat : values() )
-        {
-            if ( stat.getKey().equals( key ) )
-            {
-                return stat;
-            }
-        }
-
-        return null;
+        return Arrays.stream( values() )
+                .filter( loopValue -> loopValue.getKey().equals( key ) )
+                .findFirst();
     }
 }

+ 6 - 6
server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java

@@ -46,10 +46,9 @@ import password.pwm.util.queue.SmsQueueManager;
 
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.EnumSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -347,7 +346,7 @@ public class LDAPPermissionCalculator implements Serializable
         {
             case CHALLENGE_USER_ATTRIBUTE:
             {
-                final Set<DataStorageMethod> storageMethods = new HashSet<>();
+                final Set<DataStorageMethod> storageMethods = EnumSet.noneOf( DataStorageMethod.class );
                 storageMethods.addAll( configuration.getResponseStorageLocations( PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE ) );
                 storageMethods.addAll( configuration.getResponseStorageLocations( PwmSetting.FORGOTTEN_PASSWORD_READ_PREFERENCE ) );
                 if ( !storageMethods.contains( DataStorageMethod.LDAP ) )
@@ -446,9 +445,10 @@ public class LDAPPermissionCalculator implements Serializable
     {
 
         final Set<PwmSettingTemplate> edirInterestedTemplates =
-                Collections.unmodifiableSet( new HashSet<>( Arrays.asList(
-                        PwmSettingTemplate.NOVL, PwmSettingTemplate.NOVL_IDM ) )
-                );
+                Collections.unmodifiableSet( EnumSet.of(
+                        PwmSettingTemplate.NOVL,
+                        PwmSettingTemplate.NOVL_IDM
+                ) );
 
         final List<PermissionRecord> permissionRecords = new ArrayList<>();
 

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

@@ -360,7 +360,7 @@ public class MainClass
             final String[] args
     )
     {
-        final List<String> argList = new LinkedList<>( Arrays.asList( args ) );
+        final List<String> argList = new ArrayList<>( Arrays.asList( args ) );
         argList.remove( 0 );
 
         final CliEnvironment cliEnvironment;

+ 5 - 5
server/src/main/java/password/pwm/util/db/DBConfiguration.java

@@ -22,7 +22,7 @@ package password.pwm.util.db;
 
 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
-import lombok.Getter;
+import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
@@ -33,10 +33,10 @@ import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
-@Getter
+@Value
 @AllArgsConstructor( access = AccessLevel.PRIVATE )
 public class DBConfiguration implements Serializable
 {
@@ -47,7 +47,7 @@ public class DBConfiguration implements Serializable
     private final String columnTypeKey;
     private final String columnTypeValue;
     private final byte[] jdbcDriver;
-    private final List<JDBCDriverLoader.ClassLoaderStrategy> classLoaderStrategies;
+    private final Set<JDBCDriverLoader.ClassLoaderStrategy> classLoaderStrategies;
     private final int maxConnections;
     private final int connectionTimeout;
     private final int keyColumnLength;
@@ -83,7 +83,7 @@ public class DBConfiguration implements Serializable
         }
 
         final String strategyList = config.readAppProperty( AppProperty.DB_JDBC_LOAD_STRATEGY );
-        final List<JDBCDriverLoader.ClassLoaderStrategy> strategies = JavaHelper.readEnumListFromStringCollection(
+        final Set<JDBCDriverLoader.ClassLoaderStrategy> strategies = JavaHelper.readEnumSetFromStringCollection(
                 JDBCDriverLoader.ClassLoaderStrategy.class,
                 Arrays.asList( strategyList.split( "," ) )
         );

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

@@ -47,6 +47,7 @@ import java.sql.Driver;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class JDBCDriverLoader
@@ -60,7 +61,7 @@ public class JDBCDriverLoader
     )
             throws DatabaseException
     {
-        final List<ClassLoaderStrategy> strategies = dbConfiguration.getClassLoaderStrategies();
+        final Set<ClassLoaderStrategy> strategies = dbConfiguration.getClassLoaderStrategies();
         LOGGER.trace( () -> "attempting to load jdbc driver using strategies: " + JsonUtil.serializeCollection( strategies ) );
         final List<String> errorMsgs = new ArrayList<>();
         for ( final ClassLoaderStrategy strategy : strategies )

+ 9 - 13
server/src/main/java/password/pwm/util/java/AverageTracker.java

@@ -23,42 +23,38 @@ package password.pwm.util.java;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
-import java.util.LinkedList;
 import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 public class AverageTracker
 {
     private final int maxSamples;
-    private final Queue<BigInteger> samples = new LinkedList<>();
+    private final Queue<BigInteger> samples = new ConcurrentLinkedQueue<>();
 
     public AverageTracker( final int maxSamples )
     {
         this.maxSamples = maxSamples;
     }
 
-    public void addSample( final long input )
+    public synchronized void addSample( final long input )
     {
-        samples.add( new BigInteger( Long.toString( input ) ) );
+        samples.add( BigInteger.valueOf( input ) );
         while ( samples.size() > maxSamples )
         {
             samples.remove();
         }
     }
 
-    public BigDecimal avg( )
+    public synchronized BigDecimal avg( )
     {
         if ( samples.isEmpty() )
         {
-            throw new IllegalStateException( "unable to compute avg without samples" );
+            return BigDecimal.ZERO;
         }
 
-        BigInteger total = BigInteger.ZERO;
-        for ( final BigInteger sample : samples )
-        {
-            total = total.add( sample );
-        }
-        final BigDecimal maxAsBD = new BigDecimal( Integer.toString( maxSamples ) );
-        return new BigDecimal( total ).divide( maxAsBD, MathContext.DECIMAL32 );
+        final BigInteger total = samples.stream().reduce( BigInteger::add ).get();
+        final BigDecimal sampleSize = new BigDecimal( samples.size() );
+        return new BigDecimal( total ).divide( sampleSize, MathContext.DECIMAL128 );
     }
 
     public long avgAsLong( )

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

@@ -48,15 +48,14 @@ import java.net.URI;
 import java.nio.ByteBuffer;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.EnumSet;
 import java.util.Enumeration;
 import java.util.LinkedHashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -148,9 +147,9 @@ public class JavaHelper
         return new String( chars );
     }
 
-    public static <E extends Enum<E>> List<E> readEnumListFromStringCollection( final Class<E> enumClass, final Collection<String> inputs )
+    public static <E extends Enum<E>> Set<E> readEnumSetFromStringCollection( final Class<E> enumClass, final Collection<String> inputs )
     {
-        final List<E> returnList = new ArrayList<E>();
+        final Set<E> returnList = EnumSet.noneOf( enumClass );
         for ( final String input : inputs )
         {
             final E item = readEnumFromString( enumClass, null, input );
@@ -159,7 +158,7 @@ public class JavaHelper
                 returnList.add( item );
             }
         }
-        return Collections.unmodifiableList( returnList );
+        return Collections.unmodifiableSet( returnList );
     }
 
     public static <E extends Enum<E>> Map<String, String> enumMapToStringMap( final Map<E, String> inputMap )
@@ -663,4 +662,11 @@ public class JavaHelper
     {
         return ByteBuffer.allocate( 8 ).putLong( input ).array();
     }
+
+    public static <E extends Enum<E>> EnumSet<E> copiedEnumSet( final Collection<E> source, final Class<E> classOfT )
+    {
+        return source == null || source.isEmpty()
+                ? EnumSet.noneOf( classOfT )
+                : EnumSet.copyOf( source );
+    }
 }

+ 7 - 8
server/src/main/java/password/pwm/util/macro/InternalMacros.java

@@ -35,6 +35,7 @@ import password.pwm.util.secure.SecureEngine;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.regex.Pattern;
 
 public abstract class InternalMacros
@@ -88,12 +89,12 @@ public abstract class InternalMacros
             {
                 throw new MacroParseException( "PwmSettingReference macro requires a setting key value" );
             }
-            final PwmSetting setting = PwmSetting.forKey( settingKeyStr );
-            if ( setting == null )
+            final Optional<PwmSetting> setting = PwmSetting.forKey( settingKeyStr );
+            if ( !setting.isPresent() )
             {
                 throw new MacroParseException( "PwmSettingReference macro has unknown key value '" + settingKeyStr + "'" );
             }
-            return setting.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
+            return setting.get().toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
         }
     }
 
@@ -114,11 +115,9 @@ public abstract class InternalMacros
             {
                 throw new MacroParseException( "PwmSettingCategoryReference macro requires a setting key value" );
             }
-            final PwmSettingCategory category = PwmSettingCategory.forKey( settingKeyStr );
-            if ( category == null )
-            {
-                throw new MacroParseException( "PwmSettingCategoryReference macro has unknown key value '" + settingKeyStr + "'" );
-            }
+            final PwmSettingCategory category = PwmSettingCategory.forKey( settingKeyStr )
+                    .orElseThrow( () -> new MacroParseException( "PwmSettingCategoryReference macro has unknown key value '" + settingKeyStr + "'" ) );
+
             return category.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
         }
     }

+ 1 - 1
server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java

@@ -75,7 +75,7 @@ public class RestAuthenticationProcessor
             if ( namedSecretName != null )
             {
                 LOGGER.trace( sessionLabel, () -> "authenticating with named secret '" + namedSecretName + "'" );
-                final Set<WebServiceUsage> usages = new HashSet<>( JavaHelper.readEnumListFromStringCollection(
+                final Set<WebServiceUsage> usages = new HashSet<>( JavaHelper.readEnumSetFromStringCollection(
                         WebServiceUsage.class,
                         pwmApplication.getConfig().readSettingAsNamedPasswords( PwmSetting.WEBSERVICES_EXTERNAL_SECRET ).get( namedSecretName ).getUsage()
                 ) );

+ 8 - 0
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -462,11 +462,19 @@
         <default>
             <value>15</value>
         </default>
+        <properties>
+            <property key="Minimum">0</property>
+            <property key="Maximum">300</property>
+        </properties>
     </setting>
     <setting hidden="false" key="passwordSyncMaxWaitTime" level="2" required="true">
         <default>
             <value>90</value>
         </default>
+        <properties>
+            <property key="Minimum">0</property>
+            <property key="Maximum">300</property>
+        </properties>
     </setting>
     <setting hidden="false" key="expirePreTime" level="1" required="true">
         <default>

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

@@ -41,6 +41,7 @@ HealthMessage_Appliance_PendingUpdates=Appliance updates are available.
 HealthMessage_Appliance_UpdatesNotEnabled=Appliance auto-update service is not enabled.
 HealthMessage_Appliance_UpdateServiceNotConfigured=Appliance update service has not been configured.
 HealthMessage_Cluster_Error=The cluster system can not operate normally: %1%
+HealthMessage_Config_ValueConflict=Setting %1% value conflicts with setting %2%, reason: %3%
 HealthMessage_Config_NoSiteURL=The site URL is not configured, please configure %1%
 HealthMessage_Config_LDAPWireTrace=The %1% setting is enabled and should be disabled for proper security
 HealthMessage_Config_PromiscuousLDAP=%1% setting should be set to false for proper security

+ 50 - 1
server/src/test/java/password/pwm/config/PwmSettingPropertyTest.java

@@ -24,13 +24,14 @@ import org.junit.Assert;
 import org.junit.Test;
 import password.pwm.PwmConstants;
 
+import java.util.EnumSet;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.ResourceBundle;
 import java.util.Set;
 
 public class PwmSettingPropertyTest
 {
-
     @Test
     public void testForMissingSettings()
     {
@@ -76,4 +77,52 @@ public class PwmSettingPropertyTest
             Assert.fail( "unexpected key in PwmSetting.properties file: " + extraKeys.iterator().next() );
         }
     }
+
+    @Test
+    public void testMinMaxValueRanges()
+    {
+        for ( final PwmSetting pwmSetting : PwmSetting.values() )
+        {
+            final long minValue = Long.parseLong( pwmSetting.getProperties().getOrDefault( PwmSettingProperty.Minimum, "0" ) );
+            final long maxValue = Long.parseLong( pwmSetting.getProperties().getOrDefault( PwmSettingProperty.Maximum, "0" ) );
+            if ( maxValue != 0 )
+            {
+                Assert.assertTrue( "setting: " + pwmSetting.getKey(), maxValue > minValue );
+            }
+        }
+    }
+
+    @Test
+    public void testNumericProperties()
+    {
+        final Set<PwmSettingProperty> numericProperties = EnumSet.of(
+                PwmSettingProperty.Maximum,
+                PwmSettingProperty.Minimum,
+                PwmSettingProperty.Maximum_Values,
+                PwmSettingProperty.Minimum_Values
+        );
+
+        for ( final PwmSetting pwmSetting : PwmSetting.values() )
+        {
+            final Map<PwmSettingProperty, String> properties = pwmSetting.getProperties();
+
+            for ( final PwmSettingProperty pwmSettingProperty : numericProperties )
+            {
+                if ( properties.containsKey( pwmSettingProperty ) )
+                {
+                    try
+                    {
+                        Long.parseLong( properties.get( pwmSettingProperty ) );
+                    }
+                    catch ( final NumberFormatException e )
+                    {
+                        throw new NumberFormatException(
+                                "setting " + pwmSetting + " value for property " + pwmSettingProperty
+                                + " parse error: " + e.getMessage()
+                        );
+                    }
+                }
+            }
+        }
+    }
 }

+ 5 - 22
server/src/test/java/password/pwm/config/PwmSettingTest.java

@@ -40,7 +40,6 @@ import password.pwm.util.secure.PwmSecurityKey;
 
 import java.io.InputStream;
 import java.io.Serializable;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -61,9 +60,8 @@ public class PwmSettingTest
                 .build();
         for ( final PwmSetting pwmSetting : PwmSetting.values() )
         {
-            for ( final PwmSettingTemplate template : PwmSettingTemplate.values() )
+            for ( final PwmSettingTemplateSet templateSet : PwmSettingTemplateSet.allValues() )
             {
-                final PwmSettingTemplateSet templateSet = new PwmSettingTemplateSet( Collections.singleton( template ) );
                 final StoredValue storedValue = pwmSetting.getDefaultValue( templateSet );
                 storedValue.toNativeObject();
                 storedValue.toDebugString( PwmConstants.DEFAULT_LOCALE );
@@ -116,8 +114,8 @@ public class PwmSettingTest
         {
             final String key = result.getAttributeValue( "key" );
             Assert.assertFalse( StringUtil.isEmpty( key ) );
-            final PwmSetting pwmSetting = PwmSetting.forKey( key );
-            Assert.assertNotNull( "unknown PwmSetting.xml setting reference for key " + key );
+            final Optional<PwmSetting> pwmSetting = PwmSetting.forKey( key );
+            Assert.assertTrue( "unknown PwmSetting.xml setting reference for key " + key, pwmSetting.isPresent() );
         }
     }
 
@@ -194,24 +192,9 @@ public class PwmSettingTest
         final Set<String> seenKeys = new HashSet<>();
         for ( final PwmSetting pwmSetting : PwmSetting.values() )
         {
-            // duplicate key foud
-            Assert.assertTrue( !seenKeys.contains( pwmSetting.getKey() ) );
+            // duplicate key found
+            Assert.assertFalse( seenKeys.contains( pwmSetting.getKey() ) );
             seenKeys.add( pwmSetting.getKey() );
         }
     }
-
-    @Test
-    public void testMinMaxValueRanges()
-    {
-        for ( final PwmSetting pwmSetting : PwmSetting.values() )
-        {
-            final long minValue = Long.parseLong( pwmSetting.getProperties().getOrDefault( PwmSettingProperty.Minimum, "0" ) );
-            final long maxValue = Long.parseLong( pwmSetting.getProperties().getOrDefault( PwmSettingProperty.Maximum, "0" ) );
-            if ( maxValue != 0 )
-            {
-                Assert.assertTrue( maxValue > minValue );
-            }
-        }
-
-    }
 }

+ 3 - 2
server/src/test/java/password/pwm/config/PwmSettingXmlTest.java

@@ -31,6 +31,7 @@ import password.pwm.util.java.XmlFactory;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Optional;
 
 public class PwmSettingXmlTest
 {
@@ -110,11 +111,11 @@ public class PwmSettingXmlTest
         for ( final XmlElement element : profileElements )
         {
             final String settingKey = element.getAttributeValue( "setting" );
-            final PwmSetting setting = PwmSetting.forKey( settingKey );
+            final Optional<PwmSetting> setting = PwmSetting.forKey( settingKey );
 
             final String errorMsg = "PwmSetting.xml contains category/profile@setting key of '"
                     + settingKey + "' which does not exist in PwmSetting.java";
-            Assert.assertNotNull( errorMsg, setting );
+            Assert.assertTrue( errorMsg, setting.isPresent() );
         }
     }
 }

+ 65 - 0
server/src/test/java/password/pwm/util/java/AverageTrackerTest.java

@@ -0,0 +1,65 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.util.java;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AverageTrackerTest
+{
+    @Test
+    public void testAverage()
+    {
+        final AverageTracker averageTracker = new AverageTracker( 5 );
+        averageTracker.addSample( 5 );
+        averageTracker.addSample( 6 );
+        averageTracker.addSample( 7 );
+        averageTracker.addSample( 8 );
+        averageTracker.addSample( 9 );
+        Assert.assertEquals( 7, averageTracker.avgAsLong() );
+    }
+
+    @Test
+    public void testRollingAverage()
+    {
+        final AverageTracker averageTracker = new AverageTracker( 5 );
+        averageTracker.addSample( 5 );
+        averageTracker.addSample( 6 );
+        averageTracker.addSample( 7 );
+        averageTracker.addSample( 8 );
+        averageTracker.addSample( 9 );
+        averageTracker.addSample( 10 );
+        averageTracker.addSample( 15 );
+        Assert.assertEquals( 9, averageTracker.avgAsLong() );
+    }
+
+    @Test
+    public void testLargeAverage()
+    {
+        final AverageTracker averageTracker = new AverageTracker( 5 );
+        averageTracker.addSample( 9_223_372_036_854_775_807L  );
+        averageTracker.addSample( 9_223_372_036_854_775_806L  );
+        averageTracker.addSample( 9_223_372_036_854_775_805L  );
+        averageTracker.addSample( 9_223_372_036_854_775_804L  );
+        averageTracker.addSample( 9_223_372_036_854_775_803L  );
+        Assert.assertEquals( 9_223_372_036_854_775_805L, averageTracker.avgAsLong() );
+    }
+}