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    }