1 /** 2 * Copyright 2016 Google Inc. All Rights Reserved. 3 * 4 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * <p>http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * <p>Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 * express or implied. See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 package com.android.vts.util; 15 16 import com.android.vts.entity.DeviceInfoEntity; 17 import com.android.vts.entity.TestRunEntity; 18 import com.android.vts.entity.UserFavoriteEntity; 19 import com.google.appengine.api.datastore.DatastoreService; 20 import com.google.appengine.api.datastore.DatastoreServiceFactory; 21 import com.google.appengine.api.datastore.Entity; 22 import com.google.appengine.api.datastore.Key; 23 import com.google.appengine.api.datastore.Query; 24 import com.google.appengine.api.datastore.Query.Filter; 25 import com.google.appengine.api.datastore.Query.FilterOperator; 26 import com.google.appengine.api.datastore.Query.FilterPredicate; 27 import java.io.IOException; 28 import java.io.UnsupportedEncodingException; 29 30 import java.util.ArrayList; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Optional; 34 import java.util.Properties; 35 import java.util.Set; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 import javax.mail.Message; 39 import javax.mail.MessagingException; 40 import javax.mail.Session; 41 import javax.mail.Transport; 42 import javax.mail.internet.InternetAddress; 43 import javax.mail.internet.MimeMessage; 44 import org.apache.commons.lang.StringUtils; 45 46 /** EmailHelper, a helper class for building and sending emails. */ 47 public class EmailHelper { 48 protected static final Logger logger = Logger.getLogger(EmailHelper.class.getName()); 49 protected static String DEFAULT_EMAIL; 50 protected static String EMAIL_DOMAIN; 51 protected static String SENDER_EMAIL; 52 private static final String VTS_EMAIL_NAME = "VTS Alert Bot"; 53 setPropertyValues(Properties systemConfigProp)54 public static void setPropertyValues(Properties systemConfigProp) { 55 DEFAULT_EMAIL = systemConfigProp.getProperty("appengine.defaultEmail"); 56 EMAIL_DOMAIN = systemConfigProp.getProperty("appengine.emailDomain"); 57 SENDER_EMAIL = systemConfigProp.getProperty("appengine.senderEmail"); 58 } 59 60 /** 61 * Create an email footer with the information from the test run. 62 * 63 * @param testRun The TestRunEntity containing test run metadata, or null. 64 * @param devices The list of devices whose fingerprints to include in the email, or null. 65 * @param link A link to the Dashboard page containing more information. 66 * @return The String email footer. 67 */ getEmailFooter( TestRunEntity testRun, List<DeviceInfoEntity> devices, String link)68 public static String getEmailFooter( 69 TestRunEntity testRun, List<DeviceInfoEntity> devices, String link) { 70 StringBuilder sb = new StringBuilder(); 71 sb.append("<br><br>"); 72 if (devices != null) { 73 for (DeviceInfoEntity device : devices) { 74 sb.append("Device: " + device.getFingerprint() + "<br>"); 75 } 76 } 77 78 if (testRun != null) { 79 sb.append("VTS Build ID: " + testRun.getTestBuildId() + "<br>"); 80 sb.append("Start Time: " + TimeUtil.getDateTimeZoneString(testRun.getStartTimestamp())); 81 sb.append("<br>End Time: " + TimeUtil.getDateTimeZoneString(testRun.getEndTimestamp())); 82 } 83 sb.append( 84 "<br><br>For details, visit the" 85 + " <a href='" 86 + link 87 + "'>" 88 + "VTS dashboard.</a>"); 89 return sb.toString(); 90 } 91 92 /** 93 * Fetches the list of subscriber email addresses for a test. 94 * 95 * @param testKey The key for the test for which to fetch the email addresses. 96 * @returns List of email addresses (String). 97 * @throws IOException 98 */ getSubscriberEmails(Key testKey)99 public static List<String> getSubscriberEmails(Key testKey) throws IOException { 100 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 101 Filter testFilter = 102 new FilterPredicate(UserFavoriteEntity.TEST_KEY, FilterOperator.EQUAL, testKey); 103 Query favoritesQuery = new Query(UserFavoriteEntity.KIND).setFilter(testFilter); 104 Set<String> emailSet = new HashSet<>(); 105 if (!StringUtils.isBlank(DEFAULT_EMAIL)) { 106 emailSet.add(DEFAULT_EMAIL); 107 } 108 for (Entity favorite : datastore.prepare(favoritesQuery).asIterable()) { 109 UserFavoriteEntity favoriteEntity = UserFavoriteEntity.fromEntity(favorite); 110 // TODO this logic need to be reexamined thoroughly and improved 111 if (favoriteEntity != null 112 && favoriteEntity.user != null 113 && !favoriteEntity.muteNotifications) { 114 Optional<String> userEmail = Optional.of(favoriteEntity.user.getEmail()); 115 if (userEmail.isPresent() && 116 userEmail.orElse("").endsWith(EMAIL_DOMAIN)) { 117 emailSet.add(favoriteEntity.user.getEmail()); 118 } 119 } 120 } 121 return new ArrayList<>(emailSet); 122 } 123 124 /** 125 * Sends an email to the specified email address to notify of a test status change. 126 * 127 * @param emails List of subscriber email addresses (byte[]) to which the email should be sent. 128 * @param subject The email subject field, string. 129 * @param body The html (string) body to send in the email. 130 * @returns The Message object to be sent. 131 * @throws MessagingException, UnsupportedEncodingException 132 */ composeEmail(List<String> emails, String subject, String body)133 public static Message composeEmail(List<String> emails, String subject, String body) 134 throws MessagingException, UnsupportedEncodingException { 135 if (emails.size() == 0) { 136 throw new MessagingException("No subscriber email addresses provided"); 137 } 138 Properties props = new Properties(); 139 Session session = Session.getDefaultInstance(props, null); 140 141 Message msg = new MimeMessage(session); 142 for (String email : emails) { 143 try { 144 msg.addRecipient(Message.RecipientType.TO, new InternetAddress(email, email)); 145 } catch (MessagingException | UnsupportedEncodingException e) { 146 // Gracefully continue when a subscriber email is invalid. 147 logger.log(Level.WARNING, "Error sending email to recipient " + email + " : ", e); 148 } 149 } 150 msg.setFrom(new InternetAddress(SENDER_EMAIL, VTS_EMAIL_NAME)); 151 msg.setSubject(subject); 152 msg.setContent(body, "text/html; charset=utf-8"); 153 return msg; 154 } 155 156 /** 157 * Sends an email. 158 * 159 * @param msg Message object to send. 160 * @returns true if the message sends successfully, false otherwise 161 */ send(Message msg)162 public static boolean send(Message msg) { 163 try { 164 Transport.send(msg); 165 } catch (MessagingException e) { 166 logger.log(Level.WARNING, "Error sending email : ", e); 167 return false; 168 } 169 return true; 170 } 171 172 /** 173 * Sends a list of emails and logs any failures. 174 * 175 * @param messages List of Message objects to be sent. 176 */ sendAll(List<Message> messages)177 public static void sendAll(List<Message> messages) { 178 for (Message msg : messages) { 179 send(msg); 180 } 181 } 182 183 /** 184 * Sends an email. 185 * 186 * @param recipients List of email address strings to which an email will be sent. 187 * @param subject The subject of the email. 188 * @param body The body of the email. 189 * @returns true if the message sends successfully, false otherwise 190 */ send(List<String> recipients, String subject, String body)191 public static boolean send(List<String> recipients, String subject, String body) { 192 try { 193 Message msg = composeEmail(recipients, subject, body); 194 return send(msg); 195 } catch (MessagingException | UnsupportedEncodingException e) { 196 logger.log(Level.WARNING, "Error composing email : ", e); 197 return false; 198 } 199 } 200 } 201