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

xml factory and wrapper classes

jrivard@gmail.com пре 6 година
родитељ
комит
d635e43d5e
31 измењених фајлова са 728 додато и 462 уклоњено
  1. 1 1
      server/src/main/java/password/pwm/config/PwmSetting.java
  2. 3 3
      server/src/main/java/password/pwm/config/StoredValue.java
  3. 1 0
      server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
  4. 16 16
      server/src/main/java/password/pwm/config/stored/NGStoredConfigurationFactory.java
  5. 179 193
      server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  6. 3 11
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  7. 11 10
      server/src/main/java/password/pwm/config/value/ActionValue.java
  8. 7 6
      server/src/main/java/password/pwm/config/value/BooleanValue.java
  9. 10 11
      server/src/main/java/password/pwm/config/value/ChallengeValue.java
  10. 10 10
      server/src/main/java/password/pwm/config/value/CustomLinkValue.java
  11. 12 11
      server/src/main/java/password/pwm/config/value/EmailValue.java
  12. 14 15
      server/src/main/java/password/pwm/config/value/FileValue.java
  13. 10 10
      server/src/main/java/password/pwm/config/value/FormValue.java
  14. 10 11
      server/src/main/java/password/pwm/config/value/LocalizedStringArrayValue.java
  15. 9 10
      server/src/main/java/password/pwm/config/value/LocalizedStringValue.java
  16. 18 17
      server/src/main/java/password/pwm/config/value/NamedSecretValue.java
  17. 9 8
      server/src/main/java/password/pwm/config/value/NumericArrayValue.java
  18. 7 6
      server/src/main/java/password/pwm/config/value/NumericValue.java
  19. 9 9
      server/src/main/java/password/pwm/config/value/OptionListValue.java
  20. 10 9
      server/src/main/java/password/pwm/config/value/PasswordValue.java
  21. 13 13
      server/src/main/java/password/pwm/config/value/PrivateKeyValue.java
  22. 9 9
      server/src/main/java/password/pwm/config/value/RemoteWebServiceValue.java
  23. 11 12
      server/src/main/java/password/pwm/config/value/StringArrayValue.java
  24. 7 7
      server/src/main/java/password/pwm/config/value/StringValue.java
  25. 9 9
      server/src/main/java/password/pwm/config/value/UserPermissionValue.java
  26. 2 2
      server/src/main/java/password/pwm/config/value/ValueFactory.java
  27. 7 7
      server/src/main/java/password/pwm/config/value/VerificationMethodValue.java
  28. 9 8
      server/src/main/java/password/pwm/config/value/X509CertificateValue.java
  29. 51 10
      server/src/main/java/password/pwm/util/java/XmlDocument.java
  30. 210 1
      server/src/main/java/password/pwm/util/java/XmlElement.java
  31. 51 17
      server/src/main/java/password/pwm/util/java/XmlFactory.java

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

@@ -1280,7 +1280,7 @@ public enum PwmSetting
                 for ( final XmlElement defaultElement : defaultElements )
                 {
                     final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement );
-                    final StoredValue storedValue = ValueFactory.fromXmlValues( this, defaultElement.asJdomElement(), null );
+                    final StoredValue storedValue = ValueFactory.fromXmlValues( this, defaultElement, null );
                     returnObj.add( new TemplateSetAssociation( storedValue, definedTemplates ) );
                 }
             }

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

@@ -22,9 +22,9 @@
 
 package password.pwm.config;
 
-import org.jdom2.Element;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.XmlElement;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.io.Serializable;
@@ -33,7 +33,7 @@ import java.util.Locale;
 
 public interface StoredValue extends Serializable
 {
-    List<Element> toXmlValues( String valueElementName, PwmSecurityKey pwmSecurityKey );
+    List<XmlElement> toXmlValues( String valueElementName, PwmSecurityKey pwmSecurityKey );
 
     Object toNativeObject( );
 
@@ -51,7 +51,7 @@ public interface StoredValue extends Serializable
     {
         StoredValue fromJson( String input );
 
-        StoredValue fromXmlElement( PwmSetting pwmSetting, Element settingElement, PwmSecurityKey key )
+        StoredValue fromXmlElement( PwmSetting pwmSetting, XmlElement settingElement, PwmSecurityKey key )
                 throws PwmException;
     }
 

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

@@ -160,6 +160,7 @@ public class ConfigurationReader
                     }
             );
             this.configMode = PwmApplicationMode.ERROR;
+            e.printStackTrace(  );
             throw new PwmUnrecoverableException( errorInformation );
         }
 

+ 16 - 16
server/src/main/java/password/pwm/config/stored/NGStoredConfigurationFactory.java

@@ -22,8 +22,6 @@
 
 package password.pwm.config.stored;
 
-import org.jdom2.Document;
-import org.jdom2.Element;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
@@ -31,6 +29,8 @@ import password.pwm.config.value.StringValue;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.XmlDocument;
+import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
@@ -66,18 +66,18 @@ public class NGStoredConfigurationFactory
             final Map<StoredConfigReference, StoredValue> values = new LinkedHashMap<>();
             final Map<StoredConfigReference, ValueMetaData> metaData = new LinkedHashMap<>();
 
-            final Document inputDocument = XmlFactory.XmlFactoryJDOM.parseJDOMXml( inputStream );
-            final Element rootElement = inputDocument.getRootElement();
+            final XmlDocument inputDocument = XmlFactory.getFactory().parseXml( inputStream );
+            final XmlElement rootElement = inputDocument.getRootElement();
 
             final PwmSecurityKey pwmSecurityKey = readSecurityKey( rootElement );
 
-            final Element settingsElement = rootElement.getChild( StoredConfiguration.XML_ELEMENT_SETTINGS );
+            final XmlElement settingsElement = rootElement.getChild( StoredConfiguration.XML_ELEMENT_SETTINGS );
 
-            for ( final Element loopElement : settingsElement.getChildren() )
+            for ( final XmlElement loopElement : settingsElement.getChildren() )
             {
                 if ( StoredConfiguration.XML_ELEMENT_PROPERTIES.equals( loopElement.getName() ) )
                 {
-                    for ( final Element propertyElement : loopElement.getChildren( StoredConfiguration.XML_ELEMENT_PROPERTY ) )
+                    for ( final XmlElement propertyElement : loopElement.getChildren( StoredConfiguration.XML_ELEMENT_PROPERTY ) )
                     {
                         readInterestingElement( propertyElement, pwmSecurityKey, values, metaData );
                     }
@@ -91,7 +91,7 @@ public class NGStoredConfigurationFactory
         }
 
         static void readInterestingElement(
-                final Element loopElement,
+                final XmlElement loopElement,
                 final PwmSecurityKey pwmSecurityKey,
                 final Map<StoredConfigReference, StoredValue> values,
                 final Map<StoredConfigReference, ValueMetaData> metaData
@@ -129,7 +129,7 @@ public class NGStoredConfigurationFactory
             }
         }
 
-        static PwmSecurityKey readSecurityKey( final Element rootElement )
+        static PwmSecurityKey readSecurityKey( final XmlElement rootElement )
                 throws PwmUnrecoverableException
         {
             final String createTime = rootElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_CREATE_TIME );
@@ -138,7 +138,7 @@ public class NGStoredConfigurationFactory
 
         static StoredValue readSettingValue(
                 final StoredConfigReference storedConfigReference,
-                final Element settingElement,
+                final XmlElement settingElement,
                 final PwmSecurityKey pwmSecurityKey
         )
         {
@@ -152,7 +152,7 @@ public class NGStoredConfigurationFactory
             else
             {
                 LOGGER.trace( "parsing setting key=" + key + ", profile=" + storedConfigReference.getProfileID() );
-                final Element defaultElement = settingElement.getChild( StoredConfiguration.XML_ELEMENT_DEFAULT );
+                final XmlElement defaultElement = settingElement.getChild( StoredConfiguration.XML_ELEMENT_DEFAULT );
                 if ( defaultElement != null )
                 {
                     return null;
@@ -174,7 +174,7 @@ public class NGStoredConfigurationFactory
 
         static StoredValue readPropertyValue(
                 final StoredConfigReference storedConfigReference,
-                final Element settingElement
+                final XmlElement settingElement
         )
         {
             final String key = storedConfigReference.getRecordID();
@@ -182,12 +182,12 @@ public class NGStoredConfigurationFactory
             LOGGER.trace( "parsing property key=" + key + ", profile=" + storedConfigReference.getProfileID() );
             if ( settingElement.getChild( StoredConfiguration.XML_ELEMENT_DEFAULT ) != null )
             {
-                return new StringValue( settingElement.getValue() );
+                return new StringValue( settingElement.getText() );
             }
             return null;
         }
 
-        static StoredConfigReference referenceForElement( final Element settingElement )
+        static StoredConfigReference referenceForElement( final XmlElement settingElement )
         {
             final String key = settingElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_KEY );
             final String profileID = readProfileID( settingElement );
@@ -219,13 +219,13 @@ public class NGStoredConfigurationFactory
             );
         }
 
-        static String readProfileID( final Element settingElement )
+        static String readProfileID( final XmlElement settingElement )
         {
             final String profileIDStr = settingElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_PROFILE );
             return profileIDStr != null && !profileIDStr.isEmpty() ? profileIDStr : null;
         }
 
-        static ValueMetaData readValueMetaData( final Element element )
+        static ValueMetaData readValueMetaData( final XmlElement element )
         {
             final String modifyDateStr = element.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_MODIFY_TIME );
             Instant modifyDate = null;

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

@@ -22,14 +22,6 @@
 
 package password.pwm.config.stored;
 
-import org.jdom2.Attribute;
-import org.jdom2.CDATA;
-import org.jdom2.Comment;
-import org.jdom2.Document;
-import org.jdom2.Element;
-import org.jdom2.Text;
-import org.jdom2.xpath.XPathExpression;
-import org.jdom2.xpath.XPathFactory;
 import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
@@ -60,6 +52,8 @@ import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 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;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.BCrypt;
@@ -73,6 +67,7 @@ import java.io.OutputStream;
 import java.io.Serializable;
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -103,13 +98,16 @@ public class StoredConfigurationImpl implements StoredConfiguration
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationImpl.class );
     private static final String XML_FORMAT_VERSION = "4";
 
-    private Document document = new Document( new Element( XML_ELEMENT_ROOT ) );
+    private XmlDocument document = XmlFactory.getFactory().newDocument( XML_ELEMENT_ROOT );
     private ChangeLog changeLog = new ChangeLog();
 
     private boolean locked;
     private final boolean setting_writeLabels = true;
     private final ReentrantReadWriteLock domModifyLock = new ReentrantReadWriteLock();
 
+    private final XmlHelper xmlHelper = new XmlHelper();
+    private final ConfigurationCleaner configurationCleaner = new ConfigurationCleaner();
+
     public static StoredConfigurationImpl newStoredConfiguration( ) throws PwmUnrecoverableException
     {
         return new StoredConfigurationImpl();
@@ -118,7 +116,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
     public static StoredConfigurationImpl copy( final StoredConfigurationImpl input ) throws PwmUnrecoverableException
     {
         final StoredConfigurationImpl copy = new StoredConfigurationImpl();
-        copy.document = input.document.clone();
+        copy.document = input.document.copy();
         return copy;
     }
 
@@ -128,17 +126,18 @@ public class StoredConfigurationImpl implements StoredConfiguration
         final Instant startTime = Instant.now();
         //validateXmlSchema(xmlData);
 
-        final Document inputDocument = XmlFactory.XmlFactoryJDOM.parseJDOMXml( xmlData );
+        final XmlDocument inputDocument = XmlFactory.getFactory().parseXml( xmlData );
         final StoredConfigurationImpl newConfiguration = StoredConfigurationImpl.newStoredConfiguration();
 
         try
         {
             newConfiguration.document = inputDocument;
             newConfiguration.createTime(); // verify create time;
-            ConfigurationCleaner.cleanup( newConfiguration );
+            newConfiguration.configurationCleaner.cleanup( );
         }
         catch ( Exception e )
         {
+            e.printStackTrace(  );
             final String errorMsg = "error reading configuration file format, error=" + e.getMessage();
             final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { errorMsg } );
             throw new PwmUnrecoverableException( errorInfo );
@@ -199,11 +198,10 @@ public class StoredConfigurationImpl implements StoredConfiguration
                 this.resetSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile(), null );
                 if ( comment != null && !comment.isEmpty() )
                 {
-                    final XPathExpression xp = XPathBuilder.xpathForSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile() );
-                    final Element settingElement = ( Element ) xp.evaluateFirst( document );
+                    final XmlElement settingElement = xmlHelper.xpathForSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile() );
                     if ( settingElement != null )
                     {
-                        settingElement.addContent( new Comment( comment ) );
+                        settingElement.setComment( Collections.singletonList( comment ) );
                     }
                 }
             }
