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, or 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 }