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    }