@@ -218,17 +216,24 @@ public class StoredConfigurationImpl implements StoredConfiguration
 
     public StoredConfigurationImpl( ) throws PwmUnrecoverableException
     {
-        ConfigurationCleaner.cleanup( this );
-        final String createTime = JavaHelper.toIsoDate( Instant.now() );
-        document.getRootElement().setAttribute( XML_ATTRIBUTE_CREATE_TIME, createTime );
+        try
+        {
+            configurationCleaner.cleanup();
+            final String createTime = JavaHelper.toIsoDate( Instant.now() );
+            document.getRootElement().setAttribute( XML_ATTRIBUTE_CREATE_TIME, createTime );
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace(  );
+            throw new IllegalStateException( e );
+        }
     }
 
 
     @Override
     public String readConfigProperty( final ConfigurationProperty propertyName )
     {
-        final XPathExpression xp = XPathBuilder.xpathForConfigProperty( propertyName );
-        final Element propertyElement = ( Element ) xp.evaluateFirst( document );
+        final XmlElement propertyElement = xmlHelper.xpathForConfigProperty( propertyName );
         return propertyElement == null ? null : propertyElement.getText();
     }
 
@@ -242,26 +247,28 @@ public class StoredConfigurationImpl implements StoredConfiguration
         try
         {
 
-            final XPathExpression xp = XPathBuilder.xpathForConfigProperty( propertyName );
-            final List<Element> propertyElements = xp.evaluate( document );
-            for ( final Element propertyElement : propertyElements )
+            // remove existing element
             {
+                final XmlElement propertyElement  = xmlHelper.xpathForConfigProperty( propertyName );
                 propertyElement.detach();
             }
 
-            final Element propertyElement = new Element( XML_ELEMENT_PROPERTY );
-            propertyElement.setAttribute( new Attribute( XML_ATTRIBUTE_KEY, propertyName.getKey() ) );
-            propertyElement.setContent( new Text( value ) );
+            // add new property
+            {
+
+            }
+            final XmlElement propertyElement = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_PROPERTY );
+            propertyElement.setAttribute( XML_ATTRIBUTE_KEY, propertyName.getKey() );
+            propertyElement.addText( value );
 
-            if ( null == XPathBuilder.xpathForConfigProperties().evaluateFirst( document ) )
+            if ( null == xmlHelper.xpathForConfigProperties() )
             {
-                final Element configProperties = new Element( XML_ELEMENT_PROPERTIES );
-                configProperties.setAttribute( new Attribute( XML_ATTRIBUTE_TYPE, XML_ATTRIBUTE_VALUE_CONFIG ) );
+                final XmlElement configProperties = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_PROPERTIES );
+                configProperties.setAttribute( XML_ATTRIBUTE_TYPE, XML_ATTRIBUTE_VALUE_CONFIG );
                 document.getRootElement().addContent( configProperties );
             }
 
-            final XPathExpression xp2 = XPathBuilder.xpathForConfigProperties();
-            final Element propertiesElement = ( Element ) xp2.evaluateFirst( document );
+            final XmlElement propertiesElement = xmlHelper.xpathForConfigProperties();
             propertyElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) );
             propertiesElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) );
             propertiesElement.addContent( propertyElement );
