/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* bstefanescu, Ronan DANIELLOU <rdaniellou@nuxeo.com>
*/
package org.nuxeo.ecm.automation.core.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.nuxeo.ecm.automation.core.Constants;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.services.config.ConfigurationService;
import com.google.common.base.Objects;
/**
* Inline properties file content. This class exists to have a real type for parameters accepting properties content.
*
* @see Constants
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class Properties extends HashMap<String, String> {
private static final long serialVersionUID = 1L;
/**
* Spaces may be legitimate part of the value, there is no reason to trim them. But before NXP-19050, the behavior
* was to trim the values. We have put in place a contribution, which is overridden before Nuxeo 8 series, for
* backward compatibility. See NXP-19170.
*
* @since 8.2
*/
public static final String IS_PROPERTY_VALUE_TRIMMED_KEY = "nuxeo.automation.properties.value.trim";
/**
* Default value is <code>false</code>.
*
* @since 8.2
*/
protected static boolean isPropertyValueTrimmed() {
return Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(IS_PROPERTY_VALUE_TRIMMED_KEY);
}
public static final String PROPERTIES_MULTILINE_ESCAPE = "nuxeo" + ".automation.properties.multiline.escape";
protected static final String multiLineEscape = Objects.firstNonNull(
Framework.getProperty(PROPERTIES_MULTILINE_ESCAPE), "true");
public Properties() {
}
public Properties(int size) {
super(size);
}
public Properties(Map<String, String> props) {
super(props);
}
public Properties(String content) throws IOException {
StringReader reader = new StringReader(content);
Map<String,String> props = new HashMap<>();
loadProperties(reader, props);
putAll(props);
}
/**
* Constructs a Properties map based on a Json node.
*
* @param node
* @throws IOException
* @since 5.7.3
*/
public Properties(JsonNode node) throws IOException {
Iterator<Entry<String, JsonNode>> fields = node.getFields();
ObjectMapper om = new ObjectMapper();
while (fields.hasNext()) {
Entry<String, JsonNode> entry = fields.next();
String key = entry.getKey();
JsonNode subNode = entry.getValue();
put(key, extractValueFromNode(subNode, om));
}
}
/**
* @param om
* @param subNode
* @return
* @throws IOException
* @since 5.8-HF01
*/
private String extractValueFromNode(JsonNode node, ObjectMapper om) throws IOException {
if (!node.isNull()) {
return node.isContainerNode() ? om.writeValueAsString(node) : node.getValueAsText();
} else {
return null;
}
}
public static Map<String, String> loadProperties(Reader reader) throws IOException {
Map<String, String> map = new HashMap<String, String>();
loadProperties(reader, map);
return map;
}
public static void loadProperties(Reader reader, Map<String, String> map) throws IOException {
boolean isPropertyValueToBeTrimmed = isPropertyValueTrimmed();
BufferedReader in = new BufferedReader(reader);
String line = in.readLine();
String prevLine = null;
String lineSeparator = "\n";
while (line != null) {
if (prevLine == null) {
// we start a new property
if (line.startsWith("#") || StringUtils.isBlank(line)) {
// skip comments, empty or blank line
line = in.readLine();
continue;
}
}
if (line.endsWith("\\") && Boolean.valueOf(multiLineEscape)) {
line = line.substring(0, line.length() - 1);
prevLine = (prevLine != null ? prevLine + line : line) + lineSeparator;
line = in.readLine();
continue;
}
if (prevLine != null) {
line = prevLine + line;
}
prevLine = null;
setPropertyLine(map, line, isPropertyValueToBeTrimmed);
line = in.readLine();
}
if (prevLine != null) {
setPropertyLine(map, prevLine, isPropertyValueToBeTrimmed);
}
}
/**
* @param isPropertyValueToBeTrimmed The caller may store the value, to prevent from fetching it every time.
*/
private static void setPropertyLine(Map<String, String> map, String line, boolean isPropertyValueToBeTrimmed)
throws IOException {
int i = line.indexOf('=');
if (i == -1) {
throw new IOException("Invalid property line (cannot find a '=') in: '" + line + "'");
}
// we trim() the key, but not the value (by default, but you may override this for backward compatibility with
// former code. See: NXP-19170): spaces and new lines are legitimate part of the value
String value = line.substring(i + 1);
if (isPropertyValueToBeTrimmed) {
value = value.trim();
}
map.put(line.substring(0, i).trim(), value);
}
}