/* ====================================================================
* Limited Evaluation License:
*
* This software is open source, but licensed. The license with this package
* is an evaluation license, which may not be used for productive systems. If
* you want a full license, please contact us.
*
* The exclusive owner of this work is the OpenRate project.
* This work, including all associated documents and components
* is Copyright of the OpenRate project 2006-2015.
*
* The following restrictions apply unless they are expressly relaxed in a
* contractual agreement between the license holder or one of its officially
* assigned agents and you or your organisation:
*
* 1) This work may not be disclosed, either in full or in part, in any form
* electronic or physical, to any third party. This includes both in the
* form of source code and compiled modules.
* 2) This work contains trade secrets in the form of architecture, algorithms
* methods and technologies. These trade secrets may not be disclosed to
* third parties in any form, either directly or in summary or paraphrased
* form, nor may these trade secrets be used to construct products of a
* similar or competing nature either by you or third parties.
* 3) This work may not be included in full or in part in any application.
* 4) You may not remove or alter any proprietary legends or notices contained
* in or on this work.
* 5) This software may not be reverse-engineered or otherwise decompiled, if
* you received this work in a compiled form.
* 6) This work is licensed, not sold. Possession of this software does not
* imply or grant any right to you.
* 7) You agree to disclose any changes to this work to the copyright holder
* and that the copyright holder may include any such changes at its own
* discretion into the work
* 8) You agree not to derive other works from the trade secrets in this work,
* and that any such derivation may make you liable to pay damages to the
* copyright holder
* 9) You agree to use this software exclusively for evaluation purposes, and
* that you shall not use this software to derive commercial profit or
* support your business or personal activities.
*
* This software is provided "as is" and any expressed or impled warranties,
* including, but not limited to, the impled warranties of merchantability
* and fitness for a particular purpose are disclaimed. In no event shall
* The OpenRate Project or its officially assigned agents be liable to any
* direct, indirect, incidental, special, exemplary, or consequential damages
* (including but not limited to, procurement of substitute goods or services;
* Loss of use, data, or profits; or any business interruption) however caused
* and on theory of liability, whether in contract, strict liability, or tort
* (including negligence or otherwise) arising in any way out of the use of
* this software, even if advised of the possibility of such damage.
* This software contains portions by The Apache Software Foundation, Robert
* Half International.
* ====================================================================
*/
package OpenRate.resource.notification;
import OpenRate.OpenRate;
import OpenRate.configurationmanager.ClientManager;
import OpenRate.configurationmanager.IEventInterface;
import OpenRate.exception.InitializationException;
import OpenRate.exception.ProcessingException;
import OpenRate.resource.IResource;
import OpenRate.utils.PropertyUtils;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
/**
* The email notification cache simplifies the process of sending email messages
* from OpenRate to alert in (Near) Real Time about things which might need
* urgent intervention, such as fraud detection.
*
* There are two modes of operation:
* - Asynchronous: This is the normal mode of operation, in which we send the
* mail in the separate mailer thread. This means that we do not hold up
* the processing in order to wait for the mail server. The mailer
* thread then sends the mails in the background.
*
* - Synchronous: This sends a mail and blocks until the response is received.
* This is primarily intended for situations where we want to send a message
* from the framework, and in this case we can afford to do it the slow way.
*/
public class EmailNotificationCache implements IResource, IEventInterface
{
// List of Services that this Client supports
private final static String SERVICE_QUEUE_LENGTH = "MailQueueLength";
/**
* This is the key name we will use for referencing this object from the
* Resource context
*/
public static final String RESOURCE_KEY = "EmailNotificationCache";
// This is the symbolic name of the resource
private final String symbolicName = RESOURCE_KEY;
// The mail address we are to despatch to
private String mailTo;
// The mail address we are to despatch copies to
private String carbonCopy;
// The mail address we are to send from
private String mailFrom;
// The mail session, with the parameters pre-configured
private Session mailSession;
private InternetAddress FromAddress;
private InternetAddress[] ToAddresses;
private InternetAddress[] CCAddresses;
// Variables used to configure the mail server
private String server;
private int port;
private String userName;
private String passWord;
private boolean SSLAuthentication = false;
// Used to perform authenticated login
private Authenticator authenticator = null;
// The autonomous emailer thread
private Thread emailerThread;
private EmailerThread emailer;
// Used to send the start and stop messages
boolean notifyStartAndStop = false;
// Used to identify the source of the notification
String notificationInstanceID;
// used to simplify logging and exception handling
public String message;
/**
* constructor
*/
public EmailNotificationCache()
{
}
/**
* Perform whatever initialisation is required of the resource.
* This method should only be called once per application instance.
*
* @param ResourceName The name of the resource in the properties
*/
@Override
public void init(String ResourceName) throws InitializationException
{
if (ResourceName.equals(RESOURCE_KEY) == false)
{
message = "The linked buffer cache must be called " + RESOURCE_KEY;
throw new InitializationException(message,getSymbolicName());
}
// Initialise the mail subsystem
server = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"Server","None");
if (server.equalsIgnoreCase("None"))
{
message = "Property <Server> not defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
String sPort = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"Port","None");
if (sPort.equalsIgnoreCase("None"))
{
message = "Property <Port> not defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
// show some life
System.out.println(" Mail server <" + server + ">");
// Convert the string value
try
{
port = Integer.parseInt(sPort);
}
catch (NumberFormatException nfe)
{
message = "Property <Port> was not numeric. Received: <" + sPort + ">";
throw new InitializationException(message,getSymbolicName());
}
System.out.println(" Port <" + port + ">");
userName = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"UserName","None");
if (userName.equalsIgnoreCase("None"))
{
message = "Property <UserName> not defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
else
{
System.out.println(" User Name <" + userName + ">");
}
passWord = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"PassWord","None");
if (passWord.equalsIgnoreCase("None"))
{
message = "Property <PassWord> not defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
else
{
System.out.println(" Password set <*******>");
}
mailTo = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"MailTo","None");
if (mailTo.equalsIgnoreCase("None"))
{
message = "Property <mailTo> not defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
mailFrom = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"MailFrom","None");
if (mailFrom.equalsIgnoreCase("None"))
{
message = "Property <MailFrom> not defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
// Create the mail from address
try
{
String mailFromName = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"MailFromName","None");
if (mailFromName.equalsIgnoreCase("None"))
{
FromAddress = new InternetAddress(mailFrom);
}
else
{
FromAddress = new InternetAddress(mailFrom, mailFromName);
}
}
catch (AddressException ae)
{
message = "Invalid email address <MailFrom> defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
catch (UnsupportedEncodingException ex)
{
message = "Invalid email name <MailFromName> defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
// Create the default mail to address
try
{
String mailToName = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"MailToName","None");
// manage multiple addresses
String []tmpToAddresses = mailTo.split(";|,");
ToAddresses = new InternetAddress[tmpToAddresses.length];
for (int idx = 0 ; idx < tmpToAddresses.length ; idx++ )
{
if (mailToName.equalsIgnoreCase("None"))
{
ToAddresses[idx] = new InternetAddress(tmpToAddresses[idx]);
}
else
{
ToAddresses[idx] = new InternetAddress(tmpToAddresses[idx], mailToName);
}
}
}
catch (AddressException ae)
{
message = "Invalid email address <MailTo> defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
catch (UnsupportedEncodingException ex)
{
message = "Invalid email name <MailToName> defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
// Get the optional CC address
carbonCopy = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"MailCC","None");
if (carbonCopy.equalsIgnoreCase("None") == false)
{
// Create the default mail to address
try
{
String carbonCopyName = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"MailCCName","None");
String []tmpCCAddresses = carbonCopy.split(";|,");
CCAddresses = new InternetAddress[tmpCCAddresses.length];
for (int idx = 0 ; idx < tmpCCAddresses.length ; idx++ )
{
if (carbonCopyName.equalsIgnoreCase("None"))
{
CCAddresses[idx] = new InternetAddress(tmpCCAddresses[idx]);
}
else
{
CCAddresses[idx] = new InternetAddress(tmpCCAddresses[idx], carbonCopyName);
}
}
}
catch (AddressException ae)
{
message = "Invalid email address <MailTo> defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
catch (UnsupportedEncodingException ex)
{
message = "Invalid email name <MailToName> defined for resource <" + ResourceName + ">";
throw new InitializationException(message,getSymbolicName());
}
}
Properties props = System.getProperties();
props.put("mail.smtp.host", server);
props.put("mail.smtp.port", port);
if (userName != null && userName.length() > 0)
{
props.put("mail.smtp.auth", "true");
authenticator = new SMTPAuthenticator();
}
SSLAuthentication = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"SSLAuthentication","None").equalsIgnoreCase("true");
if (SSLAuthentication)
{
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory");
}
// get the instance id so we know who is sending
notificationInstanceID = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"NotificationID","");
System.out.println(" Instance ID <" + notificationInstanceID + ">");
mailSession = Session.getInstance(props, authenticator);
String debugMail = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"MailDebug","False");
Boolean debug = (debugMail.equalsIgnoreCase("true"));
// extended debugging
if(debug)
{
OpenRate.getOpenRateFrameworkLog().debug("Mail debugging turned on.");
mailSession.setDebug(true);
}
// get the mailer
emailer = new EmailerThread();
// Pass the mail session (for authentication) down to the handler thread
emailer.setMailSession(mailSession);
// set the symbolic name into the thread
emailer.setSymbolicName(getSymbolicName());
// start the mailer thread
Thread emailerProcess = new Thread(emailer,"EmailNotificationThread");
// Start the listener
emailerProcess.start();
// if we have a start mail configured, send it
notifyStartAndStop = PropertyUtils.getPropertyUtils().getResourcePropertyValueDef(ResourceName,"NotifyStartAndStop","false").equalsIgnoreCase("true");
if(notifyStartAndStop)
{
System.out.println(" Sending startup notification mail");
boolean sentOK = despatchMailInternalImmediate("EmailNotificationCacheStarted","The OpenRate email notification cache has started");
if (sentOK == false)
{
// problems sending the mail - abort
message = "Failed to send startup email. Aborting.";
throw new InitializationException(message,getSymbolicName());
}
}
}
/**
* Add a mail to the mail queue for despatch. This sends to the default
* email address. The sending of the mail is asynchronous.
*
* @param mailSubject the subject we will send the mail with
* @param mailBody the message body we will send the mail with
* @return true if the message was queued OK, otherwise false
*/
public boolean despatchMailInternal(String mailSubject, String mailBody)
{
// set the subject for internal messages
if (notificationInstanceID.isEmpty() == false)
{
mailSubject = "[" + notificationInstanceID + "] " + mailSubject;
}
// Fill in the blanks and send
return despatchMailExternal(mailSubject,
mailBody,
FromAddress,
ToAddresses,
CCAddresses);
}
/**
* Add a mail to the mail queue for despatch. This sends to the defined
* external email address. The sending of the mail is asynchronous.
*
* @param mailSubject the subject we will send the mail with
* @param mailBody the message body we will send the mail with
* @param fromAddress the mail address that will be used for the mail
* @param toAddresses list of addresses to be sent to
* @param ccAddresses list of addresses that should get a copy
* @return true if the message was queued OK, otherwise false
*/
public boolean despatchMailExternal(String mailSubject,
String mailBody,
InternetAddress fromAddress,
InternetAddress[] toAddresses,
InternetAddress[] ccAddresses)
{
MimeMessage msg;
try
{
// create a message
msg = new MimeMessage(mailSession);
// ========= the from address (there can be only one) ========
msg.setFrom(fromAddress);
// ========= the to addresses (there can be more than one) ========
msg.setRecipients(Message.RecipientType.TO, toAddresses);
// ========= the CC addresses (there can be more than one) ========
msg.setRecipients(Message.RecipientType.CC, ccAddresses);
// ========= set the subject ========
msg.setSubject(mailSubject);
// ========= the body ========
MimeBodyPart mbp1 = new MimeBodyPart();
mbp1.setText(mailBody);
// ========= the date ========
msg.setSentDate(Calendar.getInstance().getTime());
// create the Multipart and add its parts to it
Multipart mp = new MimeMultipart();
mp.addBodyPart(mbp1);
// add the standard text
msg.setContent(mp);
}
catch (MessagingException ex)
{
OpenRate.getOpenRateFrameworkLog().error("Error sending message");
return false;
}
// Add the mail to the queue
emailer.queueMessage(msg);
// Done!!!
return true;
}
/**
* Despatch a mail synchronously. This sends to the configured email address.
* This is designed as a low volume process and as such we open a connection
* each time we use it. This means that the sender waits for the mail to be
* despatched before returning control.
*
* @param mailSubject the subject we will send the mail with
* @param mailBody the message body we will send the mail with
* @return true if the despatch was successful, otherwise false
*/
public boolean despatchMailInternalImmediate(String mailSubject, String mailBody)
{
MimeMessage msg;
try
{
// create a message
msg = new MimeMessage(mailSession);
// ========= the from address (there can be only one) ========
msg.setFrom(FromAddress);
// ========= the to addresses (there can be more than one) ========
msg.setRecipients(Message.RecipientType.TO, ToAddresses);
// ========= the CC addresses (there can be more than one) ========
msg.setRecipients(Message.RecipientType.CC, CCAddresses);
// ========= set the subject ========
if (notificationInstanceID.isEmpty())
{
msg.setSubject(mailSubject);
}
else
{
msg.setSubject("[" + notificationInstanceID + "] " + mailSubject);
}
// ========= the body ========
MimeBodyPart mbp1 = new MimeBodyPart();
mbp1.setText(mailBody);
// ========= the date ========
msg.setSentDate(Calendar.getInstance().getTime());
// create the Multipart and add its parts to it
Multipart mp = new MimeMultipart();
mp.addBodyPart(mbp1);
// add the standard text
msg.setContent(mp);
}
catch (MessagingException ex)
{
OpenRate.getOpenRateFrameworkLog().error("Error sending message");
return false;
}
try {
// Add the mail to the queue
emailer.despatchEmailSync(msg);
}
catch (ProcessingException ex) {
OpenRate.getFrameworkExceptionHandler().reportException(ex);
}
// Done!!!
return true;
}
// -----------------------------------------------------------------------------
// -------------------- Start of local utility functions -----------------------
// -----------------------------------------------------------------------------
/**
* Perform any required cleanup.
*/
@Override
public void close()
{
// if we have a start mail configured, send it
if(notifyStartAndStop)
{
System.out.println(" Sending shutdown notification mail");
despatchMailInternalImmediate("EmailNotificationCacheStopped","The OpenRate email notification cache has stopped");
}
System.out.println(" Stopping Mailer resource.");
// mark for closedown and wait for mailer to clear the queue
emailer.markForClosedown();
while (emailerThread != null && emailerThread.isAlive())
{
OpenRate.getOpenRateFrameworkLog().info("Waiting for emailer thread to finish. Still <" + emailer.getMessageCount() + "> mails to send.");
try
{
Thread.sleep(1000);
} catch (InterruptedException ex)
{
}
}
}
/**
* Return the resource symbolic name
*/
@Override
public String getSymbolicName()
{
return symbolicName;
}
/**
* The JavaMail authenticator object. Needed for cases where we need to perform
* authenticated logins to be able to send mails.
*/
private class SMTPAuthenticator extends javax.mail.Authenticator
{
@Override
public PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication(userName, passWord);
}
}
// -----------------------------------------------------------------------------
// ------------- Start of inherited IEventInterface functions ------------------
// -----------------------------------------------------------------------------
/**
* registerClientManager registers the client module to the ClientManager class
* which manages all the client modules available in this OpenRate Application.
*
* registerClientManager registers this class as a client of the ECI listener
* and publishes the commands that the plug in understands. The listener is
* responsible for delivering only these commands to the plug in.
*
*/
@Override
public void registerClientManager() throws InitializationException
{
//Register this Client
ClientManager.getClientManager().registerClient("Resource",getSymbolicName(), this);
//Register services for this Client
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_QUEUE_LENGTH, ClientManager.PARAM_DYNAMIC);
}
/**
* processControlEvent is the event processing hook for the External Control
* Interface (ECI). This allows interaction with the external world, for
* example turning the dumping on and off.
*/
@Override
public String processControlEvent(String Command, boolean Init, String Parameter)
{
int ResultCode = -1;
if (Command.equalsIgnoreCase(SERVICE_QUEUE_LENGTH))
{
return Integer.toString(emailer.getMessageCount());
}
// Currently this cannot handle any dynamic events
if (ResultCode == 0)
{
return "OK";
}
else
{
return "Command Not Understood";
}
}
}