/**
* Copyright (C) 2015 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.francetelecom.clara.cloud.logicalmodel;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.PropertiesConfigurationLayout;
import com.francetelecom.clara.cloud.commons.BaseEqualsToStringObject;
import com.francetelecom.clara.cloud.commons.BusinessException;
import com.francetelecom.clara.cloud.commons.TechnicalException;
import com.francetelecom.clara.cloud.commons.ValidatorUtil;
import com.francetelecom.clara.cloud.logicalmodel.InvalidConfigServiceException.ErrorType;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
/**
* Utility class to help parsing and writing structured config content. It forbids duplicate keys or value lists.
*
* Unlike {@link org.apache.commons.configuration.PropertiesConfiguration} which behaves as "If a key is used more than once, the values are appended like if they were on the same line separated with commas.",
* this class will throw exceptions in such cases.
*/
public class LogicalConfigServiceUtils implements Serializable {
private static final long serialVersionUID = 6093491341610695862L;
private static final Function<ConfigEntry, String> GET_KEY = new Function<ConfigEntry, String>() {
@Override
public String apply(ConfigEntry configEntry) {
return configEntry.getKey();
}
};
public String dumpConfigContentToString(StructuredLogicalConfigServiceContent content) throws InvalidConfigServiceException {
StringWriter stringWriter = new StringWriter();
dumpConfigContent(content, stringWriter);
return stringWriter.toString();
}
public void dumpConfigContent(StructuredLogicalConfigServiceContent content, Writer writer) throws InvalidConfigServiceException {
try {
ValidatorUtil.validate(content);
} catch (TechnicalException e) {
throw new InvalidConfigServiceException("Invalid structured content:" + e, e);
}
PropertiesConfiguration propertiesConfiguration = new PropertiesConfiguration();
PropertiesConfigurationLayout layout = propertiesConfiguration.getLayout();
layout.setLineSeparator("\n");
String headerComment = content.getHeaderComment();
if (headerComment != null) {
layout.setHeaderComment(headerComment);
}
for (ConfigEntry configEntry : content.configEntries) {
String key = configEntry.getKey();
propertiesConfiguration.addProperty(key, configEntry.getValue());
String comment = configEntry.getComment();
layout.setSeparator(key, "=");
if (comment != null) {
layout.setComment(key, comment);
}
}
try {
propertiesConfiguration.save(writer);
} catch (ConfigurationException e) {
throw new InvalidConfigServiceException("Invalid structured content or output:" + e, e);
}
}
public StructuredLogicalConfigServiceContent parseConfigContent(String content) throws InvalidConfigServiceException {
return parseConfigContent(new StringReader(content));
}
/**
*
*
* @param reader
* @return
*/
public StructuredLogicalConfigServiceContent parseConfigContent(Reader reader) throws InvalidConfigServiceException {
List<ConfigEntry> parsedEntries = new ArrayList<ConfigEntry>();
PropertiesConfiguration propertiesConfiguration = new PropertiesConfiguration();
try {
propertiesConfiguration.load(reader);
} catch (ConfigurationException e) {
InvalidConfigServiceException invalidConfigServiceException = new InvalidConfigServiceException("Invalid config content. Caught:" + e, e);
throw invalidConfigServiceException;
}
PropertiesConfigurationLayout layout = propertiesConfiguration.getLayout();
String headerComment = layout.getHeaderComment();
Set<String> keys = layout.getKeys();
Set<String> duplicates = new HashSet<String>();
for (String key : keys) {
String comment = layout.getComment(key);
if (comment != null) {
comment = escapesPoundsInComments(comment);
}
if (! layout.isSingleLine(key)) {
//reject the duplicate key
duplicates.add(key);
} else {
String value = propertiesConfiguration.getString(key);
parsedEntries.add(new ConfigEntry(key, value, comment));
}
}
if (duplicates.size() > 0) {
InvalidConfigServiceException invalidConfigServiceException = new InvalidConfigServiceException("Collisions! " + duplicates);
invalidConfigServiceException.setType(ErrorType.DUPLICATE_KEYS);
invalidConfigServiceException.getDuplicateKeys().addAll(duplicates);
throw invalidConfigServiceException;
}
List<ConfigEntry> configEntries = parsedEntries;
StructuredLogicalConfigServiceContent structuredLogicalConfigServiceContent = new StructuredLogicalConfigServiceContent(headerComment, configEntries);
return structuredLogicalConfigServiceContent;
}
protected String escapesPoundsInComments(String rawComment) {
return Pattern.compile("^[#!]", Pattern.MULTILINE).matcher(rawComment).replaceAll("");
}
/**
* Provides structured typed access to a configuration string formatted in java.util.Properties format.
*/
public static class StructuredLogicalConfigServiceContent extends BaseEqualsToStringObject implements Serializable {
private static final long serialVersionUID = -3588073237349664245L;
/**
* Allows for null comment
*/
private String headerComment;
/**
* Allows for empty list
*/
@NotNull
@Valid
private List<ConfigEntry> configEntries;
public StructuredLogicalConfigServiceContent(String headerComment, List<ConfigEntry> configEntries) {
this.headerComment = headerComment;
this.configEntries = configEntries;
}
public String getHeaderComment() {
return headerComment;
}
public void setHeaderComment(String headerComment) {
this.headerComment = headerComment;
}
public List<ConfigEntry> getConfigEntries() {
return configEntries;
}
public void setConfigEntries(List<ConfigEntry> configEntries) {
this.configEntries = configEntries;
}
public Collection<String> listKeys() {
return Collections2.transform(configEntries, GET_KEY);
}
}
/**
* Represents individual entries in a a configuration string formatted in java.util.Properties format: comments, key, and value.
*/
public static class ConfigEntry extends BaseEqualsToStringObject implements Serializable {
private static final long serialVersionUID = 5371986407117742995L;
/**
* Allows for null comment
*/
private String comment;
@NotNull
private String key;
@NotNull
private String value;
public ConfigEntry(String key, String value, String comment) {
this.comment = comment;
this.key = key;
this.value = value;
}
/**
* Note that comments don't have a # or ! prefix
* @return
*/
public String getComment() {
return comment;
}
/**
* Assigns the comment
* @param comment a single or multiline comment without # or ! prefix
*/
public void setComment(String comment) {
this.comment = comment;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
/**
* Load a file from it's path, parse it's config content and return a Set of all keys from it.<br>
* @param filePath
* @return
* @throws InvalidConfigServiceException
*/
public Set<String> loadKeysFromFile(String filePath) throws InvalidConfigServiceException{
InputStreamReader inputStreamReader = new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(filePath));
StructuredLogicalConfigServiceContent releaseConfigContent = parseConfigContent(inputStreamReader);
return Sets.newTreeSet(Collections2.transform(releaseConfigContent.getConfigEntries(), GET_KEY));
}
}