From: Simon Brandhof Date: Wed, 11 Jul 2012 18:29:18 +0000 (+0200) Subject: SONAR-3646 API : new extension point to be notified on user creation X-Git-Tag: 3.2~126 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=3869be4f9026d8b677a98041d7591586e35b2942;p=sonarqube.git SONAR-3646 API : new extension point to be notified on user creation --- diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/NewUserHandler.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/NewUserHandler.java new file mode 100644 index 00000000000..833c68d7dea --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/platform/NewUserHandler.java @@ -0,0 +1,91 @@ +/* + * 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.platform; + +import com.google.common.base.Preconditions; +import org.sonar.api.ServerExtension; + +import javax.annotation.Nullable; + +/** + * @since 3.2 + */ +public interface NewUserHandler extends ServerExtension { + + public static final class Context { + private String login; + private String name; + private String email; + + private Context(String login, String name, @Nullable String email) { + Preconditions.checkNotNull(login); + Preconditions.checkNotNull(name); + this.login = login; + this.name = name; + this.email = email; + } + + public String getLogin() { + return login; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String login; + private String name; + private String email; + + private Builder() { + } + + public Builder setLogin(String s) { + this.login = s; + return this; + } + + public Builder setName(String s) { + this.name = s; + return this; + } + + public Builder setEmail(@Nullable String s) { + this.email = s; + return this; + } + + public Context build() { + return new Context(login, name, email); + } + } + } + + void doOnNewUser(Context context); +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/NewUserHandlerTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/NewUserHandlerTest.java new file mode 100644 index 00000000000..e5cc3a477a7 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/platform/NewUserHandlerTest.java @@ -0,0 +1,48 @@ +/* + * 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.platform; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.fest.assertions.Assertions.assertThat; + +public class NewUserHandlerTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void build_context() { + NewUserHandler.Context context = NewUserHandler.Context.builder().setLogin("marius").setName("Marius").setEmail("marius@lesbronzes.fr").build(); + + assertThat(context.getLogin()).isEqualTo("marius"); + assertThat(context.getName()).isEqualTo("Marius"); + assertThat(context.getEmail()).isEqualTo("marius@lesbronzes.fr"); + } + + @Test + public void login_is_mandatory() { + thrown.expect(NullPointerException.class); + + NewUserHandler.Context.builder().setName("Marius").build(); + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/platform/NewUserNotifier.java b/sonar-server/src/main/java/org/sonar/server/platform/NewUserNotifier.java new file mode 100644 index 00000000000..a6f1d22869b --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/platform/NewUserNotifier.java @@ -0,0 +1,47 @@ +/* + * 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.server.platform; + +import org.slf4j.LoggerFactory; +import org.sonar.api.ServerComponent; +import org.sonar.api.platform.NewUserHandler; + +/** + * @since 3.2 + */ +public class NewUserNotifier implements ServerComponent { + + private NewUserHandler[] handlers; + + public NewUserNotifier(NewUserHandler[] handlers) { + this.handlers = handlers; + } + + public NewUserNotifier() { + this(new NewUserHandler[0]); + } + + public void onNewUser(NewUserHandler.Context context) { + LoggerFactory.getLogger(NewUserNotifier.class).debug("User created: " + context.getLogin() + ". Notifying " + NewUserHandler.class.getSimpleName() + " handlers..."); + for (NewUserHandler handler : handlers) { + handler.doOnNewUser(context); + } + } +} \ No newline at end of file diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 2ce18b2e881..e20fed0c4c0 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -241,6 +241,7 @@ public final class Platform { servicesContainer.addSingleton(RuleI18nManager.class); servicesContainer.addSingleton(GwtI18n.class); servicesContainer.addSingleton(ResourceTypes.class); + servicesContainer.addSingleton(NewUserNotifier.class); // Notifications servicesContainer.addSingleton(EmailSettings.class); 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 76ba04b7168..d2f4a2bb449 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 @@ -27,6 +27,7 @@ import org.sonar.api.config.License; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; import org.sonar.api.platform.ComponentContainer; +import org.sonar.api.platform.NewUserHandler; import org.sonar.api.platform.PluginMetadata; import org.sonar.api.platform.PluginRepository; import org.sonar.api.profiles.ProfileExporter; @@ -37,11 +38,7 @@ import org.sonar.api.resources.ResourceTypes; import org.sonar.api.rules.RulePriority; import org.sonar.api.rules.RuleRepository; import org.sonar.api.utils.ValidationMessages; -import org.sonar.api.web.Footer; -import org.sonar.api.web.NavigationSection; -import org.sonar.api.web.Page; -import org.sonar.api.web.RubyRailsWebservice; -import org.sonar.api.web.Widget; +import org.sonar.api.web.*; import org.sonar.api.workflow.Review; import org.sonar.api.workflow.internal.DefaultReview; import org.sonar.api.workflow.internal.DefaultWorkflowContext; @@ -61,19 +58,15 @@ import org.sonar.server.filters.FilterExecutor; import org.sonar.server.filters.FilterResult; import org.sonar.server.notifications.reviews.ReviewsNotificationManager; import org.sonar.server.platform.GlobalSettingsUpdater; +import org.sonar.server.platform.NewUserNotifier; import org.sonar.server.platform.Platform; import org.sonar.server.platform.ServerIdGenerator; -import org.sonar.server.plugins.DefaultServerPluginRepository; -import org.sonar.server.plugins.PluginDeployer; -import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.UpdateCenterMatrix; -import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.plugins.*; import org.sonar.server.rules.ProfilesConsole; import org.sonar.server.rules.RulesConsole; import org.sonar.updatecenter.common.Version; import javax.annotation.Nullable; - import java.net.InetAddress; import java.sql.Connection; import java.util.Collection; @@ -319,7 +312,7 @@ public final class JRubyFacade { public void ruleSeverityChanged(int parentProfileId, int activeRuleId, int oldSeverityId, int newSeverityId, String userName) { getProfilesManager().ruleSeverityChanged(parentProfileId, activeRuleId, RulePriority.values()[oldSeverityId], - RulePriority.values()[newSeverityId], userName); + RulePriority.values()[newSeverityId], userName); } public void ruleDeactivated(int parentProfileId, int deactivatedRuleId, String userName) { @@ -524,4 +517,13 @@ public final class JRubyFacade { getContainer().getComponentByType(ResourceKeyUpdaterDao.class).bulkUpdateKey(projectId, stringToReplace, replacementString); } + + // USERS + public void onNewUser(Map fields) { + getContainer().getComponentByType(NewUserNotifier.class).onNewUser(NewUserHandler.Context.builder() + .setLogin(fields.get("login")) + .setName(fields.get("name")) + .setEmail(fields.get("email")) + .build()); + } } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/users_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/users_controller.rb index b238a0bf709..a187754727c 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/users_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/users_controller.rb @@ -116,8 +116,7 @@ class UsersController < ApplicationController def reactivate user = User.find_by_login(params[:user][:login]) if user - user.reactivate(java_facade.getSettings().getString('sonar.defaultGroup')) - user.save! + user.reactivate!(java_facade.getSettings().getString('sonar.defaultGroup')) flash[:notice] = 'User was successfully reactivated.' else flash[:error] = "A user with login #{params[:user][:login]} does not exist." diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/user.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/user.rb index 2e25ac4dad7..c70d33985a4 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/user.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/user.rb @@ -23,6 +23,7 @@ class User < ActiveRecord::Base FAVOURITE_PROPERTY_KEY='favourite' + after_create :on_create has_and_belongs_to_many :groups has_many :user_roles, :dependent => :delete_all @@ -37,17 +38,17 @@ class User < ActiveRecord::Base include NeedAuthorization::ForUser include NeedAuthentication::ForUser - validates_length_of :name, :maximum => 200, :allow_blank => true, :allow_nil => true - validates_length_of :email, :maximum => 100, :allow_blank => true, :allow_nil => true + validates_length_of :name, :maximum => 200, :allow_blank => true, :allow_nil => true + validates_length_of :email, :maximum => 100, :allow_blank => true, :allow_nil => true # The following two validations not needed, because they come with Authentication::ByPassword - see SONAR-2656 #validates_length_of :password, :within => 4..40, :if => :password_required? #validates_confirmation_of :password, :if => :password_required? - validates_presence_of :login - validates_length_of :login, :within => 2..40 - validates_uniqueness_of :login, :case_sensitive => true - validates_format_of :login, :with => Authentication.login_regex, :message => Authentication.bad_login_message + validates_presence_of :login + validates_length_of :login, :within => 2..40 + validates_uniqueness_of :login, :case_sensitive => true + validates_format_of :login, :with => Authentication.login_regex, :message => Authentication.bad_login_message # HACK HACK HACK -- how to do attr_accessible from here? @@ -90,20 +91,22 @@ class User < ActiveRecord::Base # do not validate user, for example when user created via SSO has no password self.save(false) - self.user_roles.each {|role| role.delete} - self.properties.each {|prop| prop.delete} - self.filters.each {|f| f.destroy} - self.dashboards.each {|d| d.destroy} - self.active_dashboards.each {|ad| ad.destroy} + self.user_roles.each { |role| role.delete } + self.properties.each { |prop| prop.delete } + self.filters.each { |f| f.destroy } + self.dashboards.each { |d| d.destroy } + self.active_dashboards.each { |ad| ad.destroy } end # SONAR-3258 - def reactivate(default_group_name) + def reactivate!(default_group_name) if default_group_name default_group=Group.find_by_name(default_group_name) self.groups<0 - User.find(:all, :select => 'id', :conditions => ['login in (?)', logins]).map{|user| user.id} + User.find(:all, :select => 'id', :conditions => ['login in (?)', logins]).map { |user| user.id } else [] end @@ -162,7 +165,7 @@ class User < ActiveRecord::Base def favourite_ids @favourite_ids ||= begin - properties().select{|p| p.key==FAVOURITE_PROPERTY_KEY}.map{|p| p.resource_id} + properties().select { |p| p.key==FAVOURITE_PROPERTY_KEY }.map { |p| p.resource_id } end @favourite_ids end @@ -187,7 +190,7 @@ class User < ActiveRecord::Base rid = resource.id if resource end if rid - props=properties().select{|p| p.key==FAVOURITE_PROPERTY_KEY && p.resource_id==rid} + props=properties().select { |p| p.key==FAVOURITE_PROPERTY_KEY && p.resource_id==rid } if props.size>0 properties().delete(props) return true @@ -199,4 +202,11 @@ class User < ActiveRecord::Base def favourite?(resource_id) favourite_ids().include?(resource_id.to_i) end + + + private + + def on_create + Java::OrgSonarServerUi::JRubyFacade.getInstance().onNewUser({'login' => self.login, 'name' => self.name, 'email' => self.email}) + end end diff --git a/sonar-server/src/test/java/org/sonar/server/platform/NewUserNotifierTest.java b/sonar-server/src/test/java/org/sonar/server/platform/NewUserNotifierTest.java new file mode 100644 index 00000000000..62618af3936 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/platform/NewUserNotifierTest.java @@ -0,0 +1,51 @@ +/* + * 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.server.platform; + +import org.junit.Test; +import org.sonar.api.platform.NewUserHandler; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class NewUserNotifierTest { + + private NewUserHandler.Context context = NewUserHandler.Context.builder().setLogin("marius").setName("Marius").build(); + + @Test + public void do_not_fail_if_no_handlers() { + NewUserNotifier notifier = new NewUserNotifier(); + + notifier.onNewUser(context); + } + + @Test + public void execute_handlers_on_new_user() { + NewUserHandler handler1 = mock(NewUserHandler.class); + NewUserHandler handler2 = mock(NewUserHandler.class); + NewUserNotifier notifier = new NewUserNotifier(new NewUserHandler[]{handler1, handler2}); + + + notifier.onNewUser(context); + + verify(handler1).doOnNewUser(context); + verify(handler2).doOnNewUser(context); + } +}