/* | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.nifi.bootstrap.notification.email; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Properties; | |
import java.util.Map.Entry; | |
import javax.mail.Authenticator; | |
import javax.mail.Message; | |
import javax.mail.MessagingException; | |
import javax.mail.PasswordAuthentication; | |
import javax.mail.Session; | |
import javax.mail.Transport; | |
import javax.mail.Message.RecipientType; | |
import javax.mail.internet.AddressException; | |
import javax.mail.internet.InternetAddress; | |
import javax.mail.internet.MimeMessage; | |
import org.apache.nifi.bootstrap.notification.AbstractNotificationService; | |
import org.apache.nifi.bootstrap.notification.NotificationContext; | |
import org.apache.nifi.bootstrap.notification.NotificationFailedException; | |
import org.apache.nifi.components.PropertyDescriptor; | |
import org.apache.nifi.components.ValidationContext; | |
import org.apache.nifi.components.ValidationResult; | |
import org.apache.nifi.processor.exception.ProcessException; | |
import org.apache.nifi.processor.util.StandardValidators; | |
public class EmailNotificationService extends AbstractNotificationService { | |
public static final PropertyDescriptor SMTP_HOSTNAME = new PropertyDescriptor.Builder() | |
.name("SMTP Hostname") | |
.description("The hostname of the SMTP Server that is used to send Email Notifications") | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.required(true) | |
.build(); | |
public static final PropertyDescriptor SMTP_PORT = new PropertyDescriptor.Builder() | |
.name("SMTP Port") | |
.description("The Port used for SMTP communications") | |
.required(true) | |
.defaultValue("25") | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.PORT_VALIDATOR) | |
.build(); | |
public static final PropertyDescriptor SMTP_USERNAME = new PropertyDescriptor.Builder() | |
.name("SMTP Username") | |
.description("Username for the SMTP account") | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.required(false) | |
.build(); | |
public static final PropertyDescriptor SMTP_PASSWORD = new PropertyDescriptor.Builder() | |
.name("SMTP Password") | |
.description("Password for the SMTP account") | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.required(false) | |
.sensitive(true) | |
.build(); | |
public static final PropertyDescriptor SMTP_AUTH = new PropertyDescriptor.Builder() | |
.name("SMTP Auth") | |
.description("Flag indicating whether authentication should be used") | |
.required(true) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.BOOLEAN_VALIDATOR) | |
.defaultValue("true") | |
.build(); | |
public static final PropertyDescriptor SMTP_TLS = new PropertyDescriptor.Builder() | |
.name("SMTP TLS") | |
.description("Flag indicating whether TLS should be enabled") | |
.required(true) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.BOOLEAN_VALIDATOR) | |
.defaultValue("false") | |
.build(); | |
public static final PropertyDescriptor SMTP_SOCKET_FACTORY = new PropertyDescriptor.Builder() | |
.name("SMTP Socket Factory") | |
.description("Socket Factory to use for SMTP Connection") | |
.required(true) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.defaultValue("javax.net.ssl.SSLSocketFactory") | |
.build(); | |
public static final PropertyDescriptor HEADER_XMAILER = new PropertyDescriptor.Builder() | |
.name("SMTP X-Mailer Header") | |
.description("X-Mailer used in the header of the outgoing email") | |
.required(true) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.defaultValue("NiFi") | |
.build(); | |
public static final PropertyDescriptor CONTENT_TYPE = new PropertyDescriptor.Builder() | |
.name("Content Type") | |
.description("Mime Type used to interpret the contents of the email, such as text/plain or text/html") | |
.required(true) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.defaultValue("text/plain") | |
.build(); | |
public static final PropertyDescriptor FROM = new PropertyDescriptor.Builder() | |
.name("From") | |
.description("Specifies the Email address to use as the sender") | |
.required(true) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.build(); | |
public static final PropertyDescriptor TO = new PropertyDescriptor.Builder() | |
.name("To") | |
.description("The recipients to include in the To-Line of the email") | |
.required(false) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.build(); | |
public static final PropertyDescriptor CC = new PropertyDescriptor.Builder() | |
.name("CC") | |
.description("The recipients to include in the CC-Line of the email") | |
.required(false) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.build(); | |
public static final PropertyDescriptor BCC = new PropertyDescriptor.Builder() | |
.name("BCC") | |
.description("The recipients to include in the BCC-Line of the email") | |
.required(false) | |
.expressionLanguageSupported(true) | |
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) | |
.build(); | |
/** | |
* Mapping of the mail properties to the NiFi PropertyDescriptors that will be evaluated at runtime | |
*/ | |
private static final Map<String, PropertyDescriptor> propertyToContext = new HashMap<>(); | |
static { | |
propertyToContext.put("mail.smtp.host", SMTP_HOSTNAME); | |
propertyToContext.put("mail.smtp.port", SMTP_PORT); | |
propertyToContext.put("mail.smtp.socketFactory.port", SMTP_PORT); | |
propertyToContext.put("mail.smtp.socketFactory.class", SMTP_SOCKET_FACTORY); | |
propertyToContext.put("mail.smtp.auth", SMTP_AUTH); | |
propertyToContext.put("mail.smtp.starttls.enable", SMTP_TLS); | |
propertyToContext.put("mail.smtp.user", SMTP_USERNAME); | |
propertyToContext.put("mail.smtp.password", SMTP_PASSWORD); | |
} | |
@Override | |
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { | |
final List<PropertyDescriptor> properties = new ArrayList<>(); | |
properties.add(SMTP_HOSTNAME); | |
properties.add(SMTP_PORT); | |
properties.add(SMTP_USERNAME); | |
properties.add(SMTP_PASSWORD); | |
properties.add(SMTP_AUTH); | |
properties.add(SMTP_TLS); | |
properties.add(SMTP_SOCKET_FACTORY); | |
properties.add(HEADER_XMAILER); | |
properties.add(CONTENT_TYPE); | |
properties.add(FROM); | |
properties.add(TO); | |
properties.add(CC); | |
properties.add(BCC); | |
return properties; | |
} | |
@Override | |
protected Collection<ValidationResult> customValidate(final ValidationContext context) { | |
final List<ValidationResult> errors = new ArrayList<>(super.customValidate(context)); | |
final String to = context.getProperty(TO).getValue(); | |
final String cc = context.getProperty(CC).getValue(); | |
final String bcc = context.getProperty(BCC).getValue(); | |
if (to == null && cc == null && bcc == null) { | |
errors.add(new ValidationResult.Builder().subject("To, CC, BCC").valid(false).explanation("Must specify at least one To/CC/BCC address").build()); | |
} | |
return errors; | |
} | |
@Override | |
public void notify(final NotificationContext context, final String subject, final String messageText) throws NotificationFailedException { | |
final Properties properties = getMailProperties(context); | |
final Session mailSession = createMailSession(properties); | |
final Message message = new MimeMessage(mailSession); | |
try { | |
message.setFrom(InternetAddress.parse(context.getProperty(FROM).evaluateAttributeExpressions().getValue())[0]); | |
final InternetAddress[] toAddresses = toInetAddresses(context.getProperty(TO).evaluateAttributeExpressions().getValue()); | |
message.setRecipients(RecipientType.TO, toAddresses); | |
final InternetAddress[] ccAddresses = toInetAddresses(context.getProperty(CC).evaluateAttributeExpressions().getValue()); | |
message.setRecipients(RecipientType.CC, ccAddresses); | |
final InternetAddress[] bccAddresses = toInetAddresses(context.getProperty(BCC).evaluateAttributeExpressions().getValue()); | |
message.setRecipients(RecipientType.BCC, bccAddresses); | |
message.setHeader("X-Mailer", context.getProperty(HEADER_XMAILER).evaluateAttributeExpressions().getValue()); | |
message.setSubject(subject); | |
final String contentType = context.getProperty(CONTENT_TYPE).evaluateAttributeExpressions().getValue(); | |
message.setContent(messageText, contentType); | |
message.setSentDate(new Date()); | |
Transport.send(message); | |
} catch (final ProcessException | MessagingException e) { | |
throw new NotificationFailedException("Failed to send E-mail Notification", e); | |
} | |
} | |
/** | |
* Creates an array of 0 or more InternetAddresses for the given String | |
* | |
* @param val the String to parse for InternetAddresses | |
* @return an array of 0 or more InetAddresses | |
* @throws AddressException if val contains an invalid address | |
*/ | |
private static InternetAddress[] toInetAddresses(final String val) throws AddressException { | |
if (val == null) { | |
return new InternetAddress[0]; | |
} | |
return InternetAddress.parse(val); | |
} | |
/** | |
* Uses the mapping of javax.mail properties to NiFi PropertyDescriptors to build the required Properties object to be used for sending this email | |
* | |
* @param context context | |
* @return mail properties | |
*/ | |
private Properties getMailProperties(final NotificationContext context) { | |
final Properties properties = new Properties(); | |
for (Entry<String, PropertyDescriptor> entry : propertyToContext.entrySet()) { | |
// Evaluate the property descriptor against the flow file | |
String property = entry.getKey(); | |
String propValue = context.getProperty(entry.getValue()).evaluateAttributeExpressions().getValue(); | |
// Nullable values are not allowed, so filter out | |
if (null != propValue) { | |
properties.setProperty(property, propValue); | |
} | |
} | |
return properties; | |
} | |
/** | |
* Based on the input properties, determine whether an authenticate or unauthenticated session should be used. If authenticated, creates a Password Authenticator for use in sending the email. | |
* | |
* @param properties mail properties | |
* @return session | |
*/ | |
private Session createMailSession(final Properties properties) { | |
String authValue = properties.getProperty("mail.smtp.auth"); | |
Boolean auth = Boolean.valueOf(authValue); | |
/* | |
* Conditionally create a password authenticator if the 'auth' parameter is set. | |
*/ | |
final Session mailSession = auth ? Session.getInstance(properties, new Authenticator() { | |
@Override | |
public PasswordAuthentication getPasswordAuthentication() { | |
String username = properties.getProperty("mail.smtp.user"), password = properties.getProperty("mail.smtp.password"); | |
return new PasswordAuthentication(username, password); | |
} | |
}) : Session.getInstance(properties); // without auth | |
return mailSession; | |
} | |
} |