Line data Source code
1 : // Copyright (C) 2012 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.send; 16 : 17 : import com.google.common.annotations.VisibleForTesting; 18 : import com.google.common.collect.Iterables; 19 : import com.google.common.flogger.FluentLogger; 20 : import com.google.gerrit.common.Nullable; 21 : import com.google.gerrit.entities.Account; 22 : import com.google.gerrit.entities.Address; 23 : import com.google.gerrit.entities.BranchNameKey; 24 : import com.google.gerrit.entities.NotifyConfig.NotifyType; 25 : import com.google.gerrit.exceptions.EmailException; 26 : import com.google.gerrit.exceptions.StorageException; 27 : import com.google.gerrit.extensions.api.changes.RecipientType; 28 : import com.google.gerrit.mail.MailHeader; 29 : import com.google.gerrit.server.mail.send.ProjectWatch.Watchers; 30 : import com.google.gerrit.server.mail.send.ProjectWatch.Watchers.WatcherList; 31 : import java.util.HashMap; 32 : import java.util.Map; 33 : 34 : /** Common class for notifications that are related to a project and branch */ 35 : public abstract class NotificationEmail extends OutgoingEmail { 36 152 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 37 : 38 : protected BranchNameKey branch; 39 : 40 : protected NotificationEmail(EmailArguments args, String messageClass, BranchNameKey branch) { 41 103 : super(args, messageClass); 42 103 : this.branch = branch; 43 103 : } 44 : 45 : @Override 46 : protected void init() throws EmailException { 47 103 : super.init(); 48 103 : setListIdHeader(); 49 103 : } 50 : 51 : private void setListIdHeader() { 52 : // Set a reasonable list id so that filters can be used to sort messages 53 103 : setHeader( 54 : "List-Id", 55 103 : "<gerrit-" + branch.project().get().replace('/', '-') + "." + getGerritHost() + ">"); 56 103 : if (getSettingsUrl() != null) { 57 103 : setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">"); 58 : } 59 103 : } 60 : 61 : /** Include users and groups that want notification of events. */ 62 : protected void includeWatchers(NotifyType type) { 63 57 : includeWatchers(type, true); 64 57 : } 65 : 66 : /** Include users and groups that want notification of events. */ 67 : protected void includeWatchers(NotifyType type, boolean includeWatchersFromNotifyConfig) { 68 : try { 69 103 : Watchers matching = getWatchers(type, includeWatchersFromNotifyConfig); 70 103 : add(RecipientType.TO, matching.to); 71 103 : add(RecipientType.CC, matching.cc); 72 103 : add(RecipientType.BCC, matching.bcc); 73 0 : } catch (StorageException err) { 74 : // Just don't CC everyone. Better to send a partial message to those 75 : // we already have queued up then to fail deliver entirely to people 76 : // who have a lower interest in the change. 77 0 : logger.atWarning().withCause(err).log("Cannot BCC watchers for %s", type); 78 103 : } 79 103 : } 80 : 81 : /** Returns all watchers that are relevant */ 82 : protected abstract Watchers getWatchers(NotifyType type, boolean includeWatchersFromNotifyConfig); 83 : 84 : /** Add users or email addresses to the TO, CC, or BCC list. */ 85 : protected void add(RecipientType type, WatcherList watcherList) { 86 103 : for (Account.Id user : watcherList.accounts) { 87 8 : addWatcher(type, user); 88 8 : } 89 103 : for (Address addr : watcherList.emails) { 90 2 : add(type, addr); 91 2 : } 92 103 : } 93 : 94 : protected abstract void addWatcher(RecipientType type, Account.Id to); 95 : 96 : @Nullable 97 : public String getSshHost() { 98 103 : String host = Iterables.getFirst(args.sshAddresses, null); 99 103 : if (host == null) { 100 84 : return null; 101 : } 102 20 : if (host.startsWith("*:")) { 103 8 : return getGerritHost() + host.substring(1); 104 : } 105 12 : return host; 106 : } 107 : 108 : @Override 109 : protected void setupSoyContext() { 110 103 : super.setupSoyContext(); 111 : 112 103 : String projectName = branch.project().get(); 113 103 : soyContext.put("projectName", projectName); 114 : // shortProjectName is the project name with the path abbreviated. 115 103 : soyContext.put("shortProjectName", getShortProjectName(projectName)); 116 : 117 : // instanceAndProjectName is the instance's name followed by the abbreviated project path 118 103 : soyContext.put( 119 : "instanceAndProjectName", 120 103 : getInstanceAndProjectName(args.instanceNameProvider.get(), projectName)); 121 103 : soyContext.put("addInstanceNameInSubject", args.addInstanceNameInSubject); 122 : 123 103 : soyContextEmailData.put("sshHost", getSshHost()); 124 : 125 103 : Map<String, String> branchData = new HashMap<>(); 126 103 : branchData.put("shortName", branch.shortName()); 127 103 : soyContext.put("branch", branchData); 128 : 129 103 : footers.add(MailHeader.PROJECT.withDelimiter() + branch.project().get()); 130 103 : footers.add(MailHeader.BRANCH.withDelimiter() + branch.shortName()); 131 103 : } 132 : 133 : @VisibleForTesting 134 : protected static String getShortProjectName(String projectName) { 135 103 : int lastIndexSlash = projectName.lastIndexOf('/'); 136 103 : if (lastIndexSlash == 0) { 137 1 : return projectName.substring(1); // Remove the first slash 138 : } 139 103 : if (lastIndexSlash == -1) { // No slash in the project name 140 103 : return projectName; 141 : } 142 : 143 1 : return "..." + projectName.substring(lastIndexSlash + 1); 144 : } 145 : 146 : @VisibleForTesting 147 : protected static String getInstanceAndProjectName(String instanceName, String projectName) { 148 103 : if (instanceName == null || instanceName.isEmpty()) { 149 1 : return getShortProjectName(projectName); 150 : } 151 : // Extract the project name (everything after the last slash) and prepends it with gerrit's 152 : // instance name 153 103 : return instanceName + "/" + projectName.substring(projectName.lastIndexOf('/') + 1); 154 : } 155 : }