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