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 */ 020package org.sonar.api.config; 021 022import com.google.common.base.Joiner; 023import com.google.common.base.Splitter; 024import com.google.common.base.Strings; 025import com.google.common.collect.ImmutableMap; 026import com.google.common.collect.Lists; 027import com.google.common.collect.Maps; 028import org.apache.commons.lang.ArrayUtils; 029import org.apache.commons.lang.StringUtils; 030import org.sonar.api.BatchComponent; 031import org.sonar.api.ServerComponent; 032import org.sonar.api.utils.DateUtils; 033 034import javax.annotation.Nullable; 035 036import java.util.Date; 037import java.util.List; 038import java.util.Map; 039import 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 */ 051public 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 public Settings addSystemProperties() { 335 return addProperties(System.getProperties()); 336 } 337 338 public Settings addEnvironmentVariables() { 339 return addProperties(System.getenv()); 340 } 341 342 public Settings setProperties(Map<String, String> props) { 343 clear(); 344 return addProperties(props); 345 } 346 347 public Settings setProperty(String key, @Nullable Date date, boolean includeTime) { 348 return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date)); 349 } 350 351 public Settings removeProperty(String key) { 352 return setProperty(key, (String) null); 353 } 354 355 public Settings clear() { 356 properties.clear(); 357 doOnClearProperties(); 358 return this; 359 } 360 361 /** 362 * @return immutable properties 363 */ 364 public Map<String, String> getProperties() { 365 return ImmutableMap.copyOf(properties); 366 } 367 368 public PropertyDefinitions getDefinitions() { 369 return definitions; 370 } 371 372 /** 373 * Create empty settings. Definition of available properties is loaded from the given annotated class. 374 * This method is usually used by unit tests. 375 */ 376 public static Settings createForComponent(Object component) { 377 return new Settings(new PropertyDefinitions(component)); 378 } 379 380 protected void doOnSetProperty(String key, @Nullable String value) { 381 // can be overridden 382 } 383 384 protected void doOnRemoveProperty(String key) { 385 // can be overridden 386 } 387 388 protected void doOnClearProperties() { 389 // can be overridden 390 } 391 392 protected void doOnGetProperties(String key) { 393 // can be overridden 394 } 395}