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