001/* 002 * SonarQube 003 * Copyright (C) 2009-2017 SonarSource SA 004 * mailto:info AT sonarsource DOT com 005 * 006 * This program 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 * This program 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 License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 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 java.util.ArrayList; 026import java.util.Date; 027import java.util.List; 028import java.util.Map; 029import java.util.Optional; 030import java.util.Properties; 031import java.util.stream.Collectors; 032import javax.annotation.CheckForNull; 033import javax.annotation.Nullable; 034import org.apache.commons.lang.ArrayUtils; 035import org.apache.commons.lang.StringUtils; 036import org.sonar.api.batch.ScannerSide; 037import org.sonar.api.ce.ComputeEngineSide; 038import org.sonar.api.server.ServerSide; 039import org.sonar.api.utils.DateUtils; 040 041import static java.util.Objects.requireNonNull; 042import static org.apache.commons.lang.StringUtils.trim; 043 044/** 045 * Component to get effective settings. Values of properties depend on the runtime environment: 046 * <ul> 047 * <li>project settings in scanner.</li> 048 * <li>global settings in web server. It does not allow to get the settings overridden on projects.</li> 049 * <li>project settings in Compute Engine.</li> 050 * </ul> 051 * 052 * <h3>Usage</h3> 053 * <pre> 054 * public class MyExtension { 055 * 056 * private final Settings settings; 057 * 058 * public MyExtension(Settings settings) { 059 * this.settings = settings; 060 * } 061 * public void doSomething() { 062 * String fooValue = settings.getString("sonar.foo"); 063 * // .. 064 * } 065 * } 066 * </pre> 067 * 068 * <h3>Scanner example</h3> 069 * Scanner sensor can get the reference on Settings directly through SensorContext, 070 * without injecting the component into constructor. 071 * 072 * <pre> 073 * public class MySensor implements Sensor { 074 * {@literal @}Override 075 * public void execute(SensorContext context) { 076 * String fooValue = context.settings().getString("sonar.foo"); 077 * // .. 078 * } 079 * } 080 * </pre> 081 * 082 * <p> 083 * For testing, and only for testing, the in-memory implementation {@link MapSettings} can be used. 084 * <pre> 085 * {@literal @}Test 086 * public void my_test() { 087 * Settings settings = new MapSettings(); 088 * settings.setProperty("foo", "bar"); 089 * MyExtension underTest = new MyExtension(settings); 090 * // ... 091 * } 092 * </pre> 093 * 094 * History - this class is abstract since 6.1. 095 * 096 * @see MapSettings 097 * @see PropertyDefinition 098 * @since 2.12 099 */ 100@ScannerSide 101@ServerSide 102@ComputeEngineSide 103public abstract class Settings { 104 105 private final PropertyDefinitions definitions; 106 private final Encryption encryption; 107 108 protected Settings(PropertyDefinitions definitions, Encryption encryption) { 109 this.definitions = requireNonNull(definitions); 110 this.encryption = requireNonNull(encryption); 111 } 112 113 protected abstract Optional<String> get(String key); 114 115 protected abstract void set(String key, String value); 116 117 protected abstract void remove(String key); 118 119 /** 120 * Immutable map of the properties that have non-default values. 121 * The default values defined by {@link PropertyDefinitions} are ignored, 122 * so the returned values are not the effective values. Basically only 123 * the non-empty results of {@link #getRawString(String)} are returned. 124 * <p> 125 * Values are not decrypted if they are encrypted with a secret key. 126 * </p> 127 */ 128 public abstract Map<String, String> getProperties(); 129 130 public Encryption getEncryption() { 131 return encryption; 132 } 133 134 /** 135 * The value that overrides the default value. It 136 * may be encrypted with a secret key. Use {@link #getString(String)} to get 137 * the effective and decrypted value. 138 * 139 * @since 6.1 140 */ 141 public Optional<String> getRawString(String key) { 142 return get(definitions.validKey(requireNonNull(key))); 143 } 144 145 /** 146 * All the property definitions declared by core and plugins. 147 */ 148 public PropertyDefinitions getDefinitions() { 149 return definitions; 150 } 151 152 /** 153 * The definition related to the specified property. It may 154 * be empty. 155 * 156 * @since 6.1 157 */ 158 public Optional<PropertyDefinition> getDefinition(String key) { 159 return Optional.ofNullable(definitions.get(key)); 160 } 161 162 /** 163 * @return {@code true} if the property has a non-default value, else {@code false}. 164 */ 165 public boolean hasKey(String key) { 166 return getRawString(key).isPresent(); 167 } 168 169 @CheckForNull 170 public String getDefaultValue(String key) { 171 return definitions.getDefaultValue(key); 172 } 173 174 public boolean hasDefaultValue(String key) { 175 return StringUtils.isNotEmpty(getDefaultValue(key)); 176 } 177 178 /** 179 * The effective value of the specified property. Can return 180 * {@code null} if the property is not set and has no 181 * defined default value. 182 * <p> 183 * If the property is encrypted with a secret key, 184 * then the returned value is decrypted. 185 * </p> 186 * 187 * @throws IllegalStateException if value is encrypted but fails to be decrypted. 188 */ 189 @CheckForNull 190 public String getString(String key) { 191 String effectiveKey = definitions.validKey(key); 192 Optional<String> value = getRawString(effectiveKey); 193 if (!value.isPresent()) { 194 // default values cannot be encrypted, so return value as-is. 195 return getDefaultValue(effectiveKey); 196 } 197 if (encryption.isEncrypted(value.get())) { 198 try { 199 return encryption.decrypt(value.get()); 200 } catch (Exception e) { 201 throw new IllegalStateException("Fail to decrypt the property " + effectiveKey + ". Please check your secret key.", e); 202 } 203 } 204 return value.get(); 205 } 206 207 /** 208 * Effective value as boolean. It is {@code false} if {@link #getString(String)} 209 * does not return {@code "true"}, even if it's not a boolean representation. 210 * @return {@code true} if the effective value is {@code "true"}, else {@code false}. 211 */ 212 public boolean getBoolean(String key) { 213 String value = getString(key); 214 return StringUtils.isNotEmpty(value) && Boolean.parseBoolean(value); 215 } 216 217 /** 218 * Effective value as {@code int}. 219 * @return the value as {@code int}. If the property does not have value nor default value, then {@code 0} is returned. 220 * @throws NumberFormatException if value is not empty and is not a parsable integer 221 */ 222 public int getInt(String key) { 223 String value = getString(key); 224 if (StringUtils.isNotEmpty(value)) { 225 return Integer.parseInt(value); 226 } 227 return 0; 228 } 229 230 /** 231 * Effective value as {@code long}. 232 * @return the value as {@code long}. If the property does not have value nor default value, then {@code 0L} is returned. 233 * @throws NumberFormatException if value is not empty and is not a parsable {@code long} 234 */ 235 public long getLong(String key) { 236 String value = getString(key); 237 if (StringUtils.isNotEmpty(value)) { 238 return Long.parseLong(value); 239 } 240 return 0L; 241 } 242 243 /** 244 * Effective value as {@link Date}, without time fields. Format is {@link DateUtils#DATE_FORMAT}. 245 * 246 * @return the value as a {@link Date}. If the property does not have value nor default value, then {@code null} is returned. 247 * @throws RuntimeException if value is not empty and is not in accordance with {@link DateUtils#DATE_FORMAT}. 248 */ 249 @CheckForNull 250 public Date getDate(String key) { 251 String value = getString(key); 252 if (StringUtils.isNotEmpty(value)) { 253 return DateUtils.parseDate(value); 254 } 255 return null; 256 } 257 258 /** 259 * Effective value as {@link Date}, with time fields. Format is {@link DateUtils#DATETIME_FORMAT}. 260 * 261 * @return the value as a {@link Date}. If the property does not have value nor default value, then {@code null} is returned. 262 * @throws RuntimeException if value is not empty and is not in accordance with {@link DateUtils#DATETIME_FORMAT}. 263 */ 264 @CheckForNull 265 public Date getDateTime(String key) { 266 String value = getString(key); 267 if (StringUtils.isNotEmpty(value)) { 268 return DateUtils.parseDateTime(value); 269 } 270 return null; 271 } 272 273 /** 274 * Effective value as {@code Float}. 275 * @return the value as {@code Float}. If the property does not have value nor default value, then {@code null} is returned. 276 * @throws NumberFormatException if value is not empty and is not a parsable number 277 */ 278 @CheckForNull 279 public Float getFloat(String key) { 280 String value = getString(key); 281 if (StringUtils.isNotEmpty(value)) { 282 try { 283 return Float.valueOf(value); 284 } catch (NumberFormatException e) { 285 throw new IllegalStateException(String.format("The property '%s' is not a float value", key)); 286 } 287 } 288 return null; 289 } 290 291 /** 292 * Effective value as {@code Double}. 293 * @return the value as {@code Double}. If the property does not have value nor default value, then {@code null} is returned. 294 * @throws NumberFormatException if value is not empty and is not a parsable number 295 */ 296 @CheckForNull 297 public Double getDouble(String key) { 298 String value = getString(key); 299 if (StringUtils.isNotEmpty(value)) { 300 try { 301 return Double.valueOf(value); 302 } catch (NumberFormatException e) { 303 throw new IllegalStateException(String.format("The property '%s' is not a double value", key)); 304 } 305 } 306 return null; 307 } 308 309 /** 310 * Value is split by comma and trimmed. Never returns null. 311 * <br> 312 * Examples : 313 * <ul> 314 * <li>"one,two,three " -> ["one", "two", "three"]</li> 315 * <li>" one, two, three " -> ["one", "two", "three"]</li> 316 * <li>"one, , three" -> ["one", "", "three"]</li> 317 * </ul> 318 */ 319 public String[] getStringArray(String key) { 320 Optional<PropertyDefinition> def = getDefinition(key); 321 if ((def.isPresent()) && (def.get().multiValues())) { 322 String value = getString(key); 323 if (value == null) { 324 return ArrayUtils.EMPTY_STRING_ARRAY; 325 } 326 327 List<String> values = new ArrayList<>(); 328 for (String v : Splitter.on(",").trimResults().split(value)) { 329 values.add(v.replace("%2C", ",")); 330 } 331 return values.toArray(new String[values.size()]); 332 } 333 334 return getStringArrayBySeparator(key, ","); 335 } 336 337 /** 338 * Value is split by carriage returns. 339 * 340 * @return non-null array of lines. The line termination characters are excluded. 341 * @since 3.2 342 */ 343 public String[] getStringLines(String key) { 344 String value = getString(key); 345 if (Strings.isNullOrEmpty(value)) { 346 return ArrayUtils.EMPTY_STRING_ARRAY; 347 } 348 return value.split("\r?\n|\r", -1); 349 } 350 351 /** 352 * Value is split and trimmed. 353 */ 354 public String[] getStringArrayBySeparator(String key, String separator) { 355 String value = getString(key); 356 if (value != null) { 357 String[] strings = StringUtils.splitByWholeSeparator(value, separator); 358 String[] result = new String[strings.length]; 359 for (int index = 0; index < strings.length; index++) { 360 result[index] = trim(strings[index]); 361 } 362 return result; 363 } 364 return ArrayUtils.EMPTY_STRING_ARRAY; 365 } 366 367 public Settings appendProperty(String key, @Nullable String value) { 368 Optional<String> existingValue = getRawString(definitions.validKey(key)); 369 String newValue; 370 if (!existingValue.isPresent()) { 371 newValue = trim(value); 372 } else { 373 newValue = existingValue.get() + "," + trim(value); 374 } 375 return setProperty(key, newValue); 376 } 377 378 public Settings setProperty(String key, @Nullable String[] values) { 379 Optional<PropertyDefinition> def = getDefinition(key); 380 if (!def.isPresent() || (!def.get().multiValues())) { 381 throw new IllegalStateException("Fail to set multiple values on a single value property " + key); 382 } 383 384 String text = null; 385 if (values != null) { 386 List<String> escaped = new ArrayList<>(); 387 for (String value : values) { 388 if (null != value) { 389 escaped.add(value.replace(",", "%2C")); 390 } else { 391 escaped.add(""); 392 } 393 } 394 395 String escapedValue = Joiner.on(',').join(escaped); 396 text = trim(escapedValue); 397 } 398 return setProperty(key, text); 399 } 400 401 /** 402 * Change a property value in a restricted scope only, depending on execution context. New value 403 * is <b>never</b> persisted. New value is ephemeral and kept in memory only: 404 * <ul> 405 * <li>during current analysis in the case of scanner stack</li> 406 * <li>during processing of current HTTP request in the case of web server stack</li> 407 * <li>during execution of current task in the case of Compute Engine stack</li> 408 * </ul> 409 * 410 * Property is temporarily removed if the parameter {@code value} is {@code null} 411 */ 412 public Settings setProperty(String key, @Nullable String value) { 413 String validKey = definitions.validKey(key); 414 if (value == null) { 415 removeProperty(validKey); 416 417 } else { 418 set(validKey, trim(value)); 419 } 420 return this; 421 } 422 423 /** 424 * @see #setProperty(String, String) 425 */ 426 public Settings setProperty(String key, @Nullable Boolean value) { 427 return setProperty(key, value == null ? null : String.valueOf(value)); 428 } 429 430 /** 431 * @see #setProperty(String, String) 432 */ 433 public Settings setProperty(String key, @Nullable Integer value) { 434 return setProperty(key, value == null ? null : String.valueOf(value)); 435 } 436 437 /** 438 * @see #setProperty(String, String) 439 */ 440 public Settings setProperty(String key, @Nullable Long value) { 441 return setProperty(key, value == null ? null : String.valueOf(value)); 442 } 443 444 /** 445 * @see #setProperty(String, String) 446 */ 447 public Settings setProperty(String key, @Nullable Double value) { 448 return setProperty(key, value == null ? null : String.valueOf(value)); 449 } 450 451 /** 452 * @see #setProperty(String, String) 453 */ 454 public Settings setProperty(String key, @Nullable Float value) { 455 return setProperty(key, value == null ? null : String.valueOf(value)); 456 } 457 458 /** 459 * @see #setProperty(String, String) 460 */ 461 public Settings setProperty(String key, @Nullable Date date) { 462 return setProperty(key, date, false); 463 } 464 465 public Settings addProperties(Map<String, String> props) { 466 for (Map.Entry<String, String> entry : props.entrySet()) { 467 setProperty(entry.getKey(), entry.getValue()); 468 } 469 return this; 470 } 471 472 public Settings addProperties(Properties props) { 473 for (Map.Entry<Object, Object> entry : props.entrySet()) { 474 setProperty(entry.getKey().toString(), entry.getValue().toString()); 475 } 476 return this; 477 } 478 479 /** 480 * @see #setProperty(String, String) 481 */ 482 public Settings setProperty(String key, @Nullable Date date, boolean includeTime) { 483 if (date == null) { 484 return removeProperty(key); 485 } 486 return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date)); 487 } 488 489 public Settings removeProperty(String key) { 490 remove(key); 491 return this; 492 } 493 494 public List<String> getKeysStartingWith(String prefix) { 495 return getProperties().keySet().stream() 496 .filter(key -> StringUtils.startsWith(key, prefix)) 497 .collect(Collectors.toList()); 498 } 499 500}