@@ -24,7 +24,6 @@ import java.util.List; | |||
import javax.annotation.CheckForNull; | |||
import org.sonar.api.SonarQubeSide; | |||
import org.sonar.api.SonarQubeVersion; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.internal.ApiVersion; | |||
import org.sonar.api.internal.SonarRuntimeImpl; | |||
import org.sonar.api.profiles.AnnotationProfileParser; | |||
@@ -90,11 +89,7 @@ import org.sonar.server.issue.workflow.FunctionExecutor; | |||
import org.sonar.server.issue.workflow.IssueWorkflow; | |||
import org.sonar.server.metric.CoreCustomMetrics; | |||
import org.sonar.server.metric.DefaultMetricFinder; | |||
import org.sonar.server.notification.DefaultNotificationManager; | |||
import org.sonar.server.notification.NotificationCenter; | |||
import org.sonar.server.notification.NotificationService; | |||
import org.sonar.server.notification.email.AlertsEmailTemplate; | |||
import org.sonar.server.notification.email.EmailNotificationChannel; | |||
import org.sonar.server.notification.NotificationModule; | |||
import org.sonar.server.organization.DefaultOrganizationProviderImpl; | |||
import org.sonar.server.permission.GroupPermissionChanger; | |||
import org.sonar.server.permission.PermissionTemplateService; | |||
@@ -354,7 +349,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { | |||
NewIssuesEmailTemplate.class, | |||
MyNewIssuesEmailTemplate.class, | |||
IssueChangesEmailTemplate.class, | |||
AlertsEmailTemplate.class, | |||
ChangesOnMyIssueNotificationDispatcher.class, | |||
ChangesOnMyIssueNotificationDispatcher.newMetadata(), | |||
NewIssuesNotificationDispatcher.class, | |||
@@ -364,17 +358,13 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { | |||
DoNotFixNotificationDispatcher.class, | |||
DoNotFixNotificationDispatcher.newMetadata(), | |||
NewIssuesNotificationFactory.class, // used by SendIssueNotificationsStep | |||
EmailNotificationChannel.class, | |||
// technical debt | |||
DebtModelPluginRepository.class, | |||
DebtRulesXMLImporter.class, | |||
// Notifications | |||
EmailSettings.class, | |||
NotificationService.class, | |||
NotificationCenter.class, | |||
DefaultNotificationManager.class, | |||
NotificationModule.class, | |||
// Tests | |||
TestIndexer.class, |
@@ -88,7 +88,7 @@ public class ComputeEngineContainerImplTest { | |||
assertThat(picoContainer.getComponentAdapters()) | |||
.hasSize( | |||
CONTAINER_ITSELF | |||
+ 77 // level 4 | |||
+ 83 // level 4 | |||
+ 4 // content of CeConfigurationModule | |||
+ 3 // content of CeHttpModule | |||
+ 5 // content of CeQueueModule |
@@ -19,7 +19,7 @@ | |||
*/ | |||
package org.sonar.server.notification; | |||
import com.google.common.collect.Lists; | |||
import com.google.common.collect.ImmutableList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import javax.annotation.Nullable; | |||
@@ -69,7 +69,7 @@ public class NotificationCenter { | |||
* If "propertyValue" is null, the verification is done on the existence of such a property (whatever the value). | |||
*/ | |||
public List<String> getDispatcherKeysForProperty(String propertyKey, @Nullable String propertyValue) { | |||
List<String> keys = Lists.newArrayList(); | |||
ImmutableList.Builder<String> keys = ImmutableList.builder(); | |||
for (NotificationDispatcherMetadata metadata : dispatchersMetadata) { | |||
String dispatcherKey = metadata.getDispatcherKey(); | |||
String value = metadata.getProperty(propertyKey); | |||
@@ -77,7 +77,7 @@ public class NotificationCenter { | |||
keys.add(dispatcherKey); | |||
} | |||
} | |||
return keys; | |||
return keys.build(); | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.core.platform.Module; | |||
import org.sonar.server.email.ws.EmailsWsModule; | |||
import org.sonar.server.notification.email.AlertsEmailTemplate; | |||
import org.sonar.server.notification.email.EmailNotificationChannel; | |||
import org.sonar.server.notification.ws.AddAction; | |||
import org.sonar.server.notification.ws.NotificationsWs; | |||
public class NotificationModule extends Module { | |||
@Override | |||
protected void configureModule() { | |||
add( | |||
EmailSettings.class, | |||
NotificationService.class, | |||
NotificationCenter.class, | |||
NotificationUpdater.class, | |||
DefaultNotificationManager.class, | |||
EmailsWsModule.class, | |||
NotificationDaemon.class, | |||
AlertsEmailTemplate.class, | |||
EmailNotificationChannel.class, | |||
// WS | |||
NotificationsWs.class, | |||
AddAction.class); | |||
} | |||
} |
@@ -0,0 +1,102 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification; | |||
import java.util.List; | |||
import javax.annotation.Nullable; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.property.PropertyDto; | |||
import org.sonar.db.property.PropertyQuery; | |||
import org.sonar.server.user.UserSession; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
public class NotificationUpdater { | |||
private static final String PROP_NOTIFICATION_PREFIX = "notification"; | |||
private static final String PROP_NOTIFICATION_VALUE = "true"; | |||
private final UserSession userSession; | |||
private final DbClient dbClient; | |||
public NotificationUpdater(UserSession userSession, DbClient dbClient) { | |||
this.userSession = userSession; | |||
this.dbClient = dbClient; | |||
} | |||
/** | |||
* Add a notification to the current authenticated user. | |||
* Does nothing if the user is not authenticated. | |||
*/ | |||
public void add(DbSession dbSession, String channel, String dispatcher, @Nullable ComponentDto project) { | |||
if (!userSession.isLoggedIn()) { | |||
return; | |||
} | |||
String key = String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel); | |||
Long projectId = project == null ? null : project.getId(); | |||
List<PropertyDto> existingNotification = dbClient.propertiesDao().selectByQuery( | |||
PropertyQuery.builder() | |||
.setKey(key) | |||
.setComponentId(projectId) | |||
.setUserId(userSession.getUserId()) | |||
.build(), | |||
dbSession); | |||
checkArgument(existingNotification.isEmpty() | |||
|| !PROP_NOTIFICATION_VALUE.equals(existingNotification.get(0).getValue()), "Notification already added"); | |||
dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto() | |||
.setKey(key) | |||
.setUserId(Long.valueOf(userSession.getUserId())) | |||
.setValue(PROP_NOTIFICATION_VALUE) | |||
.setResourceId(projectId)); | |||
} | |||
/** | |||
* Remove a notification to the current authenticated user. | |||
* Does nothing if the user is not authenticated. | |||
*/ | |||
public void remove(DbSession dbSession, String channel, String dispatcher, @Nullable ComponentDto project) { | |||
if (!userSession.isLoggedIn()) { | |||
return; | |||
} | |||
String key = String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel); | |||
Long projectId = project == null ? null : project.getId(); | |||
List<PropertyDto> existingNotification = dbClient.propertiesDao().selectByQuery( | |||
PropertyQuery.builder() | |||
.setKey(key) | |||
.setComponentId(projectId) | |||
.setUserId(userSession.getUserId()) | |||
.build(), | |||
dbSession); | |||
checkArgument(!existingNotification.isEmpty() && PROP_NOTIFICATION_VALUE.equals(existingNotification.get(0).getValue()), "Notification doesn't exist"); | |||
dbClient.propertiesDao().delete(dbSession, new PropertyDto() | |||
.setKey(key) | |||
.setUserId(Long.valueOf(userSession.getUserId())) | |||
.setValue(PROP_NOTIFICATION_VALUE) | |||
.setResourceId(projectId)); | |||
} | |||
} |
@@ -0,0 +1,162 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification.ws; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import java.util.function.Consumer; | |||
import java.util.function.Function; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.Stream; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.resources.Scopes; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.issue.notification.MyNewIssuesNotificationDispatcher; | |||
import org.sonar.server.notification.NotificationCenter; | |||
import org.sonar.server.notification.NotificationUpdater; | |||
import org.sonar.server.notification.email.EmailNotificationChannel; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonar.server.ws.KeyExamples; | |||
import org.sonarqube.ws.client.notification.AddRequest; | |||
import static java.util.Optional.empty; | |||
import static org.sonar.core.util.Protobuf.setNullable; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION; | |||
import static org.sonar.server.ws.WsUtils.checkRequest; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.ACTION_ADD; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT; | |||
public class AddAction implements NotificationsWsAction { | |||
private final NotificationCenter notificationCenter; | |||
private final NotificationUpdater notificationUpdater; | |||
private final DbClient dbClient; | |||
private final ComponentFinder componentFinder; | |||
private final UserSession userSession; | |||
private final List<String> globalDispatchers; | |||
private final List<String> projectDispatchers; | |||
public AddAction(NotificationCenter notificationCenter, NotificationUpdater notificationUpdater, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { | |||
this.notificationCenter = notificationCenter; | |||
this.notificationUpdater = notificationUpdater; | |||
this.dbClient = dbClient; | |||
this.componentFinder = componentFinder; | |||
this.userSession = userSession; | |||
this.globalDispatchers = notificationCenter.getDispatcherKeysForProperty(GLOBAL_NOTIFICATION, "true"); | |||
this.projectDispatchers = notificationCenter.getDispatcherKeysForProperty(PER_PROJECT_NOTIFICATION, "true"); | |||
} | |||
@Override | |||
public void define(WebService.NewController context) { | |||
WebService.NewAction action = context.createAction(ACTION_ADD) | |||
.setDescription("Add a notification for the authenticated user.<br>" + | |||
"Requires authentication. If a project is provided, requires the 'Browse' permission on the specified project.") | |||
.setSince("6.3") | |||
.setPost(true) | |||
.setHandler(this); | |||
action.createParam(PARAM_PROJECT) | |||
.setDescription("Project key") | |||
.setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001); | |||
List<NotificationChannel> channels = notificationCenter.getChannels(); | |||
action.createParam(PARAM_CHANNEL) | |||
.setDescription("Channel through which the notification is sent. For example, notifications can be sent by email.") | |||
.setPossibleValues(channels) | |||
.setDefaultValue(EmailNotificationChannel.class.getSimpleName()); | |||
action.createParam(PARAM_NOTIFICATION) | |||
.setDescription("Notification. Possible values are for:" + | |||
"<ul>" + | |||
" <li>Overall notifications: %s</li>" + | |||
" <li>Per project notifications: %s</li>" + | |||
"</ul>", | |||
globalDispatchers.stream().sorted().collect(Collectors.joining(", ")), | |||
projectDispatchers.stream().sorted().collect(Collectors.joining(", "))) | |||
.setRequired(true) | |||
.setExampleValue(MyNewIssuesNotificationDispatcher.KEY); | |||
} | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
Stream.of(request) | |||
.map(toWsRequest()) | |||
.peek(checkPermissions()) | |||
.forEach(add()); | |||
response.noContent(); | |||
} | |||
private Consumer<AddRequest> add() { | |||
return request -> { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
Optional<ComponentDto> project = searchProject(dbSession, request); | |||
notificationUpdater.add(dbSession, request.getChannel(), request.getNotification(), project.orElse(null)); | |||
dbSession.commit(); | |||
} | |||
}; | |||
} | |||
private Optional<ComponentDto> searchProject(DbSession dbSession, AddRequest request) { | |||
Optional<ComponentDto> project = request.getProject() == null ? empty() : Optional.of(componentFinder.getByKey(dbSession, request.getProject())); | |||
project.ifPresent(p -> checkRequest(Qualifiers.PROJECT.equals(p.qualifier()) && Scopes.PROJECT.equals(p.scope()), | |||
"Component '%s' must be a project", request.getProject())); | |||
return project; | |||
} | |||
private Consumer<AddRequest> checkPermissions() { | |||
return request -> userSession.checkLoggedIn(); | |||
} | |||
private Function<Request, AddRequest> toWsRequest() { | |||
return request -> { | |||
AddRequest.Builder requestBuilder = AddRequest.builder() | |||
.setNotification(request.mandatoryParam(PARAM_NOTIFICATION)) | |||
.setChannel(request.mandatoryParam(PARAM_CHANNEL)); | |||
String project = request.param(PARAM_PROJECT); | |||
setNullable(project, requestBuilder::setProject); | |||
AddRequest wsRequest = requestBuilder.build(); | |||
if (wsRequest.getProject() == null) { | |||
checkRequest(globalDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s", | |||
PARAM_NOTIFICATION, | |||
wsRequest.getNotification(), | |||
globalDispatchers); | |||
} else { | |||
checkRequest(projectDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s", | |||
PARAM_NOTIFICATION, | |||
wsRequest.getNotification(), | |||
projectDispatchers); | |||
} | |||
return wsRequest; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification.ws; | |||
import org.sonar.api.server.ws.WebService; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.CONTROLLER; | |||
public class NotificationsWs implements WebService { | |||
private final NotificationsWsAction[] actions; | |||
public NotificationsWs(NotificationsWsAction[] actions) { | |||
this.actions = actions; | |||
} | |||
@Override | |||
public void define(Context context) { | |||
NewController controller = context.createController(CONTROLLER) | |||
.setDescription("Manage notifications of the authenticated user") | |||
.setSince("6.3"); | |||
for (NotificationsWsAction action : actions) { | |||
action.define(controller); | |||
} | |||
controller.done(); | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification.ws; | |||
import org.sonar.server.ws.WsAction; | |||
public interface NotificationsWsAction extends WsAction { | |||
// marker interface | |||
} |
@@ -0,0 +1,162 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification.ws; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import java.util.function.Consumer; | |||
import java.util.function.Function; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.Stream; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.resources.Scopes; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.issue.notification.MyNewIssuesNotificationDispatcher; | |||
import org.sonar.server.notification.NotificationCenter; | |||
import org.sonar.server.notification.NotificationUpdater; | |||
import org.sonar.server.notification.email.EmailNotificationChannel; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonar.server.ws.KeyExamples; | |||
import org.sonarqube.ws.client.notification.AddRequest; | |||
import static java.util.Optional.empty; | |||
import static org.sonar.core.util.Protobuf.setNullable; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION; | |||
import static org.sonar.server.ws.WsUtils.checkRequest; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.ACTION_ADD; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT; | |||
public class RemoveAction implements NotificationsWsAction { | |||
private final NotificationCenter notificationCenter; | |||
private final NotificationUpdater notificationUpdater; | |||
private final DbClient dbClient; | |||
private final ComponentFinder componentFinder; | |||
private final UserSession userSession; | |||
private final List<String> globalDispatchers; | |||
private final List<String> projectDispatchers; | |||
public RemoveAction(NotificationCenter notificationCenter, NotificationUpdater notificationUpdater, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { | |||
this.notificationCenter = notificationCenter; | |||
this.notificationUpdater = notificationUpdater; | |||
this.dbClient = dbClient; | |||
this.componentFinder = componentFinder; | |||
this.userSession = userSession; | |||
this.globalDispatchers = notificationCenter.getDispatcherKeysForProperty(GLOBAL_NOTIFICATION, "true"); | |||
this.projectDispatchers = notificationCenter.getDispatcherKeysForProperty(PER_PROJECT_NOTIFICATION, "true"); | |||
} | |||
@Override | |||
public void define(WebService.NewController context) { | |||
WebService.NewAction action = context.createAction(ACTION_ADD) | |||
.setDescription("Remove a notification for the authenticated user.<br>" + | |||
"Requires authentication. If a project is provided, requires the 'Browse' permission on the specified project.") | |||
.setSince("6.3") | |||
.setPost(true) | |||
.setHandler(this); | |||
action.createParam(PARAM_PROJECT) | |||
.setDescription("Project key") | |||
.setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001); | |||
List<NotificationChannel> channels = notificationCenter.getChannels(); | |||
action.createParam(PARAM_CHANNEL) | |||
.setDescription("Channel through which the notification is sent. For example, notifications can be sent by email.") | |||
.setPossibleValues(channels) | |||
.setDefaultValue(EmailNotificationChannel.class.getSimpleName()); | |||
action.createParam(PARAM_NOTIFICATION) | |||
.setDescription("Notification. Possible values are for:" + | |||
"<ul>" + | |||
" <li>Overall notifications: %s</li>" + | |||
" <li>Per project notifications: %s</li>" + | |||
"</ul>", | |||
globalDispatchers.stream().sorted().collect(Collectors.joining(", ")), | |||
projectDispatchers.stream().sorted().collect(Collectors.joining(", "))) | |||
.setRequired(true) | |||
.setExampleValue(MyNewIssuesNotificationDispatcher.KEY); | |||
} | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
Stream.of(request) | |||
.map(toWsRequest()) | |||
.peek(checkPermissions()) | |||
.forEach(remove()); | |||
response.noContent(); | |||
} | |||
private Consumer<AddRequest> remove() { | |||
return request -> { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
Optional<ComponentDto> project = searchProject(dbSession, request); | |||
notificationUpdater.remove(dbSession, request.getChannel(), request.getNotification(), project.orElse(null)); | |||
dbSession.commit(); | |||
} | |||
}; | |||
} | |||
private Optional<ComponentDto> searchProject(DbSession dbSession, AddRequest request) { | |||
Optional<ComponentDto> project = request.getProject() == null ? empty() : Optional.of(componentFinder.getByKey(dbSession, request.getProject())); | |||
project.ifPresent(p -> checkRequest(Qualifiers.PROJECT.equals(p.qualifier()) && Scopes.PROJECT.equals(p.scope()), | |||
"Component '%s' must be a project", request.getProject())); | |||
return project; | |||
} | |||
private Consumer<AddRequest> checkPermissions() { | |||
return request -> userSession.checkLoggedIn(); | |||
} | |||
private Function<Request, AddRequest> toWsRequest() { | |||
return request -> { | |||
AddRequest.Builder requestBuilder = AddRequest.builder() | |||
.setNotification(request.mandatoryParam(PARAM_NOTIFICATION)) | |||
.setChannel(request.mandatoryParam(PARAM_CHANNEL)); | |||
String project = request.param(PARAM_PROJECT); | |||
setNullable(project, requestBuilder::setProject); | |||
AddRequest wsRequest = requestBuilder.build(); | |||
if (wsRequest.getProject() == null) { | |||
checkRequest(globalDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s", | |||
PARAM_NOTIFICATION, | |||
wsRequest.getNotification(), | |||
globalDispatchers); | |||
} else { | |||
checkRequest(projectDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s", | |||
PARAM_NOTIFICATION, | |||
wsRequest.getNotification(), | |||
projectDispatchers); | |||
} | |||
return wsRequest; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.server.notification.ws; | |||
import javax.annotation.ParametersAreNonnullByDefault; | |||
@@ -20,7 +20,6 @@ | |||
package org.sonar.server.platform.platformlevel; | |||
import java.util.List; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.profiles.AnnotationProfileParser; | |||
import org.sonar.api.profiles.XMLProfileParser; | |||
import org.sonar.api.profiles.XMLProfileSerializer; | |||
@@ -52,7 +51,6 @@ import org.sonar.server.debt.DebtRulesXMLImporter; | |||
import org.sonar.server.duplication.ws.DuplicationsJsonWriter; | |||
import org.sonar.server.duplication.ws.DuplicationsParser; | |||
import org.sonar.server.duplication.ws.DuplicationsWs; | |||
import org.sonar.server.email.ws.EmailsWsModule; | |||
import org.sonar.server.es.IndexCreator; | |||
import org.sonar.server.es.IndexDefinitions; | |||
import org.sonar.server.event.NewAlerts; | |||
@@ -85,12 +83,7 @@ import org.sonar.server.measure.ws.TimeMachineWs; | |||
import org.sonar.server.metric.CoreCustomMetrics; | |||
import org.sonar.server.metric.DefaultMetricFinder; | |||
import org.sonar.server.metric.ws.MetricsWsModule; | |||
import org.sonar.server.notification.DefaultNotificationManager; | |||
import org.sonar.server.notification.NotificationCenter; | |||
import org.sonar.server.notification.NotificationDaemon; | |||
import org.sonar.server.notification.NotificationService; | |||
import org.sonar.server.notification.email.AlertsEmailTemplate; | |||
import org.sonar.server.notification.email.EmailNotificationChannel; | |||
import org.sonar.server.notification.NotificationModule; | |||
import org.sonar.server.organization.ws.OrganizationsWsModule; | |||
import org.sonar.server.permission.GroupPermissionChanger; | |||
import org.sonar.server.permission.PermissionTemplateService; | |||
@@ -396,8 +389,6 @@ public class PlatformLevel4 extends PlatformLevel { | |||
DoNotFixNotificationDispatcher.class, | |||
DoNotFixNotificationDispatcher.newMetadata(), | |||
NewIssuesNotificationFactory.class, | |||
EmailNotificationChannel.class, | |||
AlertsEmailTemplate.class, | |||
// issues actions | |||
AssignAction.class, | |||
@@ -437,12 +428,7 @@ public class PlatformLevel4 extends PlatformLevel { | |||
RubyTextService.class, | |||
// Notifications | |||
EmailSettings.class, | |||
NotificationService.class, | |||
NotificationCenter.class, | |||
DefaultNotificationManager.class, | |||
EmailsWsModule.class, | |||
NotificationDaemon.class, | |||
NotificationModule.class, | |||
// Tests | |||
TestsWs.class, |
@@ -21,31 +21,25 @@ package org.sonar.server.notification; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.mockito.Mock; | |||
import org.mockito.MockitoAnnotations; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
public class NotificationCenterTest { | |||
@Mock | |||
private NotificationChannel emailChannel; | |||
private NotificationChannel emailChannel = mock(NotificationChannel.class); | |||
private NotificationChannel gtalkChannel = mock(NotificationChannel.class); | |||
@Mock | |||
private NotificationChannel gtalkChannel; | |||
private NotificationCenter notificationCenter; | |||
private NotificationCenter underTest; | |||
@Before | |||
public void init() { | |||
MockitoAnnotations.initMocks(this); | |||
NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true").setProperty("on-project", "true"); | |||
NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create("Dispatcher2").setProperty("global", "true"); | |||
NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create("Dispatcher3").setProperty("global", "FOO").setProperty("on-project", "BAR"); | |||
notificationCenter = new NotificationCenter( | |||
underTest = new NotificationCenter( | |||
new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3}, | |||
new NotificationChannel[] {emailChannel, gtalkChannel} | |||
); | |||
@@ -53,30 +47,30 @@ public class NotificationCenterTest { | |||
@Test | |||
public void shouldReturnChannels() { | |||
assertThat(notificationCenter.getChannels()).containsOnly(emailChannel, gtalkChannel); | |||
assertThat(underTest.getChannels()).containsOnly(emailChannel, gtalkChannel); | |||
} | |||
@Test | |||
public void shouldReturnDispatcherKeysForSpecificPropertyValue() { | |||
assertThat(notificationCenter.getDispatcherKeysForProperty("global", "true")).containsOnly("Dispatcher1", "Dispatcher2"); | |||
assertThat(underTest.getDispatcherKeysForProperty("global", "true")).containsOnly("Dispatcher1", "Dispatcher2"); | |||
} | |||
@Test | |||
public void shouldReturnDispatcherKeysForExistenceOfProperty() { | |||
assertThat(notificationCenter.getDispatcherKeysForProperty("on-project", null)).containsOnly("Dispatcher1", "Dispatcher3"); | |||
assertThat(underTest.getDispatcherKeysForProperty("on-project", null)).containsOnly("Dispatcher1", "Dispatcher3"); | |||
} | |||
@Test | |||
public void testDefaultConstructors() { | |||
notificationCenter = new NotificationCenter(new NotificationChannel[] {emailChannel}); | |||
assertThat(notificationCenter.getChannels()).hasSize(1); | |||
underTest = new NotificationCenter(new NotificationChannel[] {emailChannel}); | |||
assertThat(underTest.getChannels()).hasSize(1); | |||
notificationCenter = new NotificationCenter(); | |||
assertThat(notificationCenter.getChannels()).hasSize(0); | |||
underTest = new NotificationCenter(); | |||
assertThat(underTest.getChannels()).hasSize(0); | |||
notificationCenter = new NotificationCenter(new NotificationDispatcherMetadata[] {NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true")}); | |||
assertThat(notificationCenter.getChannels()).hasSize(0); | |||
assertThat(notificationCenter.getDispatcherKeysForProperty("global", null)).hasSize(1); | |||
underTest = new NotificationCenter(new NotificationDispatcherMetadata[] {NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true")}); | |||
assertThat(underTest.getChannels()).hasSize(0); | |||
assertThat(underTest.getDispatcherKeysForProperty("global", null)).hasSize(1); | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification; | |||
import org.junit.Test; | |||
import org.sonar.core.platform.ComponentContainer; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class NotificationModuleTest { | |||
@Test | |||
public void verify_count_of_added_components() { | |||
ComponentContainer container = new ComponentContainer(); | |||
new NotificationModule().configure(container); | |||
assertThat(container.size()).isEqualTo(11 + 2); | |||
} | |||
} |
@@ -0,0 +1,223 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification.ws; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.exceptions.UnauthorizedException; | |||
import org.sonar.server.notification.NotificationCenter; | |||
import org.sonar.server.notification.NotificationDispatcherMetadata; | |||
import org.sonar.server.notification.NotificationUpdater; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.TestResponse; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.client.notification.AddRequest; | |||
import static java.net.HttpURLConnection.HTTP_NO_CONTENT; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.core.util.Protobuf.setNullable; | |||
import static org.sonar.db.component.ComponentTesting.newView; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT; | |||
public class AddActionTest { | |||
private static final String NOTIF_MY_NEW_ISSUES = "Dispatcher1"; | |||
private static final String NOTIF_NEW_ISSUES = "Dispatcher2"; | |||
private static final String NOTIF_NEW_QUALITY_GATE_STATUS = "Dispatcher3"; | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone().login().setUserId(123); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private DbClient dbClient = db.getDbClient(); | |||
private DbSession dbSession = db.getSession(); | |||
private NotificationChannel emailChannel = new FakeNotificationChannel("EmailChannel"); | |||
private NotificationChannel twitterChannel = new FakeNotificationChannel("TwitterChannel"); | |||
// default channel, based on class simple name | |||
private NotificationChannel defaultChannel = new FakeNotificationChannel("EmailNotificationChannel"); | |||
private NotificationCenter notificationCenter; | |||
private AddAction underTest; | |||
private WsActionTester ws; | |||
private AddRequest.Builder request = AddRequest.builder() | |||
.setNotification(NOTIF_MY_NEW_ISSUES); | |||
@Before | |||
public void setUp() { | |||
NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create(NOTIF_MY_NEW_ISSUES) | |||
.setProperty(GLOBAL_NOTIFICATION, "true") | |||
.setProperty(PER_PROJECT_NOTIFICATION, "true"); | |||
NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create(NOTIF_NEW_ISSUES) | |||
.setProperty(GLOBAL_NOTIFICATION, "true"); | |||
NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create(NOTIF_NEW_QUALITY_GATE_STATUS) | |||
.setProperty(GLOBAL_NOTIFICATION, "true") | |||
.setProperty(PER_PROJECT_NOTIFICATION, "true"); | |||
notificationCenter = new NotificationCenter( | |||
new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3}, | |||
new NotificationChannel[] {emailChannel, twitterChannel, defaultChannel}); | |||
underTest = new AddAction(notificationCenter, new NotificationUpdater(userSession, dbClient), dbClient, new ComponentFinder(dbClient), userSession); | |||
ws = new WsActionTester(underTest); | |||
} | |||
@Test | |||
public void add_to_email_channel_by_default() { | |||
call(request); | |||
db.notifications().assertExists(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), null); | |||
} | |||
@Test | |||
public void add_to_a_specific_channel() { | |||
call(request.setNotification(NOTIF_NEW_QUALITY_GATE_STATUS).setChannel(twitterChannel.getKey())); | |||
db.notifications().assertExists(twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, userSession.getUserId(), null); | |||
} | |||
@Test | |||
public void add_a_project_notification() { | |||
ComponentDto project = db.components().insertProject(); | |||
call(request.setProject(project.getKey())); | |||
db.notifications().assertExists(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), project); | |||
} | |||
@Test | |||
public void http_no_content() { | |||
TestResponse result = call(request); | |||
assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT); | |||
} | |||
@Test | |||
public void fail_when_notification_already_exists() { | |||
call(request); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Notification already added"); | |||
call(request); | |||
} | |||
@Test | |||
public void fail_when_unknown_channel() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
call(request.setChannel("Channel42")); | |||
} | |||
@Test | |||
public void fail_when_unknown_global_dispatcher() { | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher2, Dispatcher3]"); | |||
call(request.setNotification("Dispatcher42")); | |||
} | |||
@Test | |||
public void fail_when_unknown_project_dispatcher() { | |||
ComponentDto project = db.components().insertProject(); | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher3]"); | |||
call(request.setNotification("Dispatcher42").setProject(project.key())); | |||
} | |||
@Test | |||
public void fail_when_no_dispatcher() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
ws.newRequest().execute(); | |||
} | |||
@Test | |||
public void fail_when_project_is_unknown() { | |||
expectedException.expect(NotFoundException.class); | |||
call(request.setProject("Project-42")); | |||
} | |||
@Test | |||
public void fail_when_component_is_not_a_project() { | |||
db.components().insertViewAndSnapshot(newView().setKey("VIEW_1")); | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Component 'VIEW_1' must be a project"); | |||
call(request.setProject("VIEW_1")); | |||
} | |||
@Test | |||
public void fail_when_not_authenticated() { | |||
userSession.anonymous(); | |||
expectedException.expect(UnauthorizedException.class); | |||
call(request); | |||
} | |||
private TestResponse call(AddRequest.Builder wsRequestBuilder) { | |||
AddRequest wsRequest = wsRequestBuilder.build(); | |||
TestRequest request = ws.newRequest(); | |||
request.setParam(PARAM_NOTIFICATION, wsRequest.getNotification()); | |||
setNullable(wsRequest.getChannel(), channel -> request.setParam(PARAM_CHANNEL, channel)); | |||
setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project)); | |||
return request.execute(); | |||
} | |||
private static class FakeNotificationChannel extends NotificationChannel { | |||
private final String key; | |||
private FakeNotificationChannel(String key) { | |||
this.key = key; | |||
} | |||
@Override | |||
public String getKey() { | |||
return this.key; | |||
} | |||
@Override | |||
public void deliver(Notification notification, String username) { | |||
// do nothing | |||
} | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification.ws; | |||
import org.junit.Test; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.server.ws.WebService.Controller; | |||
import org.sonar.server.ws.WsTester; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class NotificationsWsTest { | |||
private NotificationsWsAction action = new FakeNotificationAction(); | |||
private NotificationsWsAction[] actions = {action}; | |||
private WsTester ws = new WsTester(new NotificationsWs(actions)); | |||
private Controller underTest = ws.controller("api/notifications"); | |||
@Test | |||
public void definition() { | |||
assertThat(underTest.path()).isEqualTo("api/notifications"); | |||
} | |||
private static class FakeNotificationAction implements NotificationsWsAction { | |||
@Override | |||
public void define(WebService.NewController context) { | |||
context.createAction("fake") | |||
.setHandler(this); | |||
} | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
// do nothing | |||
} | |||
} | |||
} |
@@ -0,0 +1,234 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.notification.ws; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.exceptions.UnauthorizedException; | |||
import org.sonar.server.notification.NotificationCenter; | |||
import org.sonar.server.notification.NotificationDispatcherMetadata; | |||
import org.sonar.server.notification.NotificationUpdater; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.TestResponse; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.client.notification.AddRequest; | |||
import static java.net.HttpURLConnection.HTTP_NO_CONTENT; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.core.util.Protobuf.setNullable; | |||
import static org.sonar.db.component.ComponentTesting.newView; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION; | |||
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION; | |||
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT; | |||
public class RemoveActionTest { | |||
private static final String NOTIF_MY_NEW_ISSUES = "Dispatcher1"; | |||
private static final String NOTIF_NEW_ISSUES = "Dispatcher2"; | |||
private static final String NOTIF_NEW_QUALITY_GATE_STATUS = "Dispatcher3"; | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone().login().setUserId(123); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private DbClient dbClient = db.getDbClient(); | |||
private DbSession dbSession = db.getSession(); | |||
private NotificationChannel emailChannel = new FakeNotificationChannel("EmailChannel"); | |||
private NotificationChannel twitterChannel = new FakeNotificationChannel("TwitterChannel"); | |||
// default channel, based on class simple name | |||
private NotificationChannel defaultChannel = new FakeNotificationChannel("EmailNotificationChannel"); | |||
private NotificationCenter notificationCenter; | |||
private NotificationUpdater notificationUpdater; | |||
private RemoveAction underTest; | |||
private WsActionTester ws; | |||
private AddRequest.Builder request = AddRequest.builder() | |||
.setNotification(NOTIF_MY_NEW_ISSUES); | |||
@Before | |||
public void setUp() { | |||
NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create(NOTIF_MY_NEW_ISSUES) | |||
.setProperty(GLOBAL_NOTIFICATION, "true") | |||
.setProperty(PER_PROJECT_NOTIFICATION, "true"); | |||
NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create(NOTIF_NEW_ISSUES) | |||
.setProperty(GLOBAL_NOTIFICATION, "true"); | |||
NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create(NOTIF_NEW_QUALITY_GATE_STATUS) | |||
.setProperty(GLOBAL_NOTIFICATION, "true") | |||
.setProperty(PER_PROJECT_NOTIFICATION, "true"); | |||
notificationCenter = new NotificationCenter( | |||
new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3}, | |||
new NotificationChannel[] {emailChannel, twitterChannel, defaultChannel}); | |||
notificationUpdater = new NotificationUpdater(userSession, dbClient); | |||
underTest = new RemoveAction(notificationCenter, notificationUpdater, dbClient, new ComponentFinder(dbClient), userSession); | |||
ws = new WsActionTester(underTest); | |||
} | |||
@Test | |||
public void remove_to_email_channel_by_default() { | |||
notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, null); | |||
dbSession.commit(); | |||
call(request); | |||
db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), null); | |||
} | |||
@Test | |||
public void remove_from_a_specific_channel() { | |||
notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, null); | |||
dbSession.commit(); | |||
call(request.setNotification(NOTIF_NEW_QUALITY_GATE_STATUS).setChannel(twitterChannel.getKey())); | |||
db.notifications().assertDoesNotExist(twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, userSession.getUserId(), null); | |||
} | |||
@Test | |||
public void remove_a_project_notification() { | |||
ComponentDto project = db.components().insertProject(); | |||
notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, project); | |||
dbSession.commit(); | |||
call(request.setProject(project.getKey())); | |||
db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), project); | |||
} | |||
@Test | |||
public void http_no_content() { | |||
notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, null); | |||
dbSession.commit(); | |||
TestResponse result = call(request); | |||
assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT); | |||
} | |||
@Test | |||
public void fail_when_notification_does_not_exist() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Notification doesn't exist"); | |||
call(request); | |||
} | |||
@Test | |||
public void fail_when_unknown_channel() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
call(request.setChannel("Channel42")); | |||
} | |||
@Test | |||
public void fail_when_unknown_global_dispatcher() { | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher2, Dispatcher3]"); | |||
call(request.setNotification("Dispatcher42")); | |||
} | |||
@Test | |||
public void fail_when_unknown_project_dispatcher() { | |||
ComponentDto project = db.components().insertProject(); | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher3]"); | |||
call(request.setNotification("Dispatcher42").setProject(project.key())); | |||
} | |||
@Test | |||
public void fail_when_no_dispatcher() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
ws.newRequest().execute(); | |||
} | |||
@Test | |||
public void fail_when_project_is_unknown() { | |||
expectedException.expect(NotFoundException.class); | |||
call(request.setProject("Project-42")); | |||
} | |||
@Test | |||
public void fail_when_component_is_not_a_project() { | |||
db.components().insertViewAndSnapshot(newView().setKey("VIEW_1")); | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Component 'VIEW_1' must be a project"); | |||
call(request.setProject("VIEW_1")); | |||
} | |||
@Test | |||
public void fail_when_not_authenticated() { | |||
userSession.anonymous(); | |||
expectedException.expect(UnauthorizedException.class); | |||
call(request); | |||
} | |||
private TestResponse call(AddRequest.Builder wsRequestBuilder) { | |||
AddRequest wsRequest = wsRequestBuilder.build(); | |||
TestRequest request = ws.newRequest(); | |||
request.setParam(PARAM_NOTIFICATION, wsRequest.getNotification()); | |||
setNullable(wsRequest.getChannel(), channel -> request.setParam(PARAM_CHANNEL, channel)); | |||
setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project)); | |||
return request.execute(); | |||
} | |||
private static class FakeNotificationChannel extends NotificationChannel { | |||
private final String key; | |||
private FakeNotificationChannel(String key) { | |||
this.key = key; | |||
} | |||
@Override | |||
public String getKey() { | |||
return this.key; | |||
} | |||
@Override | |||
public void deliver(Notification notification, String username) { | |||
// do nothing | |||
} | |||
} | |||
} |
@@ -19,6 +19,8 @@ | |||
*/ | |||
package org.sonar.db.property; | |||
import javax.annotation.Nullable; | |||
public class PropertyQuery { | |||
private final String key; | |||
@@ -57,7 +59,7 @@ public class PropertyQuery { | |||
return this; | |||
} | |||
public Builder setComponentId(Long componentId) { | |||
public Builder setComponentId(@Nullable Long componentId) { | |||
this.componentId = componentId; | |||
return this; | |||
} |
@@ -67,6 +67,7 @@ import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.event.EventDbTester; | |||
import org.sonar.db.favorite.FavoriteDbTester; | |||
import org.sonar.db.issue.IssueDbTester; | |||
import org.sonar.db.notification.NotificationDbTester; | |||
import org.sonar.db.organization.OrganizationDbTester; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.organization.OrganizationTesting; | |||
@@ -110,6 +111,7 @@ public class DbTester extends ExternalResource { | |||
private final QualityGateDbTester qualityGateDbTester; | |||
private final IssueDbTester issueDbTester; | |||
private final RuleDbTester ruleDbTester; | |||
private final NotificationDbTester notificationDbTester; | |||
private final RootFlagAssertions rootFlagAssertions; | |||
private DbTester(System2 system2, @Nullable String schemaPath) { | |||
@@ -125,6 +127,7 @@ public class DbTester extends ExternalResource { | |||
this.qualityGateDbTester = new QualityGateDbTester(this); | |||
this.issueDbTester = new IssueDbTester(this); | |||
this.ruleDbTester = new RuleDbTester(this); | |||
this.notificationDbTester = new NotificationDbTester(this); | |||
this.rootFlagAssertions = new RootFlagAssertions(this); | |||
} | |||
@@ -225,6 +228,10 @@ public class DbTester extends ExternalResource { | |||
return ruleDbTester; | |||
} | |||
public NotificationDbTester notifications() { | |||
return notificationDbTester; | |||
} | |||
@Override | |||
protected void after() { | |||
if (session != null) { |
@@ -0,0 +1,65 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.db.notification; | |||
import java.util.List; | |||
import javax.annotation.Nullable; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.property.PropertyDto; | |||
import org.sonar.db.property.PropertyQuery; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class NotificationDbTester { | |||
private static final String PROP_NOTIFICATION_PREFIX = "notification"; | |||
private final DbTester db; | |||
private final DbClient dbClient; | |||
private final DbSession dbSession; | |||
public NotificationDbTester(DbTester db) { | |||
this.db = db; | |||
this.dbClient = db.getDbClient(); | |||
this.dbSession = db.getSession(); | |||
} | |||
public void assertExists(String channel, String dispatcher, long userId, @Nullable ComponentDto component) { | |||
List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder() | |||
.setKey(String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel)) | |||
.setComponentId(component == null ? null : component.getId()) | |||
.setUserId((int) userId) | |||
.build(), dbSession); | |||
assertThat(result).hasSize(1); | |||
assertThat(result.get(0).getValue()).isEqualTo("true"); | |||
} | |||
public void assertDoesNotExist(String channel, String dispatcher, long userId, @Nullable ComponentDto component) { | |||
List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder() | |||
.setKey(String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel)) | |||
.setComponentId(component == null ? null : component.getId()) | |||
.setUserId((int) userId) | |||
.build(), dbSession); | |||
assertThat(result).isEmpty(); | |||
} | |||
} |
@@ -0,0 +1,86 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.sonarqube.ws.client.notification; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import static java.util.Objects.requireNonNull; | |||
public class AddRequest { | |||
private final String notification; | |||
private final String channel; | |||
private final String project; | |||
private AddRequest(Builder builder) { | |||
this.channel = builder.channel; | |||
this.notification = builder.notification; | |||
this.project = builder.project; | |||
} | |||
public String getNotification() { | |||
return notification; | |||
} | |||
@CheckForNull | |||
public String getChannel() { | |||
return channel; | |||
} | |||
@CheckForNull | |||
public String getProject() { | |||
return project; | |||
} | |||
public static Builder builder() { | |||
return new Builder(); | |||
} | |||
public static class Builder { | |||
private String notification; | |||
private String channel; | |||
private String project; | |||
private Builder() { | |||
// enforce factory method | |||
} | |||
public Builder setNotification(String notification) { | |||
this.notification = notification; | |||
return this; | |||
} | |||
public Builder setChannel(@Nullable String channel) { | |||
this.channel = channel; | |||
return this; | |||
} | |||
public Builder setProject(@Nullable String project) { | |||
this.project = project; | |||
return this; | |||
} | |||
public AddRequest build() { | |||
requireNonNull(notification, "Notification is required"); | |||
return new AddRequest(this); | |||
} | |||
} | |||
} |
@@ -0,0 +1,34 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.sonarqube.ws.client.notification; | |||
public class NotificationsWsParameters { | |||
public static final String CONTROLLER = "api/notifications"; | |||
public static final String ACTION_ADD = "add"; | |||
public static final String PARAM_PROJECT = "project"; | |||
public static final String PARAM_CHANNEL = "channel"; | |||
public static final String PARAM_NOTIFICATION = "notification"; | |||
private NotificationsWsParameters() { | |||
// prevent instantiation | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonarqube.ws.client.notification; | |||
import javax.annotation.ParametersAreNonnullByDefault; | |||