@@ -282,12 +289,12 @@ public class StoredConfigurationImpl implements StoredConfiguration
         domModifyLock.readLock().lock();
         try
         {
-            final XPathExpression xp = XPathBuilder.xpathForLocaleBundleSetting( bundleName, keyName );
-            final Element localeBundleElement = ( Element ) xp.evaluateFirst( document );
+            final XmlElement localeBundleElement = xmlHelper.xpathForLocaleBundleSetting( bundleName, keyName );
+
             if ( localeBundleElement != null )
             {
                 final Map<String, String> bundleMap = new LinkedHashMap<>();
-                for ( final Element valueElement : localeBundleElement.getChildren( "value" ) )
+                for ( final XmlElement valueElement : localeBundleElement.getChildren( "value" ) )
                 {
                     final String localeStrValue = valueElement.getAttributeValue( "locale" );
                     bundleMap.put( localeStrValue == null ? "" : localeStrValue, valueElement.getText() );
@@ -348,14 +355,10 @@ public class StoredConfigurationImpl implements StoredConfiguration
         domModifyLock.writeLock().lock();
         try
         {
-            final XPathExpression xp = XPathBuilder.xpathForLocaleBundleSetting( bundleName, keyName );
-            final List<Element> oldBundleElements = xp.evaluate( document );
+            final XmlElement oldBundleElements = xmlHelper.xpathForLocaleBundleSetting( bundleName, keyName );
             if ( oldBundleElements != null )
             {
-                for ( final Element element : oldBundleElements )
-                {
-                    element.detach();
-                }
+                oldBundleElements.detach();
             }
         }
         finally
@@ -371,9 +374,9 @@ public class StoredConfigurationImpl implements StoredConfiguration
         try
         {
             preModifyActions();
-            final Element settingElement = createOrGetSettingElement( document, setting, profileID );
+            final XmlElement settingElement = createOrGetSettingElement( setting, profileID );
             settingElement.removeContent();
-            settingElement.addContent( new Element( XML_ELEMENT_DEFAULT ) );
+            settingElement.addContent( xmlHelper.getXmlFactory().newElement( XML_ELEMENT_DEFAULT ) );
             updateMetaData( settingElement, userIdentity );
         }
         finally
@@ -416,16 +419,15 @@ public class StoredConfigurationImpl implements StoredConfiguration
     public PwmSettingTemplateSet getTemplateSet( )
     {
         final Set<PwmSettingTemplate> templates = new HashSet<>();
-        templates.add( readTemplateValue( document, PwmSetting.TEMPLATE_LDAP ) );
-        templates.add( readTemplateValue( document, PwmSetting.TEMPLATE_STORAGE ) );
-        templates.add( readTemplateValue( document, PwmSetting.DB_VENDOR_TEMPLATE ) );
+        templates.add( readTemplateValue( PwmSetting.TEMPLATE_LDAP ) );
+        templates.add( readTemplateValue( PwmSetting.TEMPLATE_STORAGE ) );
+        templates.add( readTemplateValue( PwmSetting.DB_VENDOR_TEMPLATE ) );
         return new PwmSettingTemplateSet( templates );
     }
 
-    private static PwmSettingTemplate readTemplateValue( final Document document, final PwmSetting pwmSetting )
+    private PwmSettingTemplate readTemplateValue( final PwmSetting pwmSetting )
     {
-        final XPathExpression xp = XPathBuilder.xpathForSetting( pwmSetting, null );
-        final Element settingElement = ( Element ) xp.evaluateFirst( document );
+        final XmlElement settingElement = xmlHelper.xpathForSetting( pwmSetting, null );
         if ( settingElement != null )
         {
             try
@@ -567,8 +569,8 @@ public class StoredConfigurationImpl implements StoredConfiguration
     public void toXml( final OutputStream outputStream )
             throws IOException, PwmUnrecoverableException
     {
-        ConfigurationCleaner.updateMandatoryElements( document );
-        XmlFactory.XmlFactoryJDOM.outputJDOMDocument( document, outputStream );
+        configurationCleaner.updateMandatoryElements( );
+        XmlFactory.getFactory().outputDocument( document, outputStream );
     }
 
     public List<String> profilesForSetting( final PwmSetting pwmSetting )
@@ -629,8 +631,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
 
     public ValueMetaData readSettingMetadata( final PwmSetting setting, final String profileID )
     {
-        final XPathExpression xp = XPathBuilder.xpathForSetting( setting, profileID );
-        final Element settingElement = ( Element ) xp.evaluateFirst( document );
+        final XmlElement settingElement = xmlHelper.xpathForSetting( setting, profileID );
 
         if ( settingElement == null )
         {
@@ -757,7 +758,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
         if ( PwmSettingSyntax.SELECT == setting.getSyntax()
                 || PwmSettingSyntax.OPTIONLIST == setting.getSyntax()
                 || PwmSettingSyntax.VERIFICATION_METHOD == setting.getSyntax()
-                )
+        )
         {
             for ( final String key : setting.getOptions().keySet() )
             {
@@ -796,8 +797,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
         domModifyLock.readLock().lock();
         try
         {
-            final XPathExpression xp = XPathBuilder.xpathForSetting( setting, profileID );
-            final Element settingElement = ( Element ) xp.evaluateFirst( document );
+            final XmlElement settingElement = xmlHelper.xpathForSetting( setting, profileID );
 
             if ( settingElement == null )
             {
@@ -861,19 +861,19 @@ public class StoredConfigurationImpl implements StoredConfiguration
         try
         {
             domModifyLock.writeLock().lock();
-            final Element localeBundleElement = new Element( "localeBundle" );
+            final XmlElement localeBundleElement = xmlHelper.getXmlFactory().newElement( "localeBundle" );
             localeBundleElement.setAttribute( "bundle", bundleName );
             localeBundleElement.setAttribute( "key", keyName );
             for ( final Map.Entry<String, String> entry : localeMap.entrySet() )
             {
                 final String locale = entry.getKey();
                 final String value = entry.getValue();
-                final Element valueElement = new Element( "value" );
+                final XmlElement valueElement = xmlHelper.getXmlFactory().newElement( "value" );
                 if ( locale != null && locale.length() > 0 )
                 {
                     valueElement.setAttribute( "locale", locale );
                 }
-                valueElement.setContent( new CDATA( value ) );
+                valueElement.addText( value );
                 localeBundleElement.addContent( valueElement );
             }
             localeBundleElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) );
@@ -956,33 +956,47 @@ public class StoredConfigurationImpl implements StoredConfiguration
         domModifyLock.writeLock().lock();
         try
         {
-            final Element settingElement = createOrGetSettingElement( document, setting, profileID );
+            final XmlElement settingElement = createOrGetSettingElement( setting, profileID );
             settingElement.removeContent();
             settingElement.setAttribute( XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString() );
             settingElement.setAttribute( XML_ATTRIBUTE_SYNTAX_VERSION, Integer.toString( value.currentSyntaxVersion() ) );
 
             if ( setting_writeLabels )
             {
-                final Element labelElement = new Element( "label" );
-                labelElement.addContent( setting.getLabel( PwmConstants.DEFAULT_LOCALE ) );
-                settingElement.addContent( labelElement );
+                {
+                    final XmlElement existingLabel = settingElement.getChild( "label" );
+                    if ( existingLabel != null )
+                    {
+                        existingLabel.detach();
+                    }
+                }
+
+                {
+                    final XmlElement newLabelElement = xmlHelper.getXmlFactory().newElement( "label" );
+                    newLabelElement.addText( setting.getLabel( PwmConstants.DEFAULT_LOCALE ) );
+                    settingElement.addContent( newLabelElement );
+                }
             }
 
             if ( setting.getSyntax() == PwmSettingSyntax.PASSWORD )
             {
-                final List<Element> valueElements = ( ( PasswordValue ) value ).toXmlValues( "value", getKey() );
-                settingElement.addContent( new Comment( "Note: This value is encrypted and can not be edited directly." ) );
-                settingElement.addContent( new Comment( "Please use the Configuration Manager GUI to modify this value." ) );
+                final List<String> commentLines = Arrays.asList(
+                        "Note: This value is encrypted and can not be edited directly.",
+                        "Please use the Configuration Manager GUI to modify this value."
+                );
+                settingElement.setComment( commentLines );
+
+                final List<XmlElement> valueElements = ( ( PasswordValue ) value ).toXmlValues( "value", getKey() );
                 settingElement.addContent( valueElements );
             }
             else if ( setting.getSyntax() == PwmSettingSyntax.PRIVATE_KEY )
             {
-                final List<Element> valueElements = ( ( PrivateKeyValue ) value ).toXmlValues( "value", getKey() );
+                final List<XmlElement> valueElements = ( ( PrivateKeyValue ) value ).toXmlValues( "value", getKey() );
                 settingElement.addContent( valueElements );
             }
             else if ( setting.getSyntax() == PwmSettingSyntax.NAMED_SECRET )
             {
-                final List<Element> valueElements = ( ( NamedSecretValue ) value ).toXmlValues( "value", getKey() );
+                final List<XmlElement> valueElements = ( ( NamedSecretValue ) value ).toXmlValues( "value", getKey() );
                 settingElement.addContent( valueElements );
             }
             else
@@ -1063,19 +1077,21 @@ public class StoredConfigurationImpl implements StoredConfiguration
         return passwordHash != null && passwordHash.length() > 0;
     }
 
-    private abstract static class XPathBuilder
+    private class XmlHelper
     {
-        private static XPathExpression xpathForLocaleBundleSetting( final String bundleName, final String keyName )
+        private XmlFactory getXmlFactory()
         {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//localeBundle[@bundle=\"" + bundleName + "\"][@key=\"" + keyName + "\"]";
-            return xpfac.compile( xpathString );
+            return XmlFactory.getFactory();
+        }
+
+        private XmlElement xpathForLocaleBundleSetting( final String bundleName, final String keyName )
+        {
+            final String xpathString = "//localeBundle[@bundle=\"" + bundleName + "\"][@key=\"" + keyName + "\"]";
+            return document.evaluateXpathToElement( xpathString );
         }
 
-        private static XPathExpression xpathForSetting( final PwmSetting setting, final String profileID )
+        private XmlElement xpathForSetting( final PwmSetting setting, final String profileID )
         {
-            final XPathFactory xpfac = XPathFactory.instance();
             final String xpathString;
             if ( profileID == null || profileID.length() < 1 )
             {
@@ -1086,73 +1102,57 @@ public class StoredConfigurationImpl implements StoredConfiguration
                 xpathString = "//setting[@key=\"" + setting.getKey() + "\"][@profile=\"" + profileID + "\"]";
             }
 
-            return xpfac.compile( xpathString );
+            return document.evaluateXpathToElement( xpathString );
         }
 
-        private static XPathExpression xpathForAppProperty( final AppProperty appProperty )
+        private XmlElement xpathForAppProperty( final AppProperty appProperty )
         {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]/"
+            final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]/"
                     + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + appProperty.getKey() + "\"]";
-            return xpfac.compile( xpathString );
+            return document.evaluateXpathToElement( xpathString );
         }
 
-        private static XPathExpression xpathForAppProperties( )
+        private List<XmlElement> xpathForAppProperties( )
         {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]";
-            return xpfac.compile( xpathString );
+            final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]";
+            return document.evaluateXpathToElements( xpathString );
         }
 
-        private static XPathExpression xpathForConfigProperty( final ConfigurationProperty configProperty )
+        private XmlElement xpathForConfigProperty( final ConfigurationProperty configProperty )
         {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]/"
+            final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]/"
                     + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + configProperty.getKey() + "\"]";
-            return xpfac.compile( xpathString );
+            return document.evaluateXpathToElement( xpathString );
         }
 
-        private static XPathExpression xpathForConfigProperties( )
+        private XmlElement xpathForConfigProperties( )
         {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]";
-            return xpfac.compile( xpathString );
+            final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]";
+            return document.evaluateXpathToElement( xpathString );
         }
     }
 
 
-    private static class ConfigurationCleaner
+    private class ConfigurationCleaner
     {
-        private static void cleanup( final StoredConfigurationImpl configuration ) throws PwmUnrecoverableException
+        private void cleanup(
+        )
+                throws PwmUnrecoverableException
         {
-            updateProperitiesWithoutType( configuration );
-            updateMandatoryElements( configuration.document );
-            profilizeNonProfiledSettings( configuration );
-            stripOrphanedProfileSettings( configuration );
-            migrateAppProperties( configuration );
-            updateDeprecatedSettings( configuration );
-            migrateDeprecatedProperties( configuration );
+            updateProperitiesWithoutType( );
+            updateMandatoryElements();
+            profilizeNonProfiledSettings( );
+            stripOrphanedProfileSettings( );
+            migrateAppProperties( );
+            updateDeprecatedSettings( );
+            migrateDeprecatedProperties( );
         }
 
 
-        private static void updateMandatoryElements( final Document document )
+        private void updateMandatoryElements( )
         {
-            final Element rootElement = document.getRootElement();
-
-            {
-                final XPathExpression commentXPath = XPathFactory.instance().compile( "//comment()[1]" );
-                final Comment existingComment = ( Comment ) commentXPath.evaluateFirst( rootElement );
-                if ( existingComment != null )
-                {
-                    existingComment.detach();
-                }
-                final Comment comment = new Comment( generateCommentText() );
-                rootElement.addContent( 0, comment );
-            }
+            final XmlElement rootElement = document.getRootElement();
+            rootElement.setComment( Collections.singletonList( generateCommentText() ) );
 
             rootElement.setAttribute( "pwmVersion", PwmConstants.BUILD_VERSION );
             rootElement.setAttribute( "pwmBuild", PwmConstants.BUILD_NUMBER );
@@ -1161,18 +1161,16 @@ public class StoredConfigurationImpl implements StoredConfiguration
             { // migrate old properties
 
                 // read correct (new) //properties[@type="config"]
-                final XPathExpression configPropertiesXpath = XPathFactory.instance().compile(
-                        "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]" );
-                final Element configPropertiesElement = ( Element ) configPropertiesXpath.evaluateFirst( rootElement );
+                final String configPropertiesXpath = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]";
+                final XmlElement configPropertiesElement = document.evaluateXpathToElement( configPropertiesXpath );
 
                 // read list of old //properties[not (@type)]/property
-                final XPathExpression nonAttributedProperty = XPathFactory.instance().compile(
-                        "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]/" + XML_ELEMENT_PROPERTY );
-                final List<Element> nonAttributedProperties = nonAttributedProperty.evaluate( rootElement );
+                final String nonAttributedPropertyXpath = "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]/" + XML_ELEMENT_PROPERTY;
+                final List<XmlElement> nonAttributedProperties = document.evaluateXpathToElements( nonAttributedPropertyXpath );
 
                 if ( configPropertiesElement != null && nonAttributedProperties != null )
                 {
-                    for ( final Element element : nonAttributedProperties )
+                    for ( final XmlElement element : nonAttributedProperties )
                     {
                         element.detach();
                         configPropertiesElement.addContent( element );
@@ -1180,12 +1178,11 @@ public class StoredConfigurationImpl implements StoredConfiguration
                 }
 
                 // remove old //properties[not (@type] element
-                final XPathExpression oldPropertiesXpath = XPathFactory.instance().compile(
-                        "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]" );
-                final List<Element> oldPropertiesElements = oldPropertiesXpath.evaluate( rootElement );
+                final String oldPropertiesXpath = "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]";
+                final List<XmlElement> oldPropertiesElements = document.evaluateXpathToElements( oldPropertiesXpath );
                 if ( oldPropertiesElements != null )
                 {
-                    for ( final Element element : oldPropertiesElements )
+                    for ( final XmlElement element : oldPropertiesElements )
                     {
                         element.detach();
                     }
@@ -1193,7 +1190,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
             }
         }
 
-        private static String generateCommentText( )
+        private String generateCommentText( )
         {
             final StringBuilder commentText = new StringBuilder();
             commentText.append( "\t\t" ).append( " " ).append( "\n" );
@@ -1217,17 +1214,16 @@ public class StoredConfigurationImpl implements StoredConfiguration
         }
 
 
-        private static void profilizeNonProfiledSettings( final StoredConfigurationImpl storedConfiguration ) throws PwmUnrecoverableException
+        private void profilizeNonProfiledSettings()
+                throws PwmUnrecoverableException
         {
             final String NEW_PROFILE_NAME = "default";
-            final Document document = storedConfiguration.document;
             for ( final PwmSetting setting : PwmSetting.values() )
             {
                 if ( setting.getCategory().hasProfiles() )
                 {
 
-                    final XPathExpression xp = XPathBuilder.xpathForSetting( setting, null );
-                    final Element settingElement = ( Element ) xp.evaluateFirst( document );
+                    final XmlElement settingElement = xmlHelper.xpathForSetting( setting, null );
                     if ( settingElement != null )
                     {
                         settingElement.detach();
@@ -1235,7 +1231,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
                         final PwmSetting profileSetting = setting.getCategory().getProfileSetting();
                         final List<String> profileStringDefinitions = new ArrayList<>();
                         {
-                            final StringArrayValue profileDefinitions = ( StringArrayValue ) storedConfiguration.readSetting( profileSetting );
+                            final StringArrayValue profileDefinitions = ( StringArrayValue ) readSetting( profileSetting );
                             if ( profileDefinitions != null )
                             {
                                 if ( profileDefinitions.toNativeObject() != null )
@@ -1249,15 +1245,15 @@ public class StoredConfigurationImpl implements StoredConfiguration
                             profileStringDefinitions.add( NEW_PROFILE_NAME );
                         }
 
-                        final UserIdentity userIdentity = settingElement.getAttribute( XML_ATTRIBUTE_MODIFY_USER ) != null
-                                ? UserIdentity.fromDelimitedKey( settingElement.getAttribute(  XML_ATTRIBUTE_MODIFY_USER ).getValue() )
+                        final UserIdentity userIdentity = settingElement.getAttributeValue( XML_ATTRIBUTE_MODIFY_USER ) != null
+                                ? UserIdentity.fromDelimitedKey( settingElement.getAttributeValue(  XML_ATTRIBUTE_MODIFY_USER ) )
                                 : null;
 
                         for ( final String destProfile : profileStringDefinitions )
                         {
                             LOGGER.info( "moving setting " + setting.getKey() + " without profile attribute to profile \"" + destProfile + "\"." );
                             {
-                                storedConfiguration.writeSetting( profileSetting, new StringArrayValue( profileStringDefinitions ), userIdentity );
+                                writeSetting( profileSetting, new StringArrayValue( profileStringDefinitions ), userIdentity );
                             }
                         }
                     }
@@ -1265,60 +1261,52 @@ public class StoredConfigurationImpl implements StoredConfiguration
             }
         }
 
-        private static void migrateDeprecatedProperties( final StoredConfigurationImpl storedConfiguration ) throws PwmUnrecoverableException
+        private void migrateDeprecatedProperties(
+        )
+                throws PwmUnrecoverableException
         {
-            final Document document = storedConfiguration.document;
-            final XPathFactory xpfac = XPathFactory.instance();
             {
                 final String xpathString = "//property[@key=\"" + ConfigurationProperty.LDAP_TEMPLATE.getKey() + "\"]";
-                final XPathExpression xp = xpfac.compile( xpathString );
-                final List<Element> propertyElement = ( List<Element> ) xp.evaluate( document );
+                final List<XmlElement> propertyElement = document.evaluateXpathToElements( xpathString );
                 if ( propertyElement != null && !propertyElement.isEmpty() )
                 {
                     final String value = propertyElement.get( 0 ).getText();
-                    storedConfiguration.writeSetting( PwmSetting.TEMPLATE_LDAP, new StringValue( value ), null );
+                    writeSetting( PwmSetting.TEMPLATE_LDAP, new StringValue( value ), null );
                     propertyElement.get( 0 ).detach();
                 }
             }
             {
                 final String xpathString = "//property[@key=\"" + ConfigurationProperty.NOTES.getKey() + "\"]";
-                final XPathExpression xp = xpfac.compile( xpathString );
-                final List<Element> propertyElement = ( List<Element> ) xp.evaluate( document );
+                final List<XmlElement> propertyElement = document.evaluateXpathToElements( xpathString );
                 if ( propertyElement != null && !propertyElement.isEmpty() )
                 {
                     final String value = propertyElement.get( 0 ).getText();
-                    storedConfiguration.writeSetting( PwmSetting.NOTES, new StringValue( value ), null );
+                    writeSetting( PwmSetting.NOTES, new StringValue( value ), null );
                     propertyElement.get( 0 ).detach();
                 }
             }
         }
 
-        private static void updateProperitiesWithoutType( final StoredConfigurationImpl storedConfiguration )
+        private void updateProperitiesWithoutType()
         {
-            final Document document = storedConfiguration.document;
             final String xpathString = "//properties[not(@type)]";
-            final XPathFactory xpfac = XPathFactory.instance();
-            final XPathExpression xp = xpfac.compile( xpathString );
-            final List<Element> propertiesElements = ( List<Element> ) xp.evaluate( document );
-            for ( final Element propertiesElement : propertiesElements )
+            final List<XmlElement> propertiesElements = document.evaluateXpathToElements( xpathString );
+            for ( final XmlElement propertiesElement : propertiesElements )
             {
                 propertiesElement.setAttribute( XML_ATTRIBUTE_TYPE, XML_ATTRIBUTE_VALUE_CONFIG );
             }
         }
 
-        private static void stripOrphanedProfileSettings( final StoredConfigurationImpl storedConfiguration )
+        private void stripOrphanedProfileSettings()
         {
-            final Document document = storedConfiguration.document;
-            final XPathFactory xpfac = XPathFactory.instance();
             for ( final PwmSetting setting : PwmSetting.values() )
             {
                 if ( setting.getCategory().hasProfiles() )
                 {
-                    final List<String> validProfiles = storedConfiguration.profilesForSetting( setting );
+                    final List<String> validProfiles = profilesForSetting( setting );
                     final String xpathString = "//setting[@key=\"" + setting.getKey() + "\"]";
-                    final XPathExpression xp = xpfac.compile( xpathString );
-                    final List<Element> settingElements = ( List<Element> ) xp.evaluate( document );
-                    for ( final Element settingElement : settingElements )
+                    final List<XmlElement> settingElements =  document.evaluateXpathToElements( xpathString );
+                    for ( final XmlElement settingElement : settingElements )
                     {
                         final String profileID = settingElement.getAttributeValue( XML_ATTRIBUTE_PROFILE );
                         if ( profileID != null )
@@ -1334,15 +1322,15 @@ public class StoredConfigurationImpl implements StoredConfiguration
             }
         }
 
-        private static void migrateAppProperties( final StoredConfigurationImpl storedConfiguration ) throws PwmUnrecoverableException
+        private void migrateAppProperties(
+        )
+                throws PwmUnrecoverableException
         {
-            final Document document = storedConfiguration.document;
-            final XPathExpression xPathExpression = XPathBuilder.xpathForAppProperties();
-            final List<Element> appPropertiesElements = ( List<Element> ) xPathExpression.evaluate( document );
-            for ( final Element element : appPropertiesElements )
+            final List<XmlElement> appPropertiesElements = xmlHelper.xpathForAppProperties();
+            for ( final XmlElement element : appPropertiesElements )
             {
-                final List<Element> properties = element.getChildren();
-                for ( final Element property : properties )
+                final List<XmlElement> properties = element.getChildren();
+                for ( final XmlElement property : properties )
                 {
                     final String key = property.getAttributeValue( "key" );
                     final String value = property.getText();
@@ -1350,28 +1338,28 @@ public class StoredConfigurationImpl implements StoredConfiguration
                     {
                         LOGGER.info( "migrating app-property config element '" + key + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey() );
                         final String newValue = key + "=" + value;
-                        List<String> existingValues = ( List<String> ) storedConfiguration.readSetting( PwmSetting.APP_PROPERTY_OVERRIDES ).toNativeObject();
+                        List<String> existingValues = ( List<String> ) readSetting( PwmSetting.APP_PROPERTY_OVERRIDES ).toNativeObject();
                         if ( existingValues == null )
                         {
                             existingValues = new ArrayList<>();
                         }
                         existingValues = new ArrayList<>( existingValues );
                         existingValues.add( newValue );
-                        storedConfiguration.writeSetting( PwmSetting.APP_PROPERTY_OVERRIDES, new StringArrayValue( existingValues ), null );
+                        writeSetting( PwmSetting.APP_PROPERTY_OVERRIDES, new StringArrayValue( existingValues ), null );
                     }
                 }
                 element.detach();
             }
         }
 
-        private static void updateDeprecatedSettings( final StoredConfigurationImpl storedConfiguration ) throws PwmUnrecoverableException
+        private void updateDeprecatedSettings( ) throws PwmUnrecoverableException
         {
             final UserIdentity actor = new UserIdentity( "UpgradeProcessor", null );
-            for ( final String profileID : storedConfiguration.profilesForSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY ) )
+            for ( final String profileID : profilesForSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY ) )
             {
-                if ( !storedConfiguration.isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) )
+                if ( !isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) )
                 {
-                    final boolean ad2003Enabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject();
+                    final boolean ad2003Enabled = ( boolean ) readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject();
                     final StoredValue value;
                     if ( ad2003Enabled )
                     {
@@ -1383,20 +1371,20 @@ public class StoredConfigurationImpl implements StoredConfiguration
                     }
                     LOGGER.warn( "converting deprecated non-default setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
                             + " to replacement setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + value.toNativeObject().toString() );
-                    storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, actor );
-                    storedConfiguration.resetSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID, actor );
+                    writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, actor );
+                    resetSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID, actor );
                 }
             }
 
-            for ( final String profileID : storedConfiguration.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) )
+            for ( final String profileID : profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) )
             {
-                if ( !storedConfiguration.isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) )
+                if ( !isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) )
                 {
-                    final boolean enforceEnabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject();
+                    final boolean enforceEnabled = ( boolean ) readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject();
                     final StoredValue value = enforceEnabled
                             ? new StringValue( "NONE" )
                             : new StringValue( "ALLOW" );
-                    final ValueMetaData existingData = storedConfiguration.readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID );
+                    final ValueMetaData existingData = readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID );
                     LOGGER.warn( "converting deprecated non-default setting "
                             + PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug(profileID,PwmConstants.DEFAULT_LOCALE) + "/" + profileID
                             + " to replacement setting " + PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE )
