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