com.hurence.logisland.processor.mailer.MailerProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.hurence.logisland.processor.mailer.MailerProcessor.java

Source

/**
 * Copyright (C) 2017 Hurence
 * <p>
 * Licensed 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 com.hurence.logisland.processor.mailer;

import com.hurence.logisland.annotation.documentation.CapabilityDescription;
import com.hurence.logisland.annotation.documentation.Tags;
import com.hurence.logisland.component.PropertyDescriptor;
import com.hurence.logisland.processor.*;
import com.hurence.logisland.record.Field;
import com.hurence.logisland.record.FieldDictionary;
import com.hurence.logisland.record.FieldType;
import com.hurence.logisland.record.Record;
import com.hurence.logisland.util.string.JsonUtil;
import com.hurence.logisland.validator.StandardValidators;
import com.hurence.logisland.validator.ValidationContext;
import com.hurence.logisland.validator.ValidationResult;

import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.ImageHtmlEmail;
import org.apache.commons.mail.SimpleEmail;
import org.apache.commons.mail.resolver.DataSourceClassPathResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Mailer Processor.
 * This processor is able to send mails from the incoming records.
 * A minimum processor configuration is required (mainly smtp server info and some mail sender info)
 * Only records with either FIELD_MAIL_TEXT or FIELD_MAIL_HTML fields set will generate a mail.
 * - If FIELD_MAIL_TEXT is set, it holds the text content of the mail to be sent
 * - If FIELD_MAIL_HTML is set, it informs the processor to use the HTML template of its configuration for sending the
 *   mail. Any parameter defined in the template will be retrieved from the fields with the same name embedded in the
 *   record. Every parameters defined in the template with the form ${xxx} must be present in the record (the record
 *   must hold a xxx field).
 * 
 * If it is enabled in processor configuration (allow_overwrite), it is possible to overwrite some config properties
 * directly in the record with some specific fields (FIELD_MAIL_TO, FIELD_MAIL_SUBJECT...)
 * 
 * Note: you can use an html template using embedded image. Images should then be in the class path (in a jar file)
 * and the format of src attribute in the img html tag should be like:
 * <img src="path/in/classpath/spectacular_image.gif" alt="Spectacular Image">
 */
@Tags({ "smtp", "email", "e-mail", "mail", "mailer", "message", "alert", "html" })
@CapabilityDescription("The Mailer processor is aimed at sending an email (like for instance an alert email) from an incoming record."
        + " There are three ways an incoming record can generate an email according to the special fields it must embed."
        + " Here is a list of the record fields that generate a mail and how they work:\n\n"
        + "- **mail_text**: this is the simplest way for generating a mail. If present, this field means to use its content (value)"
        + " as the payload of the mail to send. The mail is sent in text format if there is only this special field in the"
        + " record. Otherwise, used with either mail_html or mail_use_template, the content of mail_text is the aletrnative"
        + " text to the HTML mail that is generated.\n\n"
        + "- **mail_html**: this field specifies that the mail should be sent as HTML and the value of the field is mail"
        + " payload. If mail_text is also present, its value is used as the alternative text for the mail. mail_html"
        + " cannot be used with mail_use_template: only one of those two fields should be present in the record.\n\n"
        + "- **mail_use_template**: If present, this field specifies that the mail should be sent as HTML and the HTML content"
        + " is to be generated from the template in the processor configuration key **html.template**. The template can contain"
        + " parameters which must also be present in the record as fields. See documentation of html.template for further"
        + " explanations. mail_use_template cannot be used with mail_html: only one of those two fields should be present"
        + " in the record.\n\n"
        + " If **allow_overwrite** configuration key is true, any mail.* (dot format) configuration key may be overwritten with a matching"
        + " field in the record of the form mail_* (underscore format). For instance if allow_overwrite is true and mail.to is"
        + " set to config_address@domain.com, a record generating a mail with a mail_to field set to record_address@domain.com"
        + " will send a mail to record_address@domain.com.\n\n"
        + " Apart from error records (when he is unable to process the incoming record or to send the mail), this processor"
        + " is not expected to produce any output records.")
