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 * For testing, you can create a new empty {@link Settings} component using {@link #Settings()} and then 047 * populate it using all variant of {@code setProperty}. <br/> 048 * If you want to test with default values of your properties taken into account there are two ways dependening on how you declare your properties. 049 * <ul> 050 * <li>If you are using annotations like: 051 * <pre> 052 * <code>{@literal @}Properties({ 053 * {@literal @}Property( 054 * key = "sonar.myProp", 055 * defaultValue = "A default value", 056 * name = "My property"), 057 * }) 058 * public class MyPlugin extends SonarPlugin { 059 * </code> 060 * </pre> 061 * then you can use: 062 * <pre> 063 * <code>new Settings(new PropertyDefinitions(MyPlugin.class)) 064 * </code> 065 * </pre> 066 * </li> 067 * <li>If you are using the {@link PropertyDefinition#builder(String)} way like: 068 * <pre> 069 * <code> 070 * public class MyPlugin extends SonarPlugin { 071 * public List getExtensions() { 072 * return Arrays.asList( 073 * PropertyDefinition.builder("sonar.myProp").name("My property").defaultValue("A default value").build() 074 * ); 075 * } 076 * } 077 * </code> 078 * </pre> 079 * then you can use: 080 * <pre> 081 * <code>new Settings(new PropertyDefinitions(new MyPlugin().getExtensions())) 082 * </code> 083 * </pre> 084 * </li> 085 * </p> 086 * @since 2.12 087 */ 088 public class Settings implements BatchComponent, ServerComponent { 089 090 protected Map<String, String> properties; 091 protected PropertyDefinitions definitions; 092 private Encryption encryption; 093 094 public Settings() { 095 this(new PropertyDefinitions()); 096 } 097 098 public Settings(PropertyDefinitions definitions) { 099 this.properties = Maps.newHashMap(); 100 this.definitions = definitions; 101 this.encryption = new Encryption(null); 102 } 103 104 /** 105 * Clone settings. Actions are not propagated to cloned settings. 106 * 107 * @since 3.1 108 */ 109 public Settings(Settings other) { 110 this.properties = Maps.newHashMap(other.properties); 111 this.definitions = other.definitions; 112 this.encryption = other.encryption; 113 } 114 115 public Encryption getEncryption() { 116 return encryption; 117 } 118 119 public String getDefaultValue(String key) { 120 return definitions.getDefaultValue(key); 121 } 122 123 public boolean hasKey(String key) { 124 return properties.containsKey(key); 125 } 126 127 public boolean hasDefaultValue(String key) { 128 return StringUtils.isNotEmpty(getDefaultValue(key)); 129 } 130 131 public String getString(String key) { 132 String value = getClearString(key); 133 if (value != null && encryption.isEncrypted(value)) { 134 try { 135 value = encryption.decrypt(value); 136 } catch (Exception e) { 137 throw new IllegalStateException("Fail to decrypt the property " + key + ". Please check your secret key.", e); 138 } 139 } 140 return value; 141 } 142 143 /** 144 * Does not decrypt value. 145 */ 146 protected String getClearString(String key) { 147 doOnGetProperties(key); 148 String validKey = definitions.validKey(key); 149 String value = properties.get(validKey); 150 if (value == null) { 151 value = getDefaultValue(validKey); 152 } 153 return value; 154 } 155 156 public boolean getBoolean(String key) { 157 String value = getString(key); 158 return StringUtils.isNotEmpty(value) && Boolean.parseBoolean(value); 159 } 160 161 /** 162 * @return the value as int. If the property does not exist and has no default value, then 0 is returned. 163 */ 164 public int getInt(String key) { 165 String value = getString(key); 166 if (StringUtils.isNotEmpty(value)) { 167 return Integer.parseInt(value); 168 } 169 return 0; 170 } 171 172 public long getLong(String key) { 173 String value = getString(key); 174 if (StringUtils.isNotEmpty(value)) { 175 return Long.parseLong(value); 176 } 177 return 0L; 178 } 179 180 public Date getDate(String key) { 181 String value = getString(key); 182 if (StringUtils.isNotEmpty(value)) { 183 return DateUtils.parseDate(value); 184 } 185 return null; 186 } 187 188 public Date getDateTime(String key) { 189 String value = getString(key); 190 if (StringUtils.isNotEmpty(value)) { 191 return DateUtils.parseDateTime(value); 192 } 193 return null; 194 } 195 196 public Float getFloat(String key) { 197 String value = getString(key); 198 if (StringUtils.isNotEmpty(value)) { 199 try { 200 return Float.valueOf(value); 201 } catch (NumberFormatException e) { 202 throw new IllegalStateException(String.format("The property '%s' is not a float value", key)); 203 } 204 } 205 return null; 206 } 207 208 public Double getDouble(String key) { 209 String value = getString(key); 210 if (StringUtils.isNotEmpty(value)) { 211 try { 212 return Double.valueOf(value); 213 } catch (NumberFormatException e) { 214 throw new IllegalStateException(String.format("The property '%s' is not a double value", key)); 215 } 216 } 217 return null; 218 } 219 220 /** 221 * Value is split by comma and trimmed. Never returns null. 222 * <p/> 223 * Examples : 224 * <ul> 225 * <li>"one,two,three " -> ["one", "two", "three"]</li> 226 * <li>" one, two, three " -> ["one", "two", "three"]</li> 227 * <li>"one, , three" -> ["one", "", "three"]</li> 228 * </ul> 229 */ 230 public String[] getStringArray(String key) { 231 PropertyDefinition property = getDefinitions().get(key); 232 if ((null != property) && (property.multiValues())) { 233 String value = getString(key); 234 if (value == null) { 235 return ArrayUtils.EMPTY_STRING_ARRAY; 236 } 237 238 List<String> values = Lists.newArrayList(); 239 for (String v : Splitter.on(",").trimResults().split(value)) { 240 values.add(v.replace("%2C", ",")); 241 } 242 return values.toArray(new String[values.size()]); 243 } 244 245 return getStringArrayBySeparator(key, ","); 246 } 247 248 /** 249 * Value is split by carriage returns. 250 * 251 * @return non-null array of lines. The line termination characters are excluded. 252 * @since 3.2 253 */ 254 public String[] getStringLines(String key) { 255 String value = getString(key); 256 if (Strings.isNullOrEmpty(value)) { 257 return ArrayUtils.EMPTY_STRING_ARRAY; 258 } 259 return value.split("\r?\n|\r", -1); 260 } 261 262 /** 263 * Value is splitted and trimmed. 264 */ 265 public String[] getStringArrayBySeparator(String key, String separator) { 266 String value = getString(key); 267 if (value != null) { 268 String[] strings = StringUtils.splitByWholeSeparator(value, separator); 269 String[] result = new String[strings.length]; 270 for (int index = 0; index < strings.length; index++) { 271 result[index] = StringUtils.trim(strings[index]); 272 } 273 return result; 274 } 275 return ArrayUtils.EMPTY_STRING_ARRAY; 276 } 277 278 public List<String> getKeysStartingWith(String prefix) { 279 List<String> result = Lists.newArrayList(); 280 for (String key : properties.keySet()) { 281 if (StringUtils.startsWith(key, prefix)) { 282 result.add(key); 283 } 284 } 285 return result; 286 } 287 288 public Settings appendProperty(String key, String value) { 289 String newValue = properties.get(definitions.validKey(key)); 290 if (StringUtils.isEmpty(newValue)) { 291 newValue = StringUtils.trim(value); 292 } else { 293 newValue += "," + StringUtils.trim(value); 294 } 295 return setProperty(key, newValue); 296 } 297 298 public Settings setProperty(String key, @Nullable String[] values) { 299 PropertyDefinition property = getDefinitions().get(key); 300 if ((null == property) || (!property.multiValues())) { 301 throw new IllegalStateException("Fail to set multiple values on a single value property " + key); 302 } 303 304 String text = null; 305 if (values != null) { 306 List<String> escaped = Lists.newArrayList(); 307 for (String value : values) { 308 if (null != value) { 309 escaped.add(value.replace(",", "%2C")); 310 } else { 311 escaped.add(""); 312 } 313 } 314 315 String escapedValue = Joiner.on(',').join(escaped); 316 text = StringUtils.trim(escapedValue); 317 } 318 return setProperty(key, text); 319 } 320 321 public Settings setProperty(String key, @Nullable String value) { 322 String validKey = definitions.validKey(key); 323 if (value == null) { 324 properties.remove(validKey); 325 doOnRemoveProperty(validKey); 326 } else { 327 properties.put(validKey, StringUtils.trim(value)); 328 doOnSetProperty(validKey, value); 329 } 330 return this; 331 } 332 333 public Settings setProperty(String key, @Nullable Boolean value) { 334 return setProperty(key, value == null ? null : String.valueOf(value)); 335 } 336 337 public Settings setProperty(String key, @Nullable Integer value) { 338 return setProperty(key, value == null ? null : String.valueOf(value)); 339 } 340 341 public Settings setProperty(String key, @Nullable Long value) { 342 return setProperty(key, value == null ? null : String.valueOf(value)); 343 } 344 345 public Settings setProperty(String key, @Nullable Double value) { 346 return setProperty(key, value == null ? null : String.valueOf(value)); 347 } 348 349 public Settings setProperty(String key, @Nullable Float value) { 350 return setProperty(key, value == null ? null : String.valueOf(value)); 351 } 352 353 public Settings setProperty(String key, @Nullable Date date) { 354 return setProperty(key, date, false); 355 } 356 357 public Settings addProperties(Map<String, String> props) { 358 for (Map.Entry<String, String> entry : props.entrySet()) { 359 setProperty(entry.getKey(), entry.getValue()); 360 } 361 return this; 362 } 363 364 public Settings addProperties(Properties props) { 365 for (Map.Entry<Object, Object> entry : props.entrySet()) { 366 setProperty(entry.getKey().toString(), entry.getValue().toString()); 367 } 368 return this; 369 } 370 371 /** 372 * @deprecated since 4.4 For embedding purpose all properties should be provided by the bootstrapper 373 */ 374 @Deprecated 375 public Settings addSystemProperties() { 376 return addProperties(System.getProperties()); 377 } 378 379 /** 380 * @deprecated since 4.4 For embedding purpose all properties should be provided by the bootstrapper 381 */ 382 @Deprecated 383 public Settings addEnvironmentVariables() { 384 return addProperties(System.getenv()); 385 } 386 387 public Settings setProperties(Map<String, String> props) { 388 clear(); 389 return addProperties(props); 390 } 391 392 public Settings setProperty(String key, @Nullable Date date, boolean includeTime) { 393 if (date == null) { 394 return removeProperty(key); 395 } 396 return setProperty(key, includeTime ? DateUtils.formatDateTime(date) : DateUtils.formatDate(date)); 397 } 398 399 public Settings removeProperty(String key) { 400 return setProperty(key, (String) null); 401 } 402 403 public Settings clear() { 404 properties.clear(); 405 doOnClearProperties(); 406 return this; 407 } 408 409 /** 410 * @return immutable properties 411 */ 412 public Map<String, String> getProperties() { 413 return ImmutableMap.copyOf(properties); 414 } 415 416 public PropertyDefinitions getDefinitions() { 417 return definitions; 418 } 419 420 /** 421 * Create empty settings. Definition of available properties is loaded from the given annotated class. 422 * This method is usually used by unit tests. 423 */ 424 public static Settings createForComponent(Object component) { 425 return new Settings(new PropertyDefinitions(component)); 426 } 427 428 protected void doOnSetProperty(String key, @Nullable String value) { 429 // can be overridden 430 } 431 432 protected void doOnRemoveProperty(String key) { 433 // can be overridden 434 } 435 436 protected void doOnClearProperties() { 437 // can be overridden 438 } 439 440 protected void doOnGetProperties(String key) { 441 // can be overridden 442 } 443 }