001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 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(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      /**
335       * @deprecated since 4.4 For embedding purpose all properties should be provided by the bootstrapper
336       */
337      @Deprecated
338      public Settings addSystemProperties() {
339        return addProperties(System.getProperties());
340      }
341    
342      /**
343       * @deprecated since 4.4 For embedding purpose all properties should be provided by the bootstrapper
344       */
345      @Deprecated
346      public Settings addEnvironmentVariables() {
347        return addProperties(System.getenv());
348      }
349    
350      public Settings setProperties(Map<String, String> props) {
351        clear();
352        return addProperties(props);
353      }
354    
355      public Settings setProperty(String key, @Nullable Date date, boolean includeTime) {
356        return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date));
357      }
358    
359      public Settings removeProperty(String key) {
360        return setProperty(key, (String) null);
361      }
362    
363      public Settings clear() {
364        properties.clear();
365        doOnClearProperties();
366        return this;
367      }
368    
369      /**
370       * @return immutable properties
371       */
372      public Map<String, String> getProperties() {
373        return ImmutableMap.copyOf(properties);
374      }
375    
376      public PropertyDefinitions getDefinitions() {
377        return definitions;
378      }
379    
380      /**
381       * Create empty settings. Definition of available properties is loaded from the given annotated class.
382       * This method is usually used by unit tests.
383       */
384      public static Settings createForComponent(Object component) {
385        return new Settings(new PropertyDefinitions(component));
386      }
387    
388      protected void doOnSetProperty(String key, @Nullable String value) {
389        // can be overridden
390      }
391    
392      protected void doOnRemoveProperty(String key) {
393        // can be overridden
394      }
395    
396      protected void doOnClearProperties() {
397        // can be overridden
398      }
399    
400      protected void doOnGetProperties(String key) {
401        // can be overridden
402      }
403    }