@@ -1404,8 +1392,8 @@ public class StoredConfigurationImpl implements StoredConfiguration
                     final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null
                             ? existingData.getUserIdentity()
                             : actor;
-                    storedConfiguration.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor );
-                    storedConfiguration.resetSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID, actor );
+                    writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor );
+                    resetSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID, actor );
                 }
             }
         }
@@ -1641,9 +1629,9 @@ public class StoredConfigurationImpl implements StoredConfiguration
         */
     }
 
-    private static void updateMetaData( final Element settingElement, final UserIdentity userIdentity )
+    private void updateMetaData( final XmlElement settingElement, final UserIdentity userIdentity )
     {
-        final Element settingsElement = settingElement.getDocument().getRootElement().getChild( XML_ELEMENT_SETTINGS );
+        final XmlElement settingsElement = document.getRootElement().getChild( XML_ELEMENT_SETTINGS );
         settingElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) );
         settingsElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) );
         settingElement.removeAttribute( XML_ATTRIBUTE_MODIFY_USER );
@@ -1655,20 +1643,18 @@ public class StoredConfigurationImpl implements StoredConfiguration
         }
     }
 
-    private static Element createOrGetSettingElement(
-            final Document document,
+    private XmlElement createOrGetSettingElement(
             final PwmSetting setting,
             final String profileID
     )
     {
-        final XPathExpression xp = XPathBuilder.xpathForSetting( setting, profileID );
-        final Element existingSettingElement = ( Element ) xp.evaluateFirst( document );
+        final XmlElement existingSettingElement = xmlHelper.xpathForSetting( setting, profileID );
         if ( existingSettingElement != null )
         {
             return existingSettingElement;
         }
 
-        final Element settingElement = new Element( XML_ELEMENT_SETTING );
+        final XmlElement settingElement = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_SETTING );
         settingElement.setAttribute( XML_ATTRIBUTE_KEY, setting.getKey() );
         settingElement.setAttribute( XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString() );
         if ( profileID != null && profileID.length() > 0 )
@@ -1676,11 +1662,11 @@ public class StoredConfigurationImpl implements StoredConfiguration
             settingElement.setAttribute( XML_ATTRIBUTE_PROFILE, profileID );
         }
 
-        Element settingsElement = document.getRootElement().getChild( XML_ELEMENT_SETTINGS );
+        XmlElement settingsElement = document.getRootElement().getChild( XML_ELEMENT_SETTINGS );
         if ( settingsElement == null )
         {
-            settingsElement = new Element( XML_ELEMENT_SETTINGS );
-            document.getRootElement().addContent( settingsElement );
+            settingsElement = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_SETTINGS );
+            document.getRootElement().addContent( settingElement );
         }
         settingsElement.addContent( settingElement );
 
