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