public class MailerProcessor extends AbstractProcessor {

    private static Logger logger = LoggerFactory.getLogger(MailerProcessor.class);

    // Easy trick to not allow debugging without changing the logger level but instead using a configuration key
    private boolean debug = false;

    private String smtpServer = null;
    private int smtpPort = 25;
    private String smtpSecurityUsername = null;
    private String smtpSecurityPassword = null;
    private boolean smtpSecuritySsl = false;

    private static final String DEFAULT_FROM_NAME = "Logisland";
    private static final String DEFAULT_SUBJECT = "[LOGISLAND] Automatic email";

    private String[] mailTos = new String[] {};
    private String mailFromAddress = null;
    private String mailFromName = DEFAULT_FROM_NAME;
    private String mailBounceAddress = null;
    private String mailReplyToAddress = null;
    private String mailSubject = DEFAULT_SUBJECT;
    private boolean allowFieldsOverwriting = true;

    // HTML template as stated in the configuration
    private String htmlTemplate = null;

    // HTML form derived from htmlTemplate, ready for parameters injection
    private MessageFormat htmlForm = null;

    // List of parameters for the HTML template. They must be present in the record as fields.
    private List<String> parameterNames = new ArrayList<String>();

    /**
     * Definitions for the fields of the incoming record
     */

    private static final String FIELD_PREFIX = "mail";

    /**
     *     Fields that trigger an email
     */
    // This holds the content of the mail to be sent as text.
    public static final String FIELD_MAIL_TEXT = FIELD_PREFIX + "_text";
    // This holds the content of the mail to be sent as html.
    public static final String FIELD_MAIL_HTML = FIELD_PREFIX + "_html";
    // If this field is present (no matter his content), the HTML template is used to send the mail.
    public static final String FIELD_MAIL_USE_TEMPLATE = FIELD_PREFIX + "_use_template";

    /**
     *     Optional fields that overwrite behavior from configuration
     */
    // May be used to overwrite mail.to configured in processor
    public static final String FIELD_MAIL_TO = FIELD_PREFIX + "_to";
    // May be used to overwrite mail.from.address configured in processor 
    public static final String FIELD_MAIL_FROM_ADDRESS = FIELD_PREFIX + "_from_address";
    // May be used to overwrite mail.from.name configured in processor
    public static final String FIELD_MAIL_FROM_NAME = FIELD_PREFIX + "_from_name";
    // May be used to overwrite mail.bounce.address configured in processor 
    public static final String FIELD_MAIL_BOUNCE_ADDRESS = FIELD_PREFIX + "_bounce_address";
    // May be used to overwrite mail.replyto.address configured in processor 
    public static final String FIELD_MAIL_REPLYTO_ADDRESS = FIELD_PREFIX + "_replyto_address";
    // May be used to overwrite mail.subject configured in processor 
    public static final String FIELD_MAIL_SUBJECT = FIELD_PREFIX + "_subject";

    /**
     * Configuration keys
     */

    // Easy trick to not allow debugging without changing the logger level but instead using a configuration key
    private static final String KEY_DEBUG = "debug";

    private static final String KEY_SMTP_SERVER = "smtp.server";
    private static final String KEY_SMTP_PORT = "smtp.port";
    private static final String KEY_SMTP_SECURITY_USERNAME = "smtp.security.username";
    private static final String KEY_SMTP_SECURITY_PASSWORD = "smtp.security.password";
    private static final String KEY_SMTP_SECURITY_SSL = "smtp.security.ssl";

    private static final String KEY_MAIL_TO = "mail.to";
    private static final String KEY_MAIL_FROM_ADDRESS = "mail.from.address";
    private static final String KEY_MAIL_FROM_NAME = "mail.from.name";
    private static final String KEY_MAIL_BOUNCE_ADDRESS = "mail.bounce.address";
    private static final String KEY_MAIL_REPLYTO_ADDRESS = "mail.replyto.address";
    private static final String KEY_MAIL_SUBJECT = "mail.subject";

