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