Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.mail; 16 : 17 : import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; 18 : import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; 19 : 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.gerrit.common.FooterConstants; 22 : import com.google.gerrit.entities.Account; 23 : import com.google.gerrit.extensions.restapi.UnprocessableEntityException; 24 : import com.google.gerrit.server.ReviewerSet; 25 : import com.google.gerrit.server.account.AccountResolver; 26 : import java.io.IOException; 27 : import java.util.Collections; 28 : import java.util.HashSet; 29 : import java.util.List; 30 : import java.util.Set; 31 : import java.util.regex.Pattern; 32 : import org.eclipse.jgit.errors.ConfigInvalidException; 33 : import org.eclipse.jgit.revwalk.FooterKey; 34 : import org.eclipse.jgit.revwalk.FooterLine; 35 : 36 0 : public class MailUtil { 37 89 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 38 : 39 : public static MailRecipients getRecipientsFromFooters( 40 : AccountResolver accountResolver, List<FooterLine> footerLines) 41 : throws IOException, ConfigInvalidException { 42 88 : MailRecipients recipients = new MailRecipients(); 43 88 : for (FooterLine footerLine : footerLines) { 44 : try { 45 88 : if (isReviewer(footerLine)) { 46 4 : Account.Id accountId = toAccountId(accountResolver, footerLine.getValue().trim()); 47 4 : recipients.reviewers.add(accountId); 48 4 : logger.atFine().log( 49 4 : "Added account %d from footer line \"%s\" as reviewer", accountId.get(), footerLine); 50 88 : } else if (footerLine.matches(FooterKey.CC)) { 51 0 : Account.Id accountId = toAccountId(accountResolver, footerLine.getValue().trim()); 52 0 : recipients.cc.add(accountId); 53 0 : logger.atFine().log( 54 0 : "Added account %d from footer line \"%s\" as cc", accountId.get(), footerLine); 55 : } 56 3 : } catch (UnprocessableEntityException e) { 57 3 : logger.atFine().log( 58 3 : "Skip adding reviewer/cc from footer line \"%s\": %s", footerLine, e.getMessage()); 59 3 : continue; 60 88 : } 61 88 : } 62 88 : return recipients; 63 : } 64 : 65 : public static MailRecipients getRecipientsFromReviewers(ReviewerSet reviewers) { 66 37 : MailRecipients recipients = new MailRecipients(); 67 37 : recipients.reviewers.addAll(reviewers.byState(REVIEWER)); 68 37 : recipients.cc.addAll(reviewers.byState(CC)); 69 37 : return recipients; 70 : } 71 : 72 : @SuppressWarnings("deprecation") 73 : private static Account.Id toAccountId(AccountResolver accountResolver, String nameOrEmail) 74 : throws UnprocessableEntityException, IOException, ConfigInvalidException { 75 4 : return accountResolver.resolveByExactNameOrEmail(nameOrEmail).asUnique().account().id(); 76 : } 77 : 78 : private static boolean isReviewer(FooterLine candidateFooterLine) { 79 88 : return candidateFooterLine.matches(FooterKey.SIGNED_OFF_BY) 80 88 : || candidateFooterLine.matches(FooterKey.ACKED_BY) 81 88 : || candidateFooterLine.matches(FooterConstants.REVIEWED_BY) 82 88 : || candidateFooterLine.matches(FooterConstants.TESTED_BY); 83 : } 84 : 85 : public static class MailRecipients { 86 : private final Set<Account.Id> reviewers; 87 : private final Set<Account.Id> cc; 88 : 89 88 : public MailRecipients() { 90 88 : this.reviewers = new HashSet<>(); 91 88 : this.cc = new HashSet<>(); 92 88 : } 93 : 94 0 : public MailRecipients(Set<Account.Id> reviewers, Set<Account.Id> cc) { 95 0 : this.reviewers = new HashSet<>(reviewers); 96 0 : this.cc = new HashSet<>(cc); 97 0 : } 98 : 99 : public void add(MailRecipients recipients) { 100 0 : reviewers.addAll(recipients.reviewers); 101 0 : cc.addAll(recipients.cc); 102 0 : } 103 : 104 : public void remove(Account.Id toRemove) { 105 88 : reviewers.remove(toRemove); 106 88 : cc.remove(toRemove); 107 88 : } 108 : 109 : public Set<Account.Id> getReviewers() { 110 88 : return Collections.unmodifiableSet(reviewers); 111 : } 112 : 113 : public Set<Account.Id> getCcOnly() { 114 88 : final Set<Account.Id> cc = new HashSet<>(this.cc); 115 88 : cc.removeAll(reviewers); 116 88 : return Collections.unmodifiableSet(cc); 117 : } 118 : 119 : public Set<Account.Id> getAll() { 120 0 : final Set<Account.Id> all = new HashSet<>(reviewers.size() + cc.size()); 121 0 : all.addAll(reviewers); 122 0 : all.addAll(cc); 123 0 : return Collections.unmodifiableSet(all); 124 : } 125 : } 126 : 127 : /** allow wildcard matching for {@code domains} */ 128 : public static Pattern glob(String[] domains) { 129 : // if domains is not set, match anything 130 1 : if (domains == null || domains.length == 0) { 131 1 : return Pattern.compile(".*"); 132 : } 133 : 134 1 : StringBuilder sb = new StringBuilder(); 135 1 : for (String domain : domains) { 136 1 : String quoted = "\\Q" + domain.replace("\\E", "\\E\\\\E\\Q") + "\\E|"; 137 1 : sb.append(quoted.replace("*", "\\E.*\\Q")); 138 : } 139 1 : return Pattern.compile(sb.substring(0, sb.length() - 1)); 140 : } 141 : }