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