/*******************************************************************************
* Copyright 2014 Miami-Dade County
*
* 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
*
* 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.sharegov.cirm.legacy;
import static org.sharegov.cirm.OWL.dataProperty;
import static org.sharegov.cirm.OWL.objectProperty;
import static org.sharegov.cirm.OWL.reasoner;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import mjson.Json;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLOntology;
import org.sharegov.cirm.BOntology;
import org.sharegov.cirm.OWL;
import org.sharegov.cirm.Refs;
import org.sharegov.cirm.rest.LegacyEmulator;
import org.sharegov.cirm.utils.ThreadLocalStopwatch;
import org.sharegov.cirm.utils.xpath.Context;
import org.sharegov.cirm.utils.xpath.Resolver;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import de.odysseus.staxon.json.JsonXMLInputFactory;
/**
* Class to apply legacy messageTemplates and builds MimeMessage(s) for email.
* It uses a json to DOM bridge to leverage xPath and XSL when evaluating
* variables. The only non-java* dependency is on staxon api to create a Dom
* from json.
*
* Call reinit() after an ontology change that affects MessageManagerConfiguration.
*
* This class is Thread Safe.
*
* @author SABBAS, hilpold
*
*/
public class MessageManager
{
public static boolean DBG = true;
public static boolean DBGVARS = false;
public static boolean DBGXML = false;
public static boolean DISABLE_SEND = false;
private static boolean INCLUDE_EXPLANATION_IN_EMAIL = true;
/**
* Send test emails to the DEFAULT_TO_ADDRESS instead of
*
* Effective only in test mode.
*/
private static boolean TEST_MODE_USE_DEFAULT_TO = true;
private static boolean FORCE_TEST_RECOVERY = false;
/**
* Uses hasLegacyBody for all templates
* TODO should be disabled in production
*/
private static boolean ALWAYS_USE_LEGACY_BODY = false;
/**
* In production mode, emails get sent to real users.
* resolved to and cc fields of the templates.
*/
public static final String MODE_PRODUCTION = "MessageManagerProductionMode";
/**
* In test mode, emails get sent to BCC users as to.
* Resolved to and cc fields of the templates will be included in the body of the message.
*/
public static final String MODE_TEST = "MessageManagerTestMode";
/**
* Used when to-field has no addresses to avoid exception.
*/
public static final String DEFAULT_TO_ADDRESS = "hilpold@miamidade.gov;sanchoo@miamidade.gov;chirino@miamidade.gov";
public static final String SR_DOM_ROOT_NODE = "sr";
public static final Pattern VAR_NAME_PATTERN = Pattern.compile("\\$\\$(.*?)\\$\\$");
public static Logger logger = Refs.logger.resolve();
private volatile ConcurrentMap<String, Object> messageVariables;
private volatile Properties configurationProperties;
private volatile Session session = null;// <prop key="mail.smtp.host">smtp.miamidade.gov</prop>
private volatile boolean initialized = false;
/**
* This is the sender address the Messagemanager uses to send emails.
*/
private volatile InternetAddress messageSenderAddress;
private static class MessageManagerHolder {
private static MessageManager instance = new MessageManager();
}
public static MessageManager get()
{
return MessageManagerHolder.instance;
}
/**
* If this is False, all emails go to real users!
* @param messageTemplate
* @return
*/
public boolean isTestMode()
{
ensureInit();
String mode = configurationProperties.getProperty("mode");
if (MODE_TEST.equals(mode))
return true;
else if (MODE_PRODUCTION.equals(mode))
return false;
else
{
System.err.println("MessageManager: No mode configured, was " + mode + " should be " + MODE_PRODUCTION + " or " + MODE_TEST);
System.err.println("MessageManager: Will use " + MODE_TEST + ", but please update ontology!");
return true;
}
}
/**
*
* @param sr : The sr to be transformed and emailed
* @param messageTemplate : The email message template defined in ontology.
* @param subject : The subject of the email
* @param to : The TO recipients of the email
* @param cc : The CC recipients of the email
* @param bcc : The BCC recipients of the email
* @return
*/
private CirmMessage createMessage(Json sr, OWLNamedIndividual messageTemplate,
String subject, String to, String cc, String bcc, String comments)
{
if (DBG) System.out.println("MessageManager: createMessage for: \r\n "+ (sr.has("iri")? sr.at("iri"): sr));
ensureInit();
CirmMessage msg = null;
try {
OWLLiteral bodyTemplate = dataProperty(messageTemplate, "legacy:hasBody");
//OWLLiteral bodyTemplate = dataProperty(messageTemplate, "legacy:hasLegacyBody"); //TODO FOR COM TEST ONLY
Map<String, String> parameters = fillParameters(sr, null, null, null, null,
bodyTemplate != null ? bodyTemplate.getLiteral() : null);
if(comments != null )
parameters.put("$$COMMENTS$$", "<Strong>Comments: </Strong>"+comments);
String body = null;
if (bodyTemplate != null)
body = applyTemplate(bodyTemplate.getLiteral(), parameters);
msg = new CirmMessage(session);
if (to != null)
msg.setRecipients(Message.RecipientType.TO, toRecipients(to));
if (cc != null)
msg.setRecipients(Message.RecipientType.CC, toRecipients(cc));
if (bcc != null)
msg.setRecipients(Message.RecipientType.BCC, toRecipients(bcc));
if(subject != null)
msg.setSubject(subject, "UTF-8");
if(body != null)
msg.setContent(body, "text/html; charset=UTF-8");
msg.setFrom(messageSenderAddress);
msg.setSentDate(new java.util.Date());
//creation explanation
msg.addExplanation("email createMessage" + messageTemplate.getIRI().getFragment());
}
catch (Exception e)
{
String boid = "";
if (sr.at("boid") != null)
{
boid = sr.at("boid").asString();
}
throw new IllegalStateException("Could not create emailMessage for sr" + boid + " with template "
+ messageTemplate, e);
}
return msg;
}
/**
*
* @param sr
* - The sr to be transformed.
* @param legacyCode
* - the legacy code for this message variable, essentially the
* owner code.
* @param messageTemplate
* - The email message template defined in the ontology.
* @return
*/
private CirmMessage createMessage(Json sr, String legacyCode, OWLNamedIndividual messageTemplate)
{
if (DBG) System.out.println("MessageManager: createMessage for: \r\n "+ (sr.has("iri")? sr.at("iri"): sr));
ensureInit();
boolean useLegacyBody = useLegacyBody(sr);
CirmMessage msg = null;
try
{
OWLNamedIndividual messageTemplateClass = OWL.individual("legacy:MessageTemplate");
OWLLiteral bccTemplate = dataProperty(messageTemplateClass, "legacy:hasBCC");
OWLLiteral bccTestTemplate = dataProperty(messageTemplateClass, "legacy:hasBCCTest");
OWLLiteral highPriorityText = dataProperty(messageTemplateClass, "legacy:hasHighPriorityText");
OWLLiteral toTemplate = dataProperty(messageTemplate, "legacy:hasTo");
OWLLiteral ccTemplate = dataProperty(messageTemplate, "legacy:hasCc");
OWLLiteral subjectTemplate = dataProperty(messageTemplate, "legacy:hasSubject");
OWLLiteral bodyTemplate;
if (useLegacyBody)
bodyTemplate = dataProperty(messageTemplate, "legacy:hasLegacyBody");
else
bodyTemplate = dataProperty(messageTemplate, "legacy:hasBody");
OWLLiteral hasHighPriority = dataProperty(messageTemplate, "legacy:hasHighPriority");
boolean highPriority = (hasHighPriority !=null && hasHighPriority.isBoolean() && hasHighPriority.parseBoolean());
Map<String, String> parameters = fillParameters(sr, legacyCode, toTemplate != null ? toTemplate
.getLiteral() : null, ccTemplate != null ? ccTemplate.getLiteral() : null,
subjectTemplate != null ? subjectTemplate.getLiteral() : null, bodyTemplate != null ? bodyTemplate
.getLiteral() : null);
String to = null;
String cc = null;
String subject = null;
String body = null;
String bcc = null;
String bccTest = null;
if (toTemplate != null)
to = applyTemplate(toTemplate.getLiteral(), parameters);
if (ccTemplate != null)
cc = applyTemplate(ccTemplate.getLiteral(), parameters);
if (subjectTemplate != null)
subject = applyTemplate(subjectTemplate.getLiteral(), parameters);
if (bodyTemplate != null)
{
//TODO String bodyTemplateStr = useLegacyBody? legacyBodyToHTML(bodyTemplate) : bodyTemplate.getLiteral();
String bodyTemplateStr = bodyTemplate.getLiteral();
boolean htmlTemplate = bodyTemplateStr.contains("$$GLOBAL_SR_TEMPLATE") || bodyTemplateStr.startsWith("<html>");
if (htmlTemplate)
{
formatMultiLineValueStringsAsHtml(parameters);
}
else
{
bodyTemplateStr = legacyBodyToHTML(bodyTemplateStr);
}
body = applyTemplate(bodyTemplateStr, parameters);
}
if (bccTemplate != null)
bcc = applyTemplate(bccTemplate.getLiteral(), parameters);
if (bccTestTemplate != null)
bccTest = applyTemplate(bccTestTemplate.getLiteral(), parameters);
//if (true || to != null || cc != null)
if (to != null || cc != null || bcc != null)
{
msg = new CirmMessage(session);
//InternetAddress sender = new InternetAddress("cirmtest@miamidade.gov");
// TODO: Test mode: For now send to some default recipients.
if (highPriority)
body = highPriorityText.getLiteral() + body;
//
// We only send to real recipients in production mode,
// but include resolved to and cc field in test emails' bodies.
//
if (isTestMode())
{
//testmode do not send to real recipients, but bcc as to only
body = getTestModeHeader(to, cc, messageTemplate) + body;
if (TEST_MODE_USE_DEFAULT_TO)
to = DEFAULT_TO_ADDRESS;
else
to = bccTest;
cc = null;
bcc = null;
}
if (to != null)
msg.setRecipients(Message.RecipientType.TO, toRecipients(to));
else
msg.setRecipients(Message.RecipientType.TO, toRecipients(DEFAULT_TO_ADDRESS));
if (cc != null)
msg.setRecipients(Message.RecipientType.CC, toRecipients(cc));
if (bcc != null)
msg.setRecipients(Message.RecipientType.BCC, toRecipients(bcc));
if (subject == null)
subject = "No subject defined (in template)!";
if (body == null)
body = "No body defined (in template)!";
msg.setFrom(messageSenderAddress);
msg.setSentDate(new java.util.Date());
msg.setSubject(subject, "UTF-8");
msg.setContent(body, "text/html; charset=UTF-8");
msg.saveChanges();
if (highPriority)
msg.setHeader("X-Priority", "1");
//creation explanation
msg.addExplanation("createMessage" + messageTemplate.getIRI().getFragment());
}
}
catch (Exception e)
{
String boid = "";
if (sr.at("boid") != null)
{
boid = sr.at("boid").asString();
}
throw new IllegalStateException("Could not create message for sr" + boid + " with template "
+ messageTemplate, e);
}
return msg;
}
/**
* Formats all non-html multi-line string values as html by adding a <br> before each \r.
* @param parameters
*/
private void formatMultiLineValueStringsAsHtml(Map<String, String> parameters) {
Set<String> keys = new HashSet<String>(parameters.keySet());
for (String key : keys) {
String value = parameters.get(key);
//If string is multiline and does not contain html or xml tags
if (value != null && !containsHtmlOrXmlTags(value)) {
if (value.contains("\r\n")) {
String newValue = value.replaceAll("\r\n", "<br>\r\n");
parameters.put(key, newValue);
} else if (value.contains("\n")) {
String newValue = value.replaceAll("\n", "<br>\n");
parameters.put(key, newValue);
} else {
//single line string, no CRLF or LF, ignore
}
}
}
}
/**
* Determines if a string contains any html or xml tags.
* @param s
* @return true if any html or xml end tag was detected
*/
private boolean containsHtmlOrXmlTags(String s) {
return s.contains("</") || s.contains("/>");
}
public String getTestModeHeader(String to, String cc, OWLNamedIndividual messageTemplate)
{
if (to != null && to.contains("@"))
{
if (DBG) System.err.println("MessageManager: test mode: not sending to: " + to);
return "<b>This is test mode, the following email would be sent to : </b> <br/> \r\n"
+ to + " <br/> \r\n "
+ "as CC: " + cc + " <br/> \r\n "
+ "and was created by template: " + messageTemplate.getIRI().toString() + "</br>\r\n<hr>\r\n";
//+ body;
} else
{
if (DBG) System.err.println("MessageManager: test mode: to was null: " + to);
return "<b>This is test mode, but a problem occured: No addresses were resolved for this email : </b> <br/> \r\n"
+ to + " <br/> \r\n "
+ "as CC: " + cc + " <br/> \r\n "
+ "and was created by template: " + messageTemplate.getIRI().toString() + "</br>\r\n<hr>\r\n";
}
}
private InternetAddress[] toRecipients(String recipientStr)
{
if (recipientStr == null) return new InternetAddress[0];
String[] tos = recipientStr.trim().split(";");
List<InternetAddress> recipients = new ArrayList<InternetAddress>();
for (int i = 0; i < tos.length; i++)
try {
recipients.add(new InternetAddress(tos[i]));
} catch (AddressException e)
{
System.err.println(ThreadLocalStopwatch.getThreadName()
+ " MessageManager: AddressException when trying to create InternetAddress to: " + tos[i]
+ " (which is one of: " + recipientStr + ")");
}
return recipients.toArray(new InternetAddress[0]);
}
/**
* Gets BO individual from verbose ontology. Throws exception, if it is not BO.
* Format expected:
* http://www.miamidade.gov/bo/MAYECC/2228124/verbose#bo
*
* @param verboseBO
* @return the original bo individual
*/
public OWLNamedIndividual getNonVerboseBO(BOntology verboseBO)
{
//http://www.miamidade.gov/bo/MAYECC/2228124/verbose#bo -->http://www.miamidade.gov/bo/MAYECC/2228124#bo
OWLOntology verboseO = verboseBO.getOntology();
OWLDataFactory df = verboseBO.getOntology().getOWLOntologyManager().getOWLDataFactory();
OWLNamedIndividual verboseBOObj = verboseBO.getBusinessObject();
String verboseBOIRIStr = verboseBOObj.getIRI().toString();
int indexVerbose = verboseBOIRIStr.indexOf("/verbose");
int indexHashbo = verboseBOIRIStr.indexOf("#bo");
if (indexVerbose < 0) throw new RuntimeException("Expected to find /verbose in " + verboseBOObj.getIRI().toString());
if (indexHashbo < 0) throw new RuntimeException("Expected to find #bo in " + verboseBOObj.getIRI().toString());
String bOIRIStr = verboseBOIRIStr.substring(0, indexVerbose) + "#bo";
OWLNamedIndividual result = df.getOWLNamedIndividual(IRI.create(bOIRIStr));
if (!verboseO.containsEntityInSignature(result)) throw new IllegalStateException("Bo does not contain: " + result);
return result;
}
public boolean isVerboseBO(BOntology bo)
{
return bo.getBusinessObject().getIRI().toString().contains("/verbose");
}
public CirmMessage createMessageFromTemplate(BOntology o, OWLLiteral legacyCode, OWLNamedIndividual emailTemplate)
{
CirmMessage msg = null;
try
{
logger.log(Level.INFO, "Creating email for sr:" + o.getObjectId() + " legacyCode: " + legacyCode + " tmpl: " + emailTemplate.getIRI().getFragment() );
OWLNamedIndividual boInd;
String boIndID;
if (isVerboseBO(o))
{
boInd = getNonVerboseBO(o);
boIndID = getNonVerboseBOObjectId(boInd);
}
else
{
boInd = o.getBusinessObject();
boIndID = o.getObjectId();
}
Json sr = OWL.toJSON(o.getOntology(), boInd);
sr.set("boid", boIndID);
msg = createMessage(sr, legacyCode.getLiteral(), emailTemplate);
if (FORCE_TEST_RECOVERY)
msg.addRecipient(RecipientType.CC, new InternetAddress("$$variable$$"));
} catch(Exception e)
{
//String errmsg = "Could not send email for sr:" + o.getObjectId() + " legacyCode: " + legacyCode + " template:" + emailTemplate.getIRI().getFragment();
String errmsg = "Could not create email for sr:" + o.getObjectId() + " legacyCode: " + legacyCode + " template:" + emailTemplate.getIRI().getFragment();
logger.log(Level.WARNING, errmsg, e);
//tryRecoverSendException(msg, e, emailTemplate);
}
return msg;
}
public CirmMessage createMessageFromTemplate(BOntology o,
OWLNamedIndividual emailTemplate,
String subject, String to, String cc, String bcc,String comments)
{
CirmMessage msg = null;
try
{
logger.log(Level.INFO,
"User sending email of sr:" + o.getObjectId() +
" tmpl: " + emailTemplate.getIRI().getFragment() );
OWLNamedIndividual boInd = o.getBusinessObject();
String boIndID = o.getObjectId();
Json sr = OWL.toJSON(o.getOntology(), boInd);
sr.set("boid", boIndID);
msg = createMessage(sr, emailTemplate, subject, to, cc, bcc, comments);
}
catch(Exception e)
{
String errmsg = "Could not send email of sr:" + o.getObjectId() +
" template:" + emailTemplate.getIRI().getFragment();
logger.log(Level.WARNING, errmsg, e);
}
return msg;
}
/**
* Sends all messages in List, tries a recovery message on exception with template information.
* A recovery email will contain a detailed creation explanation of the message.
* @param messages
*/
public void sendEmails(List<CirmMessage> messages)
{
for (CirmMessage message : messages)
{
try
{
if (INCLUDE_EXPLANATION_IN_EMAIL)
{
message.addExplanation("<br>Msg generated on server: " + getServerShortName());
message.includeExplanationInBody();
}
sendEmail(message);
} catch (Exception e)
{
tryRecoverSendException(message, e);
}
}
}
/**
* Tries to recover from a send message failure if caused by a SendFailedException.
* A recovery email will carry information on failed email addresses and it's subject is suffixed by "[R]".
*
* @param msg the failing msg
* @param e the excaption causing a failed send
* @param emailTemplate
* @param errmsg
*/
private void tryRecoverSendException(CirmMessage msg, Exception e)
{
SendFailedException sfx = findSendFailedException(e);
if (sfx != null)
{
Address[] invalid = sfx.getInvalidAddresses();
Address[] validSent = sfx.getValidSentAddresses();
Address[] validUnSent = sfx.getValidUnsentAddresses();
String subject = "unknownSubject";
try {
subject = msg.getSubject();
} catch (Exception se) {};
logger.log(Level.WARNING, "Failed email " + subject + "\r\nSTART INVALID\r\n" + toStr(invalid) + "END INVALID. \r\n" + " RESEND TO INVALID MANUALLY.");
logger.log(Level.WARNING, "Failed email explanation:" + msg.getExplanation() + " \r\n Email was successfully sent to \r\n" + toStr(validSent));
if (validUnSent != null && validUnSent.length > 0)
{
logger.log(Level.WARNING, "Attempting to send recovery email to valid unsent: \r\n" + toStr(validUnSent) + "...");
MimeMessage recoveryMsg = new MimeMessage(session);
try {
String body = (String) msg.getContent();
body = body + "<br><br><br><br>For Sysadmin: <br> Recovered message could not be send to: " + toStr(invalid);
body = body + "<br>Initial send success to : " + toStr(validSent);
body = body + "<br>Initial valid but not sent to : " + toStr(validUnSent);
recoveryMsg.setContent(body, msg.getContentType());
recoveryMsg.setSubject(msg.getSubject() + " [R]", "UTF-8");
for (Address valid : validUnSent)
recoveryMsg.addRecipient(RecipientType.TO, valid);
recoveryMsg.setFrom(messageSenderAddress);
sendEmail(recoveryMsg);
logger.log(Level.WARNING, "... recovery email sent.");
} catch (Exception ex)
{
logger.log(Level.WARNING, "... RECOVERY FAILED. (with " + ex.getClass() + ")");
}
} else
{
logger.log(Level.WARNING, "Failed email NO RECOVERY POSSIBLE.");
}
}
}
private String toStr(Address[] arr)
{
if (arr == null || arr.length == 0) return "None";
String result ="";
for (Address a :arr)
result += a.toString() + "(Type:" + a.getType() + ");\r\n";
return result;
}
/**
* Finds a SendFailedException in e's cause chain.
* @param e
* @return
*/
private SendFailedException findSendFailedException(Exception e)
{
if (e == null) return null;
Throwable t = e;
while (!(t instanceof SendFailedException) && t.getCause() != null)
{
t = t.getCause();
}
return (SendFailedException) t;
}
private String getNonVerboseBOObjectId(OWLNamedIndividual boInd)
{
String A[] = boInd.getIRI().toString().split("/");
String boIndIDWithHashBO = A[A.length - 1];
int indexOfHashBO = boIndIDWithHashBO.indexOf("#bo");
return boIndIDWithHashBO.substring(0, indexOfHashBO);
}
/**
*
*
*/
private Map<String, String> fillParameters(Json sr, String legacyCode, String... anything) throws Exception
{
long x = System.currentTimeMillis();
HashMap<String, String> result = new HashMap<String, String>();
Document dom = null;
for (String thing : anything)
{
if (thing == null)
continue;
Matcher m = VAR_NAME_PATTERN.matcher(thing);
while (m.find())
{
String var = m.group();
if (result.containsKey(var))
continue;
Object evaluator = messageVariables.get(var);
if (!messageVariables.containsKey(var))
{
OWLNamedIndividual ind = findIndividualFromVariable(var);
if (ind != null)
{
Set<OWLNamedIndividual> javaResolver = reasoner().getObjectPropertyValues(ind, objectProperty("legacy:hasVariableResolver")).getFlattened();
if(javaResolver != null && !javaResolver.isEmpty())
{
if(javaResolver.size() != 1)
throw new IllegalStateException("Only one resolver can be defined per variable " + var);
evaluator = Class.forName(javaResolver.iterator().next().getIRI().getFragment()).newInstance();
}else
{
Set<OWLLiteral> xpath = reasoner().getDataPropertyValues(ind, dataProperty("hasXPathExpression"));
Set<OWLLiteral> xsl = reasoner().getDataPropertyValues(ind, dataProperty("hasStylesheet"));
OWLLiteral expression = null;
if (xpath != null && !xpath.isEmpty())
{
expression = xpath.iterator().next();
evaluator = compileXPath(expression.getLiteral());
}
else if (xsl != null && !xsl.isEmpty())
{
expression = xsl.iterator().next();
evaluator = compileXSL(expression.getLiteral());
}
}
if (evaluator != null)
messageVariables.put(var, evaluator);
else
logger.warning(ThreadLocalStopwatch.getThreadName() + " MessageManager: No Evaluator for variable " + var + " (a legacy:MessageVariable)");
}
}else
{
evaluator = messageVariables.get(var);
}
if (evaluator != null)
{
Properties properties = new Properties();
if(legacyCode != null)
properties.setProperty("LEGACY_CODE", legacyCode);
if (evaluator instanceof XPathExpression)
{
if(dom == null)
dom = toDOM(sr);
result.put(var, evaluate((XPathExpression) evaluator, dom));
}
else if (evaluator instanceof Templates)
{
if(dom == null)
dom = toDOM(sr);
result.put(var, evaluate((Templates) evaluator, dom, properties));
}
else if (evaluator instanceof VariableResolver)
{
result.put(var, evaluate(var, (VariableResolver) evaluator, sr, properties));
}
}
}
}
logger.info("fillParameters duration " + (System.currentTimeMillis() - x) + " milliseconds.");
return result;
}
private String applyTemplate(String template, Map<String, String> parameters)
{
if (template == null)
return null;
int line = 1, col = 0;
StringBuffer result = new StringBuffer();
for (int i = 0; i < template.length();)
{
if (template.charAt(i) == '\n')
{
line++;
col = 0;
}
else
col++;
if (template.startsWith("$$", i))
{
int varEndIdx = template.indexOf("$$", i + 2);
if (varEndIdx == -1)
throw new IllegalStateException("Variable not properly enclosed -- missing closing $$. line="
+ line + " col= " + col);
String varName = template.substring(i + 2, varEndIdx);
i += 2 + varName.length() + 2;
String value = (String) parameters.get("$$" + varName + "$$");
if (value == null)
value = "$$" + varName + "$$";
result.append(value);
}
else
result.append(template.charAt(i++));
}
return result.toString();
}
public String evaluate(XPathExpression expression, Document doc)
{
String result = "";
try
{
result = (String) expression.evaluate(doc, XPathConstants.STRING);
return result;
}
catch (Exception e)
{
logger.log(Level.WARNING, "Could not evaluate xPath expression" + expression.toString(), e);
throw new IllegalStateException(e);
}
}
public String evaluate(Templates stylesheetTemplate, Document doc, Properties props)
{
String result = "";
StringWriter writer = new StringWriter();
try
{
Transformer transformer = stylesheetTemplate.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
for ( Entry<Object, Object> propertyToValue : props.entrySet())
{
transformer.setParameter((String) propertyToValue.getKey(), propertyToValue.getValue());
}
result = writer.toString();
return result;
}
catch (TransformerException e)
{
logger.log(Level.WARNING, "Could not evaluate stylesheet template", e);
throw new IllegalStateException(e);
}
}
public String evaluate(String variableName, VariableResolver resolver, Json sr, Properties properties)
{
return resolver.resolve(variableName, sr, properties);
}
public void sendEmail(String from, String to, String subject, String body)
{
ensureInit();
try
{
if (from != null && to != null)
{
MimeMessage msg = new MimeMessage(session);
InternetAddress sender = new InternetAddress(from);
String[] tos = to.trim().split(";");
InternetAddress[] recipients = new InternetAddress[tos.length];
for (int i = 0; i < tos.length; i++)
recipients[i] = new InternetAddress(tos[i]);
msg.setFrom(sender);
msg.setRecipients(Message.RecipientType.TO, recipients);
msg.setSentDate(new java.util.Date());
msg.setSubject(subject, "UTF-8");
if (containsHtmlOrXmlTags(body)) {
msg.setContent(body, "text/html; charset=UTF-8");
} else {
msg.setContent(body, "text/plain; charset=UTF-8");
}
msg.saveChanges();
sendEmail(msg);
}
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
/**
* Same as {@link #sendEmail(String, String, String, String)} except the body is attached
* with the media type specified.
*
* @param from
* @param to
* @param subject
* @param body
* @param mediaType
*/
public void emailAsAttachment(String from, String to, String subject, String body, String mediaType, String filename)
{
try
{
Multipart bodyPart = new MimeMultipart();
MimeBodyPart part = new MimeBodyPart();
part.setContent(body, mediaType);
bodyPart.addBodyPart(part);
if (filename != null)
part.setFileName(filename);
sendEmail(from, to, subject, bodyPart);
}
catch (MessagingException ex)
{
throw new RuntimeException(ex);
}
}
/**
* Sends an email.
* @param from mandatory
* @param to mandatory semicolon separated strings
* @param cc optional
* @param bcc optional
* @param subject
* @param body
*/
public void sendEmail(String from, String to, String cc, String bcc, String subject, Multipart body)
{
if (to == null) throw new IllegalArgumentException("to null");
ensureInit();
try
{
MimeMessage msg = new MimeMessage(session);
InternetAddress sender = new InternetAddress(from);
InternetAddress[] toA = toRecipients(to);
InternetAddress[] ccA = toRecipients(cc);
InternetAddress[] bccA = toRecipients(bcc);
msg.setFrom(sender);
msg.setRecipients(Message.RecipientType.TO, toA);
msg.setRecipients(Message.RecipientType.CC, ccA);
msg.setRecipients(Message.RecipientType.BCC, bccA);
msg.setSentDate(new java.util.Date());
msg.setSubject(subject, "UTF-8");
msg.setContent(body);
sendEmail(msg);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
public void sendEmail(String from, String to, String subject, Multipart body)
{
ensureInit();
try
{
if (from != null && to != null)
{
MimeMessage msg = new MimeMessage(session);
InternetAddress sender = new InternetAddress(from);
String[] tos = to.trim().split(";");
InternetAddress[] recipients = new InternetAddress[tos.length];
for (int i = 0; i < tos.length; i++)
recipients[i] = new InternetAddress(tos[i]);
msg.setFrom(sender);
msg.setRecipients(Message.RecipientType.TO, recipients);
msg.setSentDate(new java.util.Date());
msg.setSubject(subject, "UTF-8");
msg.setContent(body);
sendEmail(msg);
}
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
public void sendEmail(MimeMessage msg)
{
try
{
if(!DISABLE_SEND)
Transport.send(msg);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
public boolean isInitialized()
{
return initialized;
}
public Properties getConfiguration()
{
return configurationProperties;
}
public Document toDOM(Json sr) throws Exception
{
try
{
// This is a bit inefficient but transforming directly to a
// DOMResult throws a NPE
// transforming to a stream first works fine.
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(false);
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(toDOMString(sr))));
return doc;
}
catch (Exception e)
{
throw e;
}
}
private void addProperties(Json sr)
{
sr.set("typeDescription", OWL.getEntityLabel(OWL.individual("legacy:" + sr.at("type").asString())));
sr.delAt("transient$protected");
LegacyEmulator.replaceAnswerValuesWithLabels(sr);
}
public String toDOMString(Json sr) throws Exception
{
addProperties(sr);
StringReader json = new StringReader(sr.toString());
XMLInputFactory factory = new JsonXMLInputFactory();
factory.setProperty(JsonXMLInputFactory.PROP_VIRTUAL_ROOT, SR_DOM_ROOT_NODE);
try
{
// This is a bit inefficient but transforming directly to a
// DOMResult throws a NPE
// transforming to a stream first works fine.
XMLStreamReader reader = factory.createXMLStreamReader(json);
StringWriter writer = new StringWriter();
TransformerFactory xformFactory = TransformerFactory.newInstance();
Transformer idTransform = xformFactory.newTransformer();
idTransform.setOutputProperty(OutputKeys.INDENT, "yes");
idTransform.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
Source input = new StAXSource(reader);
idTransform.transform(input, new StreamResult(writer));
if (DBGXML)
{
System.out.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
System.out.println("MessageManager.toDOMString: \r\n" + writer.getBuffer().toString());
System.out.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
}
return writer.getBuffer().toString();
}
catch (Exception e)
{
throw e;
}
}
private void ensureInit()
{
if (!initialized)
{
synchronized (this)
{
if (!initialized)
{
try
{
OWLLiteral host = OWL.dataProperty( (OWLNamedIndividual)Refs.configSet.resolve().get("SMTPConfig"), "hasValue");
Properties newProps = new Properties();
newProps.setProperty("mail.smtp.host", host.getLiteral());
OWLNamedIndividual mmMode = (OWLNamedIndividual)Refs.configSet.resolve().get("MessageManagerConfig");
if (mmMode == null) throw new IllegalArgumentException("Exactly one value needs to be configured for MessageManagerConfig");
OWLNamedIndividual messageSender = OWL.objectProperty(mmMode, "hasEmailAddress");
InternetAddress messageSenderAdr = new InternetAddress(messageSender.getIRI().getFragment());
newProps = new Properties();
newProps.setProperty("mail.smtp.host", host.getLiteral());
newProps.setProperty("mode", mmMode.getIRI().getFragment());
//publish to volatile
configurationProperties = newProps;
messageSenderAddress = messageSenderAdr;
}
catch (Exception e)
{
throw new IllegalStateException("Messagemenager initialization failure. Check SMTPConfig, MessageManagerConfig in Ontos.", e);
}
messageVariables = new ConcurrentHashMap<String, Object>();
session = Session.getDefaultInstance(configurationProperties, null);
initialized = true;
if (!isTestMode())
System.out.println("MessageManager in PRODUCTION MODE. Emails will be sent to specified to,cc and legacy:MessageTemplate.hasBcc recipients.");
else
System.out.println("MessageManager in TEST MODE. Emails will be sent to legacy:MessageTemplate.hasBccTest recipients only.");
System.out.println("MessageManager will send email using sender address: " + messageSenderAddress);
}
}
}
}
public synchronized void reInitialize()
{
initialized = false;
}
public Object compileXPath(String expression)
{
if (expression == null)
throw new IllegalArgumentException("Cannot compile a null expression.");
Object evaluator = null;
XPathFactory xPathFactory = XPathFactory.newInstance();
xPathFactory.setXPathFunctionResolver(new Resolver());
XPath xPath = xPathFactory.newXPath();
xPath.setNamespaceContext(new Context());
try
{
evaluator = xPath.compile(expression);
return evaluator;
}
catch (Exception e)
{
logger.log(Level.WARNING, "Could not compile expression as xPath: " + expression, e);
}
throw new IllegalArgumentException("Expression could not be compiled:" + expression);
}
public Object compileXSL(String xsl)
{
if (xsl == null)
throw new IllegalArgumentException("Cannot compile a null xsl.");
Object evaluator = null;
TransformerFactory factory = TransformerFactory.newInstance();
try
{
evaluator = factory.newTemplates(new StreamSource(new StringReader(xsl)));
return evaluator;
}
catch (Exception e)
{
logger.log(Level.WARNING, "Could not create xsl template with the following: " + xsl, e);
}
throw new IllegalArgumentException("Expression could not be compiled:" + xsl);
}
/**
* Finds a MessageVariable individual (Encoded Fragment!) for a given (non encoded) variable name (e.g.$$VAR,1$$)
* in the ontology.
*
* @param var a variable name $$varname$$, not url encoded
* @return a matching individual
* @throws UnsupportedEncodingException
*/
public static OWLNamedIndividual findIndividualFromVariable(String var)
{
try {
OWLNamedIndividual varInd = OWL.individual("legacy:"+ URLEncoder.encode(var.replaceAll("\\$\\$", ""),"UTF-8"));
Set<OWLNamedIndividual> individuals = reasoner().getInstances(
OWL.owlClass("legacy:MessageVariable"), false).getFlattened();
if(individuals.contains(varInd))
return varInd;
else
return null;
} catch (UnsupportedEncodingException e)
{
throw new RuntimeException("UTF-8 not supported?", e);
}
}
/**
* Translates a non HTML legacyBody of an email template to HTML
* hasLegacyBody property is expected to be non HTML, but use newline and indents to format the email message.
*
* @param legacyBody
* @return
*/
public static String legacyBodyToHTML(String legacyBody)
{
return "<pre>" + legacyBody + "</pre>";
}
public static boolean useLegacyBody(Json sr)
{
//TODO introduce data property for SR type useLegacyEmail == true
return ALWAYS_USE_LEGACY_BODY;
}
private String getServerShortName()
{
String result;
try {
result = java.net.InetAddress.getLocalHost().getHostName();
if (result.length() >= 4)
result = result.substring(result.length() - 4);
} catch (Exception e)
{
System.err.println("Error MessageManager:getServerShortName failed with " + e.toString());
result = "Unknown";
}
return result;
}
}