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 private final static String TO = "to"; 051 private final static String CC = "cc"; 052 private final static String SUB = "subject"; 053 private final static String BOD = "body"; 054 private final static String COMMA = ","; 055 056 public EmailActionExecutor() { 057 super("email"); 058 } 059 060 @Override 061 public void initActionType() { 062 super.initActionType(); 063 } 064 065 @Override 066 public void start(Context context, WorkflowAction action) throws ActionExecutorException { 067 try { 068 context.setStartData("-", "-", "-"); 069 Element actionXml = XmlUtils.parseXml(action.getConf()); 070 validateAndMail(context, actionXml); 071 context.setExecutionData("OK", null); 072 } 073 catch (Exception ex) { 074 throw convertException(ex); 075 } 076 } 077 078 @SuppressWarnings("unchecked") 079 protected void validateAndMail(Context context, Element element) throws ActionExecutorException { 080 // The XSD does the min/max occurrence validation for us. 081 Namespace ns = Namespace.getNamespace("uri:oozie:email-action:0.1"); 082 String tos[] = new String[0]; 083 String ccs[] = new String[0]; 084 String subject = ""; 085 String body = ""; 086 Element child = null; 087 088 // <to> - One ought to exist. 089 String text = element.getChildTextTrim(TO, ns); 090 if (text.isEmpty()) { 091 throw new ActionExecutorException(ErrorType.ERROR, "EM001", "No receipents were specified in the to-address field."); 092 } 093 tos = text.split(COMMA); 094 095 // <cc> - Optional, but only one ought to exist. 096 try { 097 ccs = element.getChildTextTrim(CC, ns).split(COMMA); 098 } catch (Exception e) { 099 // It is alright for cc to be given empty or not be present. 100 ccs = new String[0]; 101 } 102 103 // <subject> - One ought to exist. 104 subject = element.getChildTextTrim(SUB, ns); 105 106 // <body> - One ought to exist. 107 body = element.getChildTextTrim(BOD, ns); 108 109 // All good - lets try to mail! 110 email(context, tos, ccs, subject, body); 111 } 112 113 protected void email(Context context, String[] to, String[] cc, String subject, String body) throws ActionExecutorException { 114 // Get mailing server details. 115 String smtpHost = getOozieConf().get("oozie.email.smtp.host", 116 "localhost"); 117 String smtpPort = getOozieConf().get("oozie.email.smtp.port", "25"); 118 Boolean smtpAuth = getOozieConf().getBoolean("oozie.email.smtp.auth", false); 119 String smtpUser = getOozieConf().get("oozie.email.smtp.username", ""); 120 String smtpPassword = getOozieConf().get("oozie.email.smtp.password", ""); 121 String fromAddr = getOozieConf().get("oozie.email.from.address", 122 "oozie@localhost"); 123 124 Properties properties = new Properties(); 125 properties.setProperty("mail.smtp.host", smtpHost); 126 properties.setProperty("mail.smtp.port", smtpPort); 127 properties.setProperty("mail.smtp.auth", smtpAuth.toString()); 128 129 Session session; 130 // Do not use default instance (i.e. Session.getDefaultInstance) 131 // (cause it may lead to issues when used second time). 132 if (!smtpAuth) { 133 session = Session.getInstance(properties); 134 } else { 135 session = Session.getInstance(properties, new JavaMailAuthenticator(smtpUser, smtpPassword)); 136 } 137 138 Message message = new MimeMessage(session); 139 InternetAddress from; 140 List<InternetAddress> toAddrs = new ArrayList<InternetAddress>(to.length); 141 List<InternetAddress> ccAddrs = new ArrayList<InternetAddress>(cc.length); 142 143 try { 144 from = new InternetAddress(fromAddr); 145 message.setFrom(from); 146 } catch (AddressException e) { 147 throw new ActionExecutorException(ErrorType.ERROR, "EM002", "Bad from address specified in ${oozie.email.from.address}.", e); 148 } catch (MessagingException e) { 149 throw new ActionExecutorException(ErrorType.ERROR, "EM003", "Error setting a from address in the message.", e); 150 } 151 152 try { 153 // Add all <to> 154 for (String toStr : to) { 155 toAddrs.add(new InternetAddress(toStr.trim())); 156 } 157 message.addRecipients(RecipientType.TO, toAddrs.toArray(new InternetAddress[0])); 158 159 // Add all <cc> 160 for (String ccStr : cc) { 161 ccAddrs.add(new InternetAddress(ccStr.trim())); 162 } 163 message.addRecipients(RecipientType.CC, ccAddrs.toArray(new InternetAddress[0])); 164 165 // Set subject, and plain-text body. 166 message.setSubject(subject); 167 message.setContent(body, "text/plain"); 168 } catch (AddressException e) { 169 throw new ActionExecutorException(ErrorType.ERROR, "EM004", "Bad address format in <to> or <cc>.", e); 170 } catch (MessagingException e) { 171 throw new ActionExecutorException(ErrorType.ERROR, "EM005", "An error occured while adding recipients.", e); 172 } 173 174 try { 175 // Send over SMTP Transport 176 // (Session+Message has adequate details.) 177 Transport.send(message); 178 } catch (NoSuchProviderException e) { 179 throw new ActionExecutorException(ErrorType.ERROR, "EM006", "Could not find an SMTP transport provider to email.", e); 180 } catch (MessagingException e) { 181 throw new ActionExecutorException(ErrorType.ERROR, "EM007", "Encountered an error while sending the email message over SMTP.", e); 182 } 183 } 184 185 @Override 186 public void end(Context context, WorkflowAction action) throws ActionExecutorException { 187 String externalStatus = action.getExternalStatus(); 188 WorkflowAction.Status status = externalStatus.equals("OK") ? WorkflowAction.Status.OK : 189 WorkflowAction.Status.ERROR; 190 context.setEndData(status, getActionSignal(status)); 191 } 192 193 @Override 194 public void check(Context context, WorkflowAction action) 195 throws ActionExecutorException { 196 197 } 198 199 @Override 200 public void kill(Context context, WorkflowAction action) 201 throws ActionExecutorException { 202 203 } 204 205 @Override 206 public boolean isCompleted(String externalStatus) { 207 return true; 208 } 209 210 private static class JavaMailAuthenticator extends Authenticator { 211 212 String user; 213 String password; 214 215 public JavaMailAuthenticator(String user, String password) { 216 this.user = user; 217 this.password = password; 218 } 219 220 @Override 221 protected PasswordAuthentication getPasswordAuthentication() { 222 return new PasswordAuthentication(user, password); 223 } 224 } 225 }