    private static final String KEY_ALLOW_OVERWRITE = "allow_overwrite";

    private static final String KEY_HTML_TEMPLATE = "html.template";

    public static final PropertyDescriptor DEBUG = new PropertyDescriptor.Builder().name(KEY_DEBUG)
            .description("Enable debug. If enabled, debug information are written to stdout.")
            .addValidator(StandardValidators.BOOLEAN_VALIDATOR).required(false).defaultValue("false").build();

    public static final PropertyDescriptor SMTP_SERVER = new PropertyDescriptor.Builder().name(KEY_SMTP_SERVER)
            .description("FQDN, hostname or IP address of the SMTP server to use.").required(true).build();

    public static final PropertyDescriptor SMTP_PORT = new PropertyDescriptor.Builder().name(KEY_SMTP_PORT)
            .description("TCP port number of the SMTP server to use.")
            .addValidator(StandardValidators.PORT_VALIDATOR).defaultValue("25").required(false).build();

    public static final PropertyDescriptor SMTP_SECURITY_USERNAME = new PropertyDescriptor.Builder()
            .name(KEY_SMTP_SECURITY_USERNAME).description("SMTP username.").required(false).build();

    public static final PropertyDescriptor SMTP_SECURITY_PASSWORD = new PropertyDescriptor.Builder()
            .name(KEY_SMTP_SECURITY_PASSWORD).description("SMTP password.").required(false).build();

    public static final PropertyDescriptor SMTP_SECURITY_SSL = new PropertyDescriptor.Builder()
            .name(KEY_SMTP_SECURITY_SSL).description("Use SSL under SMTP or not (SMTPS). Default is false.")
            .required(false).addValidator(StandardValidators.BOOLEAN_VALIDATOR).defaultValue("false").build();

    public static final PropertyDescriptor MAIL_FROM_ADDRESS = new PropertyDescriptor.Builder()
            .name(KEY_MAIL_FROM_ADDRESS).description("Valid mail sender email address.").required(true).build();

    public static final PropertyDescriptor MAIL_FROM_NAME = new PropertyDescriptor.Builder()
            .name(KEY_MAIL_FROM_NAME).description("Mail sender name.").required(false).build();

    public static final PropertyDescriptor MAIL_BOUNCE_ADDRESS = new PropertyDescriptor.Builder()
            .name(KEY_MAIL_BOUNCE_ADDRESS)
            .description(
                    "Valid bounce email address (where error mail is sent if the mail is refused by the recipient"
                            + " server).")
            .required(true).build();

    public static final PropertyDescriptor MAIL_REPLYTO_ADDRESS = new PropertyDescriptor.Builder()
            .name(KEY_MAIL_REPLYTO_ADDRESS).description("Reply to email address.").required(false).build();

    public static final PropertyDescriptor MAIL_SUBJECT = new PropertyDescriptor.Builder().name(KEY_MAIL_SUBJECT)
            .description("Mail subject.").required(false).defaultValue(DEFAULT_SUBJECT).build();

    public static final PropertyDescriptor MAIL_TO = new PropertyDescriptor.Builder().name(KEY_MAIL_TO)
            .description("Comma separated list of email recipients. If not set, the record must have a "
                    + FIELD_MAIL_TO + " field and " + KEY_ALLOW_OVERWRITE + " configuration key should be true.")
            .required(false).build();

    public static final PropertyDescriptor ALLOW_OVERWRITE = new PropertyDescriptor.Builder()
            .name(KEY_ALLOW_OVERWRITE)
            .description("If true, allows to overwrite processor configuration with special record fields ("
                    + FIELD_MAIL_TO + ", " + FIELD_MAIL_FROM_ADDRESS + ", " + FIELD_MAIL_FROM_NAME + ", "
                    + FIELD_MAIL_BOUNCE_ADDRESS + ", " + FIELD_MAIL_REPLYTO_ADDRESS + ", " + FIELD_MAIL_SUBJECT
                    + "). If false, special record fields are ignored and only processor configuration keys are used.")
            .required(false).addValidator(StandardValidators.BOOLEAN_VALIDATOR).defaultValue("true").build();