@@ -1785,7 +1771,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
 
     private String createTime( )
     {
-        final Element rootElement = document.getRootElement();
+        final XmlElement rootElement = document.getRootElement();
         final String createTimeString = rootElement.getAttributeValue( XML_ATTRIBUTE_CREATE_TIME );
         if ( createTimeString == null || createTimeString.isEmpty() )
         {
@@ -1797,7 +1783,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
     @Override
     public Instant modifyTime( )
     {
-        final Element rootElement = document.getRootElement();
+        final XmlElement rootElement = document.getRootElement();
         final String modifyTimeString = rootElement.getAttributeValue( XML_ATTRIBUTE_MODIFY_TIME );
         if ( modifyTimeString != null )
         {

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

@@ -26,11 +26,11 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.StoredValue;
+import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.TreeMap;
@@ -92,16 +92,8 @@ public abstract class StoredConfigurationUtil
 
         final Object nativeObject = storedConfiguration.readSetting( profileSetting ).toNativeObject();
         final List<String> settingValues = ( List<String> ) nativeObject;
-        final LinkedList<String> profiles = new LinkedList<>();
-        profiles.addAll( settingValues );
-        for ( final Iterator<String> iterator = profiles.iterator(); iterator.hasNext(); )
-        {
-            final String profile = iterator.next();
-            if ( profile == null || profile.length() < 1 )
-            {
-                iterator.remove();
-            }
-        }
+        final LinkedList<String> profiles = new LinkedList<>( settingValues );
+        profiles.removeIf( profile -> StringUtil.isEmpty( profile ) );
         return Collections.unmodifiableList( profiles );
 
     }

+ 11 - 10
server/src/main/java/password/pwm/config/value/ActionValue.java

@@ -23,7 +23,6 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.Element;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
@@ -35,6 +34,8 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.X509Utils;
@@ -90,7 +91,7 @@ public class ActionValue extends AbstractValue implements StoredValue
 
             public ActionValue fromXmlElement(
                     final PwmSetting pwmSetting,
-                    final Element settingElement,
+                    final XmlElement settingElement,
                     final PwmSecurityKey pwmSecurityKey
             )
                     throws PwmOperationalException
@@ -100,8 +101,8 @@ public class ActionValue extends AbstractValue implements StoredValue
 
                 final boolean oldType = PwmSettingSyntax.STRING_ARRAY.toString().equals(
                         settingElement.getAttributeValue( "syntax" ) );
-                final List<Element> valueElements = settingElement.getChildren( "value" );
-                for ( final Element loopValueElement : valueElements )
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
+                for ( final XmlElement loopValueElement : valueElements )
                 {
                     final String stringValue = loopValueElement.getText();
                     if ( !StringUtil.isEmpty( stringValue ) )
@@ -110,7 +111,7 @@ public class ActionValue extends AbstractValue implements StoredValue
                         {
                             if ( oldType )
                             {
-                                if ( loopValueElement.getAttribute( "locale" ) == null )
+                                if ( loopValueElement.getAttributeValue( "locale" ) == null )
                                 {
                                     final ActionConfigurationOldVersion1 oldVersion1 = ActionConfigurationOldVersion1.parseOldConfigString( stringValue );
                                     values.add( convertOldVersion1Values( oldVersion1 ) );
@@ -163,9 +164,9 @@ public class ActionValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final ActionConfiguration value : values )
         {
             final List<ActionConfiguration.WebAction> clonedWebActions = new ArrayList<>();
@@ -186,9 +187,9 @@ public class ActionValue extends AbstractValue implements StoredValue
             final ActionConfiguration clonedAction = value.toBuilder().webActions( clonedWebActions ).build();
 
 
-            final Element valueElement = new Element( valueElementName );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
 
-            valueElement.addContent( JsonUtil.serialize( clonedAction ) );
+            valueElement.addText( JsonUtil.serialize( clonedAction ) );
             returnList.add( valueElement );
         }
         return returnList;
@@ -366,7 +367,7 @@ public class ActionValue extends AbstractValue implements StoredValue
         return CURRENT_SYNTAX_VERSION;
     }
 
-    private static int figureCurrentStoredSyntax( final Element settingElement )
+    private static int figureCurrentStoredSyntax( final XmlElement settingElement )
     {
         final String storedSyntaxVersionString = settingElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_SYNTAX_VERSION );
         if ( !StringUtil.isEmpty( storedSyntaxVersionString ) )

+ 7 - 6
server/src/main/java/password/pwm/config/value/BooleanValue.java

@@ -22,13 +22,14 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.Element;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Display;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.io.Serializable;
@@ -55,9 +56,9 @@ public class BooleanValue implements StoredValue
                 return new BooleanValue( JsonUtil.deserialize( value, Boolean.class ) );
             }
 
-            public BooleanValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey input )
+            public BooleanValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey input )
             {
-                final Element valueElement = settingElement.getChild( "value" );
+                final XmlElement valueElement = settingElement.getChild( "value" );
                 final String value = valueElement.getText();
                 return new BooleanValue( Boolean.valueOf( value ) );
             }
@@ -71,10 +72,10 @@ public class BooleanValue implements StoredValue
     }
 
     @Override
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final Element valueElement = new Element( valueElementName );
-        valueElement.addContent( String.valueOf( value ) );
+        final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+        valueElement.addText( String.valueOf( value ) );
         return Collections.singletonList( valueElement );
     }
 

+ 10 - 11
server/src/main/java/password/pwm/config/value/ChallengeValue.java

@@ -23,13 +23,13 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.CDATA;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.value.data.ChallengeItemConfiguration;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 
@@ -70,7 +70,7 @@ public class ChallengeValue extends AbstractValue implements StoredValue
                             {
                             }
                     );
-                    srcMap = srcMap == null ? Collections.<String, List<ChallengeItemConfiguration>>emptyMap() : new TreeMap<>(
+                    srcMap = srcMap == null ? Collections.emptyMap() : new TreeMap<>(
                             srcMap );
                     return new ChallengeValue( Collections.unmodifiableMap( srcMap ) );
                 }
