Move project watchers functionality to a new class ProjectWatch

Functionality to get project watchers is moved out of ChangeEmail to
its own class, so it can be reused in other email classes.

This also reduces the amount of code in ChangeEmail.

Change-Id: Ia6f5017eec94cf90d45ffc5a8a73f41d20c0d293
This commit is contained in:
Yang Zhenhui
2013-01-14 11:04:04 +08:00
committed by David Pursehouse
parent e33ddceb1f
commit caa9a699cf
3 changed files with 222 additions and 168 deletions

View File

@@ -14,17 +14,9 @@
package com.google.gerrit.server.mail;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -32,21 +24,15 @@ import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.StarredChange;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.mail.ProjectWatch.Watchers;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.SingleGroupUser;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.diff.DiffFormatter;
@@ -62,7 +48,6 @@ import java.text.MessageFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
@@ -333,7 +318,7 @@ public abstract class ChangeEmail extends NotificationEmail {
/** Include users and groups that want notification of events. */
protected void includeWatchers(NotifyType type) {
try {
Watchers matching = getWatches(type);
Watchers matching = getWatchers(type);
add(RecipientType.TO, matching.to);
add(RecipientType.CC, matching.cc);
add(RecipientType.BCC, matching.bcc);
@@ -355,156 +340,10 @@ public abstract class ChangeEmail extends NotificationEmail {
}
}
/** Returns all watches that are relevant */
protected final Watchers getWatches(NotifyType type) throws OrmException {
Watchers matching = new Watchers();
Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(change.getProject())) {
if (w.isNotify(type)) {
projectWatchers.add(w.getAccountId());
add(matching, w);
}
}
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(args.allProjectsName)) {
if (!projectWatchers.contains(w.getAccountId()) && w.isNotify(type)) {
add(matching, w);
}
}
for (ProjectState state : projectState.tree()) {
for (NotifyConfig nc : state.getConfig().getNotifyConfigs()) {
if (nc.isNotify(type)) {
try {
add(matching, nc, state.getProject().getNameKey());
} catch (QueryParseException e) {
log.warn(String.format(
"Project %s has invalid notify %s filter \"%s\": %s",
state.getProject().getName(), nc.getName(),
nc.getFilter(), e.getMessage()));
}
}
}
}
return matching;
}
protected static class Watchers {
static class List {
protected final Set<Account.Id> accounts = Sets.newHashSet();
protected final Set<Address> emails = Sets.newHashSet();
}
protected final List to = new List();
protected final List cc = new List();
protected final List bcc = new List();
List list(NotifyConfig.Header header) {
switch (header) {
case TO:
return to;
case CC:
return cc;
default:
case BCC:
return bcc;
}
}
}
@SuppressWarnings("unchecked")
private void add(Watchers matching, NotifyConfig nc, Project.NameKey project)
throws OrmException, QueryParseException {
for (GroupReference ref : nc.getGroups()) {
ChangeQueryBuilder qb = args.queryBuilder.create(new SingleGroupUser(
args.capabilityControlFactory,
ref.getUUID()));
qb.setAllowFile(true);
Predicate<ChangeData> p = qb.is_visible();
if (nc.getFilter() != null) {
p = Predicate.and(qb.parse(nc.getFilter()), p);
p = args.queryRewriter.get().rewrite(p);
}
if (p.match(changeData)) {
deliverToMembers(matching.list(nc.getHeader()), ref.getUUID());
}
}
if (!nc.getAddresses().isEmpty()) {
if (nc.getFilter() != null) {
ChangeQueryBuilder qb = args.queryBuilder.create(args.anonymousUser);
qb.setAllowFile(true);
Predicate<ChangeData> p = qb.parse(nc.getFilter());
p = args.queryRewriter.get().rewrite(p);
if (p.match(changeData)) {
matching.list(nc.getHeader()).emails.addAll(nc.getAddresses());
}
} else {
matching.list(nc.getHeader()).emails.addAll(nc.getAddresses());
}
}
}
private void deliverToMembers(
Watchers.List matching,
AccountGroup.UUID startUUID) throws OrmException {
ReviewDb db = args.db.get();
Set<AccountGroup.UUID> seen = Sets.newHashSet();
List<AccountGroup.UUID> q = Lists.newArrayList();
seen.add(startUUID);
q.add(startUUID);
while (!q.isEmpty()) {
AccountGroup.UUID uuid = q.remove(q.size() - 1);
GroupDescription.Basic group = args.groupBackend.get(uuid);
if (!Strings.isNullOrEmpty(group.getEmailAddress())) {
// If the group has an email address, do not expand membership.
matching.emails.add(new Address(group.getEmailAddress()));
continue;
}
AccountGroup ig = GroupDescriptions.toAccountGroup(group);
if (ig == null) {
// Non-internal groups cannot be expanded by the server.
continue;
}
for (AccountGroupMember m : db.accountGroupMembers().byGroup(ig.getId())) {
matching.accounts.add(m.getAccountId());
}
for (AccountGroup.UUID m : args.groupIncludes.membersOf(uuid)) {
if (seen.add(m)) {
q.add(m);
}
}
}
}
@SuppressWarnings("unchecked")
private void add(Watchers matching, AccountProjectWatch w)
throws OrmException {
IdentifiedUser user =
args.identifiedUserFactory.create(args.db, w.getAccountId());
ChangeQueryBuilder qb = args.queryBuilder.create(user);
Predicate<ChangeData> p = qb.is_visible();
if (w.getFilter() != null) {
try {
qb.setAllowFile(true);
p = Predicate.and(qb.parse(w.getFilter()), p);
p = args.queryRewriter.get().rewrite(p);
if (p.match(changeData)) {
matching.bcc.accounts.add(w.getAccountId());
}
} catch (QueryParseException e) {
// Ignore broken filter expressions.
}
} else if (p.match(changeData)) {
matching.bcc.accounts.add(w.getAccountId());
}
/** Returns all watchers that are relevant */
protected final Watchers getWatchers(NotifyType type) throws OrmException {
ProjectWatch watch = new ProjectWatch(args, project, projectState, changeData);
return watch.getWatchers(type);
}
/** Any user who has published comments on this change. */

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.mail.ProjectWatch.Watchers;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -50,7 +51,7 @@ public class CreateChangeSender extends NewChangeSender {
try {
// Try to mark interested owners with TO and CC or BCC line.
Watchers matching = getWatches(NotifyType.NEW_CHANGES);
Watchers matching = getWatchers(NotifyType.NEW_CHANGES);
for (Account.Id user : Iterables.concat(
matching.to.accounts,
matching.cc.accounts,

View File

@@ -0,0 +1,214 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.mail;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.SingleGroupUser;
import com.google.gwtorm.server.OrmException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ProjectWatch {
private static final Logger log = LoggerFactory.getLogger(ProjectWatch.class);
protected final EmailArguments args;
protected final ProjectState projectState;
protected final Project.NameKey project;
protected final ChangeData changeData;
public ProjectWatch(EmailArguments args, Project.NameKey project,
ProjectState projectState, ChangeData changeData) {
this.args = args;
this.project = project;
this.projectState = projectState;
this.changeData = changeData;
}
/** Returns all watchers that are relevant */
public final Watchers getWatchers(NotifyType type) throws OrmException {
Watchers matching = new Watchers();
Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(project)) {
if (w.isNotify(type)) {
projectWatchers.add(w.getAccountId());
add(matching, w);
}
}
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(args.allProjectsName)) {
if (!projectWatchers.contains(w.getAccountId()) && w.isNotify(type)) {
add(matching, w);
}
}
for (ProjectState state : projectState.tree()) {
for (NotifyConfig nc : state.getConfig().getNotifyConfigs()) {
if (nc.isNotify(type)) {
try {
add(matching, nc, state.getProject().getNameKey());
} catch (QueryParseException e) {
log.warn(String.format(
"Project %s has invalid notify %s filter \"%s\": %s",
state.getProject().getName(), nc.getName(),
nc.getFilter(), e.getMessage()));
}
}
}
}
return matching;
}
public static class Watchers {
static class List {
protected final Set<Account.Id> accounts = Sets.newHashSet();
protected final Set<Address> emails = Sets.newHashSet();
}
protected final List to = new List();
protected final List cc = new List();
protected final List bcc = new List();
List list(NotifyConfig.Header header) {
switch (header) {
case TO:
return to;
case CC:
return cc;
default:
case BCC:
return bcc;
}
}
}
@SuppressWarnings("unchecked")
private void add(Watchers matching, NotifyConfig nc, Project.NameKey project)
throws OrmException, QueryParseException {
for (GroupReference ref : nc.getGroups()) {
ChangeQueryBuilder qb = args.queryBuilder.create(new SingleGroupUser(
args.capabilityControlFactory,
ref.getUUID()));
qb.setAllowFile(true);
Predicate<ChangeData> p = qb.is_visible();
if (nc.getFilter() != null) {
p = Predicate.and(qb.parse(nc.getFilter()), p);
p = args.queryRewriter.get().rewrite(p);
}
if (p.match(changeData)) {
deliverToMembers(matching.list(nc.getHeader()), ref.getUUID());
}
}
if (!nc.getAddresses().isEmpty()) {
if (nc.getFilter() != null) {
ChangeQueryBuilder qb = args.queryBuilder.create(args.anonymousUser);
qb.setAllowFile(true);
Predicate<ChangeData> p = qb.parse(nc.getFilter());
p = args.queryRewriter.get().rewrite(p);
if (p.match(changeData)) {
matching.list(nc.getHeader()).emails.addAll(nc.getAddresses());
}
} else {
matching.list(nc.getHeader()).emails.addAll(nc.getAddresses());
}
}
}
private void deliverToMembers(
Watchers.List matching,
AccountGroup.UUID startUUID) throws OrmException {
ReviewDb db = args.db.get();
Set<AccountGroup.UUID> seen = Sets.newHashSet();
List<AccountGroup.UUID> q = Lists.newArrayList();
seen.add(startUUID);
q.add(startUUID);
while (!q.isEmpty()) {
AccountGroup.UUID uuid = q.remove(q.size() - 1);
GroupDescription.Basic group = args.groupBackend.get(uuid);
if (!Strings.isNullOrEmpty(group.getEmailAddress())) {
// If the group has an email address, do not expand membership.
matching.emails.add(new Address(group.getEmailAddress()));
continue;
}
AccountGroup ig = GroupDescriptions.toAccountGroup(group);
if (ig == null) {
// Non-internal groups cannot be expanded by the server.
continue;
}
for (AccountGroupMember m : db.accountGroupMembers().byGroup(ig.getId())) {
matching.accounts.add(m.getAccountId());
}
for (AccountGroup.UUID m : args.groupIncludes.membersOf(uuid)) {
if (seen.add(m)) {
q.add(m);
}
}
}
}
@SuppressWarnings("unchecked")
private void add(Watchers matching, AccountProjectWatch w)
throws OrmException {
IdentifiedUser user =
args.identifiedUserFactory.create(args.db, w.getAccountId());
ChangeQueryBuilder qb = args.queryBuilder.create(user);
Predicate<ChangeData> p = qb.is_visible();
if (w.getFilter() != null) {
try {
qb.setAllowFile(true);
p = Predicate.and(qb.parse(w.getFilter()), p);
p = args.queryRewriter.get().rewrite(p);
if (p.match(changeData)) {
matching.bcc.accounts.add(w.getAccountId());
}
} catch (QueryParseException e) {
// Ignore broken filter expressions.
}
} else if (p.match(changeData)) {
matching.bcc.accounts.add(w.getAccountId());
}
}
}