001/* 002 * (C) Copyright 2006-2016 Nuxeo SA (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 <rdaniellou@nuxeo.com> 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.lang.StringUtils; 030import org.codehaus.jackson.JsonNode; 031import org.codehaus.jackson.map.ObjectMapper; 032import org.nuxeo.ecm.automation.core.Constants; 033import org.nuxeo.runtime.api.Framework; 034import org.nuxeo.runtime.services.config.ConfigurationService; 035 036import com.google.common.base.Objects; 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:bs@nuxeo.com">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 = Objects.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 * @param node 093 * @throws IOException 094 * @since 5.7.3 095 */ 096 public Properties(JsonNode node) throws IOException { 097 Iterator<Entry<String, JsonNode>> fields = node.getFields(); 098 ObjectMapper om = new ObjectMapper(); 099 while (fields.hasNext()) { 100 Entry<String, JsonNode> entry = fields.next(); 101 String key = entry.getKey(); 102 JsonNode subNode = entry.getValue(); 103 put(key, extractValueFromNode(subNode, om)); 104 } 105 } 106 107 /** 108 * @param om 109 * @param subNode 110 * @return 111 * @throws IOException 112 * @since 5.8-HF01 113 */ 114 private String extractValueFromNode(JsonNode node, ObjectMapper om) throws IOException { 115 if (!node.isNull()) { 116 return node.isContainerNode() ? om.writeValueAsString(node) : node.getValueAsText(); 117 } else { 118 return null; 119 } 120 } 121 122 public static Map<String, String> loadProperties(Reader reader) throws IOException { 123 Map<String, String> map = new HashMap<String, String>(); 124 loadProperties(reader, map); 125 return map; 126 } 127 128 public static void loadProperties(Reader reader, Map<String, String> map) throws IOException { 129 130 boolean isPropertyValueToBeTrimmed = isPropertyValueTrimmed(); 131 BufferedReader in = new BufferedReader(reader); 132 String line = in.readLine(); 133 String prevLine = null; 134 String lineSeparator = "\n"; 135 while (line != null) { 136 if (prevLine == null) { 137 // we start a new property 138 if (line.startsWith("#") || StringUtils.isBlank(line)) { 139 // skip comments, empty or blank line 140 line = in.readLine(); 141 continue; 142 } 143 } 144 if (line.endsWith("\\") && Boolean.valueOf(multiLineEscape)) { 145 line = line.substring(0, line.length() - 1); 146 prevLine = (prevLine != null ? prevLine + line : line) + lineSeparator; 147 line = in.readLine(); 148 continue; 149 } 150 if (prevLine != null) { 151 line = prevLine + line; 152 } 153 prevLine = null; 154 setPropertyLine(map, line, isPropertyValueToBeTrimmed); 155 line = in.readLine(); 156 } 157 if (prevLine != null) { 158 setPropertyLine(map, prevLine, isPropertyValueToBeTrimmed); 159 } 160 } 161 162 /** 163 * @param isPropertyValueToBeTrimmed The caller may store the value, to prevent from fetching it every time. 164 */ 165 private static void setPropertyLine(Map<String, String> map, String line, boolean isPropertyValueToBeTrimmed) 166 throws IOException { 167 int i = line.indexOf('='); 168 if (i == -1) { 169 throw new IOException("Invalid property line (cannot find a '=') in: '" + line + "'"); 170 } 171 // we trim() the key, but not the value (by default, but you may override this for backward compatibility with 172 // former code. See: NXP-19170): spaces and new lines are legitimate part of the value 173 String value = line.substring(i + 1); 174 if (isPropertyValueToBeTrimmed) { 175 value = value.trim(); 176 } 177 map.put(line.substring(0, i).trim(), value); 178 } 179 180}