@@ -78,16 +78,15 @@ public class ChallengeValue extends AbstractValue implements StoredValue
 
             public ChallengeValue fromXmlElement(
                     final PwmSetting pwmSetting,
-                    final Element settingElement,
+                    final XmlElement settingElement,
                     final PwmSecurityKey input
             )
             {
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final Map<String, List<ChallengeItemConfiguration>> values = new TreeMap<>();
                 final boolean oldStyle = "LOCALIZED_STRING_ARRAY".equals( settingElement.getAttributeValue( "syntax" ) );
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String localeString = loopValueElement.getAttributeValue(
                             "locale" ) == null ? "" : loopValueElement.getAttributeValue( "locale" );
                     final String value = loopValueElement.getText();
@@ -114,9 +113,9 @@ public class ChallengeValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final Map.Entry<String, List<ChallengeItemConfiguration>> entry : values.entrySet() )
         {
             final String locale = entry.getKey();
@@ -124,8 +123,8 @@ public class ChallengeValue extends AbstractValue implements StoredValue
             {
                 if ( value != null )
                 {
-                    final Element valueElement = new Element( valueElementName );
-                    valueElement.addContent( new CDATA( JsonUtil.serialize( value ) ) );
+                    final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+                    valueElement.addText( JsonUtil.serialize( value ) );
                     if ( locale != null && locale.length() > 0 )
                     {
                         valueElement.setAttribute( "locale", locale );

+ 10 - 10
server/src/main/java/password/pwm/config/value/CustomLinkValue.java

@@ -23,12 +23,13 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.Element;
 import password.pwm.config.CustomLinkConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -71,16 +72,15 @@ public class CustomLinkValue extends AbstractValue implements StoredValue
                 }
             }
 
-            public CustomLinkValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public CustomLinkValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
                     throws PwmOperationalException
             {
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final List<CustomLinkConfiguration> values = new ArrayList<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement  : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String value = loopValueElement.getText();
-                    if ( value != null && value.length() > 0 && loopValueElement.getAttribute( "locale" ) == null )
+                    if ( value != null && value.length() > 0 && loopValueElement.getAttributeValue( "locale" ) == null )
                     {
                         values.add( JsonUtil.deserialize( value, CustomLinkConfiguration.class ) );
                     }
@@ -90,13 +90,13 @@ public class CustomLinkValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final CustomLinkConfiguration value : values )
         {
-            final Element valueElement = new Element( valueElementName );
-            valueElement.addContent( JsonUtil.serialize( value ) );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+            valueElement.addText( JsonUtil.serialize( value ) );
             returnList.add( valueElement );
         }
         return returnList;

+ 12 - 11
server/src/main/java/password/pwm/config/value/EmailValue.java

@@ -23,13 +23,15 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.Element;
+
 import password.pwm.bean.EmailItemBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -75,22 +77,21 @@ public class EmailValue extends AbstractValue implements StoredValue
 
             public EmailValue fromXmlElement(
                     final PwmSetting pwmSetting,
-                    final Element settingElement,
+                    final XmlElement settingElement,
                     final PwmSecurityKey input
             )
                     throws PwmOperationalException
             {
                 final Map<String, EmailItemBean> values = new TreeMap<>();
                 {
-                    final List valueElements = settingElement.getChildren( "value" );
-                    for ( final Object loopValue : valueElements )
+                    final List<XmlElement> valueElements = settingElement.getChildren( "value" );
+                    for ( final XmlElement loopValueElement : valueElements )
                     {
-                        final Element loopValueElement = ( Element ) loopValue;
                         final String value = loopValueElement.getText();
                         if ( value != null && value.length() > 0 )
                         {
-                            final String localeValue = loopValueElement.getAttribute(
-                                    "locale" ) == null ? "" : loopValueElement.getAttribute( "locale" ).getValue();
+                            final String localeValue = loopValueElement.getAttributeValue(
+                                    "locale" ) == null ? "" : loopValueElement.getAttributeValue( "locale" );
                             values.put( localeValue, JsonUtil.deserialize( value, EmailItemBean.class ) );
                         }
                     }
@@ -100,19 +101,19 @@ public class EmailValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final Map.Entry<String, EmailItemBean> entry : values.entrySet() )
         {
             final String localeValue = entry.getKey();
             final EmailItemBean emailItemBean = entry.getValue();
-            final Element valueElement = new Element( valueElementName );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
             if ( localeValue.length() > 0 )
             {
                 valueElement.setAttribute( "locale", localeValue );
             }
-            valueElement.addContent( JsonUtil.serialize( emailItemBean ) );
+            valueElement.addText( JsonUtil.serialize( emailItemBean ) );
             returnList.add( valueElement );
         }
         return returnList;

+ 14 - 15
server/src/main/java/password/pwm/config/value/FileValue.java

@@ -23,7 +23,6 @@
 package password.pwm.config.value;
 
 import lombok.Value;
-import org.jdom2.Element;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
@@ -32,6 +31,8 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
@@ -125,23 +126,21 @@ public class FileValue extends AbstractValue implements StoredValue
         return new StoredValueFactory()
         {
 
-            public FileValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey input )
+            public FileValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey input )
                     throws PwmOperationalException
             {
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final Map<FileInformation, FileContent> values = new LinkedHashMap<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
-
-                    final Element loopFileInformation = loopValueElement.getChild( "FileInformation" );
+                    final XmlElement loopFileInformation = loopValueElement.getChild( "FileInformation" );
                     if ( loopFileInformation != null )
                     {
                         final String loopFileInformationJson = loopFileInformation.getText();
                         final FileInformation fileInformation = JsonUtil.deserialize( loopFileInformationJson,
                                 FileInformation.class );
 
-                        final Element loopFileContentElement = loopValueElement.getChild( "FileContent" );
+                        final XmlElement loopFileContentElement = loopValueElement.getChild( "FileContent" );
                         if ( loopFileContentElement != null )
                         {
                             final String fileContentString = loopFileContentElement.getText();
@@ -168,23 +167,23 @@ public class FileValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final Map.Entry<FileInformation, FileContent> entry : this.values.entrySet() )
         {
             final FileValue.FileInformation fileInformation = entry.getKey();
             final FileContent fileContent = entry.getValue();
-            final Element valueElement = new Element( valueElementName );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
 
-            final Element fileInformationElement = new Element( "FileInformation" );
-            fileInformationElement.addContent( JsonUtil.serialize( fileInformation ) );
+            final XmlElement fileInformationElement = XmlFactory.getFactory().newElement( "FileInformation" );
+            fileInformationElement.addText( JsonUtil.serialize( fileInformation ) );
             valueElement.addContent( fileInformationElement );
 
-            final Element fileContentElement = new Element( "FileContent" );
+            final XmlElement fileContentElement = XmlFactory.getFactory().newElement( "FileContent" );
             try
             {
-                fileContentElement.addContent( fileContent.toEncodedString() );
+                fileContentElement.addText( fileContent.toEncodedString() );
             }
             catch ( IOException e )
             {

+ 10 - 10
server/src/main/java/password/pwm/config/value/FormValue.java

@@ -23,7 +23,6 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.StoredValue;
@@ -32,6 +31,8 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -76,18 +77,17 @@ public class FormValue extends AbstractValue implements StoredValue
                 }
             }
 
-            public FormValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public FormValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
                     throws PwmOperationalException
             {
                 final boolean oldType = PwmSettingSyntax.LOCALIZED_STRING_ARRAY.toString().equals(
                         settingElement.getAttributeValue( "syntax" ) );
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final List<FormConfiguration> values = new ArrayList<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement  : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String value = loopValueElement.getText();
-                    if ( value != null && value.length() > 0 && loopValueElement.getAttribute( "locale" ) == null )
+                    if ( value != null && value.length() > 0 && loopValueElement.getAttributeValue( "locale" ) == null )
                     {
                         if ( oldType )
                         {
@@ -106,13 +106,13 @@ public class FormValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final FormConfiguration value : values )
         {
-            final Element valueElement = new Element( valueElementName );
-            valueElement.addContent( JsonUtil.serialize( value ) );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+            valueElement.addText( JsonUtil.serialize( value ) );
             returnList.add( valueElement );
         }
         return returnList;

+ 10 - 11
server/src/main/java/password/pwm/config/value/LocalizedStringArrayValue.java

@@ -23,12 +23,12 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.CDATA;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -57,7 +57,7 @@ public class LocalizedStringArrayValue extends AbstractValue implements StoredVa
             {
                 if ( input == null )
                 {
-                    return new LocalizedStringArrayValue( Collections.<String, List<String>>emptyMap() );
+                    return new LocalizedStringArrayValue( Collections.emptyMap() );
                 }
                 else
                 {
@@ -69,13 +69,12 @@ public class LocalizedStringArrayValue extends AbstractValue implements StoredVa
                 }
             }
 
-            public LocalizedStringArrayValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public LocalizedStringArrayValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
             {
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final Map<String, List<String>> values = new TreeMap<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement  : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String localeString = loopValueElement.getAttributeValue(
                             "locale" ) == null ? "" : loopValueElement.getAttributeValue( "locale" );
                     final String value = loopValueElement.getText();
@@ -92,16 +91,16 @@ public class LocalizedStringArrayValue extends AbstractValue implements StoredVa
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final Map.Entry<String, List<String>> entry : values.entrySet() )
         {
             final String locale = entry.getKey();
             for ( final String value : entry.getValue() )
             {
-                final Element valueElement = new Element( valueElementName );
-                valueElement.addContent( new CDATA( value ) );
+                final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+                valueElement.addText( value );
                 if ( locale != null && locale.length() > 0 )
                 {
                     valueElement.setAttribute( "locale", locale );

+ 9 - 10
server/src/main/java/password/pwm/config/value/LocalizedStringValue.java

@@ -23,12 +23,12 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.CDATA;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -69,13 +69,12 @@ public class LocalizedStringValue extends AbstractValue implements StoredValue
                 }
             }
 
-            public LocalizedStringValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public LocalizedStringValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
             {
-                final List elements = settingElement.getChildren( "value" );
+                final List<XmlElement> elements = settingElement.getChildren( "value" );
                 final Map<String, String> values = new TreeMap<>();
-                for ( final Object loopValue : elements )
+                for ( final XmlElement loopValueElement : elements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String localeString = loopValueElement.getAttributeValue( "locale" );
                     final String value = loopValueElement.getText();
                     values.put( localeString == null ? "" : localeString, value );
@@ -85,15 +84,15 @@ public class LocalizedStringValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final Map.Entry<String, String> entry : value.entrySet() )
         {
             final String locale = entry.getKey();
             final String loopValue = entry.getValue();
-            final Element valueElement = new Element( valueElementName );
-            valueElement.addContent( new CDATA( loopValue ) );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+            valueElement.addText( loopValue );
             if ( locale != null && locale.length() > 0 )
             {
                 valueElement.setAttribute( "locale", locale );

+ 18 - 17
server/src/main/java/password/pwm/config/value/NamedSecretValue.java

@@ -23,7 +23,6 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.Element;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
@@ -35,6 +34,8 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.SecureEngine;
@@ -88,30 +89,30 @@ public class NamedSecretValue implements StoredValue
 
             public NamedSecretValue fromXmlElement(
                     final PwmSetting pwmSetting,
-                    final Element settingElement,
+                    final XmlElement settingElement,
                     final PwmSecurityKey key
             )
                     throws PwmOperationalException, PwmUnrecoverableException
             {
                 final Map<String, NamedSecretData> values = new LinkedHashMap<>();
-                final List<Element> valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
 
                 try
                 {
                     if ( valueElements != null )
                     {
-                        for ( final Element value : valueElements )
+                        for ( final XmlElement value : valueElements )
                         {
                             if ( value.getChild( ELEMENT_NAME ) != null && value.getChild( ELEMENT_PASSWORD ) != null )
                             {
                                 final String name = value.getChild( ELEMENT_NAME ).getText();
                                 final String encodedValue = value.getChild( ELEMENT_PASSWORD ).getText();
                                 final PasswordData passwordData = new PasswordData( SecureEngine.decryptStringValue( encodedValue, key, PwmBlockAlgorithm.CONFIG ) );
-                                final List<Element> usages = value.getChildren( ELEMENT_USAGE );
+                                final List<XmlElement> usages = value.getChildren( ELEMENT_USAGE );
                                 final List<String> strUsages = new ArrayList<>();
                                 if ( usages != null )
                                 {
-                                    for ( final Element usageElement : usages )
+                                    for ( final XmlElement usageElement : usages )
                                     {
                                         strUsages.add( usageElement.getText() );
                                     }
@@ -132,7 +133,7 @@ public class NamedSecretValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName )
+    public List<XmlElement> toXmlValues( final String valueElementName )
     {
         throw new IllegalStateException( "password xml output requires hash key" );
     }
@@ -155,14 +156,14 @@ public class NamedSecretValue implements StoredValue
         return 0;
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey key )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey key )
     {
         if ( values == null )
         {
-            final Element valueElement = new Element( valueElementName );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
             return Collections.singletonList( valueElement );
         }
-        final List<Element> valuesElement = new ArrayList<>();
+        final List<XmlElement> valuesElement = new ArrayList<>();
         try
         {
             for ( final Map.Entry<String, NamedSecretData> entry : values.entrySet() )
@@ -170,19 +171,19 @@ public class NamedSecretValue implements StoredValue
                 final String name = entry.getKey();
                 final PasswordData passwordData = entry.getValue().getPassword();
                 final String encodedValue = SecureEngine.encryptToString( passwordData.getStringValue(), key, PwmBlockAlgorithm.CONFIG );
-                final Element newValueElement = new Element( "value" );
-                final Element nameElement = new Element( ELEMENT_NAME );
-                nameElement.setText( name );
-                final Element encodedValueElement = new Element( ELEMENT_PASSWORD );
-                encodedValueElement.setText( encodedValue );
+                final XmlElement newValueElement = XmlFactory.getFactory().newElement( "value" );
+                final XmlElement nameElement = XmlFactory.getFactory().newElement( ELEMENT_NAME );
+                nameElement.addText( name );
+                final XmlElement encodedValueElement = XmlFactory.getFactory().newElement( ELEMENT_PASSWORD );
+                encodedValueElement.addText( encodedValue );
 
                 newValueElement.addContent( nameElement );
                 newValueElement.addContent( encodedValueElement );
 
                 for ( final String usages : values.get( name ).getUsage() )
                 {
-                    final Element usageElement = new Element( ELEMENT_USAGE );
-                    usageElement.setText( usages );
+                    final XmlElement usageElement = XmlFactory.getFactory().newElement( ELEMENT_USAGE );
+                    usageElement.addText( usages );
                     newValueElement.addContent( usageElement );
                 }
 

+ 9 - 8
server/src/main/java/password/pwm/config/value/NumericArrayValue.java

@@ -22,11 +22,12 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -57,11 +58,11 @@ public class NumericArrayValue extends AbstractValue implements StoredValue
                 return new NumericArrayValue( list );
             }
 
-            public NumericArrayValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey input )
+            public NumericArrayValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey input )
             {
                 final List<Long> returnList = new ArrayList<>(  );
-                final List<Element> valueElements = settingElement.getChildren( "value" );
-                for ( final Element element : valueElements )
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
+                for ( final XmlElement element : valueElements )
                 {
                     final String strValue = element.getText();
                     final Long longValue = Long.parseLong( strValue );
@@ -73,13 +74,13 @@ public class NumericArrayValue extends AbstractValue implements StoredValue
     }
 
     @Override
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final Long value : this.values )
         {
-            final Element valueElement = new Element( valueElementName );
-            valueElement.addContent( String.valueOf( value ) );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+            valueElement.addText( String.valueOf( value ) );
             returnList.add( valueElement );
         }
         return returnList;

+ 7 - 6
server/src/main/java/password/pwm/config/value/NumericValue.java

@@ -22,11 +22,12 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingProperty;
 import password.pwm.config.StoredValue;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.Collections;
@@ -50,9 +51,9 @@ public class NumericValue extends AbstractValue implements StoredValue
                 return new NumericValue( JsonUtil.deserialize( value, Long.class ) );
             }
 
-            public NumericValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey input )
+            public NumericValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey input )
             {
-                final Element valueElement = settingElement.getChild( "value" );
+                final XmlElement valueElement = settingElement.getChild( "value" );
                 final String value = valueElement.getText();
                 return new NumericValue( normalizeValue( pwmSetting, Long.parseLong( value ) ) );
             }
@@ -78,10 +79,10 @@ public class NumericValue extends AbstractValue implements StoredValue
     }
 
     @Override
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final Element valueElement = new Element( valueElementName );
-        valueElement.addContent( Long.toString( value ) );
+        final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+        valueElement.addText( Long.toString( value ) );
         return Collections.singletonList( valueElement );
     }
 

+ 9 - 9
server/src/main/java/password/pwm/config/value/OptionListValue.java

@@ -23,11 +23,12 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -71,14 +72,13 @@ public class OptionListValue extends AbstractValue implements StoredValue
                 }
             }
 
-            public OptionListValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public OptionListValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
                     throws PwmOperationalException
             {
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final Set<String> values = new TreeSet<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String value = loopValueElement.getText();
                     if ( value != null && !value.trim().isEmpty() )
                     {
@@ -90,13 +90,13 @@ public class OptionListValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final String value : values )
         {
-            final Element valueElement = new Element( valueElementName );
-            valueElement.addContent( value );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+            valueElement.addText( value );
             returnList.add( valueElement );
         }
         return returnList;

+ 10 - 9
server/src/main/java/password/pwm/config/value/PasswordValue.java

@@ -22,7 +22,7 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.Element;
+
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
@@ -32,6 +32,8 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.SecureEngine;
@@ -80,12 +82,12 @@ public class PasswordValue implements StoredValue
 
             public PasswordValue fromXmlElement(
                     final PwmSetting pwmSetting,
-                    final Element settingElement,
+                    final XmlElement settingElement,
                     final PwmSecurityKey key
             )
                     throws PwmOperationalException, PwmUnrecoverableException
             {
-                final Element valueElement = settingElement.getChild( "value" );
+                final XmlElement valueElement = settingElement.getChild( "value" );
                 final String rawValue = valueElement.getText();
 
                 final PasswordValue newPasswordValue = new PasswordValue();
@@ -124,7 +126,7 @@ public class PasswordValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName )
+    public List<XmlElement> toXmlValues( final String valueElementName )
     {
         throw new IllegalStateException( "password xml output requires hash key" );
     }
@@ -147,22 +149,21 @@ public class PasswordValue implements StoredValue
         return 0;
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey key )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey key )
     {
         if ( value == null )
         {
-            final Element valueElement = new Element( valueElementName );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
             return Collections.singletonList( valueElement );
         }
-        final Element valueElement = new Element( valueElementName );
+        final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
         try
         {
             final String encodedValue = SecureEngine.encryptToString( value.getStringValue(), key, PwmBlockAlgorithm.CONFIG );
-            valueElement.addContent( encodedValue );
+            valueElement.addText( encodedValue );
         }
         catch ( Exception e )
         {
-            valueElement.addContent( "" );
             throw new RuntimeException( "missing required AES and SHA1 libraries, or other crypto fault: " + e.getMessage() );
         }
         return Collections.singletonList( valueElement );

+ 13 - 13
server/src/main/java/password/pwm/config/value/PrivateKeyValue.java

@@ -22,12 +22,13 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.Element;
 import password.pwm.bean.PrivateKeyCertificate;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
@@ -59,16 +60,16 @@ public class PrivateKeyValue extends AbstractValue
     {
         return new StoredValue.StoredValueFactory()
         {
-            public PrivateKeyValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public PrivateKeyValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
             {
                 if ( settingElement != null && settingElement.getChild( "value" ) != null )
                 {
 
-                    final Element valueElement = settingElement.getChild( "value" );
+                    final XmlElement valueElement = settingElement.getChild( "value" );
                     if ( valueElement != null )
                     {
                         final List<X509Certificate> certificates = new ArrayList<>();
-                        for ( final Element certificateElement : valueElement.getChildren( ELEMENT_NAME_CERTIFICATE ) )
+                        for ( final XmlElement certificateElement : valueElement.getChildren( ELEMENT_NAME_CERTIFICATE ) )
                         {
                             try
                             {
@@ -87,7 +88,7 @@ public class PrivateKeyValue extends AbstractValue
                         PrivateKey privateKey = null;
                         try
                         {
-                            final Element keyElement = valueElement.getChild( ELEMENT_NAME_KEY );
+                            final XmlElement keyElement = valueElement.getChild( ELEMENT_NAME_KEY );
                             final String encryptedText = keyElement.getText();
                             final String decryptedText = SecureEngine.decryptStringValue( encryptedText, key, PwmBlockAlgorithm.CONFIG );
                             final byte[] privateKeyBytes = StringUtil.base64Decode( decryptedText );
@@ -121,7 +122,7 @@ public class PrivateKeyValue extends AbstractValue
     }
 
 
-    public List<Element> toXmlValues( final String valueElementName )
+    public List<XmlElement> toXmlValues( final String valueElementName )
     {
         throw new IllegalStateException( "password xml output requires hash key" );
     }
@@ -144,9 +145,9 @@ public class PrivateKeyValue extends AbstractValue
         return 0;
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey key )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey key )
     {
-        final Element valueElement = new Element( "value" );
+        final XmlElement valueElement = XmlFactory.getFactory().newElement( "value" );
         if ( privateKeyCertificate != null )
         {
             try
@@ -154,22 +155,21 @@ public class PrivateKeyValue extends AbstractValue
                 {
                     for ( final X509Certificate certificate : privateKeyCertificate.getCertificates() )
                     {
-                        final Element certificateElement = new Element( ELEMENT_NAME_CERTIFICATE );
-                        certificateElement.setText( X509Utils.certificateToBase64( certificate ) );
+                        final XmlElement certificateElement = XmlFactory.getFactory().newElement( ELEMENT_NAME_CERTIFICATE );
+                        certificateElement.addText( X509Utils.certificateToBase64( certificate ) );
                         valueElement.addContent( certificateElement );
                     }
                 }
                 {
-                    final Element keyElement = new Element( ELEMENT_NAME_KEY );
+                    final XmlElement keyElement = XmlFactory.getFactory().newElement( ELEMENT_NAME_KEY );
                     final String b64EncodedKey = StringUtil.base64Encode( privateKeyCertificate.getKey().getEncoded() );
                     final String encryptedKey = SecureEngine.encryptToString( b64EncodedKey, key, PwmBlockAlgorithm.CONFIG );
-                    keyElement.setText( encryptedKey );
+                    keyElement.addText( encryptedKey );
                     valueElement.addContent( keyElement );
                 }
             }
             catch ( Exception e )
             {
-                valueElement.addContent( "" );
                 throw new RuntimeException( "missing required AES and SHA1 libraries, or other crypto fault: " + e.getMessage() );
             }
         }

+ 9 - 9
server/src/main/java/password/pwm/config/value/RemoteWebServiceValue.java

@@ -23,7 +23,6 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.jdom2.Element;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
@@ -31,6 +30,8 @@ import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.X509Utils;
@@ -83,16 +84,15 @@ public class RemoteWebServiceValue extends AbstractValue implements StoredValue
 
             public RemoteWebServiceValue fromXmlElement(
                     final PwmSetting pwmSetting,
-                    final Element settingElement,
+                    final XmlElement settingElement,
                     final PwmSecurityKey pwmSecurityKey
             )
                     throws PwmOperationalException
             {
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final List<RemoteWebServiceConfiguration> values = new ArrayList<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String value = loopValueElement.getText();
                     if ( value != null && value.length() > 0 )
                     {
@@ -106,12 +106,12 @@ public class RemoteWebServiceValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final RemoteWebServiceConfiguration value : values )
         {
-            final Element valueElement = new Element( valueElementName );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
             final RemoteWebServiceConfiguration clonedValue = JsonUtil.cloneUsingJson( value, RemoteWebServiceConfiguration.class );
             try
             {
@@ -122,7 +122,7 @@ public class RemoteWebServiceValue extends AbstractValue implements StoredValue
                 LOGGER.warn( "error decoding stored pw value: " + e.getMessage() );
             }
 
-            valueElement.addContent( JsonUtil.serialize( clonedValue ) );
+            valueElement.addText( JsonUtil.serialize( clonedValue ) );
             returnList.add( valueElement );
         }
         return returnList;

+ 11 - 12
server/src/main/java/password/pwm/config/value/StringArrayValue.java

@@ -22,11 +22,11 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.CDATA;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -54,12 +54,12 @@ public class StringArrayValue extends AbstractValue implements StoredValue
             {
                 if ( input == null )
                 {
-                    return new StringArrayValue( Collections.<String>emptyList() );
+                    return new StringArrayValue( Collections.emptyList() );
                 }
                 else
                 {
                     List<String> srcList = JsonUtil.deserializeStringList( input );
-                    srcList = srcList == null ? Collections.<String>emptyList() : srcList;
+                    srcList = srcList == null ? Collections.emptyList() : srcList;
                     while ( srcList.contains( null ) )
                     {
                         srcList.remove( null );
@@ -68,13 +68,12 @@ public class StringArrayValue extends AbstractValue implements StoredValue
                 }
             }
 
-            public StringArrayValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public StringArrayValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
             {
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final List<String> values = new ArrayList<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String value = loopValueElement.getText();
                     values.add( value );
                 }
@@ -83,13 +82,13 @@ public class StringArrayValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final String value : this.values )
         {
-            final Element valueElement = new Element( valueElementName );
-            valueElement.addContent( new CDATA( value ) );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+            valueElement.addText( value );
             returnList.add( valueElement );
         }
         return returnList;

+ 7 - 7
server/src/main/java/password/pwm/config/value/StringValue.java

@@ -22,13 +22,13 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.CDATA;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingFlag;
 import password.pwm.config.StoredValue;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.Collections;
@@ -60,18 +60,18 @@ public class StringValue extends AbstractValue implements StoredValue
                 return new StringValue( newValue );
             }
 
-            public StringValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public StringValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
             {
-                final Element valueElement = settingElement.getChild( "value" );
+                final XmlElement valueElement = settingElement.getChild( "value" );
                 return new StringValue( valueElement == null ? "" : valueElement.getText() );
             }
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final Element valueElement = new Element( valueElementName );
-        valueElement.addContent( new CDATA( value ) );
+        final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+        valueElement.addText( value );
         return Collections.singletonList( valueElement );
     }
 

+ 9 - 9
server/src/main/java/password/pwm/config/value/UserPermissionValue.java

@@ -24,7 +24,6 @@ package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
 import org.apache.commons.lang3.StringUtils;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.stored.StoredConfigurationImpl;
@@ -32,6 +31,8 @@ import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.i18n.Display;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
@@ -74,16 +75,15 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
                 }
             }
 
-            public UserPermissionValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public UserPermissionValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
                     throws PwmOperationalException
             {
                 final boolean newType = "2".equals(
                         settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_SYNTAX_VERSION ) );
-                final List valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
                 final List<UserPermission> values = new ArrayList<>();
-                for ( final Object loopValue : valueElements )
+                for ( final XmlElement loopValueElement : valueElements )
                 {
-                    final Element loopValueElement = ( Element ) loopValue;
                     final String value = loopValueElement.getText();
                     if ( value != null && !value.isEmpty() )
                     {
@@ -105,13 +105,13 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
         };
     }
 
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final UserPermission value : values )
         {
-            final Element valueElement = new Element( valueElementName );
-            valueElement.addContent( JsonUtil.serialize( value ) );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+            valueElement.addText( JsonUtil.serialize( value ) );
             returnList.add( valueElement );
         }
         return returnList;

+ 2 - 2
server/src/main/java/password/pwm/config/value/ValueFactory.java

@@ -22,12 +22,12 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
+import password.pwm.util.java.XmlElement;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 
@@ -57,7 +57,7 @@ public class ValueFactory
         }
     }
 
-    public static StoredValue fromXmlValues( final PwmSetting setting, final Element settingElement, final PwmSecurityKey key )
+    public static StoredValue fromXmlValues( final PwmSetting setting, final XmlElement settingElement, final PwmSecurityKey key )
     {
         try
         {

+ 7 - 7
server/src/main/java/password/pwm/config/value/VerificationMethodValue.java

@@ -23,8 +23,6 @@
 package password.pwm.config.value;
 
 import lombok.Value;
-import org.jdom2.CDATA;
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.option.IdentityVerificationMethod;
@@ -32,6 +30,8 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.i18n.Display;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 
@@ -126,10 +126,10 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
                 }
             }
 
-            public VerificationMethodValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public VerificationMethodValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
                     throws PwmOperationalException
             {
-                final Element valueElement = settingElement.getChild( "value" );
+                final XmlElement valueElement = settingElement.getChild( "value" );
                 final String inputStr = valueElement.getText();
                 final VerificationMethodSettings settings = JsonUtil.deserialize( inputStr, VerificationMethodSettings.class );
                 return new VerificationMethodValue( settings );
@@ -138,10 +138,10 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
     }
 
     @Override
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final Element valueElement = new Element( valueElementName );
-        valueElement.addContent( new CDATA( JsonUtil.serialize( value ) ) );
+        final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
+        valueElement.addText( JsonUtil.serialize( value ) );
         return Collections.singletonList( valueElement );
     }
 

+ 9 - 8
server/src/main/java/password/pwm/config/value/X509CertificateValue.java

@@ -22,11 +22,12 @@
 
 package password.pwm.config.value;
 
-import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
@@ -54,11 +55,11 @@ public class X509CertificateValue extends AbstractValue implements StoredValue
     {
         return new StoredValueFactory()
         {
-            public X509CertificateValue fromXmlElement( final PwmSetting pwmSetting, final Element settingElement, final PwmSecurityKey key )
+            public X509CertificateValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key )
             {
                 final List<X509Certificate> certificates = new ArrayList<>();
-                final List<Element> valueElements = settingElement.getChildren( "value" );
-                for ( final Element loopValueElement : valueElements )
+                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
+                for ( final XmlElement loopValueElement : valueElements )
                 {
                     final String b64encodedStr = loopValueElement.getText();
                     try
@@ -105,15 +106,15 @@ public class X509CertificateValue extends AbstractValue implements StoredValue
 
 
     @Override
-    public List<Element> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
+    public List<XmlElement> toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey  )
     {
-        final List<Element> returnList = new ArrayList<>();
+        final List<XmlElement> returnList = new ArrayList<>();
         for ( final X509Certificate value : certificates )
         {
-            final Element valueElement = new Element( valueElementName );
+            final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName );
             try
             {
-                valueElement.addContent( X509Utils.certificateToBase64( value ) );
+                valueElement.addText( X509Utils.certificateToBase64( value ) );
             }
             catch ( CertificateEncodingException e )
             {

+ 51 - 10
server/src/main/java/password/pwm/util/java/XmlDocument.java

@@ -24,6 +24,7 @@ package password.pwm.util.java;
 
 import org.jdom2.Document;
 import org.jdom2.Element;
+import org.jdom2.filter.Filters;
 import org.jdom2.xpath.XPathExpression;
 import org.jdom2.xpath.XPathFactory;
 import org.w3c.dom.NodeList;
@@ -31,6 +32,8 @@ import org.w3c.dom.NodeList;
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 public interface XmlDocument
@@ -39,11 +42,15 @@ public interface XmlDocument
 
     XmlElement evaluateXpathToElement( String xpathExpression );
 
+    List<XmlElement> evaluateXpathToElements( String xpathExpression );
+
+    XmlDocument copy();
+
     class XmlDocumentJDOM implements XmlDocument
     {
         final Document document;
 
-        public XmlDocumentJDOM( final Document document )
+        XmlDocumentJDOM( final Document document )
         {
             this.document = document;
         }
@@ -60,11 +67,32 @@ public interface XmlDocument
         )
         {
             final XPathFactory xpfac = XPathFactory.instance();
-            final XPathExpression<Object> xp = xpfac.compile( xpathExpression );
-            final Element settingElement = ( Element ) xp.evaluateFirst( document );
-            return settingElement == null ? null : new XmlElement.XmlElementJDOM( settingElement );
+            final XPathExpression<Element> xp = xpfac.compile( xpathExpression, Filters.element() );
+            final Element element = xp.evaluateFirst( document );
+            return element == null ? null : new XmlElement.XmlElementJDOM( element );
         }
 
+        @Override
+        public List<XmlElement> evaluateXpathToElements(
+                final String xpathExpression
+        )
+        {
+            final List<XmlElement> returnList = new ArrayList<>(  );
+
+            final XPathFactory xpfac = XPathFactory.instance();
+            final XPathExpression<Element> xp = xpfac.compile( xpathExpression, Filters.element() );
+            for ( final Element element : xp.evaluate( document ) )
+            {
+                returnList.add( new XmlElement.XmlElementJDOM( element ) );
+            }
+            return Collections.unmodifiableList( returnList );
+        }
+
+        @Override
+        public XmlDocument copy()
+        {
+            return new XmlDocumentJDOM( document.clone() );
+        }
     }
 
     class XmlDocumentW3c implements XmlDocument
@@ -86,18 +114,26 @@ public interface XmlDocument
         public XmlElement evaluateXpathToElement(
                 final String xpathExpression
         )
+        {
+            final List<XmlElement> elements = evaluateXpathToElements( xpathExpression );
+            if ( JavaHelper.isEmpty( elements ) )
+            {
+                return null;
+            }
+            return elements.iterator().next();
+        }
+
+        @Override
+        public List<XmlElement> evaluateXpathToElements(
+                final String xpathExpression
+        )
         {
             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 );
-                final List<XmlElement> elementList = XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList );
-                if ( JavaHelper.isEmpty( elementList ) )
-                {
-                    return null;
-                }
-                return elementList.iterator().next();
+                return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList );
             }
             catch ( XPathExpressionException e )
             {
@@ -105,5 +141,10 @@ public interface XmlDocument
             }
         }
 
+        @Override
+        public XmlDocument copy()
+        {
+            return new XmlDocumentW3c( ( org.w3c.dom.Document) document.cloneNode( true ) );
+        }
     }
 }

