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