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    }