+ 210 - 1
server/src/main/java/password/pwm/util/java/XmlElement.java

@@ -22,10 +22,15 @@
 
 package password.pwm.util.java;
 
+import org.jdom2.Comment;
+import org.jdom2.Content;
 import org.jdom2.Element;
+import org.jdom2.Text;
 import org.jdom2.input.DOMBuilder;
+import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+import javax.xml.parsers.DocumentBuilder;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -46,6 +51,26 @@ public interface XmlElement
 
     org.jdom2.Element asJdomElement();
 
+    String getName();
+
+    void setAttribute( String name, String value );
+
+    void detach();
+
+    void removeContent();
+
+    void removeAttribute( String attributeName );
+
+    void addContent( XmlElement element );
+
+    void addContent( List<XmlElement> elements );
+
+    void addText( String text );
+
+    void setComment( List<String> textLines );
+
+    List<XmlElement> getChildren();
+
     class XmlElementJDOM implements XmlElement
     {
         private final Element element;
@@ -55,6 +80,12 @@ public interface XmlElement
             this.element = element;
         }
 
+        @Override
+        public String getName()
+        {
+            return element.getName();
+        }
+
         @Override
         public XmlElement getChild( final String elementName )
         {
@@ -72,10 +103,19 @@ public interface XmlElement
             return element.getAttributeValue( attribute );
         }
 
