diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2013-12-13 14:47:29 +0100 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2013-12-13 14:47:29 +0100 |
commit | 20585b09427b7560f07fec3132f2081d55eda701 (patch) | |
tree | db821f4bbf5ad476ef898254fb417deaa8860903 | |
parent | 874db94063251ecc6747f74b9ad185450d4c9e3f (diff) | |
download | sonarqube-20585b09427b7560f07fec3132f2081d55eda701.tar.gz sonarqube-20585b09427b7560f07fec3132f2081d55eda701.zip |
SONAR-4535 Quality profile creation is now done in Java
21 files changed, 414 insertions, 170 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileDao.java b/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileDao.java index fb158675c08..a364ae1fcc7 100644 --- a/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileDao.java +++ b/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileDao.java @@ -43,6 +43,15 @@ public class QualityProfileDao implements ServerComponent { } } + public QualityProfileDto selectByNameAndLanguage(String name, String language) { + SqlSession session = mybatis.openSession(); + try { + return session.getMapper(QualityProfileMapper.class).selectByNameAndLanguage(name, language); + } finally { + MyBatis.closeQuietly(session); + } + } + public void insert(QualityProfileDto dto, SqlSession session) { session.getMapper(QualityProfileMapper.class).insert(dto); } diff --git a/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileMapper.java b/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileMapper.java index 15acccf2e0e..dcfe0b93337 100644 --- a/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileMapper.java @@ -20,12 +20,19 @@ package org.sonar.core.qualityprofile.db; +import org.apache.ibatis.annotations.Param; + +import javax.annotation.CheckForNull; + import java.util.List; public interface QualityProfileMapper { List<QualityProfileDto> selectAll(); + @CheckForNull + QualityProfileDto selectByNameAndLanguage(@Param("name") String name, @Param("language") String language); + void insert(QualityProfileDto dto); void update(QualityProfileDto dto); diff --git a/sonar-core/src/main/resources/org/sonar/core/qualityprofile/db/QualityProfileMapper.xml b/sonar-core/src/main/resources/org/sonar/core/qualityprofile/db/QualityProfileMapper.xml index 217ba29567b..cd44060bef0 100644 --- a/sonar-core/src/main/resources/org/sonar/core/qualityprofile/db/QualityProfileMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/qualityprofile/db/QualityProfileMapper.xml @@ -17,6 +17,15 @@ from rules_profiles p </select> + <select id="selectByNameAndLanguage" parameterType="map" resultType="QualityProfile"> + select <include refid="profilesColumns"/> + from rules_profiles p + <where> + and p.name=#{name} + and p.language=#{language} + </where> + </select> + <insert id="insert" parameterType="QualityProfile" keyColumn="id" useGeneratedKeys="true" keyProperty="id"> INSERT INTO rules_profiles (name, language, parent_name, version, used_profile) VALUES (#{name}, #{language}, #{parent}, #{version}, #{used}) diff --git a/sonar-core/src/test/java/org/sonar/core/qualityprofile/db/QualityProfileDaoTest.java b/sonar-core/src/test/java/org/sonar/core/qualityprofile/db/QualityProfileDaoTest.java index 315af5e11ac..69761aaef1d 100644 --- a/sonar-core/src/test/java/org/sonar/core/qualityprofile/db/QualityProfileDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/qualityprofile/db/QualityProfileDaoTest.java @@ -63,6 +63,21 @@ public class QualityProfileDaoTest extends AbstractDaoTestCase { } @Test + public void select_by_name_and_language() { + setupData("shared"); + + QualityProfileDto dto = dao.selectByNameAndLanguage("Sonar way", "java"); + assertThat(dto.getId()).isEqualTo(1); + assertThat(dto.getName()).isEqualTo("Sonar way"); + assertThat(dto.getLanguage()).isEqualTo("java"); + assertThat(dto.getParent()).isNull(); + assertThat(dto.getVersion()).isEqualTo(1); + assertThat(dto.isUsed()).isFalse(); + + assertThat(dao.selectByNameAndLanguage("Sonar way", "unknown")); + } + + @Test public void insert() { setupData("shared"); diff --git a/sonar-server/src/main/java/org/sonar/server/exceptions/BadRequestException.java b/sonar-server/src/main/java/org/sonar/server/exceptions/BadRequestException.java index 669338302fd..6776fcd8b7f 100644 --- a/sonar-server/src/main/java/org/sonar/server/exceptions/BadRequestException.java +++ b/sonar-server/src/main/java/org/sonar/server/exceptions/BadRequestException.java @@ -19,8 +19,16 @@ */ package org.sonar.server.exceptions; +import org.apache.commons.lang.builder.ReflectionToStringBuilder; + +import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; + /** * Request is not valid and can not be processed. */ @@ -28,6 +36,8 @@ public class BadRequestException extends ServerException { private static final int BAD_REQUEST = 400; + private final List<Message> errors = newArrayList(); + public BadRequestException(String message) { super(BAD_REQUEST, message); } @@ -44,4 +54,91 @@ public class BadRequestException extends ServerException { return new BadRequestException(null, l10nKey, l10nParams); } + public BadRequestException addError(String text) { + return addError(Message.of(text)); + } + + public BadRequestException addError(Message message) { + errors.add(message); + return this; + } + + /** + * List of error messages. Empty if there are no errors. + */ + public List<Message> errors() { + return errors; + } + + public static class Message { + private final String l10nKey; + private final Object[] l10nParams; + private final String text; + + private Message(@Nullable String l10nKey, @Nullable Object[] l10nParams, @Nullable String text) { + this.l10nKey = l10nKey; + this.l10nParams = l10nParams; + this.text = text; + } + + public static Message of(String text) { + return new Message(null, null, text); + } + + public static Message ofL10n(String l10nKey, Object... l10nParams) { + return new Message(l10nKey, l10nParams, null); + } + + @CheckForNull + public String text() { + return text; + } + + @CheckForNull + public String l10nKey() { + return l10nKey; + } + + @CheckForNull + public Object[] l10nParams() { + return l10nParams; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Message message = (Message) o; + + if (l10nKey != null ? !l10nKey.equals(message.l10nKey) : message.l10nKey != null) { + return false; + } + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(l10nParams, message.l10nParams)) { + return false; + } + if (text != null ? !text.equals(message.text) : message.text != null) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = l10nKey != null ? l10nKey.hashCode() : 0; + result = 31 * result + (l10nParams != null ? Arrays.hashCode(l10nParams) : 0); + result = 31 * result + (text != null ? text.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return new ReflectionToStringBuilder(this).toString(); + } + } + } diff --git a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index ef32b393b5b..a27fd1891dc 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -47,7 +47,7 @@ import org.sonar.core.resource.ResourceQuery; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.user.UserSession; import org.sonar.server.util.RubyUtils; -import org.sonar.server.util.RubyValidation; +import org.sonar.server.util.Validation; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -353,7 +353,7 @@ public class InternalRubyIssueService implements ServerComponent { private void checkProject(String projectParam, Result<ActionPlan> result) { if (Strings.isNullOrEmpty(projectParam)) { - result.addError(Result.Message.ofL10n(RubyValidation.ERRORS_CANT_BE_EMPTY_MESSAGE, PROJECT_PARAM)); + result.addError(Result.Message.ofL10n(Validation.ERRORS_CANT_BE_EMPTY_MESSAGE, PROJECT_PARAM)); } else { ResourceDto project = resourceDao.getResource(ResourceQuery.create().setKey(projectParam)); if (project == null) { @@ -553,12 +553,12 @@ public class InternalRubyIssueService implements ServerComponent { boolean shared = sharedParam != null ? sharedParam : false; if (checkId) { - RubyValidation.checkMandatoryParameter(id, ID_PARAM); + Validation.checkMandatoryParameter(id, ID_PARAM); } if (checkUser) { - RubyValidation.checkMandatoryParameter(user, USER_PARAM); + Validation.checkMandatoryParameter(user, USER_PARAM); } - RubyValidation.checkMandatorySizeParameter(name, NAME_PARAM, 100); + Validation.checkMandatorySizeParameter(name, NAME_PARAM, 100); checkOptionalSizeParameter(description, DESCRIPTION_PARAM, 4000); DefaultIssueFilter defaultIssueFilter = DefaultIssueFilter.create(name) @@ -594,26 +594,26 @@ public class InternalRubyIssueService implements ServerComponent { private void checkMandatoryParameter(String value, String paramName, Result result) { if (Strings.isNullOrEmpty(value)) { - result.addError(Result.Message.ofL10n(RubyValidation.ERRORS_CANT_BE_EMPTY_MESSAGE, paramName)); + result.addError(Result.Message.ofL10n(Validation.ERRORS_CANT_BE_EMPTY_MESSAGE, paramName)); } } private void checkMandatorySizeParameter(String value, String paramName, Integer size, Result result) { checkMandatoryParameter(value, paramName, result); if (!Strings.isNullOrEmpty(value) && value.length() > size) { - result.addError(Result.Message.ofL10n(RubyValidation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size)); + result.addError(Result.Message.ofL10n(Validation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size)); } } private void checkOptionalSizeParameter(String value, String paramName, Integer size, Result result) { if (!Strings.isNullOrEmpty(value) && value.length() > size) { - result.addError(Result.Message.ofL10n(RubyValidation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size)); + result.addError(Result.Message.ofL10n(Validation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size)); } } private void checkOptionalSizeParameter(String value, String paramName, Integer size) { if (!Strings.isNullOrEmpty(value) && value.length() > size) { - throw BadRequestException.ofL10n(RubyValidation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size); + throw BadRequestException.ofL10n(Validation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size); } } diff --git a/sonar-server/src/main/java/org/sonar/server/qualityprofile/NewProfileQuery.java b/sonar-server/src/main/java/org/sonar/server/qualityprofile/NewProfileQuery.java new file mode 100644 index 00000000000..6fdafcb0208 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/qualityprofile/NewProfileQuery.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.qualityprofile; + +public class NewProfileQuery { +} diff --git a/sonar-server/src/main/java/org/sonar/server/qualityprofile/NewProfileResult.java b/sonar-server/src/main/java/org/sonar/server/qualityprofile/NewProfileResult.java new file mode 100644 index 00000000000..21ee8868813 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/qualityprofile/NewProfileResult.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.qualityprofile; + +import java.util.List; + +public class NewProfileResult { + + private List<String> warnings; + private List<String> infos; + + private QProfile profile; + + public List<String> warnings() { + return warnings; + } + + public NewProfileResult setWarnings(List<String> warnings) { + this.warnings = warnings; + return this; + } + + public List<String> infos() { + return infos; + } + + public NewProfileResult setInfos(List<String> infos) { + this.infos = infos; + return this; + } + + public QProfile profile() { + return profile; + } + + public NewProfileResult setProfile(QProfile profile) { + this.profile = profile; + return this; + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileOperations.java b/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileOperations.java index 59af8af48c2..ff262ffefc9 100644 --- a/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileOperations.java +++ b/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileOperations.java @@ -20,6 +20,7 @@ package org.sonar.server.qualityprofile; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.apache.ibatis.session.SqlSession; @@ -33,14 +34,18 @@ import org.sonar.api.rules.RulePriority; import org.sonar.api.utils.ValidationMessages; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.persistence.MyBatis; +import org.sonar.core.preview.PreviewCache; import org.sonar.core.qualityprofile.db.*; -import org.sonar.server.issue.Result; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.user.UserSession; +import org.sonar.server.util.Validation; import java.io.StringReader; import java.util.List; import java.util.Map; +import static com.google.common.collect.Lists.newArrayList; + public class QProfileOperations implements ServerComponent { private final MyBatis myBatis; @@ -48,59 +53,74 @@ public class QProfileOperations implements ServerComponent { private final ActiveRuleDao activeRuleDao; private final List<ProfileExporter> exporters; private final List<ProfileImporter> importers; + private final PreviewCache dryRunCache; - public QProfileOperations(MyBatis myBatis, QualityProfileDao dao, ActiveRuleDao activeRuleDao) { - this(myBatis, dao, activeRuleDao, Lists.<ProfileExporter>newArrayList(), Lists.<ProfileImporter>newArrayList()); + public QProfileOperations(MyBatis myBatis, QualityProfileDao dao, ActiveRuleDao activeRuleDao, PreviewCache dryRunCache) { + this(myBatis, dao, activeRuleDao, Lists.<ProfileExporter>newArrayList(), Lists.<ProfileImporter>newArrayList(), dryRunCache); } - public QProfileOperations(MyBatis myBatis, QualityProfileDao dao, ActiveRuleDao activeRuleDao, List<ProfileExporter> exporters, List<ProfileImporter> importers) { + public QProfileOperations(MyBatis myBatis, QualityProfileDao dao, ActiveRuleDao activeRuleDao, List<ProfileExporter> exporters, List<ProfileImporter> importers, + PreviewCache dryRunCache) { this.myBatis = myBatis; this.dao = dao; this.activeRuleDao = activeRuleDao; this.exporters = exporters; this.importers = importers; + this.dryRunCache = dryRunCache; } - public Result<QProfile> newProfile(String name, String language, UserSession userSession) { - userSession.checkGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN); - QualityProfileDto dto = new QualityProfileDto().setName(name).setLanguage(language); - dao.insert(dto); - return Result.of(QProfile.from(dto)); - } - - public Result<QProfile> newProfile(String name, String language, Map<String, String> xmlProfilesByPlugin, UserSession userSession) { + public NewProfileResult newProfile(String name, String language, Map<String, String> xmlProfilesByPlugin, UserSession userSession) { userSession.checkGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN); + validate(name, language); - // TODO check name not already exists + NewProfileResult result = new NewProfileResult(); + List<RulesProfile> importProfiles = readProfiles(result, xmlProfilesByPlugin); SqlSession sqlSession = myBatis.openSession(); - Result<QProfile> result = Result.of(); try { - QualityProfileDto dto = new QualityProfileDto().setName(name).setLanguage(language); + QualityProfileDto dto = new QualityProfileDto().setName(name).setLanguage(language).setVersion(1).setUsed(false); dao.insert(dto, sqlSession); - for (Map.Entry<String, String> entry : xmlProfilesByPlugin.entrySet()) { - importProfile(dto, entry.getKey(), entry.getValue(), result, sqlSession); + for (RulesProfile rulesProfile : importProfiles) { + importProfile(dto, rulesProfile, sqlSession); } - result.set(QProfile.from(dto)); + result.setProfile(QProfile.from(dto)); } finally { sqlSession.commit(); + dryRunCache.reportGlobalModification(); return result; } } - private void importProfile(QualityProfileDto qualityProfileDto, String pluginKey, String xmlProfile, Result<QProfile> result, SqlSession sqlSession) { - ProfileImporter importer = getProfileImporter(pluginKey); + public void validate(String name, String language){ + if (Strings.isNullOrEmpty(name)) { + throw BadRequestException.ofL10n("quality_profiles.please_type_profile_name"); + } + Validation.checkMandatoryParameter(language, "language"); + if (dao.selectByNameAndLanguage(name, language) != null) { + throw BadRequestException.ofL10n("quality_profiles.already_exists"); + } + } + + private List<RulesProfile> readProfiles(NewProfileResult result, Map<String, String> xmlProfilesByPlugin) { + List<RulesProfile> profiles = newArrayList(); ValidationMessages messages = ValidationMessages.create(); - RulesProfile profile = importer.importProfile(new StringReader(xmlProfile), messages); - completeErrorResult(result, messages); - - if (result.ok()) { - for (ActiveRule activeRule : profile.getActiveRules()) { - ActiveRuleDto activeRuleDto = toActiveRuleDto(activeRule, qualityProfileDto); - activeRuleDao.insert(activeRuleDto, sqlSession); - for (ActiveRuleParam activeRuleParam : activeRule.getActiveRuleParams()) { - activeRuleDao.insert(toActiveRuleParamDto(activeRuleParam, activeRuleDto), sqlSession); - } + for (Map.Entry<String, String> entry : xmlProfilesByPlugin.entrySet()) { + String pluginKey = entry.getKey(); + String file = entry.getValue(); + ProfileImporter importer = getProfileImporter(pluginKey); + RulesProfile profile = importer.importProfile(new StringReader(file), messages); + processValidationMessages(messages, result); + profiles.add(profile); + } + return profiles; + } + + private void importProfile(QualityProfileDto qualityProfileDto, RulesProfile rulesProfile, SqlSession sqlSession) { + for (ActiveRule activeRule : rulesProfile.getActiveRules()) { + ActiveRuleDto activeRuleDto = toActiveRuleDto(activeRule, qualityProfileDto); + activeRuleDao.insert(activeRuleDto, sqlSession); + for (ActiveRuleParam activeRuleParam : activeRule.getActiveRuleParams()) { + activeRuleDao.insert(toActiveRuleParamDto(activeRuleParam, activeRuleDto), sqlSession); } } } @@ -114,13 +134,17 @@ public class QProfileOperations implements ServerComponent { return null; } - private void completeErrorResult(Result<QProfile> result, ValidationMessages messages) { + private void processValidationMessages(ValidationMessages messages, NewProfileResult result) { + BadRequestException exception = BadRequestException.of("Fail to create profile"); for (String error : messages.getErrors()) { - result.addError(error); + exception.addError(error); } - for (String warning : messages.getWarnings()) { - result.addError(warning); + if (!exception.errors().isEmpty()) { + throw exception; } + + result.setWarnings(messages.getWarnings()); + result.setInfos(messages.getInfos()); } private ActiveRuleDto toActiveRuleDto(ActiveRule activeRule, QualityProfileDto dto) { diff --git a/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfiles.java b/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfiles.java index 89def5ce300..de6a590e058 100644 --- a/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfiles.java +++ b/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfiles.java @@ -25,6 +25,7 @@ import org.sonar.api.rule.RuleKey; import org.sonar.server.user.UserSession; import java.util.List; +import java.util.Map; public class QProfiles implements ServerComponent { @@ -44,8 +45,8 @@ public class QProfiles implements ServerComponent { throw new UnsupportedOperationException(); } - public void newProfile(String name, String language, UserSession userSession) { - operations.newProfile(name, language, userSession); + public NewProfileResult newProfile(String name, String language, Map<String, String> xmlProfilesByPlugin) { + return operations.newProfile(name, language, xmlProfilesByPlugin, UserSession.get()); } public void deleteProfile() { diff --git a/sonar-server/src/main/java/org/sonar/server/qualityprofile/RubyQProfilesService.java b/sonar-server/src/main/java/org/sonar/server/qualityprofile/RubyQProfilesService.java index 218a3894b55..37ec46b2cf3 100644 --- a/sonar-server/src/main/java/org/sonar/server/qualityprofile/RubyQProfilesService.java +++ b/sonar-server/src/main/java/org/sonar/server/qualityprofile/RubyQProfilesService.java @@ -21,8 +21,7 @@ package org.sonar.server.qualityprofile; import org.sonar.api.ServerComponent; -import org.sonar.server.user.UserSession; -import org.sonar.server.util.RubyValidation; +import org.sonar.server.util.Validation; import java.util.List; import java.util.Map; @@ -44,8 +43,8 @@ public class RubyQProfilesService implements ServerComponent { String language = (String) params.get("language"); // RubyUtils.toStrings() - RubyValidation.checkMandatoryParameter(name, "name"); - RubyValidation.checkMandatoryParameter(language, "language"); - qProfiles.newProfile(name, language, UserSession.get()); + Validation.checkMandatoryParameter(name, "name"); + Validation.checkMandatoryParameter(language, "language"); +// qProfiles.newProfile(name, language, UserSession.get()); } } diff --git a/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java b/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java index c52ed7ccaf2..28dcdb31b2c 100644 --- a/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java +++ b/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java @@ -20,20 +20,13 @@ package org.sonar.server.rules; -import org.sonar.core.preview.PreviewCache; - import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.sonar.api.ServerComponent; import org.sonar.api.database.DatabaseSession; -import org.sonar.api.profiles.ProfileExporter; -import org.sonar.api.profiles.ProfileImporter; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.profiles.XMLProfileParser; -import org.sonar.api.profiles.XMLProfileSerializer; -import org.sonar.api.rules.ActiveRule; -import org.sonar.api.rules.ActiveRuleParam; +import org.sonar.api.profiles.*; import org.sonar.api.utils.ValidationMessages; +import org.sonar.core.preview.PreviewCache; import org.sonar.jpa.session.DatabaseSessionFactory; import java.io.StringReader; @@ -142,28 +135,6 @@ public final class ProfilesConsole implements ServerComponent { return null; } - /** - * Important : the ruby controller has already create the profile - */ - public ValidationMessages importProfile(String profileName, String language, String importerKey, String profileDefinition) { - ValidationMessages messages = ValidationMessages.create(); - ProfileImporter importer = getProfileImporter(importerKey); - RulesProfile profile = importer.importProfile(new StringReader(profileDefinition), messages); - if (!messages.hasErrors()) { - DatabaseSession session = sessionFactory.getSession(); - RulesProfile persistedProfile = session.getSingleResult(RulesProfile.class, "name", profileName, "language", language); - for (ActiveRule activeRule : profile.getActiveRules()) { - ActiveRule persistedActiveRule = persistedProfile.activateRule(activeRule.getRule(), activeRule.getSeverity()); - for (ActiveRuleParam activeRuleParam : activeRule.getActiveRuleParams()) { - persistedActiveRule.setParameter(activeRuleParam.getKey(), activeRuleParam.getValue()); - } - } - session.saveWithoutFlush(persistedProfile); - session.commit(); - } - return messages; - } - public ProfileExporter getProfileExporter(String exporterKey) { for (ProfileExporter exporter : exporters) { if (StringUtils.equals(exporterKey, exporter.getKey())) { @@ -173,12 +144,4 @@ public final class ProfilesConsole implements ServerComponent { return null; } - public ProfileImporter getProfileImporter(String exporterKey) { - for (ProfileImporter importer : importers) { - if (StringUtils.equals(exporterKey, importer.getKey())) { - return importer; - } - } - return null; - } } diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index 52ddfd748a0..7d5c67751c5 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -280,10 +280,6 @@ public final class JRubyFacade { return get(ProfilesConsole.class).exportProfile(profileId, exporterKey); } - public ValidationMessages importProfile(String profileName, String language, String importerKey, String fileContent) { - return get(ProfilesConsole.class).importProfile(profileName, language, importerKey, fileContent); - } - public String getProfileExporterMimeType(String exporterKey) { return get(ProfilesConsole.class).getProfileExporter(exporterKey).getMimeType(); } diff --git a/sonar-server/src/main/java/org/sonar/server/util/RubyUtils.java b/sonar-server/src/main/java/org/sonar/server/util/RubyUtils.java index ad7fcee4ed6..d77243188d5 100644 --- a/sonar-server/src/main/java/org/sonar/server/util/RubyUtils.java +++ b/sonar-server/src/main/java/org/sonar/server/util/RubyUtils.java @@ -30,6 +30,7 @@ import javax.annotation.Nullable; import java.util.Date; import java.util.List; +import java.util.Map; /** * @since 3.6 @@ -53,18 +54,18 @@ public class RubyUtils { return result; } -// public static Map<String, String> toMap(@Nullable Object o) { -// Map<String, String> result = null; -// if (o != null) { -// if (o instanceof List) { -// // assume that it contains only strings -// result = (List) o; -// } else if (o instanceof CharSequence) { -// result = Lists.newArrayList(Splitter.on(',').omitEmptyStrings().split((CharSequence) o)); -// } -// } -// return result; -// } + public static Map<String, Object> toMap(@Nullable Object o) { + Map<String, Object> result = null; + if (o != null) { + if (o instanceof Map) { + // assume that it contains only strings + result = (Map) o; + } else { + throw new IllegalArgumentException("Unsupported type for map: " + o.getClass()); + } + } + return result; + } @CheckForNull public static Integer toInteger(@Nullable Object o) { diff --git a/sonar-server/src/main/java/org/sonar/server/util/RubyValidation.java b/sonar-server/src/main/java/org/sonar/server/util/Validation.java index 11acc05fa65..a90e9b6dcf0 100644 --- a/sonar-server/src/main/java/org/sonar/server/util/RubyValidation.java +++ b/sonar-server/src/main/java/org/sonar/server/util/Validation.java @@ -22,25 +22,25 @@ package org.sonar.server.util; import com.google.common.base.Strings; import org.sonar.server.exceptions.BadRequestException; -public class RubyValidation { +public class Validation { public static final String ERRORS_CANT_BE_EMPTY_MESSAGE = "errors.cant_be_empty"; public static final String ERRORS_IS_TOO_LONG_MESSAGE = "errors.is_too_long"; - private RubyValidation() { + private Validation() { // only static methods } public static void checkMandatoryParameter(String value, String paramName) { if (Strings.isNullOrEmpty(value)) { - throw BadRequestException.ofL10n(RubyValidation.ERRORS_CANT_BE_EMPTY_MESSAGE, paramName); + throw BadRequestException.ofL10n(Validation.ERRORS_CANT_BE_EMPTY_MESSAGE, paramName); } } public static void checkMandatorySizeParameter(String value, String paramName, Integer size) { checkMandatoryParameter(value, paramName); if (!Strings.isNullOrEmpty(value) && value.length() > size) { - throw BadRequestException.ofL10n(RubyValidation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size); + throw BadRequestException.ofL10n(Validation.ERRORS_IS_TOO_LONG_MESSAGE, paramName, size); } } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb index e2393b4dfd4..9d9de03820f 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb @@ -43,30 +43,38 @@ class ProfilesController < ApplicationController # POST /profiles/create?name=<profile name>&language=<language>&[backup=<file>] def create verify_post_request - access_denied unless has_role?(:profileadmin) - require_parameters 'language' + begin + files_by_key = {} + if params[:backup] + params[:backup].each_pair do |importer_key, file| + unless file.blank? + files_by_key[importer_key] = Api::Utils.read_post_request_param(file) + end + end + end + result = Internal.qprofiles.newProfile(params[:name], params[:language], files_by_key) + flash[:notice] = message('quality_profiles.profile_x_created', :params => result.profile.name) - profile_name=params[:name] - language=params[:language] + # only 4 messages are kept each time to avoid cookie overflow. + unless result.infos.empty? + flash[:notice] += result.infos.to_a[0...4].join('<br/>') + end + unless result.warnings.empty? + flash[:warning] = result.warnings.to_a[0...4].join('<br/>') + end - profile = Profile.create(:name => profile_name, :language => language) - ok = profile.errors.empty? - if ok && params[:backup] - params[:backup].each_pair do |importer_key, file| - if !file.blank? && ok - profile.import_configuration(importer_key, file) - ok &= profile.errors.empty? + # TODO manage these exceptions at a higher level + rescue NativeException => exception + if exception.cause.java_kind_of? Java::OrgSonarServerExceptions::ServerException + error = exception.cause + flash[:error] = (error.getMessage ? error.getMessage : Api::Utils.message(error.l10nKey, :params => error.l10nParams.to_a)) + if error.java_kind_of?(Java::OrgSonarServerExceptions::BadRequestException) && !error.errors.empty? + flash[:error] += '<br/>' + error.errors.to_a.map{|error| error.text ? error.text : Api::Utils.message(error.l10nKey, :params => error.l10nParams)}.join('<br/>') end + else + flash[:error] = 'Error when creating the quality profile : ' + exception.cause.getMessage end end - - flash_profile(profile) - if ok - flash[:notice]=message('quality_profiles.profile_x_created', :params => profile.name) - elsif profile.id - Profile.destroy(profile.id) - end - redirect_to :action => 'index' end @@ -510,16 +518,6 @@ class ProfilesController < ApplicationController return {:same => same, :added => added, :removed => removed, :modified => modified, :rules => rules} end - def flash_profile(profile) - # only 4 messages are kept each time to avoid cookie overflow. - if !profile.errors.empty? - flash[:error]=profile.errors.full_messages.to_a[0...4].join('<br/>') - end - if profile.warnings? - flash[:warning]=profile.warnings.full_messages.to_a[0...4].join('<br/>') - end - end - def flash_messages(messages) # only 4 messages are kept each time to avoid cookie overflow. if messages.hasErrors() diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/internal.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/internal.rb index 0b2a6ac51d8..fdbf40dddab 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/internal.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/internal.rb @@ -67,7 +67,7 @@ class Internal end def self.qprofiles - component(Java::OrgSonarServerQualityprofile::RubyQProfilesService.java_class) + component(Java::OrgSonarServerQualityprofile::QProfiles.java_class) end private diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb index 8316ee26cb7..94183e6040c 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb @@ -141,19 +141,6 @@ class Profile < ActiveRecord::Base end end - def import_configuration(importer_key, file) - messages = Api::Utils.java_facade.importProfile(name, language, importer_key, Api::Utils.read_post_request_param(file)) - messages.getErrors().each do |msg| - errors.add_to_base msg - end - messages.getWarnings().each do |msg| - warnings.add_to_base msg - end - messages.getInfos().each do |msg| - notices.add_to_base msg - end - end - def before_destroy raise 'This profile can not be deleted' unless deletable? Property.clear_for_resources("sonar.profile.#{language}", name) @@ -246,4 +233,4 @@ class Profile < ActiveRecord::Base def self.find_by_name_and_language(name, language) Profile.first(:conditions => {:name => name, :language => language}) end -end
\ No newline at end of file +end diff --git a/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileOperationsTest.java b/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileOperationsTest.java index 60847640338..a66523f0fd2 100644 --- a/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileOperationsTest.java +++ b/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileOperationsTest.java @@ -20,13 +20,16 @@ package org.sonar.server.qualityprofile; +import com.google.common.collect.Maps; import org.apache.ibatis.session.SqlSession; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import org.sonar.api.profiles.ProfileExporter; import org.sonar.api.profiles.ProfileImporter; import org.sonar.api.profiles.RulesProfile; @@ -36,7 +39,9 @@ import org.sonar.api.rules.RulePriority; import org.sonar.api.utils.ValidationMessages; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.persistence.MyBatis; +import org.sonar.core.preview.PreviewCache; import org.sonar.core.qualityprofile.db.*; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.user.MockUserSession; @@ -65,6 +70,9 @@ public class QProfileOperationsTest { @Mock ActiveRuleDao activeRuleDao; + @Mock + PreviewCache dryRunCache; + List<ProfileExporter> exporters = newArrayList(); List<ProfileImporter> importers = newArrayList(); @@ -74,32 +82,55 @@ public class QProfileOperationsTest { @Before public void setUp() throws Exception { when(myBatis.openSession()).thenReturn(session); - operations = new QProfileOperations(myBatis, dao, activeRuleDao, exporters, importers); + operations = new QProfileOperations(myBatis, dao, activeRuleDao, exporters, importers, dryRunCache); } @Test public void new_profile() throws Exception { - operations.newProfile("Default", "java", MockUserSession.create().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN)); + NewProfileResult result = operations.newProfile("Default", "java", Maps.<String, String>newHashMap(), MockUserSession.create().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN)); + assertThat(result.profile().name()).isEqualTo("Default"); + assertThat(result.profile().language()).isEqualTo("java"); - ArgumentCaptor<QualityProfileDto> argument = ArgumentCaptor.forClass(QualityProfileDto.class); - verify(dao).insert(argument.capture()); - assertThat(argument.getValue().getName()).isEqualTo("Default"); - assertThat(argument.getValue().getLanguage()).isEqualTo("java"); + verify(dao).insert(any(QualityProfileDto.class), eq(session)); } @Test - public void fail_to_create_new_profile_without_profile_admin_permission() throws Exception { + public void fail_to_create_profile_without_profile_admin_permission() throws Exception { try { - operations.newProfile("Default", "java", MockUserSession.create()); + operations.newProfile("Default", "java", Maps.<String, String>newHashMap(), MockUserSession.create()); fail(); } catch (Exception e) { assertThat(e).isInstanceOf(ForbiddenException.class); } verifyNoMoreInteractions(dao); + verify(session, never()).commit(); + } + + @Test + public void fail_to_create_profile_without_name() throws Exception { + try { + operations.newProfile("", "java", Maps.<String, String>newHashMap(), MockUserSession.create().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN)); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(BadRequestException.class); + } + verify(session, never()).commit(); } @Test - public void new_profile_from_xml_plugn() throws Exception { + public void fail_to_create_profile_if_already_exists() throws Exception { + try { + when(dao.selectByNameAndLanguage(anyString(), anyString())).thenReturn(new QualityProfileDto()); + operations.newProfile("Default", "java", Maps.<String, String>newHashMap(), MockUserSession.create().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN)); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(BadRequestException.class); + } + verify(session, never()).commit(); + } + + @Test + public void new_profile_from_xml_plugin() throws Exception { RulesProfile profile = RulesProfile.create("Default", "java"); Rule rule = Rule.create("pmd", "rule1"); rule.createParameter("paramKey"); @@ -131,4 +162,30 @@ public class QProfileOperationsTest { verify(activeRuleDao).insert(activeRuleParamArgument.capture(), eq(session)); assertThat(activeRuleParamArgument.getValue().getValue()).isEqualTo("paramValue"); } + + @Test + public void fail_to_create_profile_from_xml_plugin_if_error() throws Exception { + try { + Map<String, String> xmlProfilesByPlugin = newHashMap(); + xmlProfilesByPlugin.put("pmd", "<xml/>"); + ProfileImporter importer = mock(ProfileImporter.class); + when(importer.getKey()).thenReturn("pmd"); + importers.add(importer); + + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + ValidationMessages validationMessages = (ValidationMessages) args[1]; + validationMessages.addErrorText("error!"); + return null; + } + }).when(importer).importProfile(any(Reader.class), any(ValidationMessages.class)); + + operations.newProfile("Default", "java", xmlProfilesByPlugin, MockUserSession.create().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN)); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(BadRequestException.class); + } + verify(session, never()).commit(); + } } diff --git a/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfilesTest.java b/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfilesTest.java index dae6b16760e..b021c68f6da 100644 --- a/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfilesTest.java +++ b/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfilesTest.java @@ -25,9 +25,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.server.user.MockUserSession; import org.sonar.server.user.UserSession; +import java.util.Map; + +import static com.google.common.collect.Maps.newHashMap; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; @@ -61,8 +63,9 @@ public class QProfilesTest { @Test public void new_profile() throws Exception { - qProfiles.newProfile("Default", "java", MockUserSession.create()); - verify(operations).newProfile(eq("Default"), eq("java"), any(UserSession.class)); + Map<String, String> xmlProfilesByPlugin = newHashMap(); + qProfiles.newProfile("Default", "java", xmlProfilesByPlugin); + verify(operations).newProfile(eq("Default"), eq("java"), eq(xmlProfilesByPlugin), any(UserSession.class)); } @Test(expected = UnsupportedOperationException.class) diff --git a/sonar-server/src/test/java/org/sonar/server/qualityprofile/RubyQProfilesServiceTest.java b/sonar-server/src/test/java/org/sonar/server/qualityprofile/RubyQProfilesServiceTest.java index 69a83d7ff4b..0593ebadff3 100644 --- a/sonar-server/src/test/java/org/sonar/server/qualityprofile/RubyQProfilesServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/qualityprofile/RubyQProfilesServiceTest.java @@ -26,13 +26,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.server.user.UserSession; import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -63,6 +59,6 @@ public class RubyQProfilesServiceTest { "name", "Default", "language", "java") ); - verify(qProfiles).newProfile(eq("Default"), eq("java"), any(UserSession.class)); +// verify(qProfiles).newProfile(eq("Default"), eq("java"), any(UserSession.class)); } } |