/*
* 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.nifi.controller.state.config;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.controller.state.ConfigParseException;
import org.apache.nifi.util.DomUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
public class StateManagerConfiguration {
private final Map<String, StateProviderConfiguration> providers;
private StateManagerConfiguration(final Map<String, StateProviderConfiguration> providerConfigs) {
this.providers = providerConfigs;
}
public Map<String, StateProviderConfiguration> getStateProviderConfigurations() {
return Collections.unmodifiableMap(providers);
}
public StateProviderConfiguration getStateProviderConfiguration(final String providerId) {
return providers.get(providerId);
}
public List<StateProviderConfiguration> getStateProviderConfigurations(final Scope scope) {
final List<StateProviderConfiguration> configs = new ArrayList<>();
for (final StateProviderConfiguration config : providers.values()) {
if (config.getScope() == scope) {
configs.add(config);
}
}
return configs;
}
public static StateManagerConfiguration parse(final File configFile) throws IOException, ConfigParseException {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
final Document document;
DocumentBuilder builder;
try {
builder = factory.newDocumentBuilder();
document = builder.parse(configFile);
} catch (ParserConfigurationException | SAXException e) {
throw new ConfigParseException("Unable to parse file " + configFile + ", as it does not appear to be a valid XML File", e);
}
final Element rootElement = document.getDocumentElement();
final List<Element> localProviderElements = DomUtils.getChildElementsByTagName(rootElement, "local-provider");
if (localProviderElements.isEmpty()) {
throw new ConfigParseException("State Management config file " + configFile + " is not a valid configuration file, "
+ "as it does not contain a 'local-provider' element, or the local-provider element is not the child of the root element");
}
final Map<String, StateProviderConfiguration> configs = new HashMap<>();
for (final Element localProviderElement : localProviderElements) {
final StateProviderConfiguration providerConfig = parseProviderConfiguration(localProviderElement, Scope.LOCAL, configFile);
if (configs.containsKey(providerConfig.getId())) {
throw new ConfigParseException("State Management config file " + configFile + " is not a valid configuration file, "
+ "as it contains multiple providers with the \"id\" of \"" + providerConfig.getId() + "\"");
}
configs.put(providerConfig.getId(), providerConfig);
}
final List<Element> clusterProviderElements = DomUtils.getChildElementsByTagName(rootElement, "cluster-provider");
for (final Element clusterProviderElement : clusterProviderElements) {
final StateProviderConfiguration providerConfig = parseProviderConfiguration(clusterProviderElement, Scope.CLUSTER, configFile);
if (configs.containsKey(providerConfig.getId())) {
throw new ConfigParseException("State Management config file " + configFile + " is not a valid configuration file, "
+ "as it contains multiple providers with the \"id\" of \"" + providerConfig.getId() + "\"");
}
configs.put(providerConfig.getId(), providerConfig);
}
return new StateManagerConfiguration(configs);
}
private static StateProviderConfiguration parseProviderConfiguration(final Element providerElement, final Scope scope, final File configFile) throws ConfigParseException {
final String elementName = providerElement.getNodeName();
final String id = DomUtils.getChildText(providerElement, "id");
if (id == null) {
throw new ConfigParseException("State Management config file " + configFile + " is not a valid configuration file, "
+ "as a " + elementName + " element does not contain an \"id\" element");
}
if (id.trim().isEmpty()) {
throw new ConfigParseException("State Management config file " + configFile + " is not a valid configuration file, "
+ "as a " + elementName + "'s \"id\" element is empty");
}
final String className = DomUtils.getChildText(providerElement, "class");
if (className == null) {
throw new ConfigParseException("State Management config file " + configFile + " is not a valid configuration file, "
+ "as a " + elementName + " element does not contain an \"class\" element");
}
if (className.trim().isEmpty()) {
throw new ConfigParseException("State Management config file " + configFile + " is not a valid configuration file, "
+ "as a " + elementName + "'s \"class\" element is empty");
}
final List<Element> propertyElements = DomUtils.getChildElementsByTagName(providerElement, "property");
final Map<String, String> propertyMap = new HashMap<>();
for (final Element propertyElement : propertyElements) {
final String propertyName = propertyElement.getAttribute("name");
final String propertyValue = propertyElement.getTextContent();
propertyMap.put(propertyName, propertyValue);
}
return new StateProviderConfiguration(id, className, scope, propertyMap);
}
}