    public static final PropertyDescriptor HTML_TEMPLATE = new PropertyDescriptor.Builder().name(KEY_HTML_TEMPLATE)
            .description("HTML template to use. It is used when the incoming record contains a "
                    + FIELD_MAIL_USE_TEMPLATE
                    + " field. The template may contain some parameters. The parameter format in the template is of the"
                    + " form ${xxx}. For instance ${param_user} in the template means that a field named"
                    + " param_user must be present in the record and its value will replace the ${param_user} string"
                    + " in the HTML template when the mail will be sent. If some parameters are declared in the template"
                    + ", everyone of them must be present in the record as fields, otherwise the record will generate"
                    + " an error record. If an incoming record contains a " + FIELD_MAIL_USE_TEMPLATE
                    + " field, a template"
                    + " must be present in the configuration and the HTML mail format will be used. If the record"
                    + " also contains a " + FIELD_MAIL_TEXT
                    + " field, its content will be used as an alternative text"
                    + " message to be used in the mail reader program of the recipient if it does not supports HTML.")
            .required(false).build();

    @Override
    public void init(final ProcessContext context) {
        logger.debug("Initializing Mailer Processor");
    }

    @Override
    public List<PropertyDescriptor> getSupportedPropertyDescriptors() {

        final List<PropertyDescriptor> descriptors = new ArrayList<>();
        descriptors.add(DEBUG);
        descriptors.add(SMTP_SERVER);
        descriptors.add(SMTP_PORT);
        descriptors.add(SMTP_SECURITY_USERNAME);
        descriptors.add(SMTP_SECURITY_PASSWORD);
        descriptors.add(SMTP_SECURITY_SSL);
        descriptors.add(MAIL_FROM_ADDRESS);
        descriptors.add(MAIL_FROM_NAME);
        descriptors.add(MAIL_BOUNCE_ADDRESS);
        descriptors.add(MAIL_REPLYTO_ADDRESS);
        descriptors.add(MAIL_SUBJECT);
        descriptors.add(MAIL_TO);
        descriptors.add(ALLOW_OVERWRITE);
        descriptors.add(HTML_TEMPLATE);

        return Collections.unmodifiableList(descriptors);
    }

