KNOX-2365 - Knox parses shared provider configuration from Hadoop XML type configuration files (#330)

Main changes in this commit:
- implemented a language that Knox understands when parsing shared provider configuration(s) in Haddop XML type descriptors
- XML/JSON provider configurations are moved out to their own classes from ProviderConfigurationParser
- XML/JSON provider configuration classes implement hashCode/equals properly
- XML/JSON provider classes implement Comparable (based on a predefined provider order) as well as fixing the order of fields and alphabetical order withing parameters when serializing
- changed the separator in Hadoop XML type descriptors from semicolon (;) to hash (#) to conform new requirements
- shared providers that are generated using this framework are read-only on Admimn UI
- end-users can modify/remove any parameters in a provider w/o touching others
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html
index a4e5ea9..b03854a 100644
--- a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html
+++ b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html
@@ -120,6 +120,7 @@
                     title="Remove Provider Configuration"
                     class="btn btn-default btn-sm pull-left"
                     (click)="deleteConfirmModal.open('md')"
+                    *ngIf="showEditOptions()"
                     data-toggle="tooltip">
                 <span class="glyphicon glyphicon-trash"></span>
             </button>
@@ -129,6 +130,7 @@
                 class="btn btn-default btn-sm"
                 [disabled]="!changedProviders"
                 (click)="discardConfirmModal.open('md')"
+                *ngIf="showEditOptions()"
                 data-toggle="tooltip">
           <span class="glyphicon glyphicon-refresh"></span>
         </button>
@@ -138,6 +140,7 @@
                 class="btn btn-default btn-sm"
                 [disabled]="!changedProviders"
                 (click)="persistChanges()"
+                *ngIf="showEditOptions()"
                 data-toggle="tooltip">
           <span class="glyphicon glyphicon-floppy-disk"></span>
         </button>
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts
index b6ad220..0a0a759 100644
--- a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts
+++ b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts
@@ -48,6 +48,7 @@
     resourceContent: string;
 
     providers: Array<ProviderConfig>;
+    readOnlyProviderConfig: boolean;
     changedProviders: Array<ProviderConfig>;
 
     descriptor: Descriptor;
@@ -114,11 +115,13 @@
                     // Parse the JSON representation
                     contentObj = JSON.parse(this.resourceContent);
                     this.providers = contentObj['providers'];
+                    this.readOnlyProviderConfig = contentObj['readOnly'];
                 } else if (res.name.endsWith('yaml') || res.name.endsWith('yml')) {
                     // Parse the YAML representation
                     let yaml = require('js-yaml');
                     contentObj = yaml.safeLoad(this.resourceContent);
                     this.providers = contentObj['providers'];
+                    this.readOnlyProviderConfig = contentObj['readOnly'];
                 } else if (res.name.endsWith('xml')) {
                     // Parse the XML representation
                     parseString(this.resourceContent,
@@ -146,6 +149,7 @@
                                     tempProviders.push(providerConfig);
                                 });
                                 this.providers = tempProviders;
+                                this.readOnlyProviderConfig = result['gateway'].readOnly;
                             }
                         });
                 }
@@ -563,6 +567,11 @@
         if (this.resourceType === 'Descriptors' && this.descriptor.readOnly) {
             return !Boolean(this.descriptor.readOnly);
         }
+
+        if (this.resourceType === 'Provider Configurations' && this.readOnlyProviderConfig) {
+          return !this.readOnlyProviderConfig;
+        }
+
         return true;
     }
 }
diff --git a/gateway-cm-integration/src/main/java/org/apache/knox/gateway/ClouderaManagerIntegrationMessages.java b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/ClouderaManagerIntegrationMessages.java
index 697e0ea..b5290bd 100644
--- a/gateway-cm-integration/src/main/java/org/apache/knox/gateway/ClouderaManagerIntegrationMessages.java
+++ b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/ClouderaManagerIntegrationMessages.java
@@ -36,11 +36,14 @@
   @Message(level = MessageLevel.INFO, text = "Found Knox descriptors {0} in {1}")
   void parsedClouderaManagerDescriptor(String descriptorList, String path);
 
-  @Message(level = MessageLevel.INFO, text = "Saved Knox descriptor {0}")
-  void savedSimpleDescriptorDescriptor(String path);
+  @Message(level = MessageLevel.INFO, text = "Saved Knox {0} into {1}")
+  void savedResource(String resourceType, String path);
 
