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     */
020    package org.sonar.api.config;
021    
022    import com.google.common.base.Joiner;
023    import com.google.common.base.Splitter;
024    import com.google.common.base.Strings;
025    import com.google.common.collect.ImmutableMap;
026    import com.google.common.collect.Lists;
027    import com.google.common.collect.Maps;
028    import org.apache.commons.lang.ArrayUtils;
029    import org.apache.commons.lang.StringUtils;
030    import org.sonar.api.BatchComponent;
031    import org.sonar.api.ServerComponent;
032    import org.sonar.api.utils.DateUtils;
033    
034    import javax.annotation.Nullable;
035    
036    import java.util.Date;
037    import java.util.List;
038    import java.util.Map;
039    import 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     */
051    public 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(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 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        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 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 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 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 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 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 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 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 String[] getStringArray(String key) {
193        PropertyDefinition property = getDefinitions().get(key);
194        if ((null != property) && (property.multiValues())) {
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 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 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 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 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 Settings setProperty(String key, @Nullable String[] values) {
261        PropertyDefinition property = getDefinitions().get(key);
262        if ((null == property) || (!property.multiValues())) {
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 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 Settings setProperty(String key, @Nullable Boolean value) {
296        return setProperty(key, value == null ? null : String.valueOf(value));
297      }
298    
299      public Settings setProperty(String key, @Nullable Integer value) {
300        return setProperty(key, value == null ? null : String.valueOf(value));
301      }
302    
303      public Settings setProperty(String key, @Nullable Long value) {
304        return setProperty(key, value == null ? null : String.valueOf(value));
305      }
306    
307      public Settings setProperty(String key, @Nullable Double value) {
308        return setProperty(key, value == null ? null : String.valueOf(value));
309      }
310    
311      public Settings setProperty(String key, @Nullable Float value) {
312        return setProperty(key, value == null ? null : String.valueOf(value));
313      }
314    
315      public Settings setProperty(String key, @Nullable Date date) {
316        return setProperty(key, date, false);
317      }
318    
319      public 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 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 Settings addSystemProperties() {
334        return addProperties(System.getProperties());
335      }
336    
337      public Settings addEnvironmentVariables() {
338        return addProperties(System.getenv());
339      }
340    
341      public Settings setProperties(Map<String, String> props) {
342        clear();
343        return addProperties(props);
344      }
345    
346      public Settings setProperty(String key, @Nullable Date date, boolean includeTime) {
347        return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date));
348      }
349    
350      public Settings removeProperty(String key) {
351        return setProperty(key, (String) null);
352      }
353    
354      public Settings clear() {
355        properties.clear();
356        doOnClearProperties();
357        return this;
358      }
359    
360      /**
361       * @return immutable properties
362       */
363      public Map<String, String> getProperties() {
364        return ImmutableMap.copyOf(properties);
365      }
366    
367      public 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    }