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 " -&gt; ["one", "two", "three"]</li>
263   * <li>"  one, two, three " -&gt; ["one", "two", "three"]</li>
264   * <li>"one, , three" -&gt; ["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}