aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2012-03-16 11:46:48 +0100
committerSimon Brandhof <simon.brandhof@gmail.com>2012-03-16 11:50:43 +0100
commitf6a1da456075b5307537c9f736eecb656c5d3a2d (patch)
tree66d1d3bd84833af3ba30572629637ba69537791f
parentab19fa283c7c86034ebde4823b3b8675797d9fbb (diff)
downloadsonarqube-f6a1da456075b5307537c9f736eecb656c5d3a2d.tar.gz
sonarqube-f6a1da456075b5307537c9f736eecb656c5d3a2d.zip
SONAR-1378 validate property types + update type of core properties
-rw-r--r--plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstylePlugin.java3
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java342
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java16
-rw-r--r--plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java7
-rw-r--r--plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DefaultPurgeTask.java3
-rw-r--r--plugins/sonar-findbugs-plugin/src/main/java/org/sonar/plugins/findbugs/FindbugsPlugin.java3
-rw-r--r--plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties6
-rw-r--r--plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java10
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/Property.java9
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/PropertyType.java24
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java3
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java158
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java29
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/package-info.java23
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rules/RulesCategory.java7
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java153
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java16
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java28
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java8
-rw-r--r--sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java3
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb10
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb52
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/property.rb15
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_plugins.html.erb128
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb85
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb69
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_sidebar.html.erb0
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/index.html.erb2
30 files changed, 803 insertions, 413 deletions
diff --git a/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstylePlugin.java b/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstylePlugin.java
index 515b8d15e04..9bba008989b 100644
--- a/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstylePlugin.java
+++ b/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstylePlugin.java
@@ -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() {
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index b0d273c9fe6..71c09d25209 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -20,11 +20,9 @@
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 {
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
index ddefdb4fa4c..2708f843d17 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
@@ -19,10 +19,8 @@
*/
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 {
diff --git a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java
index 15a74fa8768..4507771468f 100644
--- a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java
+++ b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java
@@ -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 {
diff --git a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DefaultPurgeTask.java b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DefaultPurgeTask.java
index 6fe63815b53..afee357487a 100644
--- a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DefaultPurgeTask.java
+++ b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DefaultPurgeTask.java
@@ -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);
diff --git a/plugins/sonar-findbugs-plugin/src/main/java/org/sonar/plugins/findbugs/FindbugsPlugin.java b/plugins/sonar-findbugs-plugin/src/main/java/org/sonar/plugins/findbugs/FindbugsPlugin.java
index 115feb801d2..070dfbee8bd 100644
--- a/plugins/sonar-findbugs-plugin/src/main/java/org/sonar/plugins/findbugs/FindbugsPlugin.java
+++ b/plugins/sonar-findbugs-plugin/src/main/java/org/sonar/plugins/findbugs/FindbugsPlugin.java
@@ -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",
diff --git a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
index 7ff09ae2a5e..2f3e06c1be6 100644
--- a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
+++ b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
@@ -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
+
+
#------------------------------------------------------------------------------
#
diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java
index 6e06dd8fde6..7932d233545 100644
--- a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java
+++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java
@@ -19,10 +19,8 @@
*/
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 {
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/Property.java b/sonar-plugin-api/src/main/java/org/sonar/api/Property.java
index b48b0f5ec5a..c7820f15d3d 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/Property.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/Property.java
@@ -46,13 +46,6 @@ import java.lang.annotation.Target;
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
index 00000000000..356e4c1a65c
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/PropertyType.java
@@ -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
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java
index 1b5ee8511ef..4413b6353bf 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java
@@ -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
index 00000000000..ef98057d6b5
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java
@@ -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;
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java
index a090c1ecb88..13c9b374fa5 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java
@@ -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
index 00000000000..d3ae915cdb2
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/package-info.java
@@ -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
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/RulesCategory.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/RulesCategory.java
index 3e5c1acdd5f..c860399da1f 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/rules/RulesCategory.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rules/RulesCategory.java
@@ -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
index 00000000000..7ccbda1d245
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java
@@ -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"));
+ }
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java
index a5d8a0b2add..1320d9e376d 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java
@@ -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")
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java
index 4e88f6c939d..72a36ece437 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java
@@ -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
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java
index bfb16d5f7af..db6f03b1106 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java
@@ -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());
}
diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java b/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
index 27d8df7e0a3..191c0e88cbd 100644
--- a/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
+++ b/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
@@ -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",
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
index c486dfd7d27..88ebe73790d 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
index 653c42d81ab..18c7e8f0797 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/property.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/property.rb
index ccef420c136..b388a5bec6b 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/property.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/property.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb
index cb5e508f23b..8e2f68f1870 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb
@@ -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
index 87a5a99b803..00000000000
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_plugins.html.erb
+++ /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
index 00000000000..fc2fbd2ae09
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb
@@ -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
index 00000000000..cbd85572a12
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb
@@ -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
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_sidebar.html.erb
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
index a6f905a3038..a317e48ef38 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/index.html.erb
index d0fc6625477..81fa7362a09 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/index.html.erb
@@ -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