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