    @Override
    public Collection<Record> process(ProcessContext context, Collection<Record> records) {
        if (debug) {
            logger.info("Mailer Processor records input: " + records);
        }

        final Collection<Record> failedRecords = new ArrayList<>();

        /**
         * Transform the records into mails and send them
         */
        for (Record record : records) {
            String mailText = getStringField(record, FIELD_MAIL_TEXT);
            boolean text = (mailText != null);
            String mailHtml = getStringField(record, FIELD_MAIL_HTML);
            boolean html = (mailHtml != null);
            Field mailUseTemplate = record.getField(FIELD_MAIL_USE_TEMPLATE);
            boolean useTemplate = (mailUseTemplate != null);

            if (html || text || useTemplate) {
                // Ok, there is a mail to send. First retrieve some potential embedded overwritten configuration fields 

                String[] finalMailTos = mailTos;
                String finalMailFromAddress = mailFromAddress;
                String finalMailFromName = mailFromName;
                String finalMailBounceAddress = mailBounceAddress;
                String finalMailReplyToAddress = mailReplyToAddress;
                String finalMailSubject = mailSubject;

                /**
                 * Overwrite some variables with special fields in the record if any and this is allowed
                 */
                if (allowFieldsOverwriting) {
                    String recordMailFromAddress = getStringField(record, FIELD_MAIL_FROM_ADDRESS);
                    if (recordMailFromAddress != null) {
                        finalMailFromAddress = recordMailFromAddress;
                    }

                    String recordMailFromName = getStringField(record, FIELD_MAIL_FROM_NAME);
                    if (recordMailFromName != null) {
                        finalMailFromName = recordMailFromName;
                    }

                    String recordMailBounceAddress = getStringField(record, FIELD_MAIL_BOUNCE_ADDRESS);
                    if (recordMailBounceAddress != null) {
                        finalMailBounceAddress = recordMailBounceAddress;
                    }

                    String recordMailReplyToAddress = getStringField(record, FIELD_MAIL_REPLYTO_ADDRESS);
                    if (recordMailReplyToAddress != null) {
                        finalMailReplyToAddress = recordMailReplyToAddress;
                    }

                    String recordMailSubject = getStringField(record, FIELD_MAIL_SUBJECT);
                    if (recordMailSubject != null) {
                        finalMailSubject = recordMailSubject;
                    }

                    String recordMailTo = getStringField(record, FIELD_MAIL_TO);
                    if (recordMailTo != null) {
                        finalMailTos = parseMailTo(recordMailTo);
                    }
                }

                if (finalMailFromAddress == null) {
                    record.addError(ProcessError.UNKNOWN_ERROR.getName(), "No From address defined");
                    failedRecords.add(record);
                    continue;
                }

                if (finalMailBounceAddress == null) {
                    record.addError(ProcessError.UNKNOWN_ERROR.getName(), "No Bounce address defined");
                    failedRecords.add(record);
                    continue;
                }

                if (html || useTemplate) {
                    if (html && useTemplate) {
                        record.addError(ProcessError.UNKNOWN_ERROR.getName(),
                                "Record has both " + FIELD_MAIL_USE_TEMPLATE + " and " + FIELD_MAIL_HTML
                                        + " fields. Only one of them is expected.");
                        failedRecords.add(record);
                        continue;
                    }

                    // HTML mail
                    try {

                        /**
                         * Create and fill the mail
                         */

                        ImageHtmlEmail htmlEmail = new ImageHtmlEmail();

                        // Set From info
                        if (finalMailFromName != null) {
                            htmlEmail.setFrom(finalMailFromAddress, finalMailFromName);
                        } else {
                            htmlEmail.setFrom(finalMailFromAddress);
                        }

                        htmlEmail.setBounceAddress(finalMailBounceAddress);

                        if (finalMailReplyToAddress != null) {
                            htmlEmail.addReplyTo(finalMailReplyToAddress);
                        }

                        htmlEmail.setSubject(finalMailSubject);

                        // Allow to retrieve embedded images of the html template from the classpath (jar files)
                        htmlEmail.setDataSourceResolver(new DataSourceClassPathResolver());

                        // Compute final HTML body from template and record fields
                        String htmlBody = null;
                        if (useTemplate) {
                            htmlBody = createHtml(record);
                            if (htmlBody == null) {
                                // Error. Error message has already been set in createHtml: just add to failed record and
                                // continue with next record
                                failedRecords.add(record);
                                continue;
                            }
                        } else {
                            // html
                            htmlBody = mailHtml;
                        }

                        htmlEmail.setHtmlMsg(htmlBody);

                        // Add alternative text mail if any defined
                        if (text) {
                            htmlEmail.setTextMsg(mailText);
                        }

                        // Set To info
                        if (finalMailTos.length == 0) {
                            record.addError(ProcessError.UNKNOWN_ERROR.getName(), "No mail recipient.");
                            failedRecords.add(record);
                            continue;
                        }
                        for (String mailTo : finalMailTos) {
                            htmlEmail.addTo(mailTo);
                        }

                        /**
                         * Set sending parameters
                         */

                        htmlEmail.setHostName(smtpServer);
                        htmlEmail.setSmtpPort(smtpPort);
                        if ((smtpSecurityUsername != null) && (smtpSecurityPassword != null)) {
                            htmlEmail.setAuthentication(smtpSecurityUsername, smtpSecurityPassword);
                        }
                        htmlEmail.setSSLOnConnect(smtpSecuritySsl);

                        // Send the mail
                        htmlEmail.send();
                    } catch (EmailException ex) {
                        record.addError(ProcessError.UNKNOWN_ERROR.getName(),
                                "Unable to send email: " + ex.getMessage());
                        failedRecords.add(record);
                    }
                } else {
                    // Only text mail
                    try {

                        /**
                         * Create and fill the mail
                         */

                        SimpleEmail textEmail = new SimpleEmail();

                        // Set From info
                        if (finalMailFromName != null) {
                            textEmail.setFrom(finalMailFromAddress, finalMailFromName);
                        } else {
                            textEmail.setFrom(finalMailFromAddress);
                        }

                        textEmail.setBounceAddress(finalMailBounceAddress);
                        if (finalMailReplyToAddress != null) {
                            textEmail.addReplyTo(finalMailReplyToAddress);
                        }

                        textEmail.setSubject(finalMailSubject);
                        textEmail.setMsg(mailText);

                        // Set To info
                        if (finalMailTos.length == 0) {
                            record.addError(ProcessError.UNKNOWN_ERROR.getName(), "No mail recipient.");
                            failedRecords.add(record);
                            continue;
                        }
                        for (String mailTo : finalMailTos) {
                            textEmail.addTo(mailTo);
                        }

                        /**
                         * Set sending parameters
                         */

                        textEmail.setHostName(smtpServer);
                        textEmail.setSmtpPort(smtpPort);
                        if ((smtpSecurityUsername != null) && (smtpSecurityPassword != null)) {
                            textEmail.setAuthentication(smtpSecurityUsername, smtpSecurityPassword);
                        }
                        textEmail.setSSLOnConnect(smtpSecuritySsl);

                        // Send the mail
                        textEmail.send();
                    } catch (EmailException ex) {
                        record.addError(ProcessError.UNKNOWN_ERROR.getName(),
                                "Unable to send email: " + ex.getMessage());
                        failedRecords.add(record);
                    }
                }
            }
        }

        if (debug) {
            logger.info("Mailer Processor records output: " + records);
        }
        return failedRecords;
    }

