/**
* Copyright (c) Codice Foundation
* <p>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.configuration.persistence.felix;
import static org.apache.commons.lang.Validate.notNull;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Set;
import org.apache.felix.cm.file.ConfigurationHandler;
import org.codice.ddf.configuration.persistence.PersistenceStrategy;
import org.codice.ddf.configuration.status.ConfigurationFileException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that persists configuration properties using the Felix' file format.
*/
public class FelixPersistenceStrategy implements PersistenceStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(FelixPersistenceStrategy.class);
@Override
@SuppressWarnings("unchecked")
public Dictionary<String, Object> read(InputStream inputStream)
throws ConfigurationFileException, IOException {
notNull(inputStream, "InputStream cannot be null");
final StringBuilder filteredOutput = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
PropertyConverter propertyConverter = createPropertyConverter(filteredOutput);
reader.lines()
.forEach(propertyConverter);
LOGGER.debug("Calling ConfigurationHandler with {}", filteredOutput.toString());
Dictionary properties;
try {
properties =
ConfigurationHandler.read(new ByteArrayInputStream(filteredOutput.toString()
.getBytes(StandardCharsets.UTF_8)));
} catch (RuntimeException e) {
LOGGER.info("ConfigurationHandler failed to read configuration from file", e);
throw new ConfigurationFileException("Failed to read configuration from file", e);
}
checkForInvalidProperties(propertyConverter.getPropertyNames(), properties);
return properties;
}
@Override
public void write(OutputStream outputStream, Dictionary<String, Object> properties)
throws IOException {
notNull(outputStream, "OutputStream cannot be null");
notNull(properties, "Properties cannot be null");
ConfigurationHandler.write(outputStream, properties);
}
PropertyConverter createPropertyConverter(StringBuilder filteredOutput) {
return new PropertyConverter(filteredOutput);
}
/*
* Checks that all properties found by the propertyConverter object have been returned
* by the ConfigurationHandler class. If not, it means that ConfigurationHandler failed
* to read in one of the values and that something is invalid in the file. This is needed
* to work-around the problem with ConfigurationHandler not reporting parsing errors.
*/
private void checkForInvalidProperties(Set<String> expectedPropertyName, Dictionary properties)
throws ConfigurationFileException {
if (properties.size() != expectedPropertyName.size()) {
@SuppressWarnings("unchecked")
Set<String> propertyNames = new HashSet<>(Collections.list(properties.keys()));
LOGGER.info("Unable to convert all config file properties. One of [{}] is invalid",
expectedPropertyName.removeAll(propertyNames));
throw new ConfigurationFileException("Unable to convert all config file properties.");
}
}
}