001/* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020package org.sonar.api.config; 021 022import com.google.common.base.Joiner; 023import com.google.common.base.Splitter; 024import com.google.common.base.Strings; 025import com.google.common.collect.Lists; 026import com.google.common.collect.Maps; 027import org.apache.commons.lang.ArrayUtils; 028import org.apache.commons.lang.StringUtils; 029import org.sonar.api.BatchComponent; 030import org.sonar.api.ServerComponent; 031import org.sonar.api.utils.DateUtils; 032 033import javax.annotation.Nullable; 034 035import java.util.Collections; 036import java.util.Date; 037import java.util.List; 038import java.util.Map; 039import java.util.Properties; 040 041/** 042 * Project Settings on batch side, Global Settings on server side. This component does not access to database, so 043 * property changed via setter methods are not persisted. 044 * <p/> 045 * <p> 046 * This component replaces the deprecated org.apache.commons.configuration.Configuration 047 * </p> 048 * 049 * @since 2.12 050 */ 051public class Settings implements BatchComponent, ServerComponent { 052 053 protected final Map<String, String> properties; 054 protected final PropertyDefinitions definitions; 055 private final Encryption encryption; 056 057 public Settings() { 058 this(new PropertyDefinitions()); 059 } 060 061 public Settings(PropertyDefinitions definitions) { 062 this.properties = Maps.newHashMap(); 063 this.definitions = definitions; 064 this.encryption = new Encryption(this); 065 } 066 067 /** 068 * Clone settings. Actions are not propagated to cloned settings. 069 * 070 * @since 3.1 071 */ 072 public Settings(Settings other) { 073 this.properties = Maps.newHashMap(other.properties); 074 this.definitions = other.definitions; 075 this.encryption = other.encryption; 076 } 077 078 public final Encryption getEncryption() { 079 return encryption; 080 } 081 082 public final String getDefaultValue(String key) { 083 return definitions.getDefaultValue(key); 084 } 085 086 public final boolean hasKey(String key) { 087 return properties.containsKey(key); 088 } 089 090 public final boolean hasDefaultValue(String key) { 091 return StringUtils.isNotEmpty(getDefaultValue(key)); 092 } 093 094 public final String getString(String key) { 095 String value = properties.get(key); 096 if (value == null) { 097 value = getDefaultValue(key); 098 } else if (encryption.isEncrypted(value)) { 099 try { 100 value = encryption.decrypt(value); 101 } catch (Exception e) { 102 throw new IllegalStateException("Fail to decrypt the property " + key + ". Please check your secret key.", e); 103 } 104 } 105 return value; 106 } 107 108 /** 109 * Does not decrypt value. 110 */ 111 protected String getClearString(String key) { 112 String value = properties.get(key); 113 if (value == null) { 114 value = getDefaultValue(key); 115 } 116 return value; 117 } 118 119 public final boolean getBoolean(String key) { 120 String value = getString(key); 121 return StringUtils.isNotEmpty(value) && Boolean.parseBoolean(value); 122 } 123 124 /** 125 * @return the value as int. If the property does not exist and has no default value, then 0 is returned. 126 */ 127 public final int getInt(String key) { 128 String value = getString(key); 129 if (StringUtils.isNotEmpty(value)) { 130 return Integer.parseInt(value); 131 } 132 return 0; 133 } 134 135 public final long getLong(String key) { 136 String value = getString(key); 137 if (StringUtils.isNotEmpty(value)) { 138 return Long.parseLong(value); 139 } 140 return 0L; 141 } 142 143 public final Date getDate(String key) { 144 String value = getString(key); 145 if (StringUtils.isNotEmpty(value)) { 146 return DateUtils.parseDate(value); 147 } 148 return null; 149 } 150 151 public final Date getDateTime(String key) { 152 String value = getString(key); 153 if (StringUtils.isNotEmpty(value)) { 154 return DateUtils.parseDateTime(value); 155 } 156 return null; 157 } 158 159 /** 160 * Value is split by comma and trimmed. 161 * <p/> 162 * Examples : 163 * <ul> 164 * <li>"one,two,three " -> ["one", "two", "three"]</li> 165 * <li>" one, two, three " -> ["one", "two", "three"]</li> 166 * <li>"one, , three" -> ["one", "", "three"]</li> 167 * </ul> 168 */ 169 public final String[] getStringArray(String key) { 170 PropertyDefinition property = getDefinitions().get(key); 171 if ((null != property) && (property.isMultiValues())) { 172 String value = getString(key); 173 if (value == null) { 174 return ArrayUtils.EMPTY_STRING_ARRAY; 175 } 176 177 List<String> values = Lists.newArrayList(); 178 for (String v : Splitter.on(",").trimResults().split(value)) { 179 values.add(v.replace("%2C", ",")); 180 } 181 return values.toArray(new String[values.size()]); 182 } 183 184 return getStringArrayBySeparator(key, ","); 185 } 186 187 /** 188 * Value is split by carriage returns. 189 * 190 * @return non-null array of lines. The line termination characters are excluded. 191 * @since 3.2 192 */ 193 public final String[] getStringLines(String key) { 194 String value = getString(key); 195 if (Strings.isNullOrEmpty(value)) { 196 return ArrayUtils.EMPTY_STRING_ARRAY; 197 } 198 return value.split("\r?\n|\r", -1); 199 } 200 201 /** 202 * Value is splitted and trimmed. 203 */ 204 public final String[] getStringArrayBySeparator(String key, String separator) { 205 String value = getString(key); 206 if (value != null) { 207 String[] strings = StringUtils.splitByWholeSeparator(value, separator); 208 String[] result = new String[strings.length]; 209 for (int index = 0; index < strings.length; index++) { 210 result[index] = StringUtils.trim(strings[index]); 211 } 212 return result; 213 } 214 return ArrayUtils.EMPTY_STRING_ARRAY; 215 } 216 217 public final List<String> getKeysStartingWith(String prefix) { 218 List<String> result = Lists.newArrayList(); 219 for (String key : properties.keySet()) { 220 if (StringUtils.startsWith(key, prefix)) { 221 result.add(key); 222 } 223 } 224 return result; 225 } 226 227 public final Settings appendProperty(String key, String value) { 228 String newValue = properties.get(key); 229 if (StringUtils.isEmpty(newValue)) { 230 newValue = StringUtils.trim(value); 231 } else { 232 newValue += "," + StringUtils.trim(value); 233 } 234 return setProperty(key, newValue); 235 } 236 237 public final Settings setProperty(String key, @Nullable String[] values) { 238 PropertyDefinition property = getDefinitions().get(key); 239 if ((null == property) || (!property.isMultiValues())) { 240 throw new IllegalStateException("Fail to set multiple values on a single value property " + key); 241 } 242 243 if (values == null) { 244 properties.remove(key); 245 doOnRemoveProperty(key); 246 } else { 247 List<String> escaped = Lists.newArrayList(); 248 for (String value : values) { 249 if (null != value) { 250 escaped.add(value.replace(",", "%2C")); 251 } else { 252 escaped.add(""); 253 } 254 } 255 256 String escapedValue = Joiner.on(',').join(escaped); 257 properties.put(key, StringUtils.trim(escapedValue)); 258 doOnSetProperty(key, escapedValue); 259 } 260 return this; 261 } 262 263 public final Settings setProperty(String key, @Nullable String value) { 264 if (value == null) { 265 properties.remove(key); 266 doOnRemoveProperty(key); 267 } else { 268 properties.put(key, StringUtils.trim(value)); 269 doOnSetProperty(key, value); 270 } 271 return this; 272 } 273 274 public final Settings setProperty(String key, @Nullable Boolean value) { 275 return setProperty(key, String.valueOf(value)); 276 } 277 278 public final Settings setProperty(String key, @Nullable Integer value) { 279 return setProperty(key, String.valueOf(value)); 280 } 281 282 public final Settings setProperty(String key, @Nullable Long value) { 283 return setProperty(key, String.valueOf(value)); 284 } 285 286 public final Settings setProperty(String key, @Nullable Double value) { 287 return setProperty(key, String.valueOf(value)); 288 } 289 290 public final Settings setProperty(String key, @Nullable Date date) { 291 return setProperty(key, date, false); 292 } 293 294 public final Settings addProperties(Map<String, String> props) { 295 for (Map.Entry<String, String> entry : props.entrySet()) { 296 setProperty(entry.getKey(), entry.getValue()); 297 } 298 return this; 299 } 300 301 public final Settings addProperties(Properties props) { 302 for (Map.Entry<Object, Object> entry : props.entrySet()) { 303 setProperty(entry.getKey().toString(), entry.getValue().toString()); 304 } 305 return this; 306 } 307 308 public final Settings addSystemProperties() { 309 return addProperties(System.getProperties()); 310 } 311 312 public final Settings addEnvironmentVariables() { 313 return addProperties(System.getenv()); 314 } 315 316 public final Settings setProperties(Map<String, String> props) { 317 clear(); 318 return addProperties(props); 319 } 320 321 public final Settings setProperty(String key, @Nullable Date date, boolean includeTime) { 322 return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date)); 323 } 324 325 public final Settings removeProperty(String key) { 326 return setProperty(key, (String) null); 327 } 328 329 public final Settings clear() { 330 properties.clear(); 331 doOnClearProperties(); 332 return this; 333 } 334 335 /** 336 * @return unmodifiable properties 337 */ 338 public final Map<String, String> getProperties() { 339 return Collections.unmodifiableMap(properties); 340 } 341 342 public final PropertyDefinitions getDefinitions() { 343 return definitions; 344 } 345 346 /** 347 * Create empty settings. Definition of available properties is loaded from the given annotated class. 348 * This method is usually used by unit tests. 349 */ 350 public static Settings createForComponent(Object component) { 351 return new Settings(new PropertyDefinitions(component)); 352 } 353 354 protected void doOnSetProperty(String key, @Nullable String value) { 355 } 356 357 protected void doOnRemoveProperty(String key) { 358 } 359 360 protected void doOnClearProperties() { 361 } 362}