    /**
     * Create the HTML message from the html template and the potential parameters in the record
     * @param record The record to inspect to retrieve the parameters of the template (from the record fields)
     * @return The filled HTML template or null if an error occurred
     */
    private String createHtml(Record record) {
        // Special case if no parameters are expected, just return the template
        int nParams = parameterNames.size();
        if (nParams == 0) {
            // Template expects no args, just return it as message body.
            // For instance may be used if body is always the same but some mail things change like subject or mailto...
            return htmlTemplate;
        }

        Object[] templateParams = new Object[nParams];
        int index = 0;
        for (String parameter : parameterNames) {
            // Find the parameter as a field in the record
            Field field = record.getField(parameter);
            if (field == null) {
                // Field not found, error
                record.addError(ProcessError.UNKNOWN_ERROR.getName(),
                        "Could not find expected HTML template parameter <" + parameter
                                + "> as field in the record.");
                return null;
            }

            templateParams[index] = field.getRawValue();

            index++;
        }

        // Now we have the parameters, fill the form and return matching HTML body
        return htmlForm.format(templateParams);
    }

    /**
     * Retrieve the record field value
     * @param fieldName The name of the string field
     * @return The value of the field or null if the field is not present in the record
     */
    private String getStringField(Record record, String fieldName) {
        Field field = record.getField(fieldName);
        if (field != null) {
            return field.asString();
        } else {
            return null;
        }
    }

