1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.mail.service.impl;
17
18 import org.apache.commons.lang.StringUtils;
19 import org.apache.log4j.Logger;
20 import org.kuali.rice.core.api.mail.EmailContent;
21 import org.kuali.rice.core.api.util.ClasspathOrFileResourceLoader;
22 import org.kuali.rice.core.api.util.RiceConstants;
23 import org.kuali.rice.core.api.util.xml.XmlHelper;
24 import org.kuali.rice.core.api.util.xml.XmlJotter;
25 import org.kuali.rice.coreservice.api.style.StyleService;
26 import org.kuali.rice.kew.api.KewApiConstants;
27 import org.kuali.rice.kew.api.WorkflowRuntimeException;
28 import org.kuali.rice.kew.api.action.ActionItem;
29 import org.kuali.rice.kew.api.util.CodeTranslator;
30 import org.kuali.rice.kew.doctype.bo.DocumentType;
31 import org.kuali.rice.kew.feedback.web.FeedbackForm;
32 import org.kuali.rice.kew.mail.CustomEmailAttribute;
33 import org.kuali.rice.kew.mail.EmailStyleHelper;
34 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
35 import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
36 import org.kuali.rice.kew.service.KEWServiceLocator;
37 import org.kuali.rice.kew.user.UserUtils;
38 import org.kuali.rice.kim.api.group.Group;
39 import org.kuali.rice.kim.api.identity.Person;
40 import org.kuali.rice.kim.api.identity.principal.Principal;
41 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
42 import org.kuali.rice.krad.util.GlobalVariables;
43 import org.kuali.rice.krad.util.ObjectUtils;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.Node;
47
48 import javax.xml.parsers.DocumentBuilder;
49 import javax.xml.parsers.DocumentBuilderFactory;
50 import javax.xml.parsers.ParserConfigurationException;
51 import javax.xml.transform.Templates;
52 import javax.xml.transform.TransformerConfigurationException;
53 import javax.xml.transform.TransformerException;
54 import javax.xml.transform.TransformerFactory;
55 import javax.xml.transform.dom.DOMSource;
56 import javax.xml.transform.stream.StreamResult;
57 import javax.xml.transform.stream.StreamSource;
58 import java.io.StringWriter;
59 import java.util.Collection;
60 import java.util.Date;
61 import java.util.Map;
62
63
64
65
66
67
68
69
70
71
72 public class StyleableEmailContentServiceImpl extends BaseEmailContentServiceImpl {
73 private static final Logger LOG = Logger.getLogger(StyleableEmailContentServiceImpl.class);
74
75 protected final String DEFAULT_EMAIL_STYLESHEET_RESOURCE_LOC = "defaultEmailStyle.xsl";
76
77 protected StyleService styleService;
78 protected EmailStyleHelper styleHelper = new EmailStyleHelper();
79 protected String globalEmailStyleSheet = KewApiConstants.EMAIL_STYLESHEET_NAME;
80
81 protected RouteHeaderService routeHeaderService;
82
83 public void setStyleService(StyleService styleService) {
84 this.styleService = styleService;
85 }
86
87 public void setGlobalEmailStyleSheet(String globalEmailStyleSheet) {
88 this.globalEmailStyleSheet = globalEmailStyleSheet;
89 }
90
91 protected static DocumentBuilder getDocumentBuilder(boolean coalesce) {
92 try {
93 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
94 dbf.setCoalescing(coalesce);
95 return dbf.newDocumentBuilder();
96 } catch (ParserConfigurationException e) {
97 String message = "Error constructing document builder";
98 LOG.error(message, e);
99 throw new WorkflowRuntimeException(message, e);
100 }
101 }
102
103 protected static void addObjectXML(Document doc, Object o, Node node, String name) throws Exception {
104 Element element = XmlHelper.propertiesToXml(doc, o, name);
105
106 if (LOG.isDebugEnabled()) {
107 LOG.debug(XmlJotter.jotNode(element));
108 }
109
110 if (node == null) {
111 node = doc;
112 }
113
114 node.appendChild(element);
115 }
116
117 protected static void addTextElement(Document doc, Element baseElement, String elementName, Object elementData) {
118 Element element = doc.createElement(elementName);
119 String dataValue = "";
120 if (elementData != null) {
121 dataValue = elementData.toString();
122 }
123 element.appendChild(doc.createTextNode(dataValue));
124 baseElement.appendChild(element);
125 }
126
127 protected static void addCDataElement(Document doc, Element baseElement, String elementName, Object elementData) {
128 Element element = doc.createElement(elementName);
129 String dataValue = "";
130 if (elementData != null) {
131 dataValue = elementData.toString();
132 }
133 element.appendChild(doc.createCDATASection(dataValue));
134 baseElement.appendChild(element);
135 }
136
137 protected static void addTimestampElement(Document doc, Element baseElement, String elementName, Date elementData) {
138 addTextElement(doc, baseElement, elementName, RiceConstants.getDefaultDateFormat().format(elementData));
139 }
140
141 protected static void addDelegatorElement(Document doc, Element baseElement, ActionItem actionItem) {
142 Element delegatorElement = doc.createElement("delegator");
143 if ( (actionItem.getDelegatorPrincipalId() != null) && (actionItem.getDelegatorPrincipalId() != null) ) {
144
145 baseElement.appendChild(delegatorElement);
146 return;
147 }
148 String delegatorType = "";
149 String delegatorId = "";
150 String delegatorDisplayValue = "";
151 if (actionItem.getDelegatorPrincipalId() != null) {
152 delegatorType = "user";
153 delegatorId = actionItem.getDelegatorPrincipalId();
154 Principal delegator = KimApiServiceLocator.getIdentityService().getPrincipal(delegatorId);
155
156 if (delegator == null) {
157 LOG.error("Cannot find user for id " + delegatorId);
158 delegatorDisplayValue = "USER NOT FOUND";
159 } else {
160 delegatorDisplayValue = UserUtils.getTransposedName(GlobalVariables.getUserSession(), delegator);
161 }
162 } else if (actionItem.getDelegatorPrincipalId() != null) {
163 delegatorType = "workgroup";
164 delegatorId = actionItem.getDelegatorGroupId().toString();
165 delegatorDisplayValue = KimApiServiceLocator.getGroupService().getGroup(actionItem.getDelegatorGroupId()).getName();
166 }
167 delegatorElement.setAttribute("type", delegatorType);
168
169 Element idElement = doc.createElement("id");
170 idElement.appendChild(doc.createTextNode(delegatorId));
171 delegatorElement.appendChild(idElement);
172
173 Element displayValElement = doc.createElement("displayValue");
174 displayValElement.appendChild(doc.createTextNode(delegatorDisplayValue));
175 delegatorElement.appendChild(displayValElement);
176 baseElement.appendChild(delegatorElement);
177 }
178
179 protected static void addWorkgroupRequestElement(Document doc, Element baseElement, ActionItem actionItem) {
180 Element workgroupElement = doc.createElement("workgroupRequest");
181 if (actionItem.getGroupId() != null) {
182
183 Element idElement = doc.createElement("id");
184 idElement.appendChild(doc.createTextNode(actionItem.getGroupId()));
185 workgroupElement.appendChild(idElement);
186
187 Element displayValElement = doc.createElement("displayValue");
188 displayValElement.appendChild(doc.createTextNode(actionItem.getGroupId()));
189 workgroupElement.appendChild(displayValElement);
190 }
191 baseElement.appendChild(workgroupElement);
192 }
193
194
195
196
197
198
199
200
201
202
203
204 protected void addSummarizedActionItem(Document doc, ActionItem actionItem, Person user, Node node, DocumentRouteHeaderValue routeHeader) throws Exception {
205 if (node == null) {
206 node = doc;
207 }
208
209 Element root = doc.createElement("summarizedActionItem");
210
211
212 addTextElement(doc, root, "documentId", actionItem.getDocumentId());
213 addTextElement(doc, root, "docName", actionItem.getDocName());
214 addCDataElement(doc, root, "docLabel", actionItem.getDocLabel());
215 addCDataElement(doc, root, "docTitle", actionItem.getDocTitle());
216
217 addTextElement(doc, root, "docRouteStatus", routeHeader.getDocRouteStatus());
218 addCDataElement(doc, root, "routeStatusLabel", routeHeader.getRouteStatusLabel());
219 addTextElement(doc, root, "actionRequestCd", actionItem.getActionRequestCd());
220 addTextElement(doc, root, "actionRequestLabel", CodeTranslator.getActionRequestLabel(
221 actionItem.getActionRequestCd()));
222 addDelegatorElement(doc, root, actionItem);
223 addTimestampElement(doc, root, "createDate", routeHeader.getCreateDate());
224 addWorkgroupRequestElement(doc, root, actionItem);
225 if (actionItem.getDateTimeAssigned() != null)
226 addTimestampElement(doc, root, "dateAssigned", actionItem.getDateTimeAssigned().toDate());
227
228 node.appendChild(root);
229 }
230
231 public DocumentRouteHeaderValue getRouteHeader(ActionItem actionItem) {
232 if (routeHeaderService == null) {
233 routeHeaderService = KEWServiceLocator.getRouteHeaderService();
234 }
235 return routeHeaderService.getRouteHeader(actionItem.getDocumentId());
236 }
237
238 protected Map<String,DocumentRouteHeaderValue> getRouteHeaders(Collection<ActionItem> actionItems) {
239 if (routeHeaderService == null) {
240 routeHeaderService = KEWServiceLocator.getRouteHeaderService();
241 }
242 return routeHeaderService.getRouteHeadersForActionItems(actionItems);
243 }
244
245 protected static String transform(Templates style, Document doc) {
246 StringWriter writer = new StringWriter();
247 StreamResult result = new StreamResult(writer);
248
249 try {
250 style.newTransformer().transform(new DOMSource(doc), result);
251 return writer.toString();
252 } catch (TransformerException te) {
253 String message = "Error transforming DOM";
254 LOG.error(message, te);
255 throw new WorkflowRuntimeException(message, te);
256 }
257 }
258
259
260
261
262
263
264
265
266 protected Templates getStyle(String styleName) {
267 Templates style = null;
268 try {
269 style = styleService.getStyleAsTranslet(styleName);
270 } catch (TransformerConfigurationException tce) {
271 String message = "Error obtaining style '" + styleName + "', using default";
272 LOG.error(message, tce);
273
274 }
275
276 if (style == null) {
277 LOG.warn("Could not find specified style, " + styleName + ", using default");
278 try {
279
280 style = TransformerFactory.newInstance().newTemplates(new StreamSource(new ClasspathOrFileResourceLoader().getResource("classpath:org/kuali/rice/kew/mail/" + DEFAULT_EMAIL_STYLESHEET_RESOURCE_LOC).getInputStream()));
281 } catch (Exception tce) {
282 String message = "Error obtaining default style from resource: " + DEFAULT_EMAIL_STYLESHEET_RESOURCE_LOC;
283 LOG.error(message, tce);
284 throw new WorkflowRuntimeException("Error obtaining style '" + styleName + "'", tce);
285 }
286 }
287 return style;
288 }
289
290 protected EmailContent generateEmailContent(String styleName, Document doc) {
291 Templates style = getStyle(styleName);
292 return styleHelper.generateEmailContent(style, doc);
293 }
294
295 protected EmailContent generateReminderForActionItems(Person user, Collection<ActionItem> actionItems, String name, String style) {
296 DocumentBuilder db = getDocumentBuilder(false);
297 Document doc = db.newDocument();
298 Element element = doc.createElement(name);
299 Map<String,DocumentRouteHeaderValue> routeHeaders = getRouteHeaders(actionItems);
300
301 setStandardAttributes(element);
302 doc.appendChild(element);
303
304 try {
305 addObjectXML(doc, user, element, "user");
306 for (ActionItem actionItem: actionItems) {
307 try {
308 addSummarizedActionItem(doc, actionItem, user, element, routeHeaders.get(actionItem.getDocumentId()));
309 } catch (Exception e) {
310 String message = "Error generating XML for action item: " + actionItem;
311 LOG.error(message, e);
312 throw new WorkflowRuntimeException(e);
313 }
314 }
315
316 } catch (Exception e) {
317 String message = "Error generating XML for action items: " + actionItems;
318 LOG.error(message, e);
319 throw new WorkflowRuntimeException(e);
320 }
321
322 return generateEmailContent(style, doc);
323 }
324
325 protected void setStandardAttributes(Element e) {
326 e.setAttribute("env", getDeploymentEnvironment());
327 e.setAttribute("applicationEmailAddress", getApplicationEmailAddress());
328 e.setAttribute("actionListUrl", getActionListUrl());
329 e.setAttribute("preferencesUrl", getPreferencesUrl());
330 e.setAttribute("routeLogUrl", getRouteLogUrl());
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352 @Override
353 public EmailContent generateImmediateReminder(Person user, ActionItem actionItem, DocumentType documentType) {
354 if (user != null) {
355 LOG.info("Starting generation of immediate email reminder...");
356 LOG.info("Action Id: " + actionItem.getId() +
357 "; ActionRequestId: " + actionItem.getActionRequestId() +
358 "; Action Item Principal Id: " + actionItem.getPrincipalId());
359 LOG.info("User Principal Id: " + user.getPrincipalId());
360
361 String styleSheet = documentType.getCustomEmailStylesheet();
362 LOG.debug(documentType.getName() + " style: " + styleSheet);
363 if (styleSheet == null) {
364 styleSheet = globalEmailStyleSheet;
365 }
366
367 LOG.info("generateImmediateReminder using style sheet: "+ styleSheet + " for Document Type " + documentType.getName());
368
369 DocumentBuilder db = getDocumentBuilder(false);
370 Document doc = db.newDocument();
371 Element element = doc.createElement("immediateReminder");
372 setStandardAttributes(element);
373 doc.appendChild(element);
374
375 try {
376 addObjectXML(doc, user, element, "user");
377
378 Node node = element;
379 if (node == null) {
380 node = doc;
381 }
382
383 Element root = doc.createElement("actionItem");
384
385 try {
386 CustomEmailAttribute customEmailAttribute = getCustomEmailAttribute(user, actionItem);
387 if (customEmailAttribute != null) {
388 String customBody = customEmailAttribute.getCustomEmailBody();
389 if (!org.apache.commons.lang.StringUtils.isEmpty(customBody)) {
390 Element bodyElement = doc.createElement("customBody");
391 bodyElement.appendChild(doc.createTextNode(customBody));
392 root.appendChild(bodyElement);
393 }
394 String customEmailSubject = customEmailAttribute.getCustomEmailSubject();
395 if (!org.apache.commons.lang.StringUtils.isEmpty(customEmailSubject)) {
396 Element subjectElement = doc.createElement("customSubject");
397 subjectElement.appendChild(doc.createTextNode(customEmailSubject));
398 root.appendChild(subjectElement);
399 }
400 }
401 } catch (Exception e) {
402 LOG.error("Error when checking for custom email body and subject.", e);
403 }
404 Person person = KimApiServiceLocator.getPersonService().getPerson(actionItem.getPrincipalId());
405 DocumentRouteHeaderValue header = getRouteHeader(actionItem);
406
407 addObjectXML(doc, actionItem, root, "actionItem");
408 addObjectXML(doc, person, root, "actionItemPerson");
409 addTextElement(doc, root, "actionItemPrincipalId", person.getPrincipalId());
410 addTextElement(doc, root, "actionItemPrincipalName", person.getPrincipalName());
411 addDocumentHeaderXML(doc, header, root, "doc");
412 addObjectXML(doc, header.getInitiatorPrincipal(), root, "docInitiator");
413 addTextElement(doc, root, "docInitiatorDisplayName", header.getInitiatorDisplayName());
414 if (ObjectUtils.isNotNull(actionItem.getGroupId())) {
415 Group group = KimApiServiceLocator.getGroupService().getGroup(actionItem.getGroupId());
416 addTextElement(doc, root, "groupName", group.getName());
417 }
418 addObjectXML(doc, header.getDocumentType(), root, "documentType");
419
420 node.appendChild(root);
421 } catch (Exception e) {
422 String message = "Error generating immediate reminder XML for action item: " + actionItem;
423 LOG.error(message, e);
424 throw new WorkflowRuntimeException(e);
425 }
426 LOG.info("Leaving generation of immeidate email reminder...");
427 return generateEmailContent(styleSheet, doc);
428 }
429 LOG.info("Skipping generation of immediate email reminder due to the user being null");
430 return null;
431 }
432
433
434
435
436
437
438
439
440 protected void addDocumentHeaderXML(Document document, DocumentRouteHeaderValue documentHeader, Node node, String elementName) throws Exception {
441 Element element = XmlHelper.propertiesToXml(document, documentHeader, elementName);
442
443 Element docContentElement = (Element)element.getElementsByTagName("docContent").item(0);
444 String documentContent = docContentElement.getTextContent();
445
446 if (!StringUtils.isBlank(documentContent) && documentContent.startsWith("<")) {
447 Document documentContentXML = XmlHelper.readXml(documentContent);
448 Element documentContentElement = documentContentXML.getDocumentElement();
449 documentContentElement = (Element)document.importNode(documentContentElement, true);
450
451
452 docContentElement.removeChild(docContentElement.getFirstChild());
453
454
455 docContentElement.appendChild(documentContentElement);
456 } else {
457
458
459
460
461
462 docContentElement.removeChild(docContentElement.getFirstChild());
463 }
464
465 if (LOG.isDebugEnabled()) {
466 LOG.debug(XmlJotter.jotNode(element));
467 }
468
469 node.appendChild(element);
470 }
471
472 @Override
473 public EmailContent generateWeeklyReminder(Person user, Collection<ActionItem> actionItems) {
474 return generateReminderForActionItems(user, actionItems, "weeklyReminder", globalEmailStyleSheet);
475 }
476
477 @Override
478 public EmailContent generateDailyReminder(Person user, Collection<ActionItem> actionItems) {
479 return generateReminderForActionItems(user, actionItems, "dailyReminder", globalEmailStyleSheet);
480 }
481
482 @Override
483 public EmailContent generateFeedback(FeedbackForm form) {
484 DocumentBuilder db = getDocumentBuilder(true);
485 Document doc = db.newDocument();
486 String styleSheet = globalEmailStyleSheet;
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501 LOG.info("form: " + form.getDocumentId());
502 try {
503 addObjectXML(doc, form, null, "feedback");
504 } catch (Exception e) {
505 String message = "Error generating XML for feedback form: " + form;
506 LOG.error(message, e);
507 throw new WorkflowRuntimeException(message, e);
508 }
509 setStandardAttributes(doc.getDocumentElement());
510
511 return generateEmailContent(styleSheet, doc);
512 }
513 }