001/* 002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bstefanescu, Ronan DANIELLOU <[email protected]> 018 */ 019package org.nuxeo.ecm.automation.core.util; 020 021import java.io.BufferedReader; 022import java.io.IOException; 023import java.io.Reader; 024import java.io.StringReader; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.Map; 028 029import org.apache.commons.lang3.StringUtils; 030import org.nuxeo.ecm.automation.core.Constants; 031import org.nuxeo.runtime.api.Framework; 032import org.nuxeo.runtime.services.config.ConfigurationService; 033 034import com.fasterxml.jackson.databind.JsonNode; 035import com.fasterxml.jackson.databind.ObjectMapper; 036import com.google.common.base.MoreObjects; 037 038/** 039 * Inline properties file content. This class exists to have a real type for parameters accepting properties content. 040 * 041 * @see Constants 042 * @author <a href="mailto:[email protected]">Bogdan Stefanescu</a> 043 */ 044public class Properties extends HashMap<String, String> { 045 046 private static final long serialVersionUID = 1L; 047 048 /** 049 * Spaces may be legitimate part of the value, there is no reason to trim them. But before NXP-19050, the behavior 050 * was to trim the values. We have put in place a contribution, which is overridden before Nuxeo 8 series, for 051 * backward compatibility. See NXP-19170. 052 * 053 * @since 8.2 054 */ 055 public static final String IS_PROPERTY_VALUE_TRIMMED_KEY = "nuxeo.automation.properties.value.trim"; 056 057 /** 058 * Default value is <code>false</code>. 059 * 060 * @since 8.2 061 */ 062 protected static boolean isPropertyValueTrimmed() { 063 return Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(IS_PROPERTY_VALUE_TRIMMED_KEY); 064 } 065 066 public static final String PROPERTIES_MULTILINE_ESCAPE = "nuxeo" + ".automation.properties.multiline.escape"; 067 068 protected static final String multiLineEscape = MoreObjects.firstNonNull( 069 Framework.getProperty(PROPERTIES_MULTILINE_ESCAPE), "true"); 070 071 public Properties() { 072 } 073 074 public Properties(int size) { 075 super(size); 076 } 077 078 public Properties(Map<String, String> props) { 079 super(props); 080 } 081 082 public Properties(String content) throws IOException { 083 StringReader reader = new StringReader(content); 084 Map<String, String> props = new HashMap<>(); 085 loadProperties(reader, props); 086 putAll(props); 087 } 088 089 /** 090 * Constructs a Properties map based on a Json node. 091 * 092 * @since 5.7.3 093 */ 094 public Properties(JsonNode node) throws IOException { 095 Iterator<Entry<String, JsonNode>> fields = node.fields(); 096 ObjectMapper om = new ObjectMapper(); 097 while (fields.hasNext()) { 098 Entry<String, JsonNode> entry = fields.next(); 099 String key = entry.getKey(); 100 JsonNode subNode = entry.getValue(); 101 put(key, extractValueFromNode(subNode, om)); 102 } 103 } 104 105 /** 106 * @since 5.8-HF01 107 */ 108 private String extractValueFromNode(JsonNode node, ObjectMapper om) throws IOException { 109 if (!node.isNull()) { 110 return node.isContainerNode() ? om.writeValueAsString(node) : node.asText(); 111 } else { 112 return null; 113 } 114 } 115 116 public static Map<String, String> loadProperties(Reader reader) throws IOException { 117 Map<String, String> map = new HashMap<>(); 118 loadProperties(reader, map); 119 return map; 120 } 121 122 public static void loadProperties(Reader reader, Map<String, String> map) throws IOException { 123 124 boolean isPropertyValueToBeTrimmed = isPropertyValueTrimmed(); 125 BufferedReader in = new BufferedReader(reader); 126 String line = in.readLine(); 127 String prevLine = null; 128 String lineSeparator = "\n"; 129 while (line != null) { 130 if (prevLine == null) { 131 // we start a new property 132 if (line.startsWith("#") || StringUtils.isBlank(line)) { 133 // skip comments, empty or blank line 134 line = in.readLine(); 135 continue; 136 } 137 } 138 if (line.endsWith("\\") && Boolean.parseBoolean(multiLineEscape)) { 139 line = line.substring(0, line.length() - 1); 140 prevLine = (prevLine != null ? prevLine + line : line) + lineSeparator; 141 line = in.readLine(); 142 continue; 143 } 144 if (prevLine != null) { 145 line = prevLine + line; 146 } 147 prevLine = null; 148 setPropertyLine(map, line, isPropertyValueToBeTrimmed); 149 line = in.readLine(); 150 } 151 if (prevLine != null) { 152 setPropertyLine(map, prevLine, isPropertyValueToBeTrimmed); 153 } 154 } 155 156 /** 157 * @param isPropertyValueToBeTrimmed The caller may store the value, to prevent from fetching it every time. 158 */ 159 private static void setPropertyLine(Map<String, String> map, String line, boolean isPropertyValueToBeTrimmed) 160 throws IOException { 161 int i = line.indexOf('='); 162 if (i == -1) { 163 throw new IOException("Invalid property line (cannot find a '=') in: '" + line + "'"); 164 } 165 // we trim() the key, but not the value (by default, but you may override this for backward compatibility with 166 // former code. See: NXP-19170): spaces and new lines are legitimate part of the value 167 String value = line.substring(i + 1); 168 if (isPropertyValueToBeTrimmed) { 169 value = value.trim(); 170 } 171 map.put(line.substring(0, i).trim(), value); 172 } 173 174}