001 /** 002 * Copyright 2005-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.kew.mail; 017 018 import com.thoughtworks.xstream.XStream; 019 import org.apache.commons.lang.StringUtils; 020 import org.kuali.rice.core.api.CoreApiServiceLocator; 021 import org.kuali.rice.core.api.config.property.ConfigContext; 022 import org.kuali.rice.core.api.mail.EmailBody; 023 import org.kuali.rice.core.api.mail.EmailContent; 024 import org.kuali.rice.core.api.mail.EmailFrom; 025 import org.kuali.rice.core.api.mail.EmailSubject; 026 import org.kuali.rice.core.api.mail.EmailTo; 027 import org.kuali.rice.core.api.util.xml.XmlHelper; 028 import org.kuali.rice.core.api.util.xml.XmlJotter; 029 import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator; 030 import org.kuali.rice.kew.api.WorkflowRuntimeException; 031 import org.kuali.rice.kew.api.document.node.RouteNodeInstance; 032 import org.kuali.rice.kew.engine.RouteContext; 033 import org.kuali.rice.kew.engine.RouteHelper; 034 import org.kuali.rice.kew.engine.node.SimpleNode; 035 import org.kuali.rice.kew.engine.node.SimpleResult; 036 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 037 import org.kuali.rice.kew.service.KEWServiceLocator; 038 import org.kuali.rice.kim.api.identity.Person; 039 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 040 import org.w3c.dom.Document; 041 import org.w3c.dom.Element; 042 import org.w3c.dom.NodeList; 043 import org.xml.sax.InputSource; 044 045 import javax.xml.parsers.DocumentBuilder; 046 import javax.xml.parsers.DocumentBuilderFactory; 047 import javax.xml.transform.Templates; 048 import javax.xml.transform.TransformerConfigurationException; 049 import java.io.StringReader; 050 051 052 /** 053 * A node which will send emails using the configured stylesheet to generate the email content. 054 * 055 * @author Kuali Rice Team (rice.collab@kuali.org) 056 */ 057 public class EmailNode implements SimpleNode { 058 059 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(EmailNode.class); 060 061 private EmailStyleHelper emailStyleHelper = new EmailStyleHelper(); 062 private String styleName; 063 private String from; 064 private String to; 065 066 public SimpleResult process(RouteContext context, RouteHelper helper) throws Exception { 067 if (context.isSimulation()) { 068 if (!context.getActivationContext().isActivateRequests()) { 069 return new SimpleResult(true); 070 } 071 } 072 loadConfiguration(context); 073 Document document = generateXmlInput(context); 074 if (LOG.isDebugEnabled()) { 075 LOG.debug("XML input for email tranformation:\n" + XmlJotter.jotNode(document)); 076 } 077 Templates style = loadStyleSheet(styleName); 078 EmailContent emailContent = emailStyleHelper.generateEmailContent(style, document); 079 if (!StringUtils.isBlank(to)) { 080 CoreApiServiceLocator.getMailer().sendEmail(new EmailFrom(from), new EmailTo(to), new EmailSubject(emailContent.getSubject()), new EmailBody(emailContent.getBody()), emailContent.isHtml()); 081 } 082 return new SimpleResult(true); 083 } 084 085 protected Document generateXmlInput(RouteContext context) throws Exception { 086 DocumentBuilder db = getDocumentBuilder(true); 087 Document doc = db.newDocument(); 088 Element emailNodeElem = doc.createElement("emailNode"); 089 doc.appendChild(emailNodeElem); 090 String principalId = null; // Added to the convertRouteHeader is not ambigious. 091 org.kuali.rice.kew.api.document.Document routeHeaderVO = DocumentRouteHeaderValue.to(context.getDocument()); 092 RouteNodeInstance routeNodeInstanceVO = org.kuali.rice.kew.engine.node.RouteNodeInstance.to(context.getNodeInstance()); 093 Document documentContent = context.getDocumentContent().getDocument(); 094 XStream xstream = new XStream(); 095 Element docElem = XmlHelper.readXml(xstream.toXML(routeHeaderVO)).getDocumentElement(); 096 Element nodeElem = XmlHelper.readXml(xstream.toXML(routeNodeInstanceVO)).getDocumentElement(); 097 emailNodeElem.appendChild(doc.importNode(docElem, true)); 098 emailNodeElem.appendChild(doc.importNode(nodeElem, true)); 099 emailNodeElem.appendChild(doc.importNode(documentContent.getDocumentElement(), true)); 100 Element dConElem = context.getDocumentContent().getApplicationContent();//Add document Content element for 101 emailNodeElem.appendChild(doc.importNode(dConElem, true));//access by the stylesheet when creating the email 102 return doc; 103 } 104 105 protected DocumentBuilder getDocumentBuilder(boolean coalesce) throws Exception { 106 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 107 dbf.setCoalescing(coalesce); 108 return dbf.newDocumentBuilder(); 109 } 110 111 protected Templates loadStyleSheet(String styleName) { 112 try { 113 Templates style = CoreServiceApiServiceLocator.getStyleService().getStyleAsTranslet(styleName); 114 if (style == null) { 115 throw new WorkflowRuntimeException("Failed to locate stylesheet with name '" + styleName + "'"); 116 } 117 return style; 118 } catch (TransformerConfigurationException tce) { 119 throw new WorkflowRuntimeException("Failed to load stylesheet with name '" + styleName + "'"); 120 } 121 } 122 123 protected boolean isProduction() { 124 return ConfigContext.getCurrentContextConfig().isProductionEnvironment(); 125 } 126 127 protected void loadConfiguration(RouteContext context) throws Exception { 128 String contentFragment = context.getNodeInstance().getRouteNode().getContentFragment(); 129 DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 130 Document document = db.parse(new InputSource(new StringReader(contentFragment))); 131 if (!isProduction()) { 132 NodeList testAddresses = document.getElementsByTagName("testAddress"); 133 if (testAddresses.getLength() >= 1) { 134 this.to = testAddresses.item(0).getTextContent(); 135 } 136 } else { 137 NodeList toAddresses = document.getElementsByTagName("to"); 138 if (toAddresses.getLength() != 1) { 139 throw new WorkflowRuntimeException("Must have exactly one 'to' address"); 140 } 141 to = toAddresses.item(0).getTextContent(); 142 if ("initiator".equalsIgnoreCase(to)) 143 { 144 Person person = KimApiServiceLocator.getPersonService().getPerson(context.getDocument().getInitiatorWorkflowId()); 145 to = (person == null ? "" : person.getEmailAddressUnmasked()); 146 } 147 if (StringUtils.isBlank(to)) { 148 throw new WorkflowRuntimeException("Email Address is missing from user's profile."); 149 } 150 } 151 152 NodeList fromAddresses = document.getElementsByTagName("from"); 153 if (fromAddresses.getLength() != 1) { 154 throw new WorkflowRuntimeException("Must have exactly one 'from' address"); 155 } 156 this.from = fromAddresses.item(0).getTextContent(); 157 158 if ("initiator".equalsIgnoreCase(this.from)) { 159 Person initiator = KEWServiceLocator.getIdentityHelperService().getPerson(context.getDocument().getInitiatorWorkflowId()); 160 // contructs the email from so that it includes name as well as address 161 // for example: "Doe, John D" <john@doe.com> 162 this.from = "\"" + initiator.getName() + "\" <"; 163 this.from += initiator.getEmailAddress() + ">"; 164 } 165 if (StringUtils.isBlank(this.from)) { 166 throw new WorkflowRuntimeException("No email address could be found found for principal with id " + context.getDocument().getInitiatorWorkflowId()); 167 } 168 169 if (LOG.isInfoEnabled()) { 170 LOG.info("Email From is set to:" + this.from); 171 LOG.info("Email To is set to:" + this.to); 172 } 173 174 NodeList styleNames = document.getElementsByTagName("style"); 175 if (styleNames.getLength() != 1) { 176 throw new WorkflowRuntimeException("Must have exactly one 'style'"); 177 } 178 this.styleName = styleNames.item(0).getTextContent(); 179 } 180 181 182 183 184 }