    @Override
    protected Collection<ValidationResult> customValidate(ValidationContext context) {
        final List<ValidationResult> validationResults = new ArrayList<>(super.customValidate(context));

        logger.debug("customValidate");

        /**
         * If the mail.to configuration key is not set, allow_overwrite should be true so that we know to who we must
         * send the mail (record must then hold a mail_to field)
         */
        if (!context.getPropertyValue(MAIL_TO).isSet()) {

            if (!context.getPropertyValue(ALLOW_OVERWRITE).asBoolean()) {
                validationResults.add(new ValidationResult.Builder().explanation(
                        "if " + MAIL_TO.getName() + " is not set, " + ALLOW_OVERWRITE.getName() + " must be true"
                                + " so that the record holds a " + FIELD_MAIL_TO + " field that is used.")
                        .valid(false).build());
            }
        }

        /**
         * Both username and password must be set or none of them
         */
        if ((context.getPropertyValue(SMTP_SECURITY_USERNAME).isSet()
                && !context.getPropertyValue(SMTP_SECURITY_PASSWORD).isSet())
                || (!context.getPropertyValue(SMTP_SECURITY_USERNAME).isSet()
                        && context.getPropertyValue(SMTP_SECURITY_PASSWORD).isSet())) {
            validationResults.add(new ValidationResult.Builder()
                    .explanation("Both " + SMTP_SECURITY_USERNAME.getName() + " and "
                            + SMTP_SECURITY_PASSWORD.getName() + " should be set" + " or none of them.")
                    .valid(false).build());
        }

        return validationResults;
    }

    @Override
    public void onPropertyModified(PropertyDescriptor descriptor, String oldValue, String newValue) {

        logger.debug("property {} value changed from {} to {}", descriptor.getName(), oldValue, newValue);

        /**
         * Handle the DEBUG property
         */
        if (descriptor.equals(DEBUG)) {
            if (newValue != null) {
                if (newValue.equalsIgnoreCase("true")) {
                    debug = true;
                }
            } else {
                debug = false;
            }
        }

        /**
         * Handle the SMTP_SERVER property
         */
        if (descriptor.equals(SMTP_SERVER)) {
            smtpServer = newValue;
        }

        /**
         * Handle the SMTP_PORT property
         */
        if (descriptor.equals(SMTP_PORT)) {
            int port = -1;
            try {
                port = new Integer(newValue);
            } catch (NumberFormatException e) {
                // TODO something to do?
            }

            smtpPort = port;
        }

        /**
         * Handle the SMTP_SECURITY_USERNAME property
         */
        if (descriptor.equals(SMTP_SECURITY_USERNAME)) {
            smtpSecurityUsername = newValue;
        }

        /**
         * Handle the SMTP_SECURITY_PASSWORD property
         */
        if (descriptor.equals(SMTP_SECURITY_PASSWORD)) {
            smtpSecurityPassword = newValue;
        }

        /**
         * Handle the SMTP_SECURITY_SSL property
         */
        if (descriptor.equals(SMTP_SECURITY_SSL)) {
            if (newValue != null) {
                if (newValue.equalsIgnoreCase("true")) {
                    smtpSecuritySsl = true;
                }
            } else {
                smtpSecuritySsl = false;
            }
        }

        /**
         * Handle the MAIL_FROM_ADDRESS property
         */
        if (descriptor.equals(MAIL_FROM_ADDRESS)) {
            mailFromAddress = newValue;
        }

        /**
         * Handle the MAIL_FROM_NAME property
         */
        if (descriptor.equals(MAIL_FROM_NAME)) {
            mailFromName = newValue;
        }

        /**
         * Handle the MAIL_BOUNCE_ADDRESS property
         */
        if (descriptor.equals(MAIL_BOUNCE_ADDRESS)) {
            mailBounceAddress = newValue;
        }

        /**
         * Handle the MAIL_REPLYTO_ADDRESS property
         */
        if (descriptor.equals(MAIL_REPLYTO_ADDRESS)) {
            mailReplyToAddress = newValue;
        }

        /**
         * Handle the MAIL_SUBJECT property
         */
        if (descriptor.equals(MAIL_SUBJECT)) {
            mailSubject = newValue;
        }

        /**
         * Handle the MAIL_TO property
         */
        if (descriptor.equals(MAIL_TO)) {
            mailTos = parseMailTo(newValue);
        }

        /**
         * Handle the ALLOW_OVERWRITE property
         */
        if (descriptor.equals(ALLOW_OVERWRITE)) {
            if (newValue != null) {
                if (newValue.equalsIgnoreCase("true")) {
                    allowFieldsOverwriting = true;
                }
            } else {
                allowFieldsOverwriting = false;
            }
        }

        /**
         * Handle the HTML_TEMPLATE property
         */
        if (descriptor.equals(HTML_TEMPLATE)) {
            htmlTemplate = newValue;
            prepareTemplateAndParameters(htmlTemplate);
        }

        if (debug) {
            displayConfig();
        }
    }

