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.Strings;
023import com.google.common.collect.Lists;
024import com.google.common.collect.Maps;
025import org.apache.commons.lang.ArrayUtils;
026import org.apache.commons.lang.StringUtils;
027import org.sonar.api.BatchComponent;
028import org.sonar.api.ServerComponent;
029import org.sonar.api.utils.DateUtils;
030
031import javax.annotation.Nullable;
032import java.util.*;
033
034/**
035 * Project Settings on batch side, Global Settings on server side. This component does not access to database, so
036 * property changed via setter methods are not persisted.
037 * <p/>
038 * <p>
039 * This component replaces the deprecated org.apache.commons.configuration.Configuration
040 * </p>
041 *
042 * @since 2.12
043 */
044public class Settings implements BatchComponent, ServerComponent {
045
046  protected final Map<String, String> properties;
047  protected final PropertyDefinitions definitions;
048  private final Encryption encryption;
049
050  public Settings() {
051    this(new PropertyDefinitions());
052  }
053
054  public Settings(PropertyDefinitions definitions) {
055    this.properties = Maps.newHashMap();
056    this.definitions = definitions;
057    this.encryption = new Encryption(this);
058  }
059
060  /**
061   * Clone settings. Actions are not propagated to cloned settings.
062   *
063   * @since 3.1
064   */
065  public Settings(Settings other) {
066    this.properties = Maps.newHashMap(other.properties);
067    this.definitions = other.definitions;
068    this.encryption = other.encryption;
069  }
070
071  public final Encryption getEncryption() {
072    return encryption;
073  }
074
075  public final String getDefaultValue(String key) {
076    return definitions.getDefaultValue(key);
077  }
078
079  public final boolean hasKey(String key) {
080    return properties.containsKey(key);
081  }
082
083  public final boolean hasDefaultValue(String key) {
084    return StringUtils.isNotEmpty(getDefaultValue(key));
085  }
086
087  public final String getString(String key) {
088    String value = properties.get(key);
089    if (value == null) {
090      value = getDefaultValue(key);
091    } else if (encryption.isEncrypted(value)) {
092      try {
093        value = encryption.decrypt(value);
094      } catch (Exception e) {
095        throw new IllegalStateException("Fail to decrypt the property " + key + ". Please check your secret key.", e);
096      }
097    }
098    return value;
099  }
100
101  /**
102   * Does not decrypt value.
103   */
104  protected String getClearString(String key) {
105    String value = properties.get(key);
106    if (value == null) {
107      value = getDefaultValue(key);
108    }
109    return value;
110  }
111
112  public final boolean getBoolean(String key) {
113    String value = getString(key);
114    return StringUtils.isNotEmpty(value) && Boolean.parseBoolean(value);
115  }
116
117  /**
118   * @return the value as int. If the property does not exist and has no default value, then 0 is returned.
119   */
120  public final int getInt(String key) {
121    String value = getString(key);
122    if (StringUtils.isNotEmpty(value)) {
123      return Integer.parseInt(value);
124    }
125    return 0;
126  }
127
128  public final long getLong(String key) {
129    String value = getString(key);
130    if (StringUtils.isNotEmpty(value)) {
131      return Long.parseLong(value);
132    }
133    return 0L;
134  }
135
136  public final Date getDate(String key) {
137    String value = getString(key);
138    if (StringUtils.isNotEmpty(value)) {
139      return DateUtils.parseDate(value);
140    }
141    return null;
142  }
143
144  public final Date getDateTime(String key) {
145    String value = getString(key);
146    if (StringUtils.isNotEmpty(value)) {
147      return DateUtils.parseDateTime(value);
148    }
149    return null;
150  }
151
152  /**
153   * Value is split by comma and trimmed.
154   * <p/>
155   * Examples :
156   * <ul>
157   * <li>"one,two,three " -> ["one", "two", "three"]</li>
158   * <li>"  one, two, three " -> ["one", "two", "three"]</li>
159   * <li>"one, , three" -> ["one", "", "three"]</li>
160   * </ul>
161   */
162  public final String[] getStringArray(String key) {
163    return getStringArrayBySeparator(key, ",");
164  }
165
166  /**
167   * Value is split by carriage returns.
168   *
169   * @return non-null array of lines. The line termination characters are excluded.
170   * @since 3.2
171   */
172  public final String[] getStringLines(String key) {
173    String value = getString(key);
174    if (Strings.isNullOrEmpty(value)) {
175      return ArrayUtils.EMPTY_STRING_ARRAY;
176    }
177    return value.split("\r?\n|\r", -1);
178  }
179
180  /**
181   * Value is splitted and trimmed.
182   */
183  public final String[] getStringArrayBySeparator(String key, String separator) {
184    String value = getString(key);
185    if (value != null) {
186      String[] strings = StringUtils.splitByWholeSeparator(value, separator);
187      String[] result = new String[strings.length];
188      for (int index = 0; index < strings.length; index++) {
189        result[index] = StringUtils.trim(strings[index]);
190      }
191      return result;
192    }
193    return ArrayUtils.EMPTY_STRING_ARRAY;
194  }
195
196  public final List<String> getKeysStartingWith(String prefix) {
197    List<String> result = Lists.newArrayList();
198    for (String key : properties.keySet()) {
199      if (StringUtils.startsWith(key, prefix)) {
200        result.add(key);
201      }
202    }
203    return result;
204  }
205
206  public final Settings appendProperty(String key, String value) {
207    String newValue = properties.get(key);
208    if (StringUtils.isEmpty(newValue)) {
209      newValue = StringUtils.trim(value);
210    } else {
211      newValue += "," + StringUtils.trim(value);
212    }
213    return setProperty(key, newValue);
214  }
215
216  public final Settings setProperty(String key, @Nullable String value) {
217    if (value == null) {
218      properties.remove(key);
219      doOnRemoveProperty(key);
220    } else {
221      properties.put(key, StringUtils.trim(value));
222      doOnSetProperty(key, value);
223    }
224    return this;
225  }
226
227  public final Settings setProperty(String key, @Nullable Boolean value) {
228    return setProperty(key, String.valueOf(value));
229  }
230
231  public final Settings setProperty(String key, @Nullable Integer value) {
232    return setProperty(key, String.valueOf(value));
233  }
234
235  public final Settings setProperty(String key, @Nullable Long value) {
236    return setProperty(key, String.valueOf(value));
237  }
238
239  public final Settings setProperty(String key, @Nullable Double value) {
240    return setProperty(key, String.valueOf(value));
241  }
242
243  public final Settings setProperty(String key, @Nullable Date date) {
244    return setProperty(key, date, false);
245  }
246
247  public final Settings addProperties(Map<String, String> props) {
248    for (Map.Entry<String, String> entry : props.entrySet()) {
249      setProperty(entry.getKey(), entry.getValue());
250    }
251    return this;
252  }
253
254  public final Settings addProperties(Properties props) {
255    for (Map.Entry<Object, Object> entry : props.entrySet()) {
256      setProperty(entry.getKey().toString(), entry.getValue().toString());
257    }
258    return this;
259  }
260
261  public final Settings addSystemProperties() {
262    return addProperties(System.getProperties());
263  }
264
265  public final Settings addEnvironmentVariables() {
266    return addProperties(System.getenv());
267  }
268
269  public final Settings setProperties(Map<String, String> props) {
270    clear();
271    return addProperties(props);
272  }
273
274  public final Settings setProperty(String key, @Nullable Date date, boolean includeTime) {
275    return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date));
276  }
277
278  public final Settings removeProperty(String key) {
279    return setProperty(key, (String) null);
280  }
281
282  public final Settings clear() {
283    properties.clear();
284    doOnClearProperties();
285    return this;
286  }
287
288  /**
289   * @return unmodifiable properties
290   */
291  public final Map<String, String> getProperties() {
292    return Collections.unmodifiableMap(properties);
293  }
294
295  public final PropertyDefinitions getDefinitions() {
296    return definitions;
297  }
298
299  /**
300   * Create empty settings. Definition of available properties is loaded from the given annotated class.
301   * This method is usually used by unit tests.
302   */
303  public static Settings createForComponent(Object component) {
304    return new Settings(new PropertyDefinitions(component));
305  }
306
307  protected void doOnSetProperty(String key, @Nullable String value) {
308  }
309
310  protected void doOnRemoveProperty(String key) {
311  }
312
313  protected void doOnClearProperties() {
314  }
315}