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            String smtpPort = getOozieConf().get("oozie.email.smtp.port", "25");
117            Boolean smtpAuth = getOozieConf().getBoolean("oozie.email.smtp.auth", false);
118            String smtpUser = getOozieConf().get("oozie.email.smtp.username", "");
119            String smtpPassword = getOozieConf().get("oozie.email.smtp.password", "");
120            String fromAddr = getOozieConf().get("oozie.email.from.address");
121    
122            Properties properties = new Properties();
123            properties.setProperty("mail.smtp.host", smtpHost);
124            properties.setProperty("mail.smtp.port", smtpPort);
125            properties.setProperty("mail.smtp.auth", smtpAuth.toString());
126    
127            Session session;
128            // Do not use default instance (i.e. Session.getDefaultInstance)
129            // (cause it may lead to issues when used second time).
130            if (!smtpAuth) {
131                session = Session.getInstance(properties);
132            } else {
133                session = Session.getInstance(properties, new JavaMailAuthenticator(smtpUser, smtpPassword));
134            }
135    
136            Message message = new MimeMessage(session);
137            InternetAddress from;
138            List<InternetAddress> toAddrs = new ArrayList<InternetAddress>(to.length);
139            List<InternetAddress> ccAddrs = new ArrayList<InternetAddress>(cc.length);
140    
141            try {
142                from = new InternetAddress(fromAddr);
143                message.setFrom(from);
144            } catch (AddressException e) {
145                throw new ActionExecutorException(ErrorType.ERROR, "EM002", "Bad from address specified in ${oozie.email.from.address}.", e);
146            } catch (MessagingException e) {
147                throw new ActionExecutorException(ErrorType.ERROR, "EM003", "Error setting a from address in the message.", e);
148            }
149    
150            try {
151                // Add all <to>
152                for (String toStr : to) {
153                    toAddrs.add(new InternetAddress(toStr.trim()));
154                }
155                message.addRecipients(RecipientType.TO, toAddrs.toArray(new InternetAddress[0]));
156    
157                // Add all <cc>
158                for (String ccStr : cc) {
159                    ccAddrs.add(new InternetAddress(ccStr.trim()));
160                }
161                message.addRecipients(RecipientType.CC, ccAddrs.toArray(new InternetAddress[0]));
162    
163                // Set subject, and plain-text body.
164                message.setSubject(subject);
165                message.setContent(body, "text/plain");
166            } catch (AddressException e) {
167                throw new ActionExecutorException(ErrorType.ERROR, "EM004", "Bad address format in <to> or <cc>.", e);
168            } catch (MessagingException e) {
169                throw new ActionExecutorException(ErrorType.ERROR, "EM005", "An error occured while adding recipients.", e);
170            }
171    
172            try {
173                // Send over SMTP Transport
174                // (Session+Message has adequate details.)
175                Transport.send(message);
176            } catch (NoSuchProviderException e) {
177                throw new ActionExecutorException(ErrorType.ERROR, "EM006", "Could not find an SMTP transport provider to email.", e);
178            } catch (MessagingException e) {
179                throw new ActionExecutorException(ErrorType.ERROR, "EM007", "Encountered an error while sending the email message over SMTP.", e);
180            }
181        }
182    
183        @Override
184        public void end(Context context, WorkflowAction action) throws ActionExecutorException {
185            String externalStatus = action.getExternalStatus();
186            WorkflowAction.Status status = externalStatus.equals("OK") ? WorkflowAction.Status.OK :
187                                           WorkflowAction.Status.ERROR;
188            context.setEndData(status, getActionSignal(status));
189        }
190    
191        @Override
192        public void check(Context context, WorkflowAction action)
193                throws ActionExecutorException {
194    
195        }
196    
197        @Override
198        public void kill(Context context, WorkflowAction action)
199                throws ActionExecutorException {
200    
201        }
202    
203        @Override
204        public boolean isCompleted(String externalStatus) {
205            return true;
206        }
207    
208        private static class JavaMailAuthenticator extends Authenticator {
209    
210            String user;
211            String password;
212    
213            public JavaMailAuthenticator(String user, String password) {
214                this.user = user;
215                this.password = password;
216            }
217    
218            @Override
219            protected PasswordAuthentication getPasswordAuthentication() {
220               return new PasswordAuthentication(user, password);
221            }
222        }
223    }