    /**
     * This parses the HTML template to
     * - get the list of needed parameters (${xxx} parameters in the template, so get the foo, bar etc variables)
     * - create a usable template using the MessageFormat system (have strings with ${0}, ${1} instead of ${foo}, ${bar})
     * @param htmlTemplate
     */
    private void prepareTemplateAndParameters(String htmlTemplate) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)}"); // Detect ${...} sequences
        Matcher matcher = pattern.matcher(htmlTemplate);

        // To construct a new template with ${0}, ${1} fields ..
        StringBuilder buffer = new StringBuilder();
        int previousStart = 0;
        int currentParameterIndex = 0;

        // Loop through the parameters in the template
        while (matcher.find()) {
            String parameter = matcher.group(1);

            String stringBeforeCurrentParam = htmlTemplate.substring(previousStart, matcher.start());

            // Add string before parameter
            buffer.append(stringBeforeCurrentParam);
            // Replace parameter with parameter index
            buffer.append("{" + currentParameterIndex + "}");

            // Save current parameter name in the list
            parameterNames.add(parameter);

            previousStart = matcher.end();
            currentParameterIndex++;
        }

        // Add string after the last parameter
        String stringAfterLastParam = htmlTemplate.substring(previousStart);
        buffer.append(stringAfterLastParam);

        // Create the HTML form
        htmlForm = new MessageFormat(buffer.toString());
    }

    /**
     * Parses content of the MAIL_TO configuration property
     */
    private String[] parseMailTo(String mailToStr) {
        String[] localMaiTos = mailToStr.split(",");
        String[] result = new String[localMaiTos.length];
        for (int i = 0; i < localMaiTos.length; i++) {
            result[i] = localMaiTos[i].trim();
        }
        return result;
    }

    /**
     * Displays processor configuration
     */
    private void displayConfig() {
        StringBuilder sb = new StringBuilder("Mailer Processor configuration:");
        sb.append("\n" + SMTP_SERVER.getName() + ": " + smtpServer);
        sb.append("\n" + SMTP_PORT.getName() + ": " + smtpPort);
        sb.append("\n" + SMTP_SECURITY_USERNAME.getName() + ": " + smtpSecurityUsername);
        sb.append("\n" + SMTP_SECURITY_PASSWORD.getName() + ": " + smtpSecurityPassword);
        sb.append("\n" + SMTP_SECURITY_SSL.getName() + ": " + smtpSecuritySsl);
        sb.append("\n" + MAIL_FROM_ADDRESS.getName() + ": " + mailFromAddress);
        sb.append("\n" + MAIL_FROM_NAME.getName() + ": " + mailFromName);
        sb.append("\n" + MAIL_BOUNCE_ADDRESS.getName() + ": " + mailBounceAddress);
        sb.append("\n" + MAIL_REPLYTO_ADDRESS.getName() + ": " + mailReplyToAddress);
        sb.append("\n" + MAIL_SUBJECT.getName() + ": " + mailSubject);
        sb.append("\n" + MAIL_TO.getName() + ":");
        for (String mailTo : mailTos) {
            sb.append(" " + mailTo);
        }
        sb.append("\n" + ALLOW_OVERWRITE.getName() + ": " + allowFieldsOverwriting);
        sb.append("\n" + HTML_TEMPLATE.getName() + ": " + htmlTemplate);
        logger.info(sb.toString());
    }
}