View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.mail;
17  
18  import com.thoughtworks.xstream.XStream;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.config.property.ConfigContext;
22  import org.kuali.rice.core.api.mail.EmailBody;
23  import org.kuali.rice.core.api.mail.EmailContent;
24  import org.kuali.rice.core.api.mail.EmailFrom;
25  import org.kuali.rice.core.api.mail.EmailSubject;
26  import org.kuali.rice.core.api.mail.EmailTo;
27  import org.kuali.rice.core.api.util.xml.XmlHelper;
28  import org.kuali.rice.core.api.util.xml.XmlJotter;
29  import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
30  import org.kuali.rice.kew.api.WorkflowRuntimeException;
31  import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
32  import org.kuali.rice.kew.engine.RouteContext;
33  import org.kuali.rice.kew.engine.RouteHelper;
34  import org.kuali.rice.kew.engine.node.SimpleNode;
35  import org.kuali.rice.kew.engine.node.SimpleResult;
36  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
37  import org.kuali.rice.kew.service.KEWServiceLocator;
38  import org.kuali.rice.kim.api.identity.Person;
39  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
40  import org.w3c.dom.Document;
41  import org.w3c.dom.Element;
42  import org.w3c.dom.NodeList;
43  import org.xml.sax.InputSource;
44  
45  import javax.xml.parsers.DocumentBuilder;
46  import javax.xml.parsers.DocumentBuilderFactory;
47  import javax.xml.transform.Templates;
48  import javax.xml.transform.TransformerConfigurationException;
49  import java.io.StringReader;
50  
51  
52  /**
53   * A node which will send emails using the configured stylesheet to generate the email content.
54   *
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   */
57  public class EmailNode implements SimpleNode {
58  
59      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(EmailNode.class);
60  
61      private EmailStyleHelper emailStyleHelper = new EmailStyleHelper();
62      private String styleName;
63      private String from;
64      private String to;
65      
66      public SimpleResult process(RouteContext context, RouteHelper helper) throws Exception {
67      	if (context.isSimulation()) {
68              if (!context.getActivationContext().isActivateRequests()) {
69              	return new SimpleResult(true);
70              }
71          } 
72  	loadConfiguration(context);
73  	Document document = generateXmlInput(context);
74  	if (LOG.isDebugEnabled()) {
75  	    LOG.debug("XML input for email tranformation:\n" + XmlJotter.jotNode(document));
76  	}
77  	Templates style = loadStyleSheet(styleName);
78  	EmailContent emailContent = emailStyleHelper.generateEmailContent(style, document);
79  	if (!StringUtils.isBlank(to)) {
80  		CoreApiServiceLocator.getMailer().sendEmail(new EmailFrom(from), new EmailTo(to), new EmailSubject(emailContent.getSubject()), new EmailBody(emailContent.getBody()), emailContent.isHtml());
81  	}
82  	return new SimpleResult(true);
83      }
84  
85      protected Document generateXmlInput(RouteContext context) throws Exception {
86  	DocumentBuilder db = getDocumentBuilder(true);
87          Document doc = db.newDocument();
88          Element emailNodeElem = doc.createElement("emailNode");
89          doc.appendChild(emailNodeElem);
90          String principalId = null;  // Added to the convertRouteHeader is not ambigious.
91          org.kuali.rice.kew.api.document.Document routeHeaderVO = DocumentRouteHeaderValue.to(context.getDocument());
92          RouteNodeInstance routeNodeInstanceVO = org.kuali.rice.kew.engine.node.RouteNodeInstance.to(context.getNodeInstance());
93          Document documentContent = context.getDocumentContent().getDocument();
94          XStream xstream = new XStream();
95          Element docElem = XmlHelper.readXml(xstream.toXML(routeHeaderVO)).getDocumentElement();
96          Element nodeElem = XmlHelper.readXml(xstream.toXML(routeNodeInstanceVO)).getDocumentElement();
97          emailNodeElem.appendChild(doc.importNode(docElem, true));
98          emailNodeElem.appendChild(doc.importNode(nodeElem, true));
99          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 }