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