-  @Message(level = MessageLevel.INFO, text = "Ignoring {0} Knox descriptor update because it did not change.")
-  void descriptorDidNotChange(String descriptorName);
+  @Message(level = MessageLevel.INFO, text = "Ignoring {0} Knox {1} update because it did not change.")
+  void resourceDidNotChange(String resourceName, String resourceType);
+
+  @Message(level = MessageLevel.ERROR, text = "Parsing Knox shared provider configuration {0} failed: {1}")
+  void failedToParseProviderConfiguration(String name, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
 
   @Message(level = MessageLevel.ERROR, text = "Parsing Knox descriptor {0} failed: {1}")
   void failedToParseDescriptor(String name, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
diff --git a/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorMonitor.java b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorMonitor.java
index 15f74af..ab6369b 100644
--- a/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorMonitor.java
+++ b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorMonitor.java
@@ -48,6 +48,7 @@
 
   private static final String CM_DESCRIPTOR_FILE_EXTENSION = ".cm";
   private static final ClouderaManagerIntegrationMessages LOG = MessagesFactory.get(ClouderaManagerIntegrationMessages.class);
+  private final String sharedProvidersDir;
   private final String descriptorsDir;
   private final long monitoringInterval;
   private final ClouderaManagerDescriptorParser cmDescriptorParser;
@@ -55,6 +56,7 @@
 
   public ClouderaManagerDescriptorMonitor(GatewayConfig gatewayConfig, ClouderaManagerDescriptorParser cmDescriptorParser) {
     this.cmDescriptorParser = cmDescriptorParser;
+    this.sharedProvidersDir = gatewayConfig.getGatewayProvidersConfigDir();
     this.descriptorsDir = gatewayConfig.getGatewayDescriptorsDir();
     this.monitoringInterval = gatewayConfig.getClouderaManagerDescriptorsMonitoringInterval();
   }
@@ -93,15 +95,38 @@
   }
 
   private void processClouderaManagerDescriptor(String descriptorFilePath, String topologyName) {
-    cmDescriptorParser.parse(descriptorFilePath, topologyName).forEach(simpleDescriptor -> {
+    final ClouderaManagerDescriptorParserResult result = cmDescriptorParser.parse(descriptorFilePath, topologyName);
+    processSharedProviders(result);
+    processDescriptors(result);
+  }
+
+  private void processSharedProviders(final ClouderaManagerDescriptorParserResult result) {
+    result.getProviders().forEach((key, value) -> {
+      try {
+        final File knoxProviderConfigFile = new File(sharedProvidersDir, key + ".json");
+        final String providersConfiguration = JsonUtils.renderAsJsonString(value);
+        if (isResourceChangedOrNew(knoxProviderConfigFile, providersConfiguration)) {
+          FileUtils.writeStringToFile(knoxProviderConfigFile, providersConfiguration, StandardCharsets.UTF_8);
+          LOG.savedResource("shared provider", knoxProviderConfigFile.getAbsolutePath());
+        } else {
+          LOG.resourceDidNotChange(key, "shared provider");
+        }
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    });
+  }
+
+  private void processDescriptors(final ClouderaManagerDescriptorParserResult result) {
+    result.getDescriptors().forEach(simpleDescriptor -> {
       try {
         final File knoxDescriptorFile = new File(descriptorsDir, simpleDescriptor.getName() + ".json");
         final String simpleDescriptorJsonString = JsonUtils.renderAsJsonString(simpleDescriptor);
-        if (isDescriptorChangedOrNew(knoxDescriptorFile, simpleDescriptorJsonString)) {
+        if (isResourceChangedOrNew(knoxDescriptorFile, simpleDescriptorJsonString)) {
           FileUtils.writeStringToFile(knoxDescriptorFile, JsonUtils.renderAsJsonString(simpleDescriptor), StandardCharsets.UTF_8);
-          LOG.savedSimpleDescriptorDescriptor(knoxDescriptorFile.getAbsolutePath());
+          LOG.savedResource("descriptor", knoxDescriptorFile.getAbsolutePath());
         } else {
-          LOG.descriptorDidNotChange(simpleDescriptor.getName());
+          LOG.resourceDidNotChange(simpleDescriptor.getName(), "descriptor");
         }
       } catch (IOException e) {
         LOG.failedToProduceKnoxDescriptor(e.getMessage(), e);
@@ -109,7 +134,7 @@
     });
   }
 
-  private boolean isDescriptorChangedOrNew(File knoxDescriptorFile, String simpleDescriptorJsonString) throws IOException {
+  private boolean isResourceChangedOrNew(File knoxDescriptorFile, String simpleDescriptorJsonString) throws IOException {
     if (knoxDescriptorFile.exists()) {
       final String currentContent = FileUtils.readFileToString(knoxDescriptorFile, StandardCharsets.UTF_8);
       return !simpleDescriptorJsonString.equals(currentContent);
diff --git a/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParser.java b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParser.java
index fc8ed8f..122b798 100644
--- a/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParser.java
+++ b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParser.java
@@ -16,21 +16,31 @@
  */
 package org.apache.knox.gateway.cm.descriptor;
 
+import java.io.File;
 import java.nio.file.Paths;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Properties;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.knox.gateway.ClouderaManagerIntegrationMessages;
+import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.topology.discovery.advanced.AdvancedServiceDiscoveryConfig;
 import org.apache.knox.gateway.topology.discovery.advanced.AdvancedServiceDiscoveryConfigChangeListener;
+import org.apache.knox.gateway.topology.simple.JSONProviderConfiguration;
+import org.apache.knox.gateway.topology.simple.JSONProviderConfiguration.JSONProvider;
+import org.apache.knox.gateway.topology.simple.ProviderConfiguration;
+import org.apache.knox.gateway.topology.simple.ProviderConfigurationParser;
 import org.apache.knox.gateway.topology.simple.SimpleDescriptor;
 import org.apache.knox.gateway.topology.simple.SimpleDescriptorImpl;
 import org.apache.knox.gateway.topology.simple.SimpleDescriptorImpl.ApplicationImpl;
@@ -38,6 +48,16 @@
 
 public class ClouderaManagerDescriptorParser implements AdvancedServiceDiscoveryConfigChangeListener {
   private static final ClouderaManagerIntegrationMessages log = MessagesFactory.get(ClouderaManagerIntegrationMessages.class);
+
+  //shared provider related constants
+  private static final String CONFIG_NAME_PROVIDER_CONFIGS_PREFIX = "providerConfigs:";
+  private static final String CONFIG_NAME_PROVIDER_CONFIGS_ROLE_PREFIX = "role=";
+  private static final String CONFIG_NAME_PROVIDER_CONFIGS_NAME_PREFIX = "name=";
+  private static final String CONFIG_NAME_PROVIDER_CONFIGS_ENABLED_PREFIX = "enabled=";
+  private static final String CONFIG_NAME_PROVIDER_CONFIGS_PARAM_PREFIX = "param.";
+  private static final String CONFIG_NAME_PROVIDER_CONFIGS_PARAM_REMOVE = "remove";
+
+  //descriptor related constants
   private static final String CONFIG_NAME_DISCOVERY_TYPE = "discoveryType";
   private static final String CONFIG_NAME_DISCOVERY_ADDRESS = "discoveryAddress";
   private static final String CONFIG_NAME_DISCOVERY_USER = "discoveryUser";
@@ -48,10 +68,12 @@
   private static final String CONFIG_NAME_SERVICE_URL = "url";
   private static final String CONFIG_NAME_SERVICE_VERSION = "version";
 
-  private Map<String, AdvancedServiceDiscoveryConfig> advancedServiceDiscoveryConfigMap;
+  private final Map<String, AdvancedServiceDiscoveryConfig> advancedServiceDiscoveryConfigMap;
+  private final String sharedProvidersDir;
 
-  public ClouderaManagerDescriptorParser() {
-    advancedServiceDiscoveryConfigMap = new ConcurrentHashMap<>();
+  public ClouderaManagerDescriptorParser(GatewayConfig gatewayConfig) {
+    this.advancedServiceDiscoveryConfigMap = new ConcurrentHashMap<>();
+    this.sharedProvidersDir = gatewayConfig.getGatewayProvidersConfigDir();
   }
 
   /**
@@ -61,7 +83,7 @@
    *          The path to the configuration file which holds descriptor information in a pre-defined format.
    * @return A SimpleDescriptor based on the contents of the given file.
    */
-  public Set<SimpleDescriptor> parse(String path) {
+  public ClouderaManagerDescriptorParserResult parse(String path) {
     return parse(path, null);
   }
 
@@ -74,33 +96,108 @@
    *          if set, the parser should only parse a descriptor with the same name
    * @return A SimpleDescriptor based on the contents of the given file.
    */
-  public Set<SimpleDescriptor> parse(String path, String topologyName) {
+  public ClouderaManagerDescriptorParserResult parse(String path, String topologyName) {
     try {
       log.parseClouderaManagerDescriptor(path, topologyName == null ? "all topologies" : topologyName);
       final Configuration xmlConfiguration = new Configuration(false);
       xmlConfiguration.addResource(Paths.get(path).toUri().toURL());
       xmlConfiguration.reloadConfiguration();
-      final Set<SimpleDescriptor> descriptors = parseXmlConfig(xmlConfiguration, topologyName);
-      log.parsedClouderaManagerDescriptor(String.join(", ", descriptors.stream().map(descriptor -> descriptor.getName()).collect(Collectors.toSet())), path);
+      final ClouderaManagerDescriptorParserResult descriptors = parseXmlConfig(xmlConfiguration, topologyName);
+      log.parsedClouderaManagerDescriptor(String.join(", ", descriptors.getDescriptors().stream().map(descriptor -> descriptor.getName()).collect(Collectors.toSet())), path);
       return descriptors;
     } catch (Exception e) {
       log.failedToParseXmlConfiguration(path, e.getMessage(), e);
-      return Collections.emptySet();
+      return new ClouderaManagerDescriptorParserResult();
     }
   }
 
-  private Set<SimpleDescriptor> parseXmlConfig(Configuration xmlConfiguration, String topologyName) {
+  private ClouderaManagerDescriptorParserResult parseXmlConfig(Configuration xmlConfiguration, String topologyName) {
+    final Map<String, ProviderConfiguration> providers = new LinkedHashMap<>();
     final Set<SimpleDescriptor> descriptors = new LinkedHashSet<>();
     xmlConfiguration.forEach(xmlDescriptor -> {
-      String descriptorName = xmlDescriptor.getKey();
-      if (topologyName == null || descriptorName.equals(topologyName)) {
-        SimpleDescriptor descriptor = parseXmlDescriptor(descriptorName, xmlDescriptor.getValue());
-        if (descriptor != null) {
-          descriptors.add(descriptor);
+      String xmlConfigurationKey = xmlDescriptor.getKey();
+      if (xmlConfigurationKey.startsWith(CONFIG_NAME_PROVIDER_CONFIGS_PREFIX)) {
+        final String[] providerConfigurations = xmlConfigurationKey.replace(CONFIG_NAME_PROVIDER_CONFIGS_PREFIX, "").split(",");
+        Arrays.asList(providerConfigurations).stream().map(providerConfigurationName -> providerConfigurationName.trim()).forEach(providerConfigurationName -> {
+          final File providerConfigFile = resolveProviderConfiguration(providerConfigurationName);
+          try {
+            final ProviderConfiguration providerConfiguration = getProviderConfiguration(providers, providerConfigFile, providerConfigurationName);
+            providerConfiguration.setReadOnly(true);
+            providerConfiguration.saveOrUpdateProviders(parseProviderConfigurations(xmlDescriptor.getValue(), providerConfiguration));
+            providers.put(providerConfigurationName, providerConfiguration);
+          } catch (Exception e) {
+            log.failedToParseProviderConfiguration(providerConfigurationName, e.getMessage(), e);
+          }
+        });
+      } else {
+        if (topologyName == null || xmlConfigurationKey.equals(topologyName)) {
+          SimpleDescriptor descriptor = parseXmlDescriptor(xmlConfigurationKey, xmlDescriptor.getValue());
+          if (descriptor != null) {
+            descriptors.add(descriptor);
+          }
         }
       }
     });
-    return descriptors;
+    return new ClouderaManagerDescriptorParserResult(providers, descriptors);
+  }
+
+  private ProviderConfiguration getProviderConfiguration(Map<String, ProviderConfiguration> providers, File providerConfigFile, String providerConfigName)
+      throws Exception {
+    if (providers.containsKey(providerConfigName)) {
+      return providers.get(providerConfigName);
+    } else {
+      return providerConfigFile == null ? new JSONProviderConfiguration() : ProviderConfigurationParser.parse(providerConfigFile);
+    }
+  }
+
+  private File resolveProviderConfiguration(String providerConfigurationName) {
+    for (String supportedExtension : ProviderConfigurationParser.SUPPORTED_EXTENSIONS) {
+      File providerConfigFile = new File(sharedProvidersDir, providerConfigurationName + "." + supportedExtension);
+      if (providerConfigFile.exists()) {
+        return providerConfigFile;
+      }
+    }
+    return null;
+  }
+
+  private Set<ProviderConfiguration.Provider> parseProviderConfigurations(String xmlValue, ProviderConfiguration providerConfiguration) {
+    final Set<ProviderConfiguration.Provider> providers = new LinkedHashSet<>();
+    final List<String> configurationPairs = Arrays.asList(xmlValue.split("#"));
+    final Set<String> roles = configurationPairs.stream().filter(configurationPair -> configurationPair.trim().startsWith(CONFIG_NAME_PROVIDER_CONFIGS_ROLE_PREFIX))
+        .map(configurationPair -> configurationPair.replace(CONFIG_NAME_PROVIDER_CONFIGS_ROLE_PREFIX, "").trim()).collect(Collectors.toSet());
+    for (String role : roles) {
+      providers.add(parseProvider(configurationPairs, role, providerConfiguration));
+    }
+    return providers;
+  }
+
+  private ProviderConfiguration.Provider parseProvider(List<String> configurationPairs, String role, ProviderConfiguration providerConfiguration) {
+    final JSONProvider provider = new JSONProvider();
+    provider.setRole(role);
+    getParamsForRole(role, providerConfiguration).forEach((key, value) -> provider.addParam(key, value)); //initializing parameters (if any)
+    provider.setEnabled(true); //may be overwritten later, but defaulting to 'true'
+    final Set<String> roleConfigurations = configurationPairs.stream().filter(configurationPair -> configurationPair.trim().startsWith(role))
+        .map(configurationPair -> configurationPair.replace(role + ".", "").trim()).collect(Collectors.toSet());
+    for (String roleConfiguration : roleConfigurations) {
+      if (roleConfiguration.startsWith(CONFIG_NAME_PROVIDER_CONFIGS_PARAM_PREFIX)) {
+        String[] paramKeyValue = roleConfiguration.replace(CONFIG_NAME_PROVIDER_CONFIGS_PARAM_PREFIX, "").split("=", 2);
+        if (CONFIG_NAME_PROVIDER_CONFIGS_PARAM_REMOVE.equals(paramKeyValue[0])) {
+          provider.removeParam(paramKeyValue[1]);
+        } else {
+          provider.addParam(paramKeyValue[0], paramKeyValue[1]);
+        }
+      } else if (roleConfiguration.startsWith(CONFIG_NAME_PROVIDER_CONFIGS_NAME_PREFIX)) {
+        provider.setName(roleConfiguration.replace(CONFIG_NAME_PROVIDER_CONFIGS_NAME_PREFIX, ""));
+      } else if (roleConfiguration.startsWith(CONFIG_NAME_PROVIDER_CONFIGS_ENABLED_PREFIX)) {
+        provider.setEnabled(Boolean.valueOf(roleConfiguration.replace(CONFIG_NAME_PROVIDER_CONFIGS_ENABLED_PREFIX, "")));
+      }
+    }
+    return provider;
+  }
+
+  private Map<String, String> getParamsForRole(String role, ProviderConfiguration providerConfiguration) {
+    final Optional<ProviderConfiguration.Provider> provider = providerConfiguration.getProviders().stream().filter(p -> p.getRole().equals(role)).findFirst();
+    return provider.isPresent() ? provider.get().getParams() : new TreeMap<>();
   }
 
   private SimpleDescriptor parseXmlDescriptor(String name, String xmlValue) {
@@ -108,7 +205,7 @@
       final SimpleDescriptorImpl descriptor = new SimpleDescriptorImpl();
       descriptor.setReadOnly(true);
       descriptor.setName(name);
-      final String[] configurationPairs = xmlValue.split(";");
+      final String[] configurationPairs = xmlValue.split("#");
       for (String configurationPair : configurationPairs) {
         String[] parameterPairParts = configurationPair.trim().split("=", 2);
         String parameterName = parameterPairParts[0].trim();
diff --git a/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParserResult.java b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParserResult.java
new file mode 100644
index 0000000..bd24626
--- /dev/null
+++ b/gateway-cm-integration/src/main/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParserResult.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.cm.descriptor;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.knox.gateway.topology.simple.ProviderConfiguration;
+import org.apache.knox.gateway.topology.simple.SimpleDescriptor;
+
+class ClouderaManagerDescriptorParserResult {
+  final Map<String, ProviderConfiguration> providers;
+  final Set<SimpleDescriptor> descriptors;
+
+  ClouderaManagerDescriptorParserResult() {
+    this(Collections.emptyMap(), Collections.emptySet());
+  }
+
+  ClouderaManagerDescriptorParserResult(Map<String, ProviderConfiguration> providers, Set<SimpleDescriptor> descriptors) {
+    this.providers = providers;
+    this.descriptors = descriptors;
+  }
+
+  public Map<String, ProviderConfiguration> getProviders() {
+    return Collections.unmodifiableMap(providers);
+  }
+
+  public Set<SimpleDescriptor> getDescriptors() {
+    return Collections.unmodifiableSet(descriptors);
+  }
+
+}
diff --git a/gateway-cm-integration/src/test/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParserTest.java b/gateway-cm-integration/src/test/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParserTest.java
index f65d36c..d1c4dd6 100644
--- a/gateway-cm-integration/src/test/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParserTest.java
+++ b/gateway-cm-integration/src/test/java/org/apache/knox/gateway/cm/descriptor/ClouderaManagerDescriptorParserTest.java
@@ -20,7 +20,11 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -30,53 +34,71 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.commons.io.FileUtils;
+import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.topology.discovery.advanced.AdvancedServiceDiscoveryConfig;
+import org.apache.knox.gateway.topology.simple.ProviderConfiguration;
 import org.apache.knox.gateway.topology.simple.SimpleDescriptor;
 import org.apache.knox.gateway.topology.simple.SimpleDescriptor.Application;
 import org.apache.knox.gateway.topology.simple.SimpleDescriptor.Service;
+import org.apache.knox.gateway.util.JsonUtils;
+import org.easymock.EasyMock;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 
 public class ClouderaManagerDescriptorParserTest {
 
+  @Rule
+  public TemporaryFolder tempDir = new TemporaryFolder();
+
+  private GatewayConfig gatewayConfigMock;
   private ClouderaManagerDescriptorParser cmDescriptorParser;
+  private File providersDir;
 
   @Before
-  public void setUp() {
-    cmDescriptorParser = new ClouderaManagerDescriptorParser();
+  public void setUp() throws IOException {
+    providersDir = tempDir.newFolder("shared-providers");
+    gatewayConfigMock = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gatewayConfigMock.getGatewayProvidersConfigDir()).andReturn(providersDir.getAbsolutePath()).anyTimes();
+    EasyMock.replay(gatewayConfigMock);
+    cmDescriptorParser = new ClouderaManagerDescriptorParser(gatewayConfigMock);
   }
 
   @Test
   public void testCMDescriptorParser() throws Exception {
     final String testConfigPath = this.getClass().getClassLoader().getResource("testDescriptor.xml").getPath();
-    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath);
+    final ClouderaManagerDescriptorParserResult parserResult = cmDescriptorParser.parse(testConfigPath);
+    final Set<SimpleDescriptor> descriptors = parserResult.getDescriptors();
     assertEquals(2, descriptors.size());
     final Iterator<SimpleDescriptor> descriptorsIterator = descriptors.iterator();
-    validateTopology1(descriptorsIterator.next());
-    validateTopology2(descriptorsIterator.next(), true);
+    validateTopology1Descriptors(descriptorsIterator.next());
+    validateTopology2Descriptors(descriptorsIterator.next(), true);
+    validateTestDescriptorProviderConfigs(parserResult.getProviders(), "ldap://localhost:33389");
   }
 
   @Test
   public void testCMDescriptorParserOnlyTopology2() throws Exception {
     final String testConfigPath = this.getClass().getClassLoader().getResource("testDescriptor.xml").getPath();
-    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath, "topology2");
+    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath, "topology2").getDescriptors();
     assertEquals(1, descriptors.size());
-    validateTopology2(descriptors.iterator().next(), true);
+    validateTopology2Descriptors(descriptors.iterator().next(), true);
   }
 
   @Test
   public void testCMDescriptorParserWrongDescriptorContent() throws Exception {
     final String testConfigPath = this.getClass().getClassLoader().getResource("testDescriptorConfigurationWithWrongDescriptor.xml").getPath();
-    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath);
+    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath).getDescriptors();
     assertEquals(1, descriptors.size());
     final Iterator<SimpleDescriptor> descriptorsIterator = descriptors.iterator();
-    validateTopology1(descriptorsIterator.next());
+    validateTopology1Descriptors(descriptorsIterator.next());
   }
 
   @Test
   public void testCMDescriptorParserWrongXMLContent() throws Exception {
     final String testConfigPath = this.getClass().getClassLoader().getResource("testDescriptorConfigurationWithNonHadoopStyleConfiguration.xml").getPath();
-    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath);
+    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath).getDescriptors();
     assertTrue(descriptors.isEmpty());
   }
 
@@ -94,7 +116,7 @@
     advancedConfigurationTopology2.put(AdvancedServiceDiscoveryConfig.PARAMETER_NAME_TOPOLOGY_NAME, "topology2");
     cmDescriptorParser.onAdvancedServiceDiscoveryConfigurationChange(advancedConfigurationTopology2);
 
-    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath);
+    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath).getDescriptors();
     assertEquals(2, descriptors.size());
     final Iterator<SimpleDescriptor> descriptorsIterator = descriptors.iterator();
     SimpleDescriptor topology1 = descriptorsIterator.next();
@@ -105,7 +127,7 @@
     SimpleDescriptor topology2 = descriptorsIterator.next();
     assertNotNull(topology2);
     // topology1 comes with ATLAS and NIFI but the latter one is disabled
-    validateTopology2(topology2, false);
+    validateTopology2Descriptors(topology2, false);
   }
 
   @Test
@@ -115,7 +137,7 @@
     advancedConfiguration.put(buildEnabledParameter("topology1", "oozie"), "true"); //it should not matter if service name is lowercase advanced configuration
     advancedConfiguration.put(AdvancedServiceDiscoveryConfig.PARAMETER_NAME_TOPOLOGY_NAME, "topology1");
     cmDescriptorParser.onAdvancedServiceDiscoveryConfigurationChange(advancedConfiguration);
-    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath);
+    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath).getDescriptors();
     final Iterator<SimpleDescriptor> descriptorsIterator = descriptors.iterator();
     SimpleDescriptor descriptor = descriptorsIterator.next();
     assertNotNull(descriptor);
@@ -123,10 +145,54 @@
     assertService(descriptor, "OOZIE", null, null, null);
 
     descriptor = descriptorsIterator.next();
-    validateTopology2(descriptor, true);
+    validateTopology2Descriptors(descriptor, true);
     assertNull(descriptor.getService("OOZIE"));
   }
 
+  @Test
+  public void testCMDescriptorParserModifyingProviderParams() {
+    String testConfigPath = this.getClass().getClassLoader().getResource("testDescriptor.xml").getPath();
+    ClouderaManagerDescriptorParserResult parserResult = cmDescriptorParser.parse(testConfigPath);
+    validateTestDescriptorProviderConfigs(parserResult.getProviders(), "ldap://localhost:33389");
+
+    //saving admin and knoxsso shared-providers with LDAP authentication provider only
+    parserResult.getProviders().forEach((key, value) -> {
+      final File knoxProviderConfigFile = new File(providersDir, key + ".json");
+      final String providersConfiguration = JsonUtils.renderAsJsonString(value);
+      try {
+        FileUtils.writeStringToFile(knoxProviderConfigFile, providersConfiguration, StandardCharsets.UTF_8);
+      } catch (IOException e) {
+        fail("Could not save " + knoxProviderConfigFile.getAbsolutePath());
+      }
+    });
+
+    //updating LDAP URL from ldap://localhost:33389 to ldaps://localhost:33390 in 'admin'
+    testConfigPath = this.getClass().getClassLoader().getResource("testDescriptorWithAdminProviderConfigUpdatedLdapUrl.xml").getPath();
+    parserResult = cmDescriptorParser.parse(testConfigPath);
+    validateTestDescriptorProviderConfigs(parserResult.getProviders(), "ldaps://localhost:33390", true, true);
+  }
+
+  @Test
+  public void testCMDescriptorParserRemovingProviderParams() {
+    String testConfigPath = this.getClass().getClassLoader().getResource("testDescriptor.xml").getPath();
+    ClouderaManagerDescriptorParserResult parserResult = cmDescriptorParser.parse(testConfigPath);
+    //saving admin and knoxsso shared-providers with LDAP authentication provider only
+    parserResult.getProviders().forEach((key, value) -> {
+      final File knoxProviderConfigFile = new File(providersDir, key + ".json");
+      final String providersConfiguration = JsonUtils.renderAsJsonString(value);
+      try {
+        FileUtils.writeStringToFile(knoxProviderConfigFile, providersConfiguration, StandardCharsets.UTF_8);
+      } catch (IOException e) {
+        fail("Could not save " + knoxProviderConfigFile.getAbsolutePath());
+      }
+    });
+
+    //removed 'main.ldapRealm.userDnTemplate' parameter from 'admin'
+    testConfigPath = this.getClass().getClassLoader().getResource("testDescriptorWithAdminProviderConfigRemovedUserDnTemplate.xml").getPath();
+    parserResult = cmDescriptorParser.parse(testConfigPath);
+    validateTestDescriptorProviderConfigs(parserResult.getProviders(), "ldap://localhost:33389", true, false);
+  }
+
   private String buildEnabledParameter(String topologyName, String serviceName) {
     return AdvancedServiceDiscoveryConfig.PARAMETER_NAME_PREFIX_ENABLED_SERVICE + topologyName + AdvancedServiceDiscoveryConfig.PARAMETER_NAME_POSTFIX_ENABLED_SERVICE + serviceName;
   }
@@ -141,7 +207,7 @@
     advancedConfiguration.put(AdvancedServiceDiscoveryConfig.PARAMETER_NAME_DISCOVERY_ADDRESS, address);
     advancedConfiguration.put(AdvancedServiceDiscoveryConfig.PARAMETER_NAME_DISCOVERY_CLUSTER, cluster);
     cmDescriptorParser.onAdvancedServiceDiscoveryConfigurationChange(advancedConfiguration);
-    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath);
+    final Set<SimpleDescriptor> descriptors = cmDescriptorParser.parse(testConfigPath).getDescriptors();
     final Iterator<SimpleDescriptor> descriptorsIterator = descriptors.iterator();
     SimpleDescriptor descriptor = descriptorsIterator.next();
     assertEquals(address, descriptor.getDiscoveryAddress());
@@ -149,7 +215,7 @@
     assertEquals("ClouderaManager", descriptor.getDiscoveryType());
   }
 
-  private void validateTopology1(SimpleDescriptor descriptor) {
+  private void validateTopology1Descriptors(SimpleDescriptor descriptor) {
     assertTrue(descriptor.isReadOnly());
     assertEquals("topology1", descriptor.getName());
     assertEquals("ClouderaManager", descriptor.getDiscoveryType());
@@ -168,7 +234,7 @@
     assertService(descriptor, "HIVE", "1.0", Collections.singletonList("http://localhost:456"), expectedServiceParameters);
   }
 
-  private void validateTopology2(SimpleDescriptor descriptor, boolean nifiExpected) {
+  private void validateTopology2Descriptors(SimpleDescriptor descriptor, boolean nifiExpected) {
     assertTrue(descriptor.isReadOnly());
     assertEquals("topology2", descriptor.getName());
     assertEquals("Ambari", descriptor.getDiscoveryType());
@@ -219,4 +285,40 @@
     }
   }
 
+  private void validateTestDescriptorProviderConfigs(Map<String, ProviderConfiguration> providers, String expectedLdapUrl) {
+    validateTestDescriptorProviderConfigs(providers, expectedLdapUrl, false, true);
+  }
+
+  private void validateTestDescriptorProviderConfigs(Map<String, ProviderConfiguration> providers, String expectedLdapUrl, boolean onlyAdminIsExpected, boolean expectUserDnTemplateParam) {
+    assertNotNull(providers);
+    assertEquals(onlyAdminIsExpected ? 1 : 2, providers.size());
+    final ProviderConfiguration adminProviderConfig = providers.get("admin");
+    assertTrue(adminProviderConfig.isReadOnly());
+    assertNotNull(adminProviderConfig);
+    assertEquals(1, adminProviderConfig.getProviders().size());
+    final ProviderConfiguration.Provider authenticationProvider = adminProviderConfig.getProviders().iterator().next();
+    assertEquals("authentication", authenticationProvider.getRole());
+    assertEquals("ShiroProvider", authenticationProvider.getName());
+    assertTrue(authenticationProvider.isEnabled());
+    assertEquals(expectUserDnTemplateParam ? 10 : 9, authenticationProvider.getParams().size());
+    assertEquals("30", authenticationProvider.getParams().get("sessionTimeout"));
+    assertEquals("org.apache.knox.gateway.shirorealm.KnoxLdapContextFactory", authenticationProvider.getParams().get("main.ldapContextFactory"));
+    assertEquals("org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm", authenticationProvider.getParams().get("main.ldapRealm"));
+    assertEquals("$ldapContextFactory", authenticationProvider.getParams().get("main.ldapRealm.contextFactory"));
+    assertEquals("simple", authenticationProvider.getParams().get("main.ldapRealm.contextFactory.authenticationMechanism"));
+    assertEquals(expectedLdapUrl, authenticationProvider.getParams().get("main.ldapRealm.contextFactory.url"));
+    assertEquals("uid=guest,ou=people,dc=hadoop,dc=apache,dc=org", authenticationProvider.getParams().get("main.ldapRealm.contextFactory.systemUsername"));
+    assertEquals("${ALIAS=knoxLdapSystemPassword}", authenticationProvider.getParams().get("main.ldapRealm.contextFactory.systemPassword"));
+    if (expectUserDnTemplateParam) {
+      assertEquals("uid={0},ou=people,dc=hadoop,dc=apache,dc=org", authenticationProvider.getParams().get("main.ldapRealm.userDnTemplate"));
+    } else {
+      assertNull(authenticationProvider.getParams().get("main.ldapRealm.userDnTemplate"));
+    }
+    assertEquals("authcBasic", authenticationProvider.getParams().get("urls./**"));
+    if (!onlyAdminIsExpected) {
+      final ProviderConfiguration knoxSsoProviderConfig = providers.get("knoxsso");
+      assertNotNull(knoxSsoProviderConfig);
+      assertEquals(adminProviderConfig, knoxSsoProviderConfig);
+    }
+  }
 }
diff --git a/gateway-cm-integration/src/test/resources/testDescriptor.xml b/gateway-cm-integration/src/test/resources/testDescriptor.xml
index a2593df..64308a6 100644
--- a/gateway-cm-integration/src/test/resources/testDescriptor.xml
+++ b/gateway-cm-integration/src/test/resources/testDescriptor.xml
@@ -18,31 +18,48 @@
   <property>
     <name>topology1</name>
     <value>
-        discoveryType=ClouderaManager;
-        discoveryAddress=http://host:123;
-        discoveryUser=user;
-        discoveryPasswordAlias=alias;
-        cluster=Cluster 1;
-        providerConfigRef=topology1-provider;
-        app:knoxauth:param1.name=param1.value;
-        app:admin-ui;
-        HIVE:url=http://localhost:456;
-        HIVE:version=1.0;
-        HIVE:httpclient.connectionTimeout=5m;
+        discoveryType=ClouderaManager#
+        discoveryAddress=http://host:123#
+        discoveryUser=user#
+        discoveryPasswordAlias=alias#
+        cluster=Cluster 1#
+        providerConfigRef=topology1-provider#
+        app:knoxauth:param1.name=param1.value#
+        app:admin-ui#
+        HIVE:url=http://localhost:456#
+        HIVE:version=1.0#
+        HIVE:httpclient.connectionTimeout=5m#
         HIVE:httpclient.socketTimeout=100m
     </value>
   </property>
   <property>
     <name>topology2</name>
     <value>
-        discoveryType=Ambari;
-        discoveryAddress=http://host:456;
-        cluster=Cluster 2;
-        providerConfigRef=topology2-provider;
-        ATLAS-API:url=http://localhost:456;
-        ATLAS-API:httpclient.connectionTimeout=5m;
-        ATLAS-API:httpclient.socketTimeout=100m;
+        discoveryType=Ambari#
+        discoveryAddress=http://host:456#
+        cluster=Cluster 2#
+        providerConfigRef=topology2-provider#
+        ATLAS-API:url=http://localhost:456#
+        ATLAS-API:httpclient.connectionTimeout=5m#
+        ATLAS-API:httpclient.socketTimeout=100m#
         NIFI
     </value>
   </property>
+  <property>
+      <name>providerConfigs:admin, knoxsso</name>
+      <value>
+          role=authentication#
+          authentication.name=ShiroProvider#
+          authentication.param.sessionTimeout=30#
+          authentication.param.main.ldapRealm=org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm#
+          authentication.param.main.ldapContextFactory=org.apache.knox.gateway.shirorealm.KnoxLdapContextFactory#
+          authentication.param.main.ldapRealm.contextFactory=$ldapContextFactory#
+          authentication.param.main.ldapRealm.contextFactory.authenticationMechanism=simple#
+          authentication.param.main.ldapRealm.contextFactory.url=ldap://localhost:33389#
+          authentication.param.main.ldapRealm.contextFactory.systemUsername=uid=guest,ou=people,dc=hadoop,dc=apache,dc=org#
+          authentication.param.main.ldapRealm.contextFactory.systemPassword=${ALIAS=knoxLdapSystemPassword}#
+          authentication.param.main.ldapRealm.userDnTemplate=uid={0},ou=people,dc=hadoop,dc=apache,dc=org#
+          authentication.param.urls./**=authcBasic
+      </value>
+  </property>
 </configuration>
\ No newline at end of file
diff --git a/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithNonHadoopStyleConfiguration.xml b/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithNonHadoopStyleConfiguration.xml
index 43deb1e..9638739 100644
--- a/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithNonHadoopStyleConfiguration.xml
+++ b/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithNonHadoopStyleConfiguration.xml
@@ -18,17 +18,17 @@
   <config> <!-- should be property -->
     <name>topology1</name>
     <value>
-        discoveryType=ClouderaManager;
-        discoveryAddress=http://host:123;
-        discoveryUser=user;
-        discoveryPasswordAlias=alias;
-        cluster=Cluster 1;
-        providerConfigRef=topology1-provider;
-        app:knoxauth:param1.name=param1.value;
-        app:admin-ui;
-        HIVE:url=http://localhost:456;
-        HIVE:version=1.0;
-        HIVE:httpclient.connectionTimeout=5m;
+        discoveryType=ClouderaManager#
+        discoveryAddress=http://host:123#
+        discoveryUser=user#
+        discoveryPasswordAlias=alias#
+        cluster=Cluster 1#
+        providerConfigRef=topology1-provider#
+        app:knoxauth:param1.name=param1.value#
+        app:admin-ui#
+        HIVE:url=http://localhost:456#
+        HIVE:version=1.0#
+        HIVE:httpclient.connectionTimeout=5m#
         HIVE:httpclient.socketTimeout=100m
     </value>
   </config>
diff --git a/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithWrongDescriptor.xml b/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithWrongDescriptor.xml
index a249162..9188843 100644
--- a/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithWrongDescriptor.xml
+++ b/gateway-cm-integration/src/test/resources/testDescriptorConfigurationWithWrongDescriptor.xml
@@ -18,28 +18,28 @@
   <property>
     <name>topology1</name>
     <value>
-        discoveryType=ClouderaManager;
-        discoveryAddress=http://host:123;
-        discoveryUser=user;
-        discoveryPasswordAlias=alias;
-        cluster=Cluster 1;
-        providerConfigRef=topology1-provider;
-        app:knoxauth:param1.name=param1.value;
-        app:admin-ui;
-        HIVE:url=http://localhost:456;
-        HIVE:version=1.0;
-        HIVE:httpclient.connectionTimeout=5m;
+        discoveryType=ClouderaManager#
+        discoveryAddress=http://host:123#
+        discoveryUser=user#
+        discoveryPasswordAlias=alias#
+        cluster=Cluster 1#
+        providerConfigRef=topology1-provider#
+        app:knoxauth:param1.name=param1.value#
+        app:admin-ui#
+        HIVE:url=http://localhost:456#
+        HIVE:version=1.0#
+        HIVE:httpclient.connectionTimeout=5m#
         HIVE:httpclient.socketTimeout=100m
     </value>
   </property>
   <property>
     <name>topology2</name>
     <value>
-        discoveryType=Ambari;
-        discoveryAddress=http://host:456;
-        cluster=Cluster 2;
-        providerConfigRef=topology2-provider;
-        HDFS:url=http://localhost:456;
+        discoveryType=Ambari#
+        discoveryAddress=http://host:456#
+        cluster=Cluster 2#
+        providerConfigRef=topology2-provider#
+        HDFS:url=http://localhost:456#
         HDFS:noValueParam <!-- can not be parsed -->
     </value>
   </property>
diff --git a/gateway-cm-integration/src/test/resources/testDescriptorWithAdminProviderConfigRemovedUserDnTemplate.xml b/gateway-cm-integration/src/test/resources/testDescriptorWithAdminProviderConfigRemovedUserDnTemplate.xml
new file mode 100644
index 0000000..7dcfd3c
--- /dev/null
+++ b/gateway-cm-integration/src/test/resources/testDescriptorWithAdminProviderConfigRemovedUserDnTemplate.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<configuration>
+  <property>
+      <name>providerConfigs:admin</name>
+      <value>
+          role=authentication#
+          authentication.name=ShiroProvider#
+          authentication.param.remove=main.ldapRealm.userDnTemplate
+      </value>
+  </property>
+</configuration>
\ No newline at end of file
diff --git a/gateway-cm-integration/src/test/resources/testDescriptorWithAdminProviderConfigUpdatedLdapUrl.xml b/gateway-cm-integration/src/test/resources/testDescriptorWithAdminProviderConfigUpdatedLdapUrl.xml
new file mode 100644
index 0000000..b22e974
--- /dev/null
+++ b/gateway-cm-integration/src/test/resources/testDescriptorWithAdminProviderConfigUpdatedLdapUrl.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<configuration>
+  <property>
+      <name>providerConfigs:admin</name>
+      <value>
+          role=authentication#
+          authentication.name=ShiroProvider#
+          authentication.param.main.ldapRealm.contextFactory.url=ldaps://localhost:33390
+      </value>
+  </property>
+</configuration>
\ No newline at end of file
diff --git a/gateway-cm-integration/src/test/resources/testDescriptorWithoutDiscoveryDetails.xml b/gateway-cm-integration/src/test/resources/testDescriptorWithoutDiscoveryDetails.xml
index 2773d35..efa6715 100644
--- a/gateway-cm-integration/src/test/resources/testDescriptorWithoutDiscoveryDetails.xml
+++ b/gateway-cm-integration/src/test/resources/testDescriptorWithoutDiscoveryDetails.xml
@@ -18,10 +18,10 @@
   <property>
     <name>topology1</name>
     <value>
-        providerConfigRef=topology1-provider;
-        app:knoxauth:param1.name=param1.value;
-        app:admin-ui;
-        HIVE:url=http://localhost:123;
+        providerConfigRef=topology1-provider#
+        app:knoxauth:param1.name=param1.value#
+        app:admin-ui#
+        HIVE:url=http://localhost:123
     </value>
   </property>
 </configuration>
\ No newline at end of file
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
index 7a1c340..3b36ee8 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
@@ -715,7 +715,7 @@
   }
 
   private void handleClouderaManagerDescriptors() {
-    final ClouderaManagerDescriptorParser cmDescriptorParser = new ClouderaManagerDescriptorParser();
+    final ClouderaManagerDescriptorParser cmDescriptorParser = new ClouderaManagerDescriptorParser(config);
     final ClouderaManagerDescriptorMonitor cmDescriptorMonitor = new ClouderaManagerDescriptorMonitor(config, cmDescriptorParser);
     final AdvancedServiceDiscoveryConfigurationMonitor advancedServiceDiscoveryConfigurationMonitor = new AdvancedServiceDiscoveryConfigurationMonitor(config);
     advancedServiceDiscoveryConfigurationMonitor.registerListener(cmDescriptorParser);
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParserTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParserTest.java
index e295302..bde99e8 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParserTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParserTest.java
@@ -29,8 +29,8 @@
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
-import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -84,7 +84,7 @@
     ProviderConfiguration pc = doTestParseProviderConfiguration(XML, "my-providers.xml");
     assertNotNull(pc);
 
-    List<ProviderConfiguration.Provider> providers = pc.getProviders();
+    Set<ProviderConfiguration.Provider> providers = pc.getProviders();
     assertNotNull(providers);
     assertFalse(providers.isEmpty());
     assertEquals(3, providers.size());
@@ -130,13 +130,13 @@
     "      \"name\":\"ShiroProvider\",\n" +
     "      \"enabled\":\"true\",\n" +
     "      \"params\":{\n" +
-    "        \"sessionTimeout\":\"30\",\n" +
-    "        \"main.ldapRealm\":\"org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm\",\n" +
     "        \"main.ldapContextFactory\":\"org.apache.hadoop.gateway.shirorealm.KnoxLdapContextFactory\",\n" +
+    "        \"main.ldapRealm\":\"org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm\",\n" +
     "        \"main.ldapRealm.contextFactory\":\"$ldapContextFactory\",\n" +
     "        \"main.ldapRealm.userDnTemplate\":\"uid={0},ou=people,dc=hadoop,dc=apache,dc=org\",\n" +
     "        \"main.ldapRealm.contextFactory.url\":\"ldap://localhost:33389\",\n" +
     "        \"main.ldapRealm.contextFactory.authenticationMechanism\":\"simple\",\n" +
+    "        \"sessionTimeout\":\"30\",\n" +
     "        \"urls./**\":\"authcBasic\"\n" +
     "      }\n" +
     "    },\n" +
@@ -158,8 +158,8 @@
     "      \"name\":\"HaProvider\",\n" +
     "      \"enabled\":\"false\",\n" +
     "      \"params\":{\n" +
-    "        \"WEBHDFS\":\"maxFailoverAttempts=3;failoverSleep=1000;enabled=true\",\n" +
-    "        \"HIVE\":\"maxFailoverAttempts=3;failoverSleep=1000;enabled=true\"\n" +
+    "        \"HIVE\":\"maxFailoverAttempts=3;failoverSleep=1000;enabled=true\",\n" +
+    "        \"WEBHDFS\":\"maxFailoverAttempts=3;failoverSleep=1000;enabled=true\"\n" +
     "      }\n" +
     "    }\n" +
     "  ]\n" +
@@ -168,7 +168,7 @@
     ProviderConfiguration pc = doTestParseProviderConfiguration(JSON, "my-providers." + "json");
     assertNotNull(pc);
 
-    List<ProviderConfiguration.Provider> providers = pc.getProviders();
+    Set<ProviderConfiguration.Provider> providers = pc.getProviders();
     assertNotNull(providers);
     assertFalse(providers.isEmpty());
     assertEquals(4, providers.size());
@@ -225,7 +225,7 @@
 
     assertNotNull(pc);
 
-    List<ProviderConfiguration.Provider> providers = pc.getProviders();
+    Set<ProviderConfiguration.Provider> providers = pc.getProviders();
     assertNotNull(providers);
     assertFalse(providers.isEmpty());
     assertEquals(4, providers.size());
@@ -235,7 +235,7 @@
   }
 
 
-  private void validateParsedProviders(List<ProviderConfiguration.Provider> providers) throws Exception {
+  private void validateParsedProviders(Set<ProviderConfiguration.Provider> providers) throws Exception {
     // Validate the providers
     for (ProviderConfiguration.Provider provider : providers) {
       String role = provider.getRole();
@@ -255,13 +255,13 @@
         assertEquals(params.get("urls./**"), "authcBasic");
 
         // Verify the param order was maintained during parsing (KNOX-1188)
-        String[] expectedParameterOrder = new String[] {"sessionTimeout",
+        String[] expectedParameterOrder = new String[] {"main.ldapContextFactory",
                                                         "main.ldapRealm",
-                                                        "main.ldapContextFactory",
                                                         "main.ldapRealm.contextFactory",
-                                                        "main.ldapRealm.userDnTemplate",
-                                                        "main.ldapRealm.contextFactory.url",
                                                         "main.ldapRealm.contextFactory.authenticationMechanism",
+                                                        "main.ldapRealm.contextFactory.url",
+                                                        "main.ldapRealm.userDnTemplate",
+                                                        "sessionTimeout",
                                                         "urls./**"};
         int index = 0;
         for (String name : params.keySet()) {
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java
index 58304a2..3610389 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java
@@ -49,6 +49,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -889,8 +890,8 @@
         assertNotNull(generatedProviderConfiguration);
 
         // Compare the generated ProviderConfiguration to the expected one
-        List<ProviderConfiguration.Provider> expectedProviders = expected.getProviders();
-        List<ProviderConfiguration.Provider> actualProviders = generatedProviderConfiguration.getProviders();
+        Set<ProviderConfiguration.Provider> expectedProviders = expected.getProviders();
+        Set<ProviderConfiguration.Provider> actualProviders = generatedProviderConfiguration.getProviders();
         assertEquals("The number of providers should be the same.", expectedProviders.size(), actualProviders.size());
 
         for (ProviderConfiguration.Provider expectedProvider : expectedProviders) {
@@ -905,7 +906,7 @@
      * @param expected        A Provider that should be among the specified actual providers
      * @param actualProviders The set of actual providers.
      */
-    private boolean validateProvider(ProviderConfiguration.Provider expected, List<ProviderConfiguration.Provider> actualProviders) {
+    private boolean validateProvider(ProviderConfiguration.Provider expected, Set<ProviderConfiguration.Provider> actualProviders) {
         boolean foundMatch = false;
 
         for (ProviderConfiguration.Provider actual : actualProviders) {
diff --git a/gateway-topology-simple/pom.xml b/gateway-topology-simple/pom.xml
index 8d78f87..8e48a2b 100644
--- a/gateway-topology-simple/pom.xml
+++ b/gateway-topology-simple/pom.xml
@@ -63,6 +63,10 @@
             <artifactId>jaxb-api</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.hadoop</groupId>
             <artifactId>hadoop-common</artifactId>
         </dependency>
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/JSONProviderConfiguration.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/JSONProviderConfiguration.java
new file mode 100644
index 0000000..66199d7
--- /dev/null
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/JSONProviderConfiguration.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.simple;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+@JsonPropertyOrder({ "providers", "readOnly" })
+public class JSONProviderConfiguration implements ProviderConfiguration {
+
+  @JsonProperty("providers")
+  @JsonSerialize(contentAs = JSONProvider.class)
+  @JsonDeserialize(contentAs = JSONProvider.class)
+  private Set<Provider> providers;
+
+  @JsonProperty("readOnly")
+  private boolean readOnly;
+
+  @Override
+  public Set<Provider> getProviders() {
+    return providers == null ? Collections.emptySet() : Collections.unmodifiableSet(new TreeSet<>(providers));
+  }
+
+  @Override
+  public void saveOrUpdateProviders(Set<Provider> providersToReplace) {
+    if (this.providers == null) {
+      this.providers = new TreeSet<>();
+      this.providers.addAll(providersToReplace);
+    } else {
+      providersToReplace.forEach(providerToAdd -> {
+        final Optional<Provider> toBeRemoved = this.providers.stream().filter(provider -> provider.getRole().equals(providerToAdd.getRole())).findFirst();
+        if (toBeRemoved.isPresent()) {
+          this.providers.remove(toBeRemoved.get());
+        }
+        this.providers.add(providerToAdd);
+      });
+    }
+  }
+
+  @Override
+  public boolean isReadOnly() {
+    return readOnly;
+  }
+
+  @Override
+  public void setReadOnly(boolean readOnly) {
+    this.readOnly = readOnly;
+  }
+
+  @Override
+  public int hashCode() {
+    return HashCodeBuilder.reflectionHashCode(this);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    return EqualsBuilder.reflectionEquals(this, obj);
+  }
+
+  @JsonPropertyOrder({ "role", "name", "enabled", "params" })
+  public static class JSONProvider implements ProviderConfiguration.Provider, Comparable<JSONProvider> {
+
+    @JsonProperty("role")
+    private String role;
+
+    @JsonProperty("name")
+    private String name;
+
+    @JsonProperty("enabled")
+    private boolean enabled;
+
+    @JsonProperty("params")
+    private Map<String, String> params;
+
+    @Override
+    public String getRole() {
+      return role;
+    }
+
+    public void setRole(String role) {
+      this.role = role;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public boolean isEnabled() {
+      return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+      this.enabled = enabled;
+    }
+
+    @Override
+    public Map<String, String> getParams() {
+      Map<String, String> result = new TreeMap<>();
+      if (params != null) {
+        result.putAll(params);
+      }
+      return result;
+    }
+
+    public void addParam(String key, String value) {
+      if (params == null) {
+        params = new TreeMap<>();
+      }
+      params.put(key, value);
+    }
+
+    public void removeParam(String key) {
+      if (params != null) {
+        params.remove(key);
+      }
+    }
+
+    @Override
+    public int hashCode() {
+      return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @Override
+    public int compareTo(JSONProvider other) {
+      return Integer.compare(ProviderOrder.getOrdinalForRole(role), ProviderOrder.getOrdinalForRole(other.role));
+    }
+  }
+
+}
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfiguration.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfiguration.java
index 4894036..45e3697 100644
--- a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfiguration.java
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfiguration.java
@@ -17,13 +17,18 @@
 package org.apache.knox.gateway.topology.simple;
 
 
-import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public interface ProviderConfiguration {
 
-  List<Provider> getProviders();
+  Set<Provider> getProviders();
 
+  void saveOrUpdateProviders(Set<Provider> providers);
+
+  boolean isReadOnly();
+
+  void setReadOnly(boolean readOnly);
 
   interface Provider {
 
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParser.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParser.java
index 73b707c..cb8905e 100644
--- a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParser.java
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderConfigurationParser.java
@@ -16,28 +16,22 @@
  */
 package org.apache.knox.gateway.topology.simple;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import org.apache.commons.io.FilenameUtils;
-
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.commons.io.FilenameUtils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 
 public class ProviderConfigurationParser {
   private static final JAXBContext jaxbContext = getJAXBContext();
@@ -93,32 +87,26 @@
     return EXT_YAML.equals(extension) || EXT_YML.equals(extension);
   }
 
-
   static ProviderConfiguration parseXML(File file) throws Exception {
     return parseXML(Files.newInputStream(file.toPath()));
   }
 
-
   static ProviderConfiguration parseXML(InputStream in) throws Exception {
-    XMLProviderConfiguration providerConfig;
     Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
-    providerConfig = (XMLProviderConfiguration) jaxbUnmarshaller.unmarshal(in);
+    XMLProviderConfiguration providerConfig = (XMLProviderConfiguration) jaxbUnmarshaller.unmarshal(in);
 
     return providerConfig;
   }
 
-
   static ProviderConfiguration parseJSON(File file) throws IOException {
     return parseJSON(Files.newInputStream(file.toPath()));
   }
 
-
   static ProviderConfiguration parseJSON(InputStream in) throws IOException {
     final ObjectMapper mapper = new ObjectMapper();
     return mapper.readValue(in, JSONProviderConfiguration.class);
   }
 
-
   static ProviderConfiguration parseYAML(File file) throws IOException {
     return parseYAML(Files.newInputStream(file.toPath()));
   }
@@ -128,139 +116,4 @@
     return mapper.readValue(in, JSONProviderConfiguration.class);
   }
 
-
-  // XML Provider Configuration Model
-  @XmlRootElement(name="gateway")
-  private static class XMLProviderConfiguration implements ProviderConfiguration {
-
-    @XmlElement(name="provider")
-    private List<XMLProvider> providers;
-
-    @Override
-    public List<Provider> getProviders() {
-      List<Provider> plist = new ArrayList<>();
-      if (providers != null) {
-        plist.addAll(providers);
-      }
-      return plist;
-    }
-
-    @XmlAccessorType(XmlAccessType.NONE)
-    private static class XMLProvider implements ProviderConfiguration.Provider {
-      @XmlElement
-      private String role;
-
-      @XmlElement
-      private String name;
-
-      @XmlElement
-      private boolean enabled;
-
-      @XmlElement(name = "param")
-      private List<XMLParam> params;
-
-      @Override
-      public String getRole() {
-        return role;
-      }
-
-      @Override
-      public String getName() {
-        return name;
-      }
-
-      @Override
-      public boolean isEnabled() {
-        return enabled;
-      }
-
-      @Override
-      public Map<String, String> getParams() {
-        Map<String, String> result = new LinkedHashMap<>();
-        if (params != null) {
-          for (XMLParam p : params) {
-            result.put(p.name, p.value);
-          }
-        }
-        return result;
-      }
-
-      @XmlAccessorType(XmlAccessType.NONE)
-      private static class XMLParam {
-        @XmlElement
-        private String name;
-
-        @XmlElement
-        private String value;
-
-        String getName() {
-          return name;
-        }
-
-        String getValue() {
-          return value;
-        }
-      }
-    }
-
-  }
-
-
-  // JSON/YAML Provider Configuration Model
-  private static class JSONProviderConfiguration implements ProviderConfiguration {
-
-    @JsonProperty("providers")
-    private List<JSONProvider> providers;
-
-    @Override
-    public List<Provider> getProviders() {
-      List<Provider> plist = new ArrayList<>();
-      if (providers != null) {
-        plist.addAll(providers);
-      }
-      return plist;
-    }
-
-    private static class JSONProvider implements ProviderConfiguration.Provider {
-
-      @JsonProperty("role")
-      private String role;
-
-      @JsonProperty("name")
-      private String name;
-
-      @JsonProperty("enabled")
-      private boolean enabled;
-
-      @JsonProperty("params")
-      private Map<String, String> params;
-
-      @Override
-      public String getRole() {
-        return role;
-      }
-
-      @Override
-      public String getName() {
-        return name;
-      }
-
-      @Override
-      public boolean isEnabled() {
-        return enabled;
-      }
-
-      @Override
-      public Map<String, String> getParams() {
-        Map<String, String> result = new LinkedHashMap<>();
-        if (params != null) {
-          result.putAll(params);
-        }
-        return result;
-      }
-    }
-
-  }
-
-
 }
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderOrder.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderOrder.java
new file mode 100644
index 0000000..73fbeb8
--- /dev/null
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/ProviderOrder.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.simple;
+
+public enum ProviderOrder {
+  WEBAPPSEC(1), AUTHENTICATION(2), FEDERATION(3), IDENTITY_ASSERTION(4), AUTHORIZATION(5), HOSTMAP(6), HA(7);
+
+  final int ordinal;
+  final String name;
+
+  ProviderOrder(int ordinal) {
+    this.ordinal = ordinal;
+    this.name = name().replace("_", "-");
+  }
+
+  static int getOrdinalForRole(String role) {
+    int count = 0;
+    for (ProviderOrder providerOrder : values()) {
+      count++;
+      if (providerOrder.name.equalsIgnoreCase(role)) {
+        return providerOrder.ordinal;
+      }
+    }
+    return ++count;
+  }
+}
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/XMLProviderConfiguration.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/XMLProviderConfiguration.java
new file mode 100644
index 0000000..6606206
--- /dev/null
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/XMLProviderConfiguration.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.simple;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+@XmlRootElement(name = "gateway")
+@XmlAccessorType(XmlAccessType.FIELD)
+class XMLProviderConfiguration implements ProviderConfiguration {
+
+  @XmlElement(name = "provider", type=XMLProvider.class)
+  private Set<Provider> providers;
+
+  @XmlElement(name = "readOnly")
+  private boolean readOnly;
+
+  @Override
+  public Set<Provider> getProviders() {
+    return Collections.unmodifiableSet(providers);
+  }
+
+  @Override
+  public void saveOrUpdateProviders(Set<Provider> providersToReplace) {
+    if (providers == null) {
+      providers = new TreeSet<>();
+      this.providers.addAll(providersToReplace);
+    } else {
+      providersToReplace.forEach(providerToAdd -> {
+        final Optional<Provider> toBeRemoved = providers.stream().filter(provider -> provider.getRole().equals(providerToAdd.getRole())).findFirst();
+        if (toBeRemoved.isPresent()) {
+          providers.remove(toBeRemoved.get());
+        }
+        providers.add(providerToAdd);
+      });
+    }
+  }
+
+  @Override
+  public boolean isReadOnly() {
+    return readOnly;
+  }
+
+  @Override
+  public void setReadOnly(boolean readOnly) {
+    this.readOnly = readOnly;
+  }
+
+  @Override
+  public int hashCode() {
+    return HashCodeBuilder.reflectionHashCode(this);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    return EqualsBuilder.reflectionEquals(this, obj);
+  }
+
+  @XmlAccessorType(XmlAccessType.NONE)
+  @XmlType(propOrder = { "role", "name", "enabled", "params" })
+  private static class XMLProvider implements ProviderConfiguration.Provider, Comparable<XMLProvider> {
+    @XmlElement
+    private String role;
+
+    @XmlElement
+    private String name;
+
+    @XmlElement
+    private boolean enabled;
+
+    @XmlElement(name = "param")
+    private List<XMLParam> params;
+
+    @Override
+    public String getRole() {
+      return role;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public boolean isEnabled() {
+      return enabled;
+    }
+
+    @Override
+    public Map<String, String> getParams() {
+      Map<String, String> result = new TreeMap<>();
+      if (params != null) {
+        for (XMLParam p : params) {
+          result.put(p.name, p.value);
+        }
+      }
+      return result;
+    }
+
+    @Override
+    public int hashCode() {
+      return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    private static class XMLParam {
+      @XmlElement
+      private String name;
+
+      @XmlElement
+      private String value;
+    }
+
+    @Override
+    public int compareTo(XMLProvider other) {
+      return Integer.compare(ProviderOrder.getOrdinalForRole(role), ProviderOrder.getOrdinalForRole(other.role));
+    }
+  }
+}