001 /** 002 * Copyright (c) 2011 Yahoo! Inc. All rights reserved. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. See accompanying LICENSE file. 014 */ 015 package org.apache.oozie.action.email; 016 017 import java.util.ArrayList; 018 import java.util.List; 019 import java.util.Properties; 020 021 import javax.mail.Authenticator; 022 import javax.mail.Message; 023 import javax.mail.Message.RecipientType; 024 import javax.mail.MessagingException; 025 import javax.mail.NoSuchProviderException; 026 import javax.mail.PasswordAuthentication; 027 import javax.mail.Session; 028 import javax.mail.Transport; 029 import javax.mail.internet.AddressException; 030 import javax.mail.internet.InternetAddress; 031 import javax.mail.internet.MimeMessage; 032 033 import org.apache.oozie.action.ActionExecutor; 034 import org.apache.oozie.action.ActionExecutorException; 035 import org.apache.oozie.action.ActionExecutorException.ErrorType; 036 import org.apache.oozie.client.WorkflowAction; 037 import org.apache.oozie.util.XmlUtils; 038 import org.jdom.Element; 039 import org.jdom.Namespace; 040 041 /** 042 * Email action executor. It takes to, cc addresses along with a subject and body and sends 043 * out an email. 044 */ 045 public class EmailActionExecutor extends ActionExecutor { 046 047 private final static String TO = "to"; 048 private final static String CC = "cc"; 049 private final static String SUB = "subject"; 050 private final static String BOD = "body"; 051 private final static String COMMA = ","; 052 053 public EmailActionExecutor() { 054 super("email"); 055 } 056 057 @Override 058 public void initActionType() { 059 super.initActionType(); 060 } 061 062 @Override 063 public void start(Context context, WorkflowAction action) throws ActionExecutorException { 064 try { 065 context.setStartData("-", "-", "-"); 066 Element actionXml = XmlUtils.parseXml(action.getConf()); 067 validateAndMail(context, actionXml); 068 context.setExecutionData("OK", null); 069 } 070 catch (Exception ex) { 071 throw convertException(ex); 072 } 073 } 074 075 @SuppressWarnings("unchecked") 076 protected void validateAndMail(Context context, Element element) throws ActionExecutorException { 077 // The XSD does the min/max occurrence validation for us. 078 Namespace ns = Namespace.getNamespace("uri:oozie:email-action:0.1"); 079 String tos[] = new String[0]; 080 String ccs[] = new String[0]; 081 String subject = ""; 082 String body = ""; 083 Element child = null; 084 085 // <to> - One ought to exist. 086 String text = element.getChildTextTrim(TO, ns); 087 if (text.isEmpty()) { 088 throw new ActionExecutorException(ErrorType.ERROR, "EM001", "No receipents were specified in the to-address field."); 089 } 090 tos = text.split(COMMA); 091 092 // <cc> - Optional, but only one ought to exist. 093 try { 094 ccs = element.getChildTextTrim(CC, ns).split(COMMA); 095 } catch (Exception e) { 096 // It is alright for cc to be given empty or not be present. 097 ccs = new String[0]; 098 } 099 100 // <subject> - One ought to exist. 101 subject = element.getChildTextTrim(SUB, ns); 102 103 // <body> - One ought to exist. 104 body = element.getChildTextTrim(BOD, ns); 105 106 // All good - lets try to mail! 107 email(context, tos, ccs, subject, body); 108 } 109 110 protected void email(Context context, String[] to, String[] cc, String subject, String body) throws ActionExecutorException { 111 // Get mailing server details. 112 String smtpHost = getOozieConf().get("oozie.email.smtp.host"); 113 String smtpPort = getOozieConf().get("oozie.email.smtp.port", "25"); 114 Boolean smtpAuth = getOozieConf().getBoolean("oozie.email.smtp.auth", false); 115 String smtpUser = getOozieConf().get("oozie.email.smtp.username", ""); 116 String smtpPassword = getOozieConf().get("oozie.email.smtp.password", ""); 117 String fromAddr = getOozieConf().get("oozie.email.from.address"); 118 119 Properties properties = new Properties(); 120 properties.setProperty("mail.smtp.host", smtpHost); 121 properties.setProperty("mail.smtp.port", smtpPort); 122 properties.setProperty("mail.smtp.auth", smtpAuth.toString()); 123 124 Session session; 125 // Do not use default instance (i.e. Session.getDefaultInstance) 126 // (cause it may lead to issues when used second time). 127 if (!smtpAuth) { 128 session = Session.getInstance(properties); 129 } else { 130 session = Session.getInstance(properties, new JavaMailAuthenticator(smtpUser, smtpPassword)); 131 } 132 133 Message message = new MimeMessage(session); 134 InternetAddress from; 135 List<InternetAddress> toAddrs = new ArrayList<InternetAddress>(to.length); 136 List<InternetAddress> ccAddrs = new ArrayList<InternetAddress>(cc.length); 137 138 try { 139 from = new InternetAddress(fromAddr); 140 message.setFrom(from); 141 } catch (AddressException e) { 142 throw new ActionExecutorException(ErrorType.ERROR, "EM002", "Bad from address specified in ${oozie.email.from.address}.", e); 143 } catch (MessagingException e) { 144 throw new ActionExecutorException(ErrorType.ERROR, "EM003", "Error setting a from address in the message.", e); 145 } 146 147 try { 148 // Add all <to> 149 for (String toStr : to) { 150 toAddrs.add(new InternetAddress(toStr.trim())); 151 } 152 message.addRecipients(RecipientType.TO, toAddrs.toArray(new InternetAddress[0])); 153 154 // Add all <cc> 155 for (String ccStr : cc) { 156 ccAddrs.add(new InternetAddress(ccStr.trim())); 157 } 158 message.addRecipients(RecipientType.CC, ccAddrs.toArray(new InternetAddress[0])); 159 160 // Set subject, and plain-text body. 161 message.setSubject(subject); 162 message.setContent(body, "text/plain"); 163 } catch (AddressException e) { 164 throw new ActionExecutorException(ErrorType.ERROR, "EM004", "Bad address format in <to> or <cc>.", e); 165 } catch (MessagingException e) { 166 throw new ActionExecutorException(ErrorType.ERROR, "EM005", "An error occured while adding recipients.", e); 167 } 168 169 try { 170 // Send over SMTP Transport 171 // (Session+Message has adequate details.) 172 Transport.send(message); 173 } catch (NoSuchProviderException e) { 174 throw new ActionExecutorException(ErrorType.ERROR, "EM006", "Could not find an SMTP transport provider to email.", e); 175 } catch (MessagingException e) { 176 throw new ActionExecutorException(ErrorType.ERROR, "EM007", "Encountered an error while sending the email message over SMTP.", e); 177 } 178 } 179 180 @Override 181 public void end(Context context, WorkflowAction action) throws ActionExecutorException { 182 String externalStatus = action.getExternalStatus(); 183 WorkflowAction.Status status = externalStatus.equals("OK") ? WorkflowAction.Status.OK : 184 WorkflowAction.Status.ERROR; 185 context.setEndData(status, getActionSignal(status)); 186 } 187 188 @Override 189 public void check(Context context, WorkflowAction action) 190 throws ActionExecutorException { 191 192 } 193 194 @Override 195 public void kill(Context context, WorkflowAction action) 196 throws ActionExecutorException { 197 198 } 199 200 @Override 201 public boolean isCompleted(String externalStatus) { 202 return true; 203 } 204 205 private static class JavaMailAuthenticator extends Authenticator { 206 207 String user; 208 String password; 209 210 public JavaMailAuthenticator(String user, String password) { 211 this.user = user; 212 this.password = password; 213 } 214 215 @Override 216 protected PasswordAuthentication getPasswordAuthentication() { 217 return new PasswordAuthentication(user, password); 218 } 219 } 220 }