+        @Override
+        public List<XmlElement> getChildren()
+        {
+            return getChildren( null );
+        }
+
         @Override
         public List<XmlElement> getChildren( final String elementName )
         {
-            final List<Element> children = element.getChildren( elementName );
+
+            final List<Element> children = elementName == null
+                    ? element.getChildren()
+                    : element.getChildren( elementName );
             if ( children == null )
             {
                 return Collections.emptyList();
@@ -116,6 +156,71 @@ public interface XmlElement
         {
             return element;
         }
+
+        @Override
+        public void setAttribute( final String name, final String value )
+        {
+            element.setAttribute( name, value );
+        }
+
+        @Override
+        public void detach()
+        {
+            element.detach();
+        }
+
+        @Override
+        public void removeContent()
+        {
+            element.removeContent();
+        }
+
+        @Override
+        public void removeAttribute( final String attributeName )
+        {
+            element.removeAttribute( attributeName );
+        }
+
+        @Override
+        public void addContent( final XmlElement element )
+        {
+            this.element.addContent( ( ( XmlElementJDOM) element ).element );
+        }
+
+        public void addContent( final List<XmlElement> elements )
+        {
+            for ( final XmlElement loopElement : elements )
+            {
+                final Element jdomElement = ( ( XmlElementJDOM ) loopElement ).element;
+                this.element.addContent( jdomElement );
+            }
+        }
+
+        @Override
+        public void addText( final String text )
+        {
+            element.addContent( new Text( text ) );
+        }
+
+        @Override
+        public void setComment( final List<String> textLines )
+        {
+            final List<Content> contentList = new ArrayList<>( element.getContent() );
+            for ( final Content content : contentList )
+            {
+                if ( content instanceof Comment )
+                {
+                    content.detach();
+                }
+            }
+
+            final List<String> reversedList = new ArrayList<>( textLines );
+            Collections.reverse( reversedList );
+            for ( final String text : textLines )
+            {
+                element.addContent( 0, new Comment( text ) );
+            }
+        }
     }
 
     class XmlElementW3c implements XmlElement
@@ -127,6 +232,12 @@ public interface XmlElement
             this.element = element;
         }
 
+        @Override
+        public String getName()
+        {
+            return element.getTagName();
+        }
+
         @Override
         public XmlElement getChild( final String elementName )
         {
@@ -145,6 +256,13 @@ public interface XmlElement
             return StringUtil.isEmpty( attrValue ) ? null : attrValue;
         }
 
+        @Override
+        public List<XmlElement> getChildren()
+        {
+            final NodeList nodeList = element.getChildNodes();
+            return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList );
+        }
+
         @Override
         public List<XmlElement> getChildren( final String elementName )
         {
@@ -183,5 +301,96 @@ public interface XmlElement
             final DOMBuilder domBuilder = new DOMBuilder();
             return domBuilder.build( element );
         }
+
+        @Override
+        public void setAttribute( final String name, final String value )
+        {
+            element.setAttribute( name, value );
+        }
+
+        @Override
+        public void detach()
+        {
+            element.getParentNode().removeChild( element );
+        }
+
+        @Override
+        public void removeContent()
+        {
+            final NodeList nodeList = element.getChildNodes();
+            for ( final XmlElement child : XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList ) )
+            {
+                element.removeChild( ( (XmlElementW3c) child ).element );
+            }
+        }
+
+        @Override
+        public void removeAttribute( final String attributeName )
+        {
+            element.removeAttribute( attributeName );
+        }
+
+        @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 );
+        }
+
+        public void addContent( final List<XmlElement> elements )
+        {
+            for ( final XmlElement element : elements )
+            {
+                final org.w3c.dom.Element w3cElement = ( ( XmlElementW3c ) element ).element;
+                this.element.getOwnerDocument().adoptNode( w3cElement );
+                this.element.appendChild( w3cElement );
+            }
+        }
+
+        @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 );
+        }
+
+        @Override
+        public void setComment( final List<String> textLines )
+        {
+            final NodeList nodeList = element.getChildNodes();
+            for ( int i = 0; i < nodeList.getLength(); i++ )
+            {
+                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 );
+
+                if ( element.hasChildNodes() )
+                {
+                    element.insertBefore( textNode, element.getFirstChild() );
+                }
+                else
+                {
+                    element.appendChild( textNode );
+                }
+
+            }
+        }
     }
 }

+ 51 - 17
server/src/main/java/password/pwm/util/java/XmlFactory.java

@@ -59,8 +59,13 @@ public interface XmlFactory
     void outputDocument( XmlDocument document, OutputStream outputStream )
                     throws IOException;
 
+    XmlDocument newDocument( String rootElementName );
+
+    XmlElement newElement( String name );
+
     static XmlFactory getFactory()
     {
+        //return new XmlFactoryW3c();
         return new XmlFactoryJDOM();
     }
 
@@ -72,13 +77,6 @@ public interface XmlFactory
         {
         }
 
-        public static Document parseJDOMXml( final InputStream inputStream )
-                throws PwmUnrecoverableException
-        {
-            final XmlDocument xmlDocument = new XmlFactoryJDOM().parseXml( inputStream );
-                return ( ( XmlDocument.XmlDocumentJDOM ) xmlDocument ).document;
-        }
-
         @Override
         public XmlDocument parseXml( final InputStream inputStream )
                 throws PwmUnrecoverableException
@@ -137,6 +135,19 @@ public interface XmlFactory
             return builder;
         }
 
+        @Override
+        public XmlDocument newDocument( final String rootElementName )
+        {
+            final org.jdom2.Element rootElement = new org.jdom2.Element( rootElementName );
+            final org.jdom2.Document newDoc = new org.jdom2.Document( rootElement );
+            return new XmlDocument.XmlDocumentJDOM( newDoc );
+        }
+
+        @Override
+        public XmlElement newElement( final String name )
+        {
+            return new XmlElement.XmlElementJDOM( new org.jdom2.Element ( name ) );
+        }
     }
 
     class XmlFactoryW3c implements XmlFactory
@@ -168,17 +179,22 @@ public interface XmlFactory
             return new XmlDocument.XmlDocumentW3c( inputDocument );
         }
 
-        private static DocumentBuilder getBuilder( )
-                throws ParserConfigurationException
+        static DocumentBuilder getBuilder()
         {
-            final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
-
-            dbFactory.setFeature( "http://apache.org/xml/features/disallow-doctype-decl", false );
-            dbFactory.setExpandEntityReferences( false );
-            dbFactory.setValidating( false );
-            dbFactory.setXIncludeAware( false );
-            dbFactory.setExpandEntityReferences( false );
-            return dbFactory.newDocumentBuilder();
+            try
+            {
+                final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+                dbFactory.setFeature( "http://apache.org/xml/features/disallow-doctype-decl", false );
+                dbFactory.setExpandEntityReferences( false );
+                dbFactory.setValidating( false );
+                dbFactory.setXIncludeAware( false );
+                dbFactory.setExpandEntityReferences( false );
+                return dbFactory.newDocumentBuilder();
+            }
+            catch ( ParserConfigurationException e )
+            {
+                throw new IllegalArgumentException( "unable to generate dom xml builder: " + e.getMessage() );
+            }
         }
 
         @Override
@@ -213,6 +229,24 @@ public interface XmlFactory
             return null;
         }
 
+        @Override
+        public XmlDocument newDocument( final String rootElementName )
+        {
+            final DocumentBuilder documentBuilder = getBuilder();
+            final org.w3c.dom.Document document = documentBuilder.newDocument();
+            final org.w3c.dom.Element rootElement = document.createElement( rootElementName );
+            document.appendChild( rootElement );
+            return new XmlDocument.XmlDocumentW3c( document );
+        }
+
+        @Override
+        public XmlElement newElement( final String name )
+        {
+            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 );
+        }
 
     }
 }