]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1378 validate property types + update type of core properties
authorSimon Brandhof <simon.brandhof@gmail.com>
Fri, 16 Mar 2012 10:46:48 +0000 (11:46 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Fri, 16 Mar 2012 10:50:43 +0000 (11:50 +0100)
30 files changed:
plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstylePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java
plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DefaultPurgeTask.java
plugins/sonar-findbugs-plugin/src/main/java/org/sonar/plugins/findbugs/FindbugsPlugin.java
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java
sonar-plugin-api/src/main/java/org/sonar/api/Property.java
sonar-plugin-api/src/main/java/org/sonar/api/PropertyType.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java
sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java
sonar-plugin-api/src/main/java/org/sonar/api/config/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/rules/RulesCategory.java
sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java
sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java
sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java
sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/models/property.rb
sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_plugins.html.erb [deleted file]
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_sidebar.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/index.html.erb

index 515b8d15e04d3f426552607cfad46f903396ae06..9bba008989bcdebfbe79b52de7479cd0b1b5b97f 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.plugins.checkstyle;
 
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
 import org.sonar.api.SonarPlugin;
 
 import java.util.Arrays;
@@ -35,7 +36,7 @@ import java.util.List;
       + " See <a href='http://checkstyle.sourceforge.net/config.html'>Checkstyle configuration page</a> to get more information on those filters.",
     project = false,
     global = true,
-    type = Property.Type.TEXT)})
+    type = PropertyType.TEXT)})
 public final class CheckstylePlugin extends SonarPlugin {
 
   public List getExtensions() {
index b0d273c9fe6f1f8aed0dfff45050f9bfd0ab4b78..71c09d25209e5bba182dcfd8e4ff09ea33c6aa01 100644 (file)
 package org.sonar.plugins.core;
 
 import com.google.common.collect.Lists;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.Properties;
-import org.sonar.api.Property;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.*;
 import org.sonar.api.checks.NoSonarFilter;
+import org.sonar.api.PropertyType;
 import org.sonar.api.resources.Java;
 import org.sonar.plugins.core.batch.ExcludedResourceFilter;
 import org.sonar.plugins.core.batch.IndexProjectPostJob;
@@ -50,177 +48,177 @@ import org.sonar.plugins.core.widgets.reviews.*;
 import java.util.List;
 
 @Properties({
-    @Property(
-        key = CoreProperties.SERVER_BASE_URL,
-        defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE,
-        name = "Server base URL",
-        description = "HTTP URL of this Sonar server, such as <i>http://yourhost.yourdomain/sonar</i>. This value is used i.e. to create links in emails.",
-        project = false,
-        global = true,
-        category = CoreProperties.CATEGORY_GENERAL),
-    @Property(
-        key = CoreProperties.CORE_COVERAGE_PLUGIN_PROPERTY,
-        defaultValue = "cobertura",
-        name = "Code coverage plugin",
-        description = "Key of the code coverage plugin to use.",
-        project = true,
-        global = true,
-        category = CoreProperties.CATEGORY_CODE_COVERAGE),
-    @Property(
-        key = CoreProperties.CORE_IMPORT_SOURCES_PROPERTY,
-        defaultValue = "" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE,
-        name = "Import sources",
-        description = "Set to false if sources should not be displayed, e.g. for security reasons.",
-        project = true,
-        module = true,
-        global = true,
-        category = CoreProperties.CATEGORY_SECURITY,
-        type = Property.Type.BOOLEAN),
-    @Property(
-        key = CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY,
-        defaultValue = "" + CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE,
-        name = "Tendency period",
-        description = TendencyDecorator.PROP_DAYS_DESCRIPTION,
-        project = false,
-        global = true,
-        category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS,
-        type = Property.Type.INTEGER),
-    @Property(
-        key = CoreProperties.SKIP_TENDENCIES_PROPERTY,
-        defaultValue = "" + CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE,
-        name = "Skip tendencies",
-        description = "Skip calculation of measure tendencies",
-        project = true,
-        module = false,
-        global = true,
-        category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS,
-        type = Property.Type.BOOLEAN),
-    @Property(
-        key = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY,
-        name = "Exclude modules",
-        description = "Maven artifact ids of modules to exclude (comma-separated).",
-        project = true,
-        global = false,
-        category = CoreProperties.CATEGORY_GENERAL),
-    @Property(
-        key = CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY,
-        defaultValue = "" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE,
-        name = "Force user authentication",
-        description = "Forcing user authentication stops un-logged users to access Sonar.",
-        project = false,
-        global = true,
-        category = CoreProperties.CATEGORY_SECURITY,
-        type = Property.Type.BOOLEAN),
-    @Property(
-        key = CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY,
-        defaultValue = "" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE,
-        name = "Allow users to sign up online",
-        description = "Users can sign up online.",
-        project = false,
-        global = true,
-        category = CoreProperties.CATEGORY_SECURITY,
-        type = Property.Type.BOOLEAN),
-    @Property(
-        key = CoreProperties.CORE_DEFAULT_GROUP,
-        defaultValue = CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE,
-        name = "Default user group",
-        description = "Any new users will automatically join this group.",
-        project = false,
-        global = true,
-        category = CoreProperties.CATEGORY_SECURITY),
-    @Property(
-        key = CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY,
-        defaultValue = "en",
-        name = "Locale used for violation messages",
-        description = "Locale to be used when generating violation messages. It's up to each rule engine to support this global internationalization property",
-        project = true,
-        global = true,
-        category = CoreProperties.CATEGORY_L10N),
-    @Property(
-        key = "sonar.timemachine.period1",
-        name = "Period 1",
-        description = "Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " +
-            "analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " +
-            "compare to previous analysis</li></ul>" +
-            "Changing this property only take effect after subsequent project inspections.",
-        project = false,
-        global = true,
-        defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1,
-        category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
-    @Property(
-        key = "sonar.timemachine.period2",
-        name = "Period 2",
-        description = "See the property 'Period 1'",
-        project = false,
-        global = true,
-        defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2,
-        category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
-    @Property(
-        key = "sonar.timemachine.period3",
-        name = "Period 3",
-        description = "See the property 'Period 1'",
-        project = false,
-        global = true,
-        defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3,
-        category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
-    @Property(
-        key = "sonar.timemachine.period4",
-        name = "Period 4",
-        description = "Period used to compare measures and track new violations. This property is specific to the project. Values are : " +
-            "<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " +
-            "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li><li>A version, for example 1.2</li></ul>" +
-            "Changing this property only take effect after subsequent project inspection.",
-        project = true,
-        global = false,
-        defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4,
-        category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
-    @Property(
-        key = "sonar.timemachine.period5",
-        name = "Period 5",
-        description = "See the property 'Period 4'",
-        project = true,
-        global = false,
-        defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5,
-        category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
+  @Property(
+    key = CoreProperties.SERVER_BASE_URL,
+    defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE,
+    name = "Server base URL",
+    description = "HTTP URL of this Sonar server, such as <i>http://yourhost.yourdomain/sonar</i>. This value is used i.e. to create links in emails.",
+    project = false,
+    global = true,
+    category = CoreProperties.CATEGORY_GENERAL),
+  @Property(
+    key = CoreProperties.CORE_COVERAGE_PLUGIN_PROPERTY,
+    defaultValue = "cobertura",
+    name = "Code coverage plugin",
+    description = "Key of the code coverage plugin to use.",
+    project = true,
+    global = true,
+    category = CoreProperties.CATEGORY_CODE_COVERAGE),
+  @Property(
+    key = CoreProperties.CORE_IMPORT_SOURCES_PROPERTY,
+    defaultValue = "" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE,
+    name = "Import sources",
+    description = "Set to false if sources should not be displayed, e.g. for security reasons.",
+    project = true,
+    module = true,
+    global = true,
+    category = CoreProperties.CATEGORY_SECURITY,
+    type = PropertyType.BOOLEAN),
+  @Property(
+    key = CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY,
+    defaultValue = "" + CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE,
+    name = "Tendency period",
+    description = TendencyDecorator.PROP_DAYS_DESCRIPTION,
+    project = false,
+    global = true,
+    category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS,
+    type = PropertyType.INTEGER),
+  @Property(
+    key = CoreProperties.SKIP_TENDENCIES_PROPERTY,
+    defaultValue = "" + CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE,
+    name = "Skip tendencies",
+    description = "Skip calculation of measure tendencies",
+    project = true,
+    module = false,
+    global = true,
+    category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS,
+    type = PropertyType.BOOLEAN),
+  @Property(
+    key = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY,
+    name = "Exclude modules",
+    description = "Maven artifact ids of modules to exclude (comma-separated).",
+    project = true,
+    global = false,
+    category = CoreProperties.CATEGORY_GENERAL),
+  @Property(
+    key = CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY,
+    defaultValue = "" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE,
+    name = "Force user authentication",
+    description = "Forcing user authentication stops un-logged users to access Sonar.",
+    project = false,
+    global = true,
+    category = CoreProperties.CATEGORY_SECURITY,
+    type = PropertyType.BOOLEAN),
+  @Property(
+    key = CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY,
+    defaultValue = "" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE,
+    name = "Allow users to sign up online",
+    description = "Users can sign up online.",
+    project = false,
+    global = true,
+    category = CoreProperties.CATEGORY_SECURITY,
+    type = PropertyType.BOOLEAN),
+  @Property(
+    key = CoreProperties.CORE_DEFAULT_GROUP,
+    defaultValue = CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE,
+    name = "Default user group",
+    description = "Any new users will automatically join this group.",
+    project = false,
+    global = true,
+    category = CoreProperties.CATEGORY_SECURITY),
+  @Property(
+    key = CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY,
+    defaultValue = "en",
+    name = "Locale used for violation messages",
+    description = "Locale to be used when generating violation messages. It's up to each rule engine to support this global internationalization property",
+    project = true,
+    global = true,
+    category = CoreProperties.CATEGORY_L10N),
+  @Property(
+    key = "sonar.timemachine.period1",
+    name = "Period 1",
+    description = "Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " +
+      "analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " +
+      "compare to previous analysis</li></ul>" +
+      "Changing this property only take effect after subsequent project inspections.",
+    project = false,
+    global = true,
+    defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1,
+    category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
+  @Property(
+    key = "sonar.timemachine.period2",
+    name = "Period 2",
+    description = "See the property 'Period 1'",
+    project = false,
+    global = true,
+    defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2,
+    category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
+  @Property(
+    key = "sonar.timemachine.period3",
+    name = "Period 3",
+    description = "See the property 'Period 1'",
+    project = false,
+    global = true,
+    defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3,
+    category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
+  @Property(
+    key = "sonar.timemachine.period4",
+    name = "Period 4",
+    description = "Period used to compare measures and track new violations. This property is specific to the project. Values are : " +
+      "<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " +
+      "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li><li>A version, for example 1.2</li></ul>" +
+      "Changing this property only take effect after subsequent project inspection.",
+    project = true,
+    global = false,
+    defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4,
+    category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
+  @Property(
+    key = "sonar.timemachine.period5",
+    name = "Period 5",
+    description = "See the property 'Period 4'",
+    project = true,
+    global = false,
+    defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5,
+    category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS),
 
 
-    // SERVER-SIDE TECHNICAL PROPERTIES
+  // SERVER-SIDE TECHNICAL PROPERTIES
 
-    @Property(
-        key = "sonar.security.realm",
-        name = "Security Realm",
-        project = false,
-        global = false
-    ),
-    @Property(
-        key = "sonar.security.savePassword",
-        name = "Save external password",
-        project = false,
-        global = false
-    ),
-    @Property(
-        key = "sonar.authenticator.downcase",
-        name = "Downcase login",
-        description = "Downcase login during user authentication, typically for Active Directory",
-        project = false,
-        global = false,
-        defaultValue = "false",
-        type = Property.Type.BOOLEAN),
-    @Property(
-        key = CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS,
-        name = "Create user accounts",
-        description = "Create accounts when authenticating users via an external system",
-        project = false,
-        global = false,
-        defaultValue = "true",
-        type = Property.Type.BOOLEAN),
-    @Property(
-        key = CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE,
-        name = "Ignore failures during authenticator startup",
-        defaultValue = "false",
-        project = false,
-        global = false,
-        type = Property.Type.BOOLEAN)
+  @Property(
+    key = "sonar.security.realm",
+    name = "Security Realm",
+    project = false,
+    global = false
+  ),
+  @Property(
+    key = "sonar.security.savePassword",
+    name = "Save external password",
+    project = false,
+    global = false
+  ),
+  @Property(
+    key = "sonar.authenticator.downcase",
+    name = "Downcase login",
+    description = "Downcase login during user authentication, typically for Active Directory",
+    project = false,
+    global = false,
+    defaultValue = "false",
+    type = PropertyType.BOOLEAN),
+  @Property(
+    key = CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS,
+    name = "Create user accounts",
+    description = "Create accounts when authenticating users via an external system",
+    project = false,
+    global = false,
+    defaultValue = "true",
+    type = PropertyType.BOOLEAN),
+  @Property(
+    key = CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE,
+    name = "Ignore failures during authenticator startup",
+    defaultValue = "false",
+    project = false,
+    global = false,
+    type = PropertyType.BOOLEAN)
 })
 public final class CorePlugin extends SonarPlugin {
 
index ddefdb4fa4c8c90257a04023d0c254554ef3df04..2708f843d17c03a3e115864bd89463571d591f13 100644 (file)
  */
 package org.sonar.plugins.cpd;
 
-import org.sonar.api.CoreProperties;
-import org.sonar.api.Properties;
-import org.sonar.api.Property;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.*;
+import org.sonar.api.PropertyType;
 import org.sonar.plugins.cpd.decorators.DuplicationDensityDecorator;
 import org.sonar.plugins.cpd.decorators.SumDuplicationsDecorator;
 import org.sonar.plugins.cpd.index.IndexFactory;
@@ -43,7 +41,7 @@ import java.util.List;
     module = true,
     global = true,
     category = CoreProperties.CATEGORY_DUPLICATIONS,
-    type = Property.Type.SINGLE_SELECT_LIST,
+    type = PropertyType.SINGLE_SELECT_LIST,
     options = {"sonar", "pmd"}),
   @Property(
     key = CoreProperties.CPD_CROSS_RPOJECT,
@@ -56,7 +54,7 @@ import java.util.List;
     module = true,
     global = true,
     category = CoreProperties.CATEGORY_DUPLICATIONS,
-    type = Property.Type.BOOLEAN),
+    type = PropertyType.BOOLEAN),
   @Property(
     key = CoreProperties.CPD_MINIMUM_TOKENS_PROPERTY,
     defaultValue = CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE + "",
@@ -67,7 +65,7 @@ import java.util.List;
     module = true,
     global = true,
     category = CoreProperties.CATEGORY_DUPLICATIONS,
-    type = Property.Type.INTEGER),
+    type = PropertyType.INTEGER),
   @Property(
     key = CoreProperties.CPD_IGNORE_LITERALS_PROPERTY,
     defaultValue = CoreProperties.CPD_IGNORE_LITERALS_DEFAULT_VALUE + "",
@@ -79,7 +77,7 @@ import java.util.List;
     module = true,
     global = true,
     category = CoreProperties.CATEGORY_DUPLICATIONS,
-    type = Property.Type.BOOLEAN),
+    type = PropertyType.BOOLEAN),
   @Property(
     key = CoreProperties.CPD_IGNORE_IDENTIFIERS_PROPERTY,
     defaultValue = CoreProperties.CPD_IGNORE_IDENTIFIERS_DEFAULT_VALUE + "",
@@ -90,7 +88,7 @@ import java.util.List;
     module = true,
     global = true,
     category = CoreProperties.CATEGORY_DUPLICATIONS,
-    type = Property.Type.BOOLEAN)
+    type = PropertyType.BOOLEAN)
 })
 public final class CpdPlugin extends SonarPlugin {
 
index 15a74fa8768e386876a5fa224270fbba8f542b4d..4507771468f2cea3256d242ebc62b9ce7a2aea1d 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.plugins.dbcleaner;
 
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
 import org.sonar.api.SonarPlugin;
 import org.sonar.plugins.dbcleaner.api.DbCleanerConstants;
 import org.sonar.plugins.dbcleaner.period.DefaultPeriodCleaner;
@@ -35,20 +36,20 @@ import java.util.List;
       + "the DbCleaner keeps the first one and fully delete the other ones.",
     global = true,
     project = true,
-    type = Property.Type.INTEGER),
+    type = PropertyType.INTEGER),
   @Property(key = DbCleanerConstants.WEEKS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_MONTH, defaultValue = "52",
     name = "Number of weeks before starting to keep only one snapshot by month",
     description = "After this number of weeks, if there are several snapshots during the same month, "
       + "the DbCleaner keeps the first one and fully delete the other ones.",
     global = true,
     project = true,
-    type = Property.Type.INTEGER),
+    type = PropertyType.INTEGER),
   @Property(key = DbCleanerConstants.WEEKS_BEFORE_DELETING_ALL_SNAPSHOTS, defaultValue = "260",
     name = "Number of weeks before starting to delete all remaining snapshots",
     description = "After this number of weeks, all snapshots are fully deleted.",
     global = true,
     project = true,
-    type = Property.Type.INTEGER)
+    type = PropertyType.INTEGER)
 }
 )
 public final class DbCleanerPlugin extends SonarPlugin {
index 6fe63815b53cbd2bb547fe48064142fc2068409c..afee357487ad8a7e747c710403f82ffeeabd1a63 100644 (file)
@@ -23,6 +23,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
 import org.sonar.api.config.Settings;
 import org.sonar.api.resources.Scopes;
 import org.sonar.core.purge.PurgeDao;
@@ -41,7 +42,7 @@ import org.sonar.plugins.dbcleaner.period.DefaultPeriodCleaner;
     global = true,
     project = true,
     module = false,
-    type = Property.Type.BOOLEAN)
+    type = PropertyType.BOOLEAN)
 })
 public class DefaultPurgeTask implements PurgeTask {
   private static final Logger LOG = LoggerFactory.getLogger(DefaultPurgeTask.class);
index 115feb801d295d7c5a4bfe143196a1123c7bbaf1..070dfbee8bd0a90a27cc063e1ef350207a3ff05b 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.plugins.findbugs;
 
 import org.sonar.api.*;
+import org.sonar.api.PropertyType;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -43,7 +44,7 @@ import java.util.List;
     project = true,
     module = true,
     global = true,
-    type = Property.Type.INTEGER),
+    type = PropertyType.INTEGER),
   @Property(
     key = FindbugsConstants.EXCLUDES_FILTERS_PROPERTY,
     name = "Excludes Filters",
index 7ff09ae2a5e1dc1c4a4247bafe782acf21af6272..2f3e06c1be65bb9606549f6460805931a1693f37 100644 (file)
@@ -549,6 +549,12 @@ property.category.duplications=Duplications
 property.category.localization=Localization
 property.category.server_id=Server ID
 
+property.error.notBoolean=Valid options are "true" and "false"
+property.error.notInteger=Only digits are allowed
+property.error.notFloat=Not a floating point number
+property.error.notInOptions=Not a valid option
+
+
 
 #------------------------------------------------------------------------------
 #
index 6e06dd8fde6811e58d30f51109de2a4383bd9fc8..7932d233545668c42767afc34e3fc98e557911e3 100644 (file)
  */
 package org.sonar.plugins.squid;
 
-import org.sonar.api.CoreProperties;
-import org.sonar.api.Properties;
-import org.sonar.api.Property;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.*;
+import org.sonar.api.PropertyType;
 import org.sonar.plugins.squid.decorators.*;
 
 import java.util.Arrays;
@@ -37,7 +35,7 @@ import java.util.List;
     project = true,
     global = true,
     category = CoreProperties.CATEGORY_JAVA,
-    type = Property.Type.BOOLEAN),
+    type = PropertyType.BOOLEAN),
   @Property(key = SquidPluginProperties.FIELDS_TO_EXCLUDE_FROM_LCOM4_COMPUTATION,
     defaultValue = SquidPluginProperties.FIELDS_TO_EXCLUDE_FROM_LCOM4_COMPUTATION_DEFAULT_VALUE,
     name = "List of fields to exclude from LCOM4 computation",
@@ -55,7 +53,7 @@ import java.util.List;
     project = true,
     global = true,
     category = CoreProperties.CATEGORY_JAVA,
-    type = Property.Type.BOOLEAN)
+    type = PropertyType.BOOLEAN)
 })
 public final class SquidPlugin extends SonarPlugin {
 
index b48b0f5ec5a014881d3ed86525cc835c78313a65..c7820f15d3d5a787d54d26d3b1eefb0ae4098645 100644 (file)
@@ -45,13 +45,6 @@ import java.lang.annotation.Target;
 @Target(ElementType.TYPE)
 public @interface Property {
 
-  /**
-   * @since 2.15
-   */
-  public static enum Type {
-    STRING, TEXT, PASSWORD, BOOLEAN, INTEGER, FLOAT, SINGLE_SELECT_LIST
-  }
-
   /**
    * Unique key within all plugins. It's recommended to prefix the key by 'sonar.' and the plugin name. Examples :
    * 'sonar.cobertura.reportPath' and 'sonar.cpd.minimumTokens'.
@@ -87,7 +80,7 @@ public @interface Property {
   /**
    * @since 2.15
    */
-  Type type() default Type.STRING;
+  PropertyType type() default PropertyType.STRING;
 
   /**
    * Options for *_LIST types
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/PropertyType.java b/sonar-plugin-api/src/main/java/org/sonar/api/PropertyType.java
new file mode 100644 (file)
index 0000000..356e4c1
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api;
+
+public enum PropertyType {
+  STRING, TEXT, PASSWORD, BOOLEAN, INTEGER, FLOAT, SINGLE_SELECT_LIST
+}
index 1b5ee8511ef3a167a21bc49ce1bbe5e8dbb351d9..4413b6353bf173930a64dcffdfea26dc0b0b1c0e 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.CoreProperties;
 
+import javax.annotation.Nullable;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
@@ -92,7 +93,7 @@ final class AesCipher extends Cipher {
   }
 
   @VisibleForTesting
-  Key loadSecretFileFromFile(String path) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidKeyException {
+  Key loadSecretFileFromFile(@Nullable String path) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidKeyException {
     if (StringUtils.isBlank(path)) {
       throw new IllegalStateException("Secret key not found. Please set the property " + CoreProperties.ENCRYPTION_SECRET_KEY_FILE);
     }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java
new file mode 100644 (file)
index 0000000..ef98057
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.math.NumberUtils;
+import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
+
+import javax.annotation.Nullable;
+
+/**
+ * @since 2.15
+ */
+public final class PropertyDefinition {
+
+  public static final class Result {
+    private static final Result SUCCESS = new Result(null);
+
+    private String errorKey = null;
+
+    private static Result newError(String key) {
+      return new Result(key);
+    }
+
+    private Result(String errorKey) {
+      this.errorKey = errorKey;
+    }
+
+    public boolean isValid() {
+      return StringUtils.isBlank(errorKey);
+    }
+
+    public @Nullable String getErrorKey() {
+      return errorKey;
+    }
+  }
+
+  private String key;
+  private String defaultValue;
+  private String name;
+  private PropertyType type = PropertyType.STRING;
+  private String[] options;
+  private String description;
+  private String category;
+  private boolean onProject = false;
+  private boolean onModule = false;
+  private boolean isGlobal = true;
+
+  private PropertyDefinition(Property annotation) {
+    this.key = annotation.key();
+    this.name = annotation.name();
+    this.defaultValue = annotation.defaultValue();
+    this.description = annotation.description();
+    this.isGlobal = annotation.global();
+    this.onProject = annotation.project();
+    this.onModule = annotation.module();
+    this.category = annotation.category();
+    this.type = annotation.type();
+    this.options = annotation.options();
+  }
+
+  @VisibleForTesting
+  PropertyDefinition(PropertyType type, String[] options) {
+    this.type = type;
+    this.options = options;
+  }
+
+  public static PropertyDefinition create(Property annotation) {
+    return new PropertyDefinition(annotation);
+  }
+
+  public Result validate(@Nullable String value) {
+    // TODO REFACTORING REQUIRED HERE
+    Result result = Result.SUCCESS;
+    if (StringUtils.isNotBlank(value)) {
+      if (type == PropertyType.BOOLEAN) {
+        if (!StringUtils.equalsIgnoreCase(value, "true") && !StringUtils.equalsIgnoreCase(value, "false")) {
+          result = Result.newError("notBoolean");
+        }
+      } else if (type == PropertyType.INTEGER) {
+        if (!NumberUtils.isDigits(value)) {
+          result = Result.newError("notInteger");
+        }
+      } else if (type == PropertyType.FLOAT) {
+        try {
+          Double.parseDouble(value);
+        } catch (NumberFormatException e) {
+          result = Result.newError("notFloat");
+        }
+      } else if (type == PropertyType.SINGLE_SELECT_LIST) {
+        if (!ArrayUtils.contains(options, value)) {
+          result = Result.newError("notInOptions");
+        }
+      }
+    }
+    return result;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public String getDefaultValue() {
+    return defaultValue;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public PropertyType getType() {
+    return type;
+  }
+
+  public String[] getOptions() {
+    return options;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public String getCategory() {
+    return category;
+  }
+
+  public boolean isOnProject() {
+    return onProject;
+  }
+
+  public boolean isOnModule() {
+    return onModule;
+  }
+
+  public boolean isGlobal() {
+    return isGlobal;
+  }
+}
index a090c1ecb88d24a886db96223bb77cec2cc7f8da..13c9b374fa58d05c14abc323b156f50406e18917 100644 (file)
@@ -38,7 +38,7 @@ import java.util.Map;
  */
 public final class PropertyDefinitions implements BatchComponent, ServerComponent {
 
-  private Map<String, Property> properties = Maps.newHashMap();
+  private Map<String, PropertyDefinition> definitions = Maps.newHashMap();
   private Map<String, String> categories = Maps.newHashMap();
 
   public PropertyDefinitions(Object... components) {
@@ -76,30 +76,31 @@ public final class PropertyDefinitions implements BatchComponent, ServerComponen
     return this;
   }
 
-  PropertyDefinitions addProperty(Property property) {
-    return addProperty(property, "");
+  private PropertyDefinitions addProperty(Property property, String defaultCategory) {
+    PropertyDefinition definition = PropertyDefinition.create(property);
+    return add(definition, defaultCategory);
   }
 
-  PropertyDefinitions addProperty(Property property, String defaultCategory) {
-    if (!properties.containsKey(property.key())) {
-      properties.put(property.key(), property);
-      categories.put(property.key(), StringUtils.defaultIfBlank(property.category(), defaultCategory));
+  private PropertyDefinitions add(PropertyDefinition definition, String defaultCategory) {
+    if (!definitions.containsKey(definition.getKey())) {
+      definitions.put(definition.getKey(), definition);
+      categories.put(definition.getKey(), StringUtils.defaultIfBlank(definition.getCategory(), defaultCategory));
     }
     return this;
   }
 
-  public Property getProperty(String key) {
-    return properties.get(key);
+  public PropertyDefinition get(String key) {
+    return definitions.get(key);
   }
 
-  public Collection<Property> getProperties() {
-    return properties.values();
+  public Collection<PropertyDefinition> getAll() {
+    return definitions.values();
   }
 
   public String getDefaultValue(String key) {
-    Property prop = getProperty(key);
-    if (prop != null) {
-      return StringUtils.defaultIfEmpty(prop.defaultValue(), null);
+    PropertyDefinition def = get(key);
+    if (def != null) {
+      return StringUtils.defaultIfEmpty(def.getDefaultValue(), null);
     }
     return null;
   }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/package-info.java
new file mode 100644 (file)
index 0000000..d3ae915
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.api.config;
+
+import javax.annotation.ParametersAreNonnullByDefault;
\ No newline at end of file
index 3e5c1acdd5f852c6aabc08f296a23fa7ab62e0ff..c860399da1f12ed101ab02dace6b3a5d6201618c 100644 (file)
@@ -22,15 +22,8 @@ package org.sonar.api.rules;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
 import org.apache.commons.lang.builder.ToStringBuilder;
-import org.hibernate.annotations.Cache;
-import org.hibernate.annotations.CacheConcurrencyStrategy;
-import org.hibernate.annotations.Immutable;
 import org.sonar.api.database.BaseIdentifiable;
 
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.Table;
-
 /**
  * @deprecated since 2.5 See http://jira.codehaus.org/browse/SONAR-2007
  */
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java
new file mode 100644 (file)
index 0000000..7ccbda1
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.config;
+
+import org.hamcrest.core.Is;
+import org.junit.Test;
+import org.sonar.api.Properties;
+import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
+import org.sonar.api.utils.AnnotationUtils;
+
+import java.util.Arrays;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+
+public class PropertyDefinitionTest {
+
+  @Test
+  public void createFromAnnotation() {
+    Properties props = AnnotationUtils.getClassAnnotation(Init.class, Properties.class);
+    Property prop = props.value()[0];
+
+    PropertyDefinition def = PropertyDefinition.create(prop);
+
+    assertThat(def.getKey(), Is.is("hello"));
+    assertThat(def.getName(), Is.is("Hello"));
+    assertThat(def.getDefaultValue(), Is.is("world"));
+    assertThat(def.getCategory(), Is.is("categ"));
+    assertThat(def.getOptions().length, Is.is(2));
+    assertThat(Arrays.asList(def.getOptions()), hasItems("de", "en"));
+    assertThat(def.getDescription(), Is.is("desc"));
+    assertThat(def.getType(), Is.is(PropertyType.FLOAT));
+    assertThat(def.isGlobal(), Is.is(false));
+    assertThat(def.isOnProject(), Is.is(true));
+    assertThat(def.isOnModule(), Is.is(true));
+  }
+
+  @Properties({
+    @Property(key = "hello", name = "Hello", defaultValue = "world", description = "desc",
+      options = {"de", "en"}, category = "categ", type = PropertyType.FLOAT, global = false, project = true, module = true)
+  })
+  static class Init {
+  }
+
+  @Test
+  public void createFromAnnotation_default_values() {
+    Properties props = AnnotationUtils.getClassAnnotation(DefaultValues.class, Properties.class);
+    Property prop = props.value()[0];
+
+    PropertyDefinition def = PropertyDefinition.create(prop);
+
+    assertThat(def.getKey(), Is.is("hello"));
+    assertThat(def.getName(), Is.is("Hello"));
+    assertThat(def.getDefaultValue(), Is.is(""));
+    assertThat(def.getCategory(), Is.is(""));
+    assertThat(def.getOptions().length, Is.is(0));
+    assertThat(def.getDescription(), Is.is(""));
+    assertThat(def.getType(), Is.is(PropertyType.STRING));
+    assertThat(def.isGlobal(), Is.is(true));
+    assertThat(def.isOnProject(), Is.is(false));
+    assertThat(def.isOnModule(), Is.is(false));
+  }
+
+  @Properties({
+    @Property(key = "hello", name = "Hello")
+  })
+  static class DefaultValues {
+  }
+
+  @Test
+  public void validate_string() {
+    PropertyDefinition def = new PropertyDefinition(PropertyType.STRING, new String[0]);
+
+    assertThat(def.validate(null).isValid(), is(true));
+    assertThat(def.validate("").isValid(), is(true));
+    assertThat(def.validate("   ").isValid(), is(true));
+    assertThat(def.validate("foo").isValid(), is(true));
+  }
+
+  @Test
+  public void validate_boolean() {
+    PropertyDefinition def = new PropertyDefinition(PropertyType.BOOLEAN, new String[0]);
+
+    assertThat(def.validate(null).isValid(), is(true));
+    assertThat(def.validate("").isValid(), is(true));
+    assertThat(def.validate("   ").isValid(), is(true));
+    assertThat(def.validate("true").isValid(), is(true));
+    assertThat(def.validate("false").isValid(), is(true));
+
+    assertThat(def.validate("foo").isValid(), is(false));
+    assertThat(def.validate("foo").getErrorKey(), is("notBoolean"));
+  }
+
+  @Test
+  public void validate_integer() {
+    PropertyDefinition def = new PropertyDefinition(PropertyType.INTEGER, new String[0]);
+
+    assertThat(def.validate(null).isValid(), is(true));
+    assertThat(def.validate("").isValid(), is(true));
+    assertThat(def.validate("   ").isValid(), is(true));
+    assertThat(def.validate("123456").isValid(), is(true));
+
+    assertThat(def.validate("foo").isValid(), is(false));
+    assertThat(def.validate("foo").getErrorKey(), is("notInteger"));
+  }
+
+  @Test
+  public void validate_float() {
+    PropertyDefinition def = new PropertyDefinition(PropertyType.FLOAT, new String[0]);
+
+    assertThat(def.validate(null).isValid(), is(true));
+    assertThat(def.validate("").isValid(), is(true));
+    assertThat(def.validate("   ").isValid(), is(true));
+    assertThat(def.validate("123456").isValid(), is(true));
+    assertThat(def.validate("3.14").isValid(), is(true));
+
+    assertThat(def.validate("foo").isValid(), is(false));
+    assertThat(def.validate("foo").getErrorKey(), is("notFloat"));
+  }
+
+  @Test
+  public void validate_single_select_list() {
+    PropertyDefinition def = new PropertyDefinition(PropertyType.SINGLE_SELECT_LIST, new String[]{"de", "en"});
+
+    assertThat(def.validate(null).isValid(), is(true));
+    assertThat(def.validate("").isValid(), is(true));
+    assertThat(def.validate("   ").isValid(), is(true));
+    assertThat(def.validate("de").isValid(), is(true));
+    assertThat(def.validate("en").isValid(), is(true));
+
+    assertThat(def.validate("fr").isValid(), is(false));
+    assertThat(def.validate("fr").getErrorKey(), is("notInOptions"));
+  }
+}
index a5d8a0b2add94cf0b386fc8d9bff23a4a59d61e0..1320d9e376d930b9b9064c4a8188922e1da68d3a 100644 (file)
@@ -43,16 +43,16 @@ public class PropertyDefinitionsTest {
     assertProperties(def);
   }
 
-  private void assertProperties(PropertyDefinitions def) {
-    assertThat(def.getProperty("foo").name(), is("Foo"));
-    assertThat(def.getProperty("one").name(), is("One"));
-    assertThat(def.getProperty("two").name(), is("Two"));
-    assertThat(def.getProperty("unknown"), nullValue());
+  private void assertProperties(PropertyDefinitions definitions) {
+    assertThat(definitions.get("foo").getName(), is("Foo"));
+    assertThat(definitions.get("one").getName(), is("One"));
+    assertThat(definitions.get("two").getName(), is("Two"));
+    assertThat(definitions.get("unknown"), nullValue());
 
-    assertThat(def.getDefaultValue("foo"), nullValue());
-    assertThat(def.getDefaultValue("two"), is("2"));
+    assertThat(definitions.getDefaultValue("foo"), nullValue());
+    assertThat(definitions.getDefaultValue("two"), is("2"));
 
-    assertThat(def.getProperties().size(), is(3));
+    assertThat(definitions.getAll().size(), is(3));
   }
 
   @Property(key = "foo", name = "Foo")
index 4e88f6c939dcb72201a45a402e7ed5c2d8b1ba6c..72a36ece437bb7838bc35034ba9c45b5d09fc16e 100644 (file)
@@ -23,34 +23,32 @@ import com.google.common.collect.ImmutableMap;
 import org.hamcrest.CoreMatchers;
 import org.junit.Before;
 import org.junit.Test;
+import org.sonar.api.Properties;
 import org.sonar.api.Property;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 public class SettingsTest {
 
   private PropertyDefinitions definitions;
 
+  @Properties({
+    @Property(key = "hello", name = "Hello", defaultValue = "world"),
+    @Property(key = "date", name = "Date", defaultValue = "2010-05-18"),
+    @Property(key = "boolean", name = "Boolean", defaultValue = "true"),
+    @Property(key = "falseboolean", name = "False Boolean", defaultValue = "false"),
+    @Property(key = "integer", name = "Integer", defaultValue = "12345"),
+    @Property(key = "array", name = "Array", defaultValue = "one,two,three")
+  })
+  static class Init {
+  }
+
   @Before
   public void initDefinitions() {
     definitions = new PropertyDefinitions();
-    definitions.addProperty(newProperty("hello", "world"));
-    definitions.addProperty(newProperty("date", "2010-05-18"));
-    definitions.addProperty(newProperty("boolean", "true"));
-    definitions.addProperty(newProperty("falseboolean", "false"));
-    definitions.addProperty(newProperty("integer", "12345"));
-    definitions.addProperty(newProperty("array", "one,two,three"));
-  }
-
-  private Property newProperty(String key, String defaultValue) {
-    Property prop = mock(Property.class);
-    when(prop.key()).thenReturn(key);
-    when(prop.defaultValue()).thenReturn(defaultValue);
-    return prop;
+    definitions.addComponent(Init.class);
   }
 
   @Test
index bfb16d5f7af4045e0adb78400fb50b8ef8c8150a..db6f03b11068c611c203fa33729acc2e2a5bb726 100644 (file)
@@ -102,8 +102,8 @@ public class ComponentContainerTest {
     container.addSingleton(ComponentWithProperty.class);
 
     PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
-    assertThat(propertyDefinitions.getProperty("foo"), notNullValue());
-    assertThat(propertyDefinitions.getProperty("foo").defaultValue(), is("bar"));
+    assertThat(propertyDefinitions.get("foo"), notNullValue());
+    assertThat(propertyDefinitions.get("foo").getDefaultValue(), is("bar"));
   }
 
   @Test
@@ -113,7 +113,7 @@ public class ComponentContainerTest {
     container.declareExtension(plugin, ComponentWithProperty.class);
 
     PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
-    assertThat(propertyDefinitions.getProperty("foo"), notNullValue());
+    assertThat(propertyDefinitions.get("foo"), notNullValue());
     assertThat(container.getComponentByType(ComponentWithProperty.class), nullValue());
   }
 
@@ -124,7 +124,7 @@ public class ComponentContainerTest {
     container.addExtension(plugin, ComponentWithProperty.class);
 
     PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
-    assertThat(propertyDefinitions.getProperty("foo"), notNullValue());
+    assertThat(propertyDefinitions.get("foo"), notNullValue());
     assertThat(container.getComponentByType(ComponentWithProperty.class), notNullValue());
   }
 
index 27d8df7e0a3ee2801c861b8d7d99742d4b8b55de..191c0e88cbd9a218ba952a868827b7498a76ac7c 100644 (file)
@@ -23,6 +23,7 @@ import org.apache.commons.io.IOUtils;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.config.Settings;
 import org.sonar.api.utils.HttpDownloader;
@@ -49,7 +50,7 @@ import java.util.Date;
     project = false,
     global = false, // hidden from UI
     category = "Update Center",
-    type = Property.Type.BOOLEAN),
+    type = PropertyType.BOOLEAN),
   @Property(
     key = UpdateCenterClient.URL_PROPERTY,
     defaultValue = "http://update.sonarsource.org/update-center.properties",
index c486dfd7d2721ecf54d41d8161459ed5d7a855fe..88ebe73790d70969264c4759134269cf38a630d8 100644 (file)
@@ -126,13 +126,13 @@ class ProjectController < ApplicationController
     end
 
     @category=params[:category] ||= 'general'
-    @properties_per_category={}
+    @definitions_per_category={}
     definitions = java_facade.getPropertyDefinitions()
-    properties = definitions.getProperties().select { |property| (@project.module? && property.module()) || (@project.project? && property.project()) }
+    properties = definitions.getAll().select { |property| (@project.module? && property.isOnModule()) || (@project.project? && property.isOnProject()) }
     properties.each do |property|
-      category = definitions.getCategory(property.key())
-      @properties_per_category[category]||=[]
-      @properties_per_category[category]<<property
+      category = definitions.getCategory(property.getKey())
+      @definitions_per_category[category]||=[]
+      @definitions_per_category[category]<<property
     end
   end
 
index 653c42d81ab5b0093403191d27474f82e81e34e4..18c7e8f0797d93d9c29f8736ae2f56a121ebfd2f 100644 (file)
@@ -20,7 +20,7 @@
 class SettingsController < ApplicationController
 
   SECTION=Navigation::SECTION_CONFIGURATION
-  
+
   SPECIAL_CATEGORIES=['email', 'encryption', 'server_id']
 
   verify :method => :post, :only => ['update'], :redirect_to => {:action => :index}
@@ -32,39 +32,39 @@ class SettingsController < ApplicationController
   end
 
   def update
-    project=nil
+    @project=nil
     if params[:resource_id]
-      project=Project.by_key(params[:resource_id])
-      access_denied unless (project && is_admin?(project))
+      @project=Project.by_key(params[:resource_id])
+      access_denied unless (@project && is_admin?(@project))
     else
       access_denied unless is_admin?
     end
 
-    load_properties(project)
+    load_properties(@project)
+
+    @persisted_properties_per_key={}
+    if @category && @definitions_per_category[@category]
+      @definitions_per_category[@category].each do |property|
+        value=params[property.getKey()]
 
-    if @category && @properties_per_category[@category]
-      @properties_per_category[@category].each do |property|
-        value=params[property.key()]
-        persisted_property = Property.find(:first, :conditions => {:prop_key=> property.key(), :resource_id => (project ? project.id : nil), :user_id => nil})
+        persisted_property = Property.find(:first, :conditions => {:prop_key=> property.key(), :resource_id => (@project ? @project.id : nil), :user_id => nil})
         if persisted_property
           if value.empty?
-            Property.delete_all('prop_key' => property.key(), 'resource_id' => (project ? project.id : nil), 'user_id' => nil)
+            Property.delete_all('prop_key' => property.key(), 'resource_id' => (@project ? @project.id : nil), 'user_id' => nil)
           elsif persisted_property.text_value != value.to_s
             persisted_property.text_value = value.to_s
-            persisted_property.save!
+            persisted_property.save
+            @persisted_properties_per_key[persisted_property.key]=persisted_property
           end
         elsif !value.blank?
-          Property.create(:prop_key => property.key(), :text_value => value.to_s, :resource_id => (project ? project.id : nil))
+          persisted_property=Property.create(:prop_key => property.key(), :text_value => value.to_s, :resource_id => (@project ? @project.id : nil))
+          @persisted_properties_per_key[persisted_property.key]=persisted_property
         end
       end
       java_facade.reloadConfiguration()
-      flash[:notice] = 'Parameters updated'
-    end
 
-    if project
-      redirect_to :controller => 'project', :action => 'settings', :id => project.id, :category => @category
-    else
-      redirect_to :controller => 'settings', :action => 'index', :category => @category
+      params[:layout]='false'
+      render :partial => 'settings/properties'
     end
   end
 
@@ -72,18 +72,18 @@ class SettingsController < ApplicationController
 
   def load_properties(project)
     @category=params[:category]
-    @properties_per_category={}
+    @definitions_per_category={}
     definitions = java_facade.getPropertyDefinitions()
-    definitions.getProperties().select {|property|
-      (project.nil? && property.global) || (project && project.module? && property.module()) || (project && project.project? && property.project())
-    }.each do |property|
-      category = definitions.getCategory(property.key())
-      @properties_per_category[category]||=[]
-      @properties_per_category[category]<<property
+    definitions.getAll().select { |property_definition|
+      (project.nil? && property_definition.isGlobal()) || (project && project.module? && property_definition.isOnModule()) || (project && project.project? && property_definition.isOnProject())
+    }.each do |property_definition|
+      category = definitions.getCategory(property_definition.getKey())
+      @definitions_per_category[category]||=[]
+      @definitions_per_category[category]<<property_definition
     end
 
     SPECIAL_CATEGORIES.each do |category|
-      @properties_per_category[category]=[]
+      @definitions_per_category[category]=[]
     end
   end
 end
index ccef420c1369767710a01be30384cce43c25575e..b388a5bec6bc2db2f1ae504c91d0011b25650b26 100644 (file)
@@ -18,6 +18,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 #
 class Property < ActiveRecord::Base
+  validates_presence_of :prop_key
 
   def key
     prop_key
@@ -87,8 +88,22 @@ class Property < ActiveRecord::Base
     xml
   end
 
+  def java_definition
+    @java_definition ||=
+      begin
+        Java::OrgSonarServerUi::JRubyFacade.getInstance().getPropertyDefinitions().get(key)
+      end
+  end
+
   private
 
+  def validate
+    if java_definition
+      validation_result=java_definition.validate(text_value)
+      errors.add_to_base(validation_result.getErrorKey()) unless validation_result.isValid()
+    end
+  end
+
   def self.reload_java_configuration
     Java::OrgSonarServerUi::JRubyFacade.getInstance().reloadConfiguration()
   end
index cb5e508f23b8e045f4f8b48c7fc1e89887fc6e44..8e2f68f187035230c87dda77c8725b3ad5ed75ca 100644 (file)
@@ -3,7 +3,7 @@
   <h1 class="marginbottom10">Settings</h1>
   
   <div class="yui-g widget" id="widget_plugins">
-    <%= render :partial => 'settings/plugins', :locals => {:project=>@project} %>
+    <%= render :partial => 'settings/settings', :locals => {:project=>@project} %>
   </div>
 
 </div>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_plugins.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_plugins.html.erb
deleted file mode 100644 (file)
index 87a5a99..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<style type="text/css">
-  #plugins .plugin {
-    padding: 5px;
-    border: 1px solid #ddd;
-    background-color: #fff;
-  }
-
-  #plugins .plugin h2 {
-    margin-left: 10px;
-    font-size: 122%;
-    color: #333;
-  }
-
-  #plugins .plugin h3 {
-    margin-left: 5px;
-  }
-
-  #plugins .plugin p {
-    padding: 5px 5px;
-  }
-
-  #plugins .plugin img {
-    padding: 5px 0 0 5px;
-  }
-</style>
-<script type="text/javascript">
-  function enlargeTextInput(propertyKey) {
-    var eltId = 'input_' + propertyKey;
-    var textValue = $F(eltId);
-    var textArea = '<textarea class="width100" id="' + propertyKey + '" name="' + propertyKey + '" rows="10" id="input_' + propertyKey + '">' + textValue + '</textarea>';
-    $(eltId).parentNode.replace(textArea);
-  }
-</script>
-<div id="plugins">
-  <table width="100%">
-    <tr>
-      <td width="1%" nowrap class="column first">
-        <table class="data selector">
-          <thead>
-          <tr>
-            <th>
-              <span>Category</span>
-            </th>
-          </tr>
-          </thead>
-          <tbody>
-          <%
-             @properties_per_category.keys.sort_by { |category| message("property.category.#{category}", :default => category).upcase }.each do |category|
-               if !@properties_per_category[category].empty? || SettingsController::SPECIAL_CATEGORIES.include?(category)
-          %>
-              <tr class="select <%= cycle('even', 'odd', :name => 'category') -%> <%= 'selected' if @category==category -%>" id="select_<%= category -%>">
-                <td><%= link_to message("property.category.#{category}", :default => category), :overwrite_params => {:category => category} -%></td>
-              </tr>
-            <% end
-               end
-            %>
-          </tbody>
-        </table>
-        <br/>
-      </td>
-
-      <td class="column">
-        <% if @category && @properties_per_category[@category]
-             category_name = message("property.category.#{@category}", :default => @category)
-             if SettingsController::SPECIAL_CATEGORIES.include?(@category)
-        %>
-            <%= render :partial => 'special', :locals => {:url => url_for(:controller => "#{@category}_configuration")} -%>
-          <%
-             elsif !@properties_per_category[@category].empty?
-          %>
-            <% form_tag :controller => :settings, :action => :update do %>
-              <%= hidden_field_tag('category', @category) -%>
-              <% if @project %>
-                <input type="hidden" name="resource_id" value="<%= @project.id -%>"/>
-              <% end %>
-              <table class="data marginbottom10">
-                <thead>
-                <tr>
-                  <th>
-                    <span><%= h(category_name) -%></span>
-                  </th>
-                </tr>
-                </thead>
-                <tbody>
-                <%
-                   if @properties_per_category[@category]
-                     @properties_per_category[@category].each do |property|
-                       value = Property.value(property.key(), (@project ? @project.id : nil), '')
-
-                       # for backward-compatibility with properties that do not define the type TEXT
-                       property_type = value.include?("\n") ? 'TEXT' : property.type
-                %>
-                    <tr class="<%= cycle('even', 'odd', :name => 'properties') -%>">
-                      <td style="padding: 10px">
-                        <h3>
-                          <%= message("property.#{property.key()}.name", :default => property.name()) -%>
-                          <br/><span class="note"><%= property.key() -%></span>
-                        </h3>
-                        <%
-                           desc=message("property.#{property.key()}.description", :default => property.description())
-                           if desc.present? %>
-                          <p class="marginbottom10"><%= desc -%></p>
-                        <% end %>
-                        <div><%= render :partial => "settings/type_#{property_type}", :locals => {:property => property, :value => value} -%></div>
-                        <p>
-                          <%
-                             default_prop_value = (@project ? Property.value(property.key(), nil, property.defaultValue()) : property.defaultValue())
-                             unless default_prop_value.blank? %>
-                            <span class="note">Default : <%= h default_prop_value -%></span>
-                          <% end %>
-                        </p>
-                      </td>
-                    </tr>
-                  <% end
-                     end
-                  %>
-                </tbody>
-              </table>
-              <% save_message=message('settings.save_category', :params => [category_name]) %>
-              <%= submit_tag(save_message, :disable_with => save_message, :id => 'save') -%>
-            <% end %>
-          <% end
-             end
-          %>
-      </td>
-    </tr>
-  </table>
-</div>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb
new file mode 100644 (file)
index 0000000..fc2fbd2
--- /dev/null
@@ -0,0 +1,85 @@
+<% if @category && @definitions_per_category[@category]
+     category_name = message("property.category.#{@category}", :default => @category)
+     if SettingsController::SPECIAL_CATEGORIES.include?(@category)
+%>
+    <%= render :partial => 'special', :locals => {:url => url_for(:controller => "#{@category}_configuration")} -%>
+  <%
+     elsif !@definitions_per_category[@category].empty?
+  %>
+    <% form_remote_tag :url => {:controller => 'settings', :action => 'update', :category => @category, :resource_id => @project ? @project.id : nil},
+                       :method => :post,
+                       :before => "$('submit_settings').hide();$('loading_settings').show()",
+                       :update => 'properties' do -%>
+
+      <table class="data marginbottom10">
+        <thead>
+        <tr>
+          <th>
+            <span><%= h(category_name) -%></span>
+          </th>
+        </tr>
+        </thead>
+        <tbody>
+        <%
+           if @definitions_per_category[@category]
+             @definitions_per_category[@category].each do |property|
+               value = nil
+
+               # set when form has been submitted but some errors have been raised
+               if @persisted_properties_per_key
+                 p = @persisted_properties_per_key[property.key]
+                 if p
+                   value = p.text_value
+                 end
+               end
+
+
+               # if fresh form or no error, get the current value
+               value = Property.value(property.getKey(), (@project ? @project.id : nil), '') unless value
+
+               # for backward-compatibility with properties that do not define the type TEXT
+               property_type = value.include?("\n") ? 'TEXT' : property.getType()
+        %>
+            <tr class="<%= cycle('even', 'odd', :name => 'properties') -%>">
+              <td style="padding: 10px" id="foo_<%= property.getKey() -%>">
+                <h3>
+                  <%= message("property.#{property.key()}.name", :default => property.name()) -%>
+                  <br/><span class="note"><%= property.getKey() -%></span>
+                </h3>
+                <%
+                   desc=message("property.#{property.key()}.description", :default => property.description())
+                   if desc.present? %>
+                  <p class="marginbottom10"><%= desc -%></p>
+                <% end %>
+                <div><%= render :partial => "settings/type_#{property_type}", :locals => {:property => property, :value => value} -%></div>
+                <%
+                   if p
+                     p.errors.each_full do |error|
+                %>
+                    <div class="error"><%= message("property.error.#{error}") -%></div>
+                  <%
+                     end
+                     end
+                  %>
+                <p>
+                  <%
+                     default_prop_value = (@project ? Property.value(property.key(), nil, property.defaultValue()) : property.defaultValue())
+                     unless default_prop_value.blank? %>
+                    <span class="note">Default : <%= h default_prop_value -%></span>
+                  <% end %>
+                </p>
+              </td>
+            </tr>
+          <% end
+             end
+          %>
+        </tbody>
+      </table>
+      <div>
+      <%= submit_tag(message('settings.save_category', :params => [category_name]), :id => 'submit_settings') -%>
+      <img src="<%= ApplicationController.root_context -%>/images/loading.gif" id="loading_settings" style="display:none">
+      </div>
+    <% end %>
+  <% end
+     end
+  %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb
new file mode 100644 (file)
index 0000000..cbd8557
--- /dev/null
@@ -0,0 +1,69 @@
+<style type="text/css">
+  #plugins .plugin {
+    padding: 5px;
+    border: 1px solid #ddd;
+    background-color: #fff;
+  }
+
+  #plugins .plugin h2 {
+    margin-left: 10px;
+    font-size: 122%;
+    color: #333;
+  }
+
+  #plugins .plugin h3 {
+    margin-left: 5px;
+  }
+
+  #plugins .plugin p {
+    padding: 5px 5px;
+  }
+
+  #plugins .plugin img {
+    padding: 5px 0 0 5px;
+  }
+</style>
+<script type="text/javascript">
+  function enlargeTextInput(propertyKey) {
+    var eltId = 'input_' + propertyKey;
+    var textValue = $F(eltId);
+    var textArea = '<textarea class="width100" id="' + propertyKey + '" name="' + propertyKey + '" rows="10" id="input_' + propertyKey + '">' + textValue + '</textarea>';
+    $(eltId).parentNode.replace(textArea);
+  }
+</script>
+<div id="plugins">
+  <table width="100%">
+    <tr>
+      <td width="1%" nowrap class="column first">
+        <table class="data selector">
+          <thead>
+          <tr>
+            <th>
+              <span>Category</span>
+            </th>
+          </tr>
+          </thead>
+          <tbody>
+          <%
+             @definitions_per_category.keys.sort_by { |category| message("property.category.#{category}", :default => category).upcase }.each do |category|
+               if !@definitions_per_category[category].empty? || SettingsController::SPECIAL_CATEGORIES.include?(category)
+          %>
+              <tr class="select <%= cycle('even', 'odd', :name => 'category') -%> <%= 'selected' if @category==category -%>" id="select_<%= category -%>">
+                <td><%= link_to message("property.category.#{category}", :default => category), :overwrite_params => {:category => category} -%></td>
+              </tr>
+            <% end
+               end
+            %>
+          </tbody>
+        </table>
+        <br/>
+      </td>
+
+      <td class="column">
+        <div id="properties">
+          <%= render :partial => 'settings/properties' -%>
+        </div>
+      </td>
+    </tr>
+  </table>
+</div>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_sidebar.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_sidebar.html.erb
new file mode 100644 (file)
index 0000000..e69de29
index a6f905a30380f7d66149ba777c6da79c00d0925b..a317e48ef385e0b1272641480177670d348da917 100644 (file)
@@ -1,6 +1,6 @@
 <select name="<%= h property.key -%>" id="input_<%= h property.key-%>">
   <option value=""><%= message('default') -%></option>
   <% property.options.each do |option| %>
-  <option value="<%= h option -%>"><%= h option -%></option>
+    <option value="<%= h option -%>" <%= 'selected' if value && value==option -%>><%= h option -%></option>
   <% end %>
 </select>
\ No newline at end of file
index d0fc6625477b4ff247a8a60f9f0baa51a4b0d3e3..81fa7362a09615d5875bc5dac4801385ec7da155 100644 (file)
@@ -1 +1 @@
-<%= render :partial => 'plugins', :locals => {:project=>nil} %>
\ No newline at end of file
+<%= render :partial => 'settings', :locals => {:project=>nil} %>
\ No newline at end of file