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 = getClearString(key); 096 if (value != null && encryption.isEncrypted(value)) { 097 try { 098 value = encryption.decrypt(value); 099 } catch (Exception e) { 100 throw new IllegalStateException("Fail to decrypt the property " + key + ". Please check your secret key.", e); 101 } 102 } 103 return value; 104 } 105 106 /** 107 * Does not decrypt value. 108 */ 109 protected String getClearString(String key) { 110 String validKey = definitions.validKey(key); 111 String value = properties.get(validKey); 112 if (value == null) { 113 value = getDefaultValue(validKey); 114 } 115 return value; 116 } 117 118 public final boolean getBoolean(String key) { 119 String value = getString(key); 120 return StringUtils.isNotEmpty(value) && Boolean.parseBoolean(value); 121 } 122 123 /** 124 * @return the value as int. If the property does not exist and has no default value, then 0 is returned. 125 */ 126 public final int getInt(String key) { 127 String value = getString(key); 128 if (StringUtils.isNotEmpty(value)) { 129 return Integer.parseInt(value); 130 } 131 return 0; 132 } 133 134 public final long getLong(String key) { 135 String value = getString(key); 136 if (StringUtils.isNotEmpty(value)) { 137 return Long.parseLong(value); 138 } 139 return 0L; 140 } 141 142 public final Date getDate(String key) { 143 String value = getString(key); 144 if (StringUtils.isNotEmpty(value)) { 145 return DateUtils.parseDate(value); 146 } 147 return null; 148 } 149 150 public final Date getDateTime(String key) { 151 String value = getString(key); 152 if (StringUtils.isNotEmpty(value)) { 153 return DateUtils.parseDateTime(value); 154 } 155 return null; 156 } 157 158 public final Float getFloat(String key) { 159 String value = getString(key); 160 if (StringUtils.isNotEmpty(value)) { 161 try { 162 return Float.valueOf(value); 163 } catch (NumberFormatException e) { 164 throw new IllegalStateException(String.format("The property '%s' is not a float value", key)); 165 } 166 } 167 return null; 168 } 169 170 public final Double getDouble(String key) { 171 String value = getString(key); 172 if (StringUtils.isNotEmpty(value)) { 173 try { 174 return Double.valueOf(value); 175 } catch (NumberFormatException e) { 176 throw new IllegalStateException(String.format("The property '%s' is not a double value", key)); 177 } 178 } 179 return null; 180 } 181 182 /** 183 * Value is split by comma and trimmed. Never returns null. 184 * <p/> 185 * Examples : 186 * <ul> 187 * <li>"one,two,three " -> ["one", "two", "three"]</li> 188 * <li>" one, two, three " -> ["one", "two", "three"]</li> 189 * <li>"one, , three" -> ["one", "", "three"]</li> 190 * </ul> 191 */ 192 public final String[] getStringArray(String key) { 193 PropertyDefinition property = getDefinitions().get(key); 194 if ((null != property) && (property.isMultiValues())) { 195 String value = getString(key); 196 if (value == null) { 197 return ArrayUtils.EMPTY_STRING_ARRAY; 198 } 199 200 List<String> values = Lists.newArrayList(); 201 for (String v : Splitter.on(",").trimResults().split(value)) { 202 values.add(v.replace("%2C", ",")); 203 } 204 return values.toArray(new String[values.size()]); 205 } 206 207 return getStringArrayBySeparator(key, ","); 208 } 209 210 /** 211 * Value is split by carriage returns. 212 * 213 * @return non-null array of lines. The line termination characters are excluded. 214 * @since 3.2 215 */ 216 public final String[] getStringLines(String key) { 217 String value = getString(key); 218 if (Strings.isNullOrEmpty(value)) { 219 return ArrayUtils.EMPTY_STRING_ARRAY; 220 } 221 return value.split("\r?\n|\r", -1); 222 } 223 224 /** 225 * Value is splitted and trimmed. 226 */ 227 public final String[] getStringArrayBySeparator(String key, String separator) { 228 String value = getString(key); 229 if (value != null) { 230 String[] strings = StringUtils.splitByWholeSeparator(value, separator); 231 String[] result = new String[strings.length]; 232 for (int index = 0; index < strings.length; index++) { 233 result[index] = StringUtils.trim(strings[index]); 234 } 235 return result; 236 } 237 return ArrayUtils.EMPTY_STRING_ARRAY; 238 } 239 240 public final List<String> getKeysStartingWith(String prefix) { 241 List<String> result = Lists.newArrayList(); 242 for (String key : properties.keySet()) { 243 if (StringUtils.startsWith(key, prefix)) { 244 result.add(key); 245 } 246 } 247 return result; 248 } 249 250 public final Settings appendProperty(String key, String value) { 251 String newValue = properties.get(definitions.validKey(key)); 252 if (StringUtils.isEmpty(newValue)) { 253 newValue = StringUtils.trim(value); 254 } else { 255 newValue += "," + StringUtils.trim(value); 256 } 257 return setProperty(key, newValue); 258 } 259 260 public final Settings setProperty(String key, @Nullable String[] values) { 261 PropertyDefinition property = getDefinitions().get(key); 262 if ((null == property) || (!property.isMultiValues())) { 263 throw new IllegalStateException("Fail to set multiple values on a single value property " + key); 264 } 265 266 String text = null; 267 if (values != null) { 268 List<String> escaped = Lists.newArrayList(); 269 for (String value : values) { 270 if (null != value) { 271 escaped.add(value.replace(",", "%2C")); 272 } else { 273 escaped.add(""); 274 } 275 } 276 277 String escapedValue = Joiner.on(',').join(escaped); 278 text = StringUtils.trim(escapedValue); 279 } 280 return setProperty(key, text); 281 } 282 283 public final Settings setProperty(String key, @Nullable String value) { 284 String validKey = definitions.validKey(key); 285 if (value == null) { 286 properties.remove(validKey); 287 doOnRemoveProperty(validKey); 288 } else { 289 properties.put(validKey, StringUtils.trim(value)); 290 doOnSetProperty(validKey, value); 291 } 292 return this; 293 } 294 295 public final Settings setProperty(String key, @Nullable Boolean value) { 296 return setProperty(key, value == null ? null : String.valueOf(value)); 297 } 298 299 public final Settings setProperty(String key, @Nullable Integer value) { 300 return setProperty(key, value == null ? null : String.valueOf(value)); 301 } 302 303 public final Settings setProperty(String key, @Nullable Long value) { 304 return setProperty(key, value == null ? null : String.valueOf(value)); 305 } 306 307 public final Settings setProperty(String key, @Nullable Double value) { 308 return setProperty(key, value == null ? null : String.valueOf(value)); 309 } 310 311 public final Settings setProperty(String key, @Nullable Float value) { 312 return setProperty(key, value == null ? null : String.valueOf(value)); 313 } 314 315 public final Settings setProperty(String key, @Nullable Date date) { 316 return setProperty(key, date, false); 317 } 318 319 public final Settings addProperties(Map<String, String> props) { 320 for (Map.Entry<String, String> entry : props.entrySet()) { 321 setProperty(entry.getKey(), entry.getValue()); 322 } 323 return this; 324 } 325 326 public final Settings addProperties(Properties props) { 327 for (Map.Entry<Object, Object> entry : props.entrySet()) { 328 setProperty(entry.getKey().toString(), entry.getValue().toString()); 329 } 330 return this; 331 } 332 333 public final Settings addSystemProperties() { 334 return addProperties(System.getProperties()); 335 } 336 337 public final Settings addEnvironmentVariables() { 338 return addProperties(System.getenv()); 339 } 340 341 public final Settings setProperties(Map<String, String> props) { 342 clear(); 343 return addProperties(props); 344 } 345 346 public final Settings setProperty(String key, @Nullable Date date, boolean includeTime) { 347 return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date)); 348 } 349 350 public final Settings removeProperty(String key) { 351 return setProperty(key, (String) null); 352 } 353 354 public final Settings clear() { 355 properties.clear(); 356 doOnClearProperties(); 357 return this; 358 } 359 360 /** 361 * @return unmodifiable properties 362 */ 363 public final Map<String, String> getProperties() { 364 return Collections.unmodifiableMap(properties); 365 } 366 367 public final PropertyDefinitions getDefinitions() { 368 return definitions; 369 } 370 371 /** 372 * Create empty settings. Definition of available properties is loaded from the given annotated class. 373 * This method is usually used by unit tests. 374 */ 375 public static Settings createForComponent(Object component) { 376 return new Settings(new PropertyDefinitions(component)); 377 } 378 379 protected void doOnSetProperty(String key, @Nullable String value) { 380 } 381 382 protected void doOnRemoveProperty(String key) { 383 } 384 385 protected void doOnClearProperties() { 386 } 387}