blob: 35b999ad22bd2a4688e06a20db7b01dd7f71ddc7 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.ofbiz.party.communication;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.ofbiz.base.location.FlexibleLocation;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.GeneralException;
import org.apache.ofbiz.base.util.StringUtil;
import org.apache.ofbiz.base.util.UtilDateTime;
import org.apache.ofbiz.base.util.UtilHttp;
import org.apache.ofbiz.base.util.UtilMisc;
import org.apache.ofbiz.base.util.UtilProperties;
import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.common.email.NotificationServices;
import org.apache.ofbiz.content.data.DataResourceWorker;
import org.apache.ofbiz.entity.Delegator;
import org.apache.ofbiz.entity.GenericEntityException;
import org.apache.ofbiz.entity.GenericValue;
import org.apache.ofbiz.entity.condition.EntityCondition;
import org.apache.ofbiz.entity.condition.EntityOperator;
import org.apache.ofbiz.entity.util.EntityListIterator;
import org.apache.ofbiz.entity.util.EntityQuery;
import org.apache.ofbiz.entity.util.EntityUtil;
import org.apache.ofbiz.entity.util.EntityUtilProperties;
import org.apache.ofbiz.service.DispatchContext;
import org.apache.ofbiz.service.GenericServiceException;
import org.apache.ofbiz.service.LocalDispatcher;
import org.apache.ofbiz.service.ServiceUtil;
import org.apache.ofbiz.service.mail.MimeMessageWrapper;
public class CommunicationEventServices {
private static final String MODULE = CommunicationEventServices.class.getName();
private static final String RESOURCE = "PartyErrorUiLabels";
public static Map<String, Object> sendCommEventAsEmail(DispatchContext ctx, Map<String, ? extends Object> context) {
Delegator delegator = ctx.getDelegator();
LocalDispatcher dispatcher = ctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
String communicationEventId = (String) context.get("communicationEventId");
Map<String, Object> result = ServiceUtil.returnSuccess();
List<Object> errorMessages = new LinkedList<>(); // used to keep a list of all error messages returned from sending emails to contact list
try {
// find the communication event and make sure that it is actually an email
GenericValue communicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId",
communicationEventId).queryOne();
if (communicationEvent == null) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_not_found_failure", locale);
return ServiceUtil.returnError(errMsg + " " + communicationEventId);
}
String communicationEventType = communicationEvent.getString("communicationEventTypeId");
if (communicationEventType == null || !("EMAIL_COMMUNICATION".equals(communicationEventType)
|| "AUTO_EMAIL_COMM".equals(communicationEventType))) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_must_be_email_for_email", locale);
return ServiceUtil.returnError(errMsg + " " + communicationEventId);
}
// make sure the from contact mech is an email if it is specified
if ((communicationEvent.getRelatedOne("FromContactMech", false) == null)
|| (!("EMAIL_ADDRESS".equals(communicationEvent.getRelatedOne("FromContactMech", false).getString("contactMechTypeId")))
|| (communicationEvent.getRelatedOne("FromContactMech", false).getString("infoString") == null))) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_from_contact_mech_must_be_email", locale);
return ServiceUtil.returnError(errMsg + " " + communicationEventId);
}
// assign some default values because required by sendmail and better not make them defaults over there
if (UtilValidate.isEmpty(communicationEvent.getString("subject"))) {
communicationEvent.put("subject", " ");
}
if (UtilValidate.isEmpty(communicationEvent.getString("content"))) {
communicationEvent.put("content", " ");
}
// prepare the email
Map<String, Object> sendMailParams = new HashMap<>();
sendMailParams.put("sendFrom", communicationEvent.getRelatedOne("FromContactMech", false).getString("infoString"));
sendMailParams.put("subject", communicationEvent.getString("subject"));
sendMailParams.put("contentType", communicationEvent.getString("contentMimeTypeId"));
sendMailParams.put("userLogin", userLogin);
Debug.logInfo("Sending communicationEvent: " + communicationEventId, MODULE);
// check for attachments
boolean isMultiPart = false;
List<GenericValue> comEventContents = EntityQuery.use(delegator).from("CommEventContentAssoc").where("communicationEventId",
communicationEventId).filterByDate().queryList();
if (UtilValidate.isNotEmpty(comEventContents)) {
isMultiPart = true;
List<Map<String, ? extends Object>> bodyParts = new LinkedList<>();
if (UtilValidate.isNotEmpty(communicationEvent.getString("content"))) {
bodyParts.add(UtilMisc.<String, Object>toMap("content", communicationEvent.getString("content"), "type",
communicationEvent.getString("contentMimeTypeId")));
}
for (GenericValue comEventContent : comEventContents) {
GenericValue content = comEventContent.getRelatedOne("FromContent", false);
GenericValue dataResource = content.getRelatedOne("DataResource", false);
ByteBuffer dataContent = DataResourceWorker.getContentAsByteBuffer(delegator, dataResource.getString("dataResourceId"),
null, null, locale, null);
bodyParts.add(UtilMisc.<String, Object>toMap("content", dataContent.array(), "type", dataResource.getString("mimeTypeId"),
"filename", dataResource.getString("dataResourceName")));
}
sendMailParams.put("bodyParts", bodyParts);
} else {
sendMailParams.put("body", communicationEvent.getString("content"));
}
// if there is no contact list, then send look for a contactMechIdTo and partyId
if ((UtilValidate.isEmpty(communicationEvent.getString("contactListId")))) {
// send to address
String sendTo = communicationEvent.getString("toString");
if (UtilValidate.isEmpty(sendTo)) {
GenericValue toContactMech = communicationEvent.getRelatedOne("ToContactMech", false);
if (toContactMech != null && "EMAIL_ADDRESS".equals(toContactMech.getString("contactMechTypeId"))) {
sendTo = toContactMech.getString("infoString");
}
}
if (UtilValidate.isEmpty(sendTo)) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_to_contact_mech_must_be_email",
locale);
return ServiceUtil.returnError(errMsg + " " + communicationEventId);
}
// add other parties from roles
String sendCc = null;
String sendBcc = null;
List<GenericValue> commRoles = communicationEvent.getRelated("CommunicationEventRole", null, null, false);
if (UtilValidate.isNotEmpty(commRoles)) {
for (GenericValue commRole : commRoles) { // 'from' and 'to' already defined on communication event
if (commRole.getString("partyId").equals(communicationEvent.getString("partyIdFrom"))
|| commRole.getString("partyId").equals(communicationEvent.getString("partyIdTo"))) {
continue;
}
GenericValue contactMech = commRole.getRelatedOne("ContactMech", false);
if (contactMech != null && UtilValidate.isNotEmpty(contactMech.getString("infoString"))) {
if ("ADDRESSEE".equals(commRole.getString("roleTypeId"))) {
sendTo += "," + contactMech.getString("infoString");
} else if ("CC".equals(commRole.getString("roleTypeId"))) {
if (sendCc != null) {
sendCc += "," + contactMech.getString("infoString");
} else {
sendCc = contactMech.getString("infoString");
}
} else if ("BCC".equals(commRole.getString("roleTypeId"))) {
if (sendBcc != null) {
sendBcc += "," + contactMech.getString("infoString");
} else {
sendBcc = contactMech.getString("infoString");
}
}
}
}
}
sendMailParams.put("communicationEventId", communicationEventId);
sendMailParams.put("sendTo", sendTo);
if (sendCc != null) {
sendMailParams.put("sendCc", sendCc);
}
if (sendBcc != null) {
sendMailParams.put("sendBcc", sendBcc);
}
sendMailParams.put("partyId", communicationEvent.getString("partyIdTo")); // who it's going to
// send it - using a new transaction
Map<String, Object> tmpResult = null;
if (isMultiPart) {
tmpResult = dispatcher.runSync("sendMailMultiPart", sendMailParams, 360, true);
if (ServiceUtil.isError(tmpResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(tmpResult));
}
} else {
tmpResult = dispatcher.runSync("sendMail", sendMailParams, 360, true);
if (ServiceUtil.isError(tmpResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(tmpResult));
}
}
if (ServiceUtil.isError(tmpResult)) {
if (ServiceUtil.getErrorMessage(tmpResult).startsWith("[ADDRERR]")) {
// address error; mark the communication event as BOUNCED
communicationEvent.set("statusId", "COM_BOUNCED");
try {
communicationEvent.store();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
} else {
// setup or communication error
errorMessages.add(ServiceUtil.getErrorMessage(tmpResult));
}
} else {
// set the message ID on this communication event
String messageId = (String) tmpResult.get("messageId");
communicationEvent.set("messageId", messageId);
try {
communicationEvent.store();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
Map<String, Object> completeResult = dispatcher.runSync("setCommEventComplete",
UtilMisc.<String, Object>toMap("communicationEventId", communicationEventId, "partyIdFrom", communicationEvent
.getString("partyIdFrom"), "userLogin", userLogin));
if (ServiceUtil.isError(completeResult)) {
errorMessages.add(ServiceUtil.getErrorMessage(completeResult));
}
}
} else {
// Call the sendEmailToContactList service if there's a contactListId present
Map<String, Object> sendEmailToContactListContext = new HashMap<>();
sendEmailToContactListContext.put("contactListId", communicationEvent.getString("contactListId"));
sendEmailToContactListContext.put("communicationEventId", communicationEventId);
sendEmailToContactListContext.put("userLogin", userLogin);
try {
dispatcher.runAsync("sendEmailToContactList", sendEmailToContactListContext);
} catch (GenericServiceException e) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.errorCallingSendEmailToContactListService", locale);
Debug.logError(e, errMsg, MODULE);
errorMessages.add(errMsg);
errorMessages.addAll(e.getMessageList());
}
}
} catch (IOException | GeneralException eey) {
return ServiceUtil.returnError(eey.getMessage());
}
// If there were errors, then the result of this service should be error with the full list of messages
if (!errorMessages.isEmpty()) {
result = ServiceUtil.returnError(errorMessages);
}
return result;
}
/**
* Service to send all content associated to a FILE_TRANSFER_COMM CommunicationEvent,
* with contactMechIdTo as a FtpAdress contactMech
* @param ctx
* @param context
* @return
*/
public static Map<String, Object> sendCommEventAsFtp(DispatchContext ctx, Map<String, ?> context) {
Delegator delegator = ctx.getDelegator();
LocalDispatcher dispatcher = ctx.getDispatcher();
Locale locale = (Locale) context.get("locale");
GenericValue userLogin = (GenericValue) context.get("userLogin");
String communicationEventId = (String) context.get("communicationEventId");
List<String> errorMessages = new ArrayList<>();
try {
GenericValue communicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId",
communicationEventId).queryOne();
if (communicationEvent == null) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_not_found_failure", locale);
return ServiceUtil.returnError(errMsg + " " + communicationEventId);
}
if ("COM_COMPLETE".equals(communicationEvent.getString("statusId"))) return ServiceUtil.returnSuccess();
String communicationEventType = communicationEvent.getString("communicationEventTypeId");
if (communicationEventType == null || !"FILE_TRANSFER_COMM".equals(communicationEventType)) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_must_be_ftp_for_ftp", locale);
return ServiceUtil.returnError(errMsg + " " + communicationEventId);
}
String contactMechId = communicationEvent.getString("contactMechIdTo");
// Check contactMech type to FTP_ADDRESS
GenericValue contactMech = EntityQuery.use(delegator).from("ContactMech").cache().where("contactMechId", contactMechId).queryOne();
GenericValue ftpAddress = EntityQuery.use(delegator).from("FtpAddress").cache().where("contactMechId", contactMechId).queryOne();
if (null == contactMech || null == ftpAddress || !"FTP_ADDRESS".equals(contactMech.getString("contactMechTypeId"))) {
String errMsg = UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_to_contact_mech_must_be_ftp", locale);
return ServiceUtil.returnError(errMsg + " " + communicationEventId);
}
// Get list of children communication events, to avoid same content multi-send
List<GenericValue> childrenCommunicationEvent = EntityQuery.use(delegator).select("communicationEventId", "statusId")
.from("CommunicationEvent").where("parentCommEventId", communicationEventId).cache().queryList();
List<String> childrenCommunicationEventIds = EntityUtil.getFieldListFromEntityList(childrenCommunicationEvent,
"communicationEventId", true);
// Retrieve all contents to send
List<GenericValue> contents = EntityQuery.use(delegator).from("CommEventContentDataResource").where("communicationEventId",
communicationEventId).cache().queryList();
if (UtilValidate.isNotEmpty(contents)) {
if (UtilValidate.isEmpty(communicationEvent.getTimestamp("datetimeStarted"))) {
//store the startDate into the communication
Map<String, Object> updateCommEventResult = dispatcher.runSync("updateCommunicationEvent",
UtilMisc.toMap("communicationEventId", communicationEventId, "datetimeStarted", UtilDateTime.nowTimestamp(),
"userLogin", userLogin), 600, true);
if (ServiceUtil.isError(updateCommEventResult)) {
errorMessages.add(ServiceUtil.getErrorMessage(updateCommEventResult));
}
}
for (GenericValue content : contents) {
Map<String, Object> ftpServiceMap = new HashMap<>();
//store the child Communication Event, to keep track of errorMessages in note field
String childCommunicationEventId = "";
ftpServiceMap.put("userLogin", userLogin);
ftpServiceMap.put("contentId", content.getString("contentId"));
ftpServiceMap.put("partyId", communicationEvent.getString("partyIdTo"));
ftpServiceMap.put("contactMechId", contactMechId);
// no need to create a child CommEvent if it is a single content transfer
if (contents.size() == 1) {
ftpServiceMap.put("communicationEventId", communicationEvent.get("communicationEventId"));
} else {
// check if currentContent is already sent by an existing children communicationEvent
EntityCondition sentCond = EntityCondition.makeCondition(UtilMisc.toList(
EntityCondition.makeCondition("communicationEventId", EntityOperator.IN, childrenCommunicationEventIds),
EntityCondition.makeCondition("contentId", content.getString("contentId"))));
GenericValue alreadySent = EntityQuery.use(delegator).from("CommEventContentAssoc").where(sentCond).cache().queryFirst();
if (null != alreadySent) {
GenericValue childCommEvent = EntityUtil.getFirst(EntityUtil.filterByCondition(childrenCommunicationEvent,
EntityCondition.makeCondition("communicationEventId", alreadySent.getString("communicationEventId"))));
// if completely sent, continue to next content
if ("COM_COMPLETE".equals(childCommEvent.getString("statusId"))) continue;
ftpServiceMap.put("communicationEventId", childCommEvent.getString("communicationEventId"));
}
}
Map<String, Object> resultTmp = dispatcher.runSync("sendContentToFtp", ftpServiceMap, 600, true);
if (ServiceUtil.isError(resultTmp)) {
errorMessages.add(ServiceUtil.getErrorMessage(resultTmp));
}
// attach the parent communication event to the new event created when sending the content, and store error if needed
if (UtilValidate.isNotEmpty(resultTmp.get("communicationEventId"))) {
childCommunicationEventId = (String) resultTmp.get("communicationEventId");
}
if (UtilValidate.isNotEmpty(childCommunicationEventId) && !childCommunicationEventId.equals(communicationEventId)) {
GenericValue childCommunicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId",
childCommunicationEventId).queryOne();
childCommunicationEvent.set("parentCommEventId", communicationEventId);
if (ServiceUtil.isError(resultTmp)) {
childCommunicationEvent.set("statusId", "COM_BOUNCED");
childCommunicationEvent.set("note", ServiceUtil.getErrorMessage(resultTmp));
}
childCommunicationEvent.store();
}
}
} else {
errorMessages.add(UtilProperties.getMessage(RESOURCE, "commeventservices.communication_event_not_without_content", locale));
}
if (!errorMessages.isEmpty()) {
communicationEvent.set("statusId", "COM_BOUNCED");
communicationEvent.set("note", errorMessages.toString());
communicationEvent.store();
} else {
//Update content status
for (GenericValue content : contents) {
Map<String, Object> updateContentResult = dispatcher.runSync("setContentStatus", UtilMisc.<String, Object>toMap("contentId",
content.getString("contentId"), "statusId", "CTNT_PUBLISHED", "userLogin", userLogin));
if (ServiceUtil.isError(updateContentResult)) {
errorMessages.add(ServiceUtil.getErrorMessage(updateContentResult));
}
}
Map<String, Object> completeResult = dispatcher.runSync("setCommEventComplete",
UtilMisc.<String, Object>toMap("communicationEventId", communicationEventId, "userLogin", userLogin));
if (ServiceUtil.isError(completeResult)) {
errorMessages.add(ServiceUtil.getErrorMessage(completeResult));
}
}
} catch (GenericEntityException | GenericServiceException e) {
return ServiceUtil.returnError(e.getMessage());
}
if (!errorMessages.isEmpty()) {
return ServiceUtil.returnFailure(errorMessages);
}
return ServiceUtil.returnSuccess();
}
public static Map<String, Object> sendEmailToContactList(DispatchContext ctx, Map<String, ? extends Object> context) {
Delegator delegator = ctx.getDelegator();
LocalDispatcher dispatcher = ctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
List<Object> errorMessages = new LinkedList<>();
String errorCallingUpdateContactListPartyService = UtilProperties.getMessage(RESOURCE,
"commeventservices.errorCallingUpdateContactListPartyService", locale);
String errorCallingSendMailService = UtilProperties.getMessage(RESOURCE, "commeventservices.errorCallingSendMailService", locale);
String errorInSendEmailToContactListService = UtilProperties.getMessage(RESOURCE,
"commeventservices.errorInSendEmailToContactListService", locale);
String skippingInvalidEmailAddress = UtilProperties.getMessage(RESOURCE, "commeventservices.skippingInvalidEmailAddress", locale);
String contactListId = (String) context.get("contactListId");
String communicationEventId = (String) context.get("communicationEventId");
// Any exceptions thrown in this block will cause the service to return error
try {
GenericValue communicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId",
communicationEventId).queryOne();
GenericValue contactList = EntityQuery.use(delegator).from("ContactList").where("contactListId", contactListId).queryOne();
Map<String, Object> sendMailParams = new HashMap<>();
sendMailParams.put("sendFrom", communicationEvent.getRelatedOne("FromContactMech", false).getString("infoString"));
sendMailParams.put("subject", communicationEvent.getString("subject"));
sendMailParams.put("contentType", communicationEvent.getString("contentMimeTypeId"));
sendMailParams.put("userLogin", userLogin);
// Find a list of distinct email addresses from active, ACCEPTED parties in the contact list
// using a list iterator (because there can be a large number)
List<EntityCondition> conditionList = UtilMisc.toList(
EntityCondition.makeCondition("contactListId", EntityOperator.EQUALS, contactList.get("contactListId")),
EntityCondition.makeCondition("statusId", EntityOperator.EQUALS, "CLPT_ACCEPTED"),
EntityCondition.makeCondition("preferredContactMechId", EntityOperator.NOT_EQUAL, null),
EntityUtil.getFilterByDateExpr(), EntityUtil.getFilterByDateExpr("contactFromDate", "contactThruDate"));
EntityQuery eq = EntityQuery.use(delegator).select("partyId", "preferredContactMechId", "fromDate", "infoString")
.from("ContactListPartyAndContactMech")
.where(EntityCondition.makeCondition(conditionList, EntityOperator.AND))
.cursorScrollInsensitive()
.distinct();
try (EntityListIterator eli = eq.queryIterator()) {
// Send an email to each contact list member
// loop through the list iterator
for (GenericValue contactListPartyAndContactMech; (contactListPartyAndContactMech = eli.next()) != null;) {
Debug.logInfo("Contact info: " + contactListPartyAndContactMech, MODULE);
// Any exceptions thrown in this inner block will only relate to a single email of the list, so should
// only be logged and not cause the service to return an error
try {
String emailAddress = contactListPartyAndContactMech.getString("infoString");
if (UtilValidate.isEmpty(emailAddress)) {
continue;
}
emailAddress = emailAddress.trim();
if (!UtilValidate.isEmail(emailAddress)) {
// If validation fails, just log and skip the email address
Debug.logError(skippingInvalidEmailAddress + ": " + emailAddress, MODULE);
errorMessages.add(skippingInvalidEmailAddress + ": " + emailAddress);
continue;
}
// Because we're retrieving infoString only above (so as not to pollute the distinctness), we
// need to retrieve the partyId it's related to. Since this could be multiple parties, get
// only the most recent valid one via ContactListPartyAndContactMech.
List<EntityCondition> clpConditionList = UtilMisc.makeListWritable(conditionList);
clpConditionList.add(EntityCondition.makeCondition("infoString", EntityOperator.EQUALS, emailAddress));
GenericValue lastContactListPartyACM = EntityQuery.use(delegator).from("ContactListPartyAndContactMech")
.where(EntityCondition.makeCondition(clpConditionList, EntityOperator.AND))
.orderBy("-fromDate")
.cache(true)
.queryFirst();
if (lastContactListPartyACM == null) {
continue;
}
String partyId = lastContactListPartyACM.getString("partyId");
sendMailParams.put("sendTo", emailAddress);
sendMailParams.put("partyId", partyId);
// Retrieve a record for this contactMechId from ContactListCommStatus
Map<String, String> contactListCommStatusRecordMap = UtilMisc.toMap("contactListId", contactListId, "communicationEventId",
communicationEventId, "contactMechId", lastContactListPartyACM.getString("preferredContactMechId"));
GenericValue contactListCommStatusRecord = EntityQuery.use(delegator).from("ContactListCommStatus")
.where(contactListCommStatusRecordMap)
.queryOne();
if (contactListCommStatusRecord == null) {
// No attempt has been made previously to send to this address, so create a record to reflect
// the beginning of the current attempt
Map<String, String> newContactListCommStatusRecordMap = UtilMisc.makeMapWritable(contactListCommStatusRecordMap);
newContactListCommStatusRecordMap.put("statusId", "COM_IN_PROGRESS");
newContactListCommStatusRecordMap.put("partyId", partyId);
contactListCommStatusRecord = delegator.create("ContactListCommStatus", newContactListCommStatusRecordMap);
} else if (contactListCommStatusRecord.get("statusId") != null && "COM_COMPLETE"
.equals(contactListCommStatusRecord.getString("statusId"))) {
// There was a successful earlier attempt, so skip this address
continue;
}
// Send e-mail
Debug.logInfo("Sending email to contact list [" + contactListId + "] party [" + partyId + "] : " + emailAddress, MODULE);
// Make the attempt to send the email to the address
Map<String, Object> tmpResult = null;
// Retrieve a contact list party status
GenericValue contactListPartyStatus = EntityQuery.use(delegator).from("ContactListPartyStatus")
.where("contactListId", contactListId, "partyId", contactListPartyAndContactMech.getString("partyId"),
"fromDate", contactListPartyAndContactMech.getTimestamp("fromDate"), "statusId", "CLPT_ACCEPTED")
.queryFirst();
if (contactListPartyStatus != null) {
// prepare body parameters
Map<String, Object> bodyParameters = new HashMap<>();
bodyParameters.put("contactListId", contactListId);
bodyParameters.put("partyId", contactListPartyAndContactMech.getString("partyId"));
bodyParameters.put("preferredContactMechId", contactListPartyAndContactMech.getString("preferredContactMechId"));
bodyParameters.put("emailAddress", emailAddress);
bodyParameters.put("fromDate", contactListPartyAndContactMech.getTimestamp("fromDate"));
bodyParameters.put("optInVerifyCode", contactListPartyStatus.getString("optInVerifyCode"));
bodyParameters.put("content", communicationEvent.getString("content"));
NotificationServices.setBaseUrl(delegator, contactList.getString("verifyEmailWebSiteId"), bodyParameters);
GenericValue webSite = EntityQuery.use(delegator).from("WebSite").where("webSiteId", contactList
.getString("verifyEmailWebSiteId")).queryOne();
if (webSite != null) {
GenericValue productStore = webSite.getRelatedOne("ProductStore", false);
if (productStore != null) {
List<GenericValue> productStoreEmailSettings = productStore.getRelated("ProductStoreEmailSetting",
UtilMisc.toMap("emailType", "CONT_EMAIL_TEMPLATE"), null, false);
GenericValue productStoreEmailSetting = EntityUtil.getFirst(productStoreEmailSettings);
if (productStoreEmailSetting != null) {
// send e-mail using screen template
sendMailParams.put("bodyScreenUri", productStoreEmailSetting.getString("bodyScreenLocation"));
sendMailParams.put("bodyParameters", bodyParameters);
sendMailParams.remove("body");
tmpResult = dispatcher.runSync("sendMailFromScreen", sendMailParams, 360, true);
if (ServiceUtil.isError(tmpResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(tmpResult));
}
}
}
}
}
// If the e-mail does not be sent then send normal e-mail
if (UtilValidate.isEmpty(tmpResult)) {
sendMailParams.put("body", communicationEvent.getString("content"));
tmpResult = dispatcher.runSync("sendMail", sendMailParams, 360, true);
if (ServiceUtil.isError(tmpResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(tmpResult));
}
}
if (tmpResult == null || ServiceUtil.isError(tmpResult)) {
if (ServiceUtil.getErrorMessage(tmpResult).startsWith("[ADDRERR]")) {
// address error; mark the communication event as BOUNCED
contactListCommStatusRecord.set("statusId", "COM_BOUNCED");
try {
contactListCommStatusRecord.store();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
errorMessages.add(e.getMessage());
}
// deactivate from the contact list
try {
GenericValue contactListParty = contactListPartyAndContactMech.getRelatedOne("ContactListParty", false);
if (contactListParty != null) {
contactListParty.set("statusId", "CLPT_INVALID");
contactListParty.store();
}
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
errorMessages.add(e.getMessage());
}
continue;
}
// If the send attempt fails, just log and skip the email address
Debug.logError(errorCallingSendMailService + ": " + ServiceUtil.getErrorMessage(tmpResult),
MODULE);
errorMessages.add(errorCallingSendMailService + ": " + ServiceUtil.getErrorMessage(
tmpResult));
continue;
}
// attach the parent communication event to the new event created when sending
// the mail
String thisCommEventId = (String) tmpResult.get("communicationEventId");
GenericValue thisCommEvent = EntityQuery.use(delegator).from("CommunicationEvent").where(
"communicationEventId", thisCommEventId).queryOne();
if (thisCommEvent != null) {
thisCommEvent.set("contactListId", contactListId);
thisCommEvent.set("parentCommEventId", communicationEventId);
thisCommEvent.store();
}
String messageId = (String) tmpResult.get("messageId");
contactListCommStatusRecord.set("messageId", messageId);
if ("Y".equals(contactList.get("singleUse"))) {
// Expire the ContactListParty if the list is single use and sendEmail finishes successfully
tmpResult = dispatcher.runSync("updateContactListParty", UtilMisc.toMap("contactListId",
lastContactListPartyACM.get("contactListId"),
"partyId", partyId, "fromDate", lastContactListPartyACM.get("fromDate"),
"thruDate", UtilDateTime.nowTimestamp(), "userLogin", userLogin));
if (ServiceUtil.isError(tmpResult)) {
// If the expiry fails, just log and skip the email address
Debug.logError(errorCallingUpdateContactListPartyService + ": " + ServiceUtil.getErrorMessage(tmpResult), MODULE);
errorMessages.add(errorCallingUpdateContactListPartyService + ": " + ServiceUtil.getErrorMessage(tmpResult));
continue;
}
}
// All is successful, so update the ContactListCommStatus record
contactListCommStatusRecord.set("statusId", "COM_COMPLETE");
delegator.store(contactListCommStatusRecord);
// Don't return a service error just because of failure for one address - just log the error and continue
} catch (GenericEntityException | GenericServiceException nonFatalGEE) {
Debug.logError(nonFatalGEE, errorInSendEmailToContactListService, MODULE);
errorMessages.add(errorInSendEmailToContactListService + ": " + nonFatalGEE.getMessage());
}
}
} catch (GenericEntityException fatalGEE) {
return ServiceUtil.returnError(fatalGEE.getMessage());
}
} catch (GenericEntityException fatalGEE) {
return ServiceUtil.returnError(fatalGEE.getMessage());
}
return errorMessages.isEmpty() ? ServiceUtil.returnSuccess() : ServiceUtil.returnError(errorMessages);
}
public static Map<String, Object> setCommEventComplete(DispatchContext dctx, Map<String, ? extends Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
GenericValue userLogin = (GenericValue) context.get("userLogin");
String communicationEventId = (String) context.get("communicationEventId");
String partyIdFrom = (String) context.get("partyIdFrom");
try {
GenericValue communicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId",
communicationEventId).cache().queryOne();
if (communicationEvent == null) {
return ServiceUtil.returnError(UtilProperties.getMessage("PartyUiLabels", "PartyCommunicationEventNotFound",
UtilMisc.toMap("communicationEventId", communicationEventId), (Locale) context.get("locale")));
}
Timestamp endDate = communicationEvent.getTimestamp("datetimeEnded");
if (endDate == null) {
endDate = UtilDateTime.nowTimestamp();
}
Map<String, Object> result = dispatcher.runSync("updateCommunicationEvent", UtilMisc.<String, Object>toMap("communicationEventId",
communicationEventId, "partyIdFrom", partyIdFrom, "statusId", "COM_COMPLETE", "datetimeEnded", endDate, "userLogin", userLogin));
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
} catch (GeneralException esx) {
return ServiceUtil.returnError(esx.getMessage());
}
return ServiceUtil.returnSuccess();
}
/*
* Store an outgoing file transfer as a communication event;
* runs as a pre-invoke ECA on sendContentToFtp service
* - service should run as the 'system' user
*/
public static Map<String, Object> createCommEventFromFtpTransfer(DispatchContext dctx, Map<String, ? extends Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
String contentId = (String) context.get("contentId");
String contactMechId = (String) context.get("contactMechId");
String partyId = (String) context.get("partyId");
String communicationEventId;
Timestamp now = UtilDateTime.nowTimestamp();
Map<String, Object> commEventMap = new HashMap<>();
commEventMap.put("communicationEventTypeId", "FILE_TRANSFER_COMM");
commEventMap.put("contactMechTypeId", "FTP_ADDRESS");
commEventMap.put("contactMechIdTo", contactMechId);
commEventMap.put("statusId", "COM_PENDING");
commEventMap.put("datetimeStarted", now);
commEventMap.put("entryDate", now);
commEventMap.put("userLogin", userLogin);
if (UtilValidate.isNotEmpty(partyId)) {
commEventMap.put("partyIdTo", partyId);
}
Map<String, Object> createResult;
try {
createResult = dispatcher.runSync("createCommunicationEvent", commEventMap);
if (ServiceUtil.isError(createResult)) {
return createResult;
}
communicationEventId = (String) createResult.get("communicationEventId");
//add content to newly created commEvent
Map<String, Object> createCommEventContentMap = new HashMap<>();
createCommEventContentMap.put("userLogin", userLogin);
createCommEventContentMap.put("contentId", contentId);
createCommEventContentMap.put("communicationEventId", communicationEventId);
createResult = dispatcher.runSync("createCommEventContentAssoc", createCommEventContentMap);
if (ServiceUtil.isError(createResult)) {
return createResult;
}
} catch (GenericServiceException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("communicationEventId", communicationEventId);
return result;
}
/*
* Store an outgoing email as a communication event;
* runs as a pre-invoke ECA on sendMail and sendMultipartMail services
* - service should run as the 'system' user
*/
public static Map<String, Object> createCommEventFromEmail(DispatchContext dctx, Map<String, ? extends Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
GenericValue userLogin = (GenericValue) context.get("userLogin");
String subject = (String) context.get("subject");
String sendFrom = (String) context.get("sendFrom");
String sendTo = (String) context.get("sendTo");
String partyId = (String) context.get("partyId");
String contentType = (String) context.get("contentType");
String statusId = (String) context.get("statusId");
String orderId = (String) context.get("orderId");
String returnId = (String) context.get("returnId");
if (statusId == null) {
statusId = "COM_PENDING";
}
// get the from contact mech info
String contactMechIdFrom = null;
String partyIdFrom = null;
GenericValue fromCm;
try {
fromCm = EntityQuery.use(delegator).from("PartyAndContactMech").where("infoString", sendFrom).orderBy("-fromDate")
.filterByDate().queryFirst();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (fromCm != null) {
contactMechIdFrom = fromCm.getString("contactMechId");
partyIdFrom = fromCm.getString("partyId");
}
// get the to contact mech info
String contactMechIdTo = null;
GenericValue toCm;
try {
toCm = EntityQuery.use(delegator).from("PartyAndContactMech").where("infoString", sendTo, "partyId", partyId)
.orderBy("-fromDate").filterByDate().queryFirst();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (toCm != null) {
contactMechIdTo = toCm.getString("contactMechId");
}
Timestamp now = UtilDateTime.nowTimestamp();
Map<String, Object> commEventMap = new HashMap<>();
commEventMap.put("communicationEventTypeId", "EMAIL_COMMUNICATION");
commEventMap.put("contactMechTypeId", "EMAIL_ADDRESS");
commEventMap.put("contactMechIdFrom", contactMechIdFrom);
commEventMap.put("contactMechIdTo", contactMechIdTo);
commEventMap.put("statusId", statusId);
commEventMap.put("partyIdFrom", partyIdFrom);
commEventMap.put("partyIdTo", partyId);
commEventMap.put("datetimeStarted", now);
commEventMap.put("entryDate", now);
commEventMap.put("subject", subject);
commEventMap.put("userLogin", userLogin);
commEventMap.put("contentMimeTypeId", contentType);
if (UtilValidate.isNotEmpty(orderId)) {
commEventMap.put("orderId", orderId);
}
if (UtilValidate.isNotEmpty(returnId)) {
commEventMap.put("returnId", returnId);
}
Map<String, Object> createResult;
try {
createResult = dispatcher.runSync("createCommunicationEvent", commEventMap);
if (ServiceUtil.isError(createResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(createResult));
}
} catch (GenericServiceException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(createResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(createResult));
}
String communicationEventId = (String) createResult.get("communicationEventId");
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("communicationEventId", communicationEventId);
return result;
}
/*
* Update the communication event with information from the email;
* runs as a post-commit ECA on sendMail and sendMultiPartMail services
* - service should run as the 'system' user
*/
public static Map<String, Object> updateCommEventAfterEmail(DispatchContext dctx, Map<String, ? extends Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
String communicationEventId = (String) context.get("communicationEventId");
MimeMessageWrapper wrapper = (MimeMessageWrapper) context.get("messageWrapper");
Map<String, Object> commEventMap = new HashMap<>();
commEventMap.put("communicationEventId", communicationEventId);
commEventMap.put("subject", wrapper.getSubject());
commEventMap.put("statusId", "COM_COMPLETE");
commEventMap.put("datetimeEnded", UtilDateTime.nowTimestamp());
commEventMap.put("entryDate", wrapper.getSentDate());
commEventMap.put("messageId", wrapper.getMessageId());
commEventMap.put("userLogin", userLogin);
commEventMap.put("content", wrapper.getMessageBody());
// populate the address (to/from/cc/bcc) data
populateAddressesFromMessage(wrapper, commEventMap);
// save the communication event
try {
Map<String, Object> result = dispatcher.runSync("updateCommunicationEvent", commEventMap);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
} catch (GenericServiceException e) {
return ServiceUtil.returnError(e.getMessage());
}
// attachments
try {
createAttachmentContent(dispatcher, dctx.getDelegator(), wrapper, communicationEventId, userLogin);
} catch (GenericServiceException | GenericEntityException e) {
return ServiceUtil.returnError(e.getMessage());
}
return ServiceUtil.returnSuccess();
}
/**
* This service is the main one for processing incoming emails.
* Its only argument is a wrapper for the JavaMail MimeMessage object.
* From this object, all the fields, headers and content of the message can be accessed.
* The first thing this service does is try to discover the partyId of the message sender
* by doing a reverse find on the email address. It uses the findPartyFromEmailAddress service to do this.
* It then creates a CommunicationEvent entity by calling the createCommunicationEvent service using the appropriate fields from the email and the
* discovered partyId, if it exists, as the partyIdFrom. Note that it sets the communicationEventTypeId
* field to AUTO_EMAIL_COMM. This is useful for tracking email generated communications.
* The service tries to find appropriate content for inclusion in the CommunicationEvent.content field.
* If the contentType of the content starts with "text", the getContent() call returns a string and it is used.
* If the contentType starts with "multipart", then the "parts" of the content are iterated thru and the first
* one of mime type, "text/..." is used.
* If the contentType has a value of "multipart" then the parts of the content (except the one used in the main
* CommunicationEvent.content field) are cycled thru and attached to the CommunicationEvent entity using the
* createCommContentDataResource service. This happens in the EmailWorker.addAttachmentsToCommEvent method.
* However multiparts can contain multiparts. A recursive function has been added.
* -Al Byers - Hans Bakker
* @param dctx the dispatch context
* @param context the context
* @return returns the result of the service execution
*/
public static Map<String, Object> storeIncomingEmail(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
MimeMessageWrapper wrapper = (MimeMessageWrapper) context.get("messageWrapper");
Timestamp nowTimestamp = UtilDateTime.nowTimestamp();
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
String partyIdTo = null;
String partyIdFrom = null;
String communicationEventId = null;
String contactMechIdFrom = null;
String contactMechIdTo = null;
Map<String, Object> result = null;
try {
Address[] addressesFrom = wrapper.getFrom();
Address[] addressesTo = wrapper.getTo();
Address[] addressesCC = wrapper.getCc();
Address[] addressesBCC = wrapper.getBcc();
String messageId = wrapper.getMessageId().replaceAll("[<>]", "");
String aboutThisEmail = "message [" + messageId + "] from ["
+ ((addressesFrom == null || addressesFrom[0] == null) ? "not found" : addressesFrom[0].toString()) + "] to ["
+ ((addressesTo == null || addressesTo[0] == null) ? "not found" : addressesTo[0].toString()) + "]";
if (Debug.verboseOn()) {
Debug.logVerbose("Processing Incoming Email " + aboutThisEmail, MODULE);
}
// ignore the message when the spam status = yes
String spamHeaderName = EntityUtilProperties.getPropertyValue("general", "mail.spam.name", "N", delegator);
String configHeaderValue = EntityUtilProperties.getPropertyValue("general", "mail.spam.value", delegator);
// only execute when config file has been set && header variable found
if (!"N".equals(spamHeaderName) && wrapper.getHeader(spamHeaderName) != null && wrapper.getHeader(spamHeaderName).length > 0) {
String msgHeaderValue = wrapper.getHeader(spamHeaderName)[0];
if (msgHeaderValue != null && msgHeaderValue.startsWith(configHeaderValue)) {
Debug.logInfo("Incoming Email message ignored, was detected by external spam checker", MODULE);
return ServiceUtil.returnSuccess(UtilProperties.getMessage(RESOURCE,
"PartyCommEventMessageIgnoredDetectedByExternalSpamChecker", locale));
}
}
// if no 'from' addresses specified ignore the message
if (addressesFrom == null) {
Debug.logInfo("Incoming Email message ignored, had not 'from' email address", MODULE);
return ServiceUtil.returnSuccess(UtilProperties.getMessage(RESOURCE,
"PartyCommEventMessageIgnoredNoFromAddressSpecified", locale));
}
// make sure this isn't a duplicate
List<GenericValue> commEvents;
try {
commEvents = EntityQuery.use(delegator).from("CommunicationEvent").where("messageId", messageId).queryList();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (!commEvents.isEmpty()) {
Debug.logInfo("Ignoring Duplicate Email: " + aboutThisEmail, MODULE);
return ServiceUtil.returnSuccess(UtilProperties.getMessage(RESOURCE,
"PartyCommEventMessageIgnoredDuplicateMessageId", locale));
}
// get the related partId's
List<Map<String, Object>> toParties = buildListOfPartyInfoFromEmailAddresses(addressesTo, userLogin, dispatcher);
List<Map<String, Object>> ccParties = buildListOfPartyInfoFromEmailAddresses(addressesCC, userLogin, dispatcher);
List<Map<String, Object>> bccParties = buildListOfPartyInfoFromEmailAddresses(addressesBCC, userLogin, dispatcher);
//Get the first address from the list - this is the partyIdTo field of the CommunicationEvent
if (!toParties.isEmpty()) {
Map<String, Object> firstAddressTo = toParties.get(0);
partyIdTo = (String) firstAddressTo.get("partyId");
contactMechIdTo = (String) firstAddressTo.get("contactMechId");
}
String deliveredTo = wrapper.getFirstHeader("Delivered-To");
if (deliveredTo != null) {
// check if started with the domain name if yes remove including the dash.
String dn = deliveredTo.substring(deliveredTo.indexOf('@') + 1, deliveredTo.length());
if (deliveredTo.startsWith(dn)) {
deliveredTo = deliveredTo.substring(dn.length() + 1, deliveredTo.length());
}
}
// if partyIdTo not found try to find the "to" address using the delivered-to header
if ((partyIdTo == null) && (deliveredTo != null)) {
result = dispatcher.runSync("findPartyFromEmailAddress", UtilMisc.<String, Object>toMap("address",
deliveredTo, "userLogin", userLogin));
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
partyIdTo = (String) result.get("partyId");
contactMechIdTo = (String) result.get("contactMechId");
}
if (userLogin.get("partyId") == null && partyIdTo != null) {
int ch = 0;
for (ch = partyIdTo.length(); ch > 0 && Character.isDigit(partyIdTo.charAt(ch - 1)); ch--) {
Debug.log("Increase partyIdTo string to create a prefix", MODULE);
}
userLogin.put("partyId", partyIdTo.substring(0, ch)); //allow services to be called to have prefix
}
// get the 'from' partyId
result = getParyInfoFromEmailAddress(addressesFrom, userLogin, dispatcher);
partyIdFrom = (String) result.get("partyId");
contactMechIdFrom = (String) result.get("contactMechId");
Map<String, Object> commEventMap = new HashMap<>();
commEventMap.put("communicationEventTypeId", "AUTO_EMAIL_COMM");
commEventMap.put("contactMechTypeId", "EMAIL_ADDRESS");
commEventMap.put("messageId", messageId);
String subject = wrapper.getSubject();
commEventMap.put("subject", subject);
// Set sent and received dates
commEventMap.put("entryDate", nowTimestamp);
commEventMap.put("datetimeStarted", UtilDateTime.toTimestamp(wrapper.getSentDate()));
commEventMap.put("datetimeEnded", UtilDateTime.toTimestamp(wrapper.getReceivedDate()));
// default role types (_NA_)
commEventMap.put("roleTypeIdFrom", "_NA_");
commEventMap.put("roleTypeIdTo", "_NA_");
// get the content(type) part
String messageBodyContentType = wrapper.getMessageBodyContentType();
if (messageBodyContentType.indexOf(';') > -1) {
messageBodyContentType = messageBodyContentType.substring(0, messageBodyContentType.indexOf(';'));
}
// select the plain text bodypart
String messageBody = null;
if (wrapper.getMainPartCount() > 1) {
for (int ind = 0; ind < wrapper.getMainPartCount(); ind++) {
BodyPart p = wrapper.getPart(ind + "");
if (p.getContentType().toLowerCase(Locale.getDefault()).indexOf("text/plain") > -1) {
messageBody = (String) p.getContent();
break;
}
}
}
if (messageBody == null) {
messageBody = wrapper.getMessageBody();
}
commEventMap.put("content", messageBody);
commEventMap.put("contentMimeTypeId", messageBodyContentType.toLowerCase(Locale.getDefault()));
// check for for a reply to communication event (using in-reply-to the parent messageID)
String[] inReplyTo = wrapper.getHeader("In-Reply-To");
if (inReplyTo != null && inReplyTo[0] != null) {
GenericValue parentCommEvent = null;
try {
parentCommEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("messageId",
inReplyTo[0].replaceAll("[<>]", "")).queryFirst();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
}
if (parentCommEvent != null) {
String parentCommEventId = parentCommEvent.getString("communicationEventId");
String orgCommEventId = parentCommEvent.getString("origCommEventId");
if (orgCommEventId == null) {
orgCommEventId = parentCommEventId;
}
commEventMap.put("parentCommEventId", parentCommEventId);
commEventMap.put("origCommEventId", orgCommEventId);
}
}
// populate the address (to/from/cc/bcc) data
populateAddressesFromMessage(wrapper, commEventMap);
// store from/to parties, but when not found make a note of the email to/from address in the workEffort Note Section.
String commNote = "";
if (partyIdFrom != null) {
commEventMap.put("partyIdFrom", partyIdFrom);
commEventMap.put("contactMechIdFrom", contactMechIdFrom);
} else {
commNote += "Sent from: " + ((InternetAddress) addressesFrom[0]).getAddress() + "; ";
commNote += "Sent Name from: " + ((InternetAddress) addressesFrom[0]).getPersonal() + "; ";
}
if (partyIdTo != null) {
commEventMap.put("partyIdTo", partyIdTo);
commEventMap.put("contactMechIdTo", contactMechIdTo);
} else {
commNote += "Sent to: " + ((InternetAddress) addressesTo[0]).getAddress() + "; ";
if (deliveredTo != null) {
commNote += "Delivered-To: " + deliveredTo + "; ";
}
}
commNote += "Sent to: " + ((InternetAddress) addressesTo[0]).getAddress() + "; ";
commNote += "Delivered-To: " + deliveredTo + "; ";
if (partyIdTo != null && partyIdFrom != null) {
commEventMap.put("statusId", "COM_ENTERED");
} else {
commEventMap.put("statusId", "COM_UNKNOWN_PARTY");
}
if (commNote.length() > 255) {
commNote = commNote.substring(0, 255);
}
if (!("".equals(commNote))) {
commEventMap.put("note", commNote);
}
commEventMap.put("userLogin", userLogin);
// Populate the CommunicationEvent.headerString field with the email headers
StringBuilder headerString = new StringBuilder();
Enumeration<?> headerLines = wrapper.getMessage().getAllHeaderLines();
while (headerLines.hasMoreElements()) {
headerString.append(System.getProperty("line.separator"));
headerString.append(headerLines.nextElement());
}
String header = headerString.toString();
commEventMap.put("headerString", header.replaceAll("[<>]", ""));
result = dispatcher.runSync("createCommunicationEvent", commEventMap);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
communicationEventId = (String) result.get("communicationEventId");
Debug.logInfo("Persisting New Email: " + aboutThisEmail + " into CommunicationEventId: " + communicationEventId, MODULE);
// handle the attachments
createAttachmentContent(dispatcher, delegator, wrapper, communicationEventId, userLogin);
// For all addresses create a CommunicationEventRoles
createCommEventRoles(userLogin, delegator, dispatcher, communicationEventId, toParties, "ADDRESSEE");
createCommEventRoles(userLogin, delegator, dispatcher, communicationEventId, ccParties, "CC");
createCommEventRoles(userLogin, delegator, dispatcher, communicationEventId, bccParties, "BCC");
// get the related work effort info
List<Map<String, Object>> toWorkEffortInfos = buildListOfWorkEffortInfoFromEmailAddresses(addressesTo, userLogin, dispatcher);
List<Map<String, Object>> ccWorkEffortInfos = buildListOfWorkEffortInfoFromEmailAddresses(addressesCC, userLogin, dispatcher);
List<Map<String, Object>> bccWorkEffortInfos = buildListOfWorkEffortInfoFromEmailAddresses(addressesBCC, userLogin, dispatcher);
// For all WorkEffort addresses create a CommunicationEventWorkEffs
createCommunicationEventWorkEffs(userLogin, dispatcher, toWorkEffortInfos, communicationEventId);
createCommunicationEventWorkEffs(userLogin, dispatcher, ccWorkEffortInfos, communicationEventId);
createCommunicationEventWorkEffs(userLogin, dispatcher, bccWorkEffortInfos, communicationEventId);
Map<String, Object> results = ServiceUtil.returnSuccess();
results.put("communicationEventId", communicationEventId);
results.put("statusId", commEventMap.get("statusId"));
return results;
} catch (MessagingException | GenericServiceException | GenericEntityException | IOException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
}
private static void populateAddressesFromMessage(MimeMessageWrapper wrapper, Map<String, Object> commEventMap) {
// Retrieve all the addresses from the email
Address[] addressesFrom = wrapper.getFrom();
Address[] addressesTo = wrapper.getTo();
Address[] addressesCC = wrapper.getCc();
Address[] addressesBCC = wrapper.getBcc();
Set<String> emailAddressesFrom = new TreeSet<>();
Set<String> emailAddressesTo = new TreeSet<>();
Set<String> emailAddressesCC = new TreeSet<>();
Set<String> emailAddressesBCC = new TreeSet<>();
for (Address element : addressesFrom) {
emailAddressesFrom.add(((InternetAddress) element).getAddress());
}
for (Address element : addressesTo) {
emailAddressesTo.add(((InternetAddress) element).getAddress());
}
if (addressesCC != null) {
for (Address element : addressesCC) {
emailAddressesCC.add(((InternetAddress) element).getAddress());
}
}
if (addressesBCC != null) {
for (Address element : addressesBCC) {
emailAddressesBCC.add(((InternetAddress) element).getAddress());
}
}
String fromString = StringUtil.join(emailAddressesFrom, ",");
String toString = StringUtil.join(emailAddressesTo, ",");
String ccString = StringUtil.join(emailAddressesCC, ",");
String bccString = StringUtil.join(emailAddressesBCC, ",");
if (UtilValidate.isNotEmpty(fromString)) {
commEventMap.put("fromString", fromString);
}
if (UtilValidate.isNotEmpty(toString)) {
commEventMap.put("toString", toString);
}
if (UtilValidate.isNotEmpty(ccString)) {
commEventMap.put("ccString", ccString);
}
if (UtilValidate.isNotEmpty(bccString)) {
commEventMap.put("bccString", bccString);
}
}
private static List<String> getCommEventAttachmentNames(final Delegator delegator, final String communicationEventId)
throws GenericEntityException {
List<GenericValue> commEventContentAssocList = EntityQuery.use(delegator)
.from("CommEventContentDataResource")
.where(EntityCondition.makeCondition("communicationEventId", communicationEventId))
.filterByDate()
.queryList();
List<String> attachmentNames = new ArrayList<>();
for (GenericValue commEventContentAssoc : commEventContentAssocList) {
String dataResourceName = commEventContentAssoc.getString("drDataResourceName");
attachmentNames.add(dataResourceName);
}
return attachmentNames;
}
private static void createAttachmentContent(LocalDispatcher dispatcher, Delegator delegator, MimeMessageWrapper wrapper,
String communicationEventId, GenericValue userLogin) throws GenericServiceException, GenericEntityException {
// handle the attachments
String subject = wrapper.getSubject();
List<String> attachmentIndexes = wrapper.getAttachmentIndexes();
List<String> currentAttachmentNames = getCommEventAttachmentNames(delegator, communicationEventId);
if (!attachmentIndexes.isEmpty()) {
Debug.logInfo("=== message has attachments [" + attachmentIndexes.size() + "] =====", MODULE);
for (String attachmentIdx : attachmentIndexes) {
String attFileName = wrapper.getPartFilename(attachmentIdx);
if (currentAttachmentNames.contains(attFileName)) {
Debug.logWarning(String.format("CommunicationEvent [%s] already has attachment named '%s'", communicationEventId,
attFileName), MODULE);
continue;
}
Map<String, Object> attachmentMap = new HashMap<>();
attachmentMap.put("communicationEventId", communicationEventId);
attachmentMap.put("contentTypeId", "DOCUMENT");
attachmentMap.put("mimeTypeId", "text/html");
attachmentMap.put("userLogin", userLogin);
if (subject != null && subject.length() > 80) {
subject = subject.substring(0, 80); // make sure not too big for database field. (20 characters for filename)
}
String attContentType = wrapper.getPartContentType(attachmentIdx);
if (attContentType != null && attContentType.indexOf(';') > -1) {
attContentType = attContentType.toLowerCase(Locale.getDefault()).substring(0, attContentType.indexOf(';'));
}
if (UtilValidate.isNotEmpty(attFileName)) {
attachmentMap.put("contentName", attFileName);
attachmentMap.put("description", subject + "-" + attachmentIdx);
} else {
attachmentMap.put("contentName", subject + "-" + attachmentIdx);
}
attachmentMap.put("drMimeTypeId", attContentType);
if (attContentType != null && attContentType.startsWith("text")) {
String text = wrapper.getPartText(attachmentIdx);
attachmentMap.put("drDataResourceTypeId", "ELECTRONIC_TEXT");
attachmentMap.put("textData", text);
} else {
ByteBuffer data = wrapper.getPartByteBuffer(attachmentIdx);
if (Debug.infoOn()) {
Debug.logInfo("Binary attachment size: " + data.limit(), MODULE);
}
attachmentMap.put("drDataResourceName", attFileName);
attachmentMap.put("imageData", data);
attachmentMap.put("drDataResourceTypeId", "IMAGE_OBJECT"); // TODO: why always use IMAGE
attachmentMap.put("_imageData_contentType", attContentType);
}
// save the content
Map<String, Object> result = dispatcher.runSync("createCommContentDataResource", attachmentMap);
if (ServiceUtil.isError(result)) {
String errorMessage = ServiceUtil.getErrorMessage(result);
Debug.logError(errorMessage, MODULE);
throw new GenericServiceException(errorMessage);
}
}
}
}
private static void createCommEventRoles(GenericValue userLogin, Delegator delegator, LocalDispatcher dispatcher, String
communicationEventId, List<Map<String, Object>> parties, String roleTypeId) {
// It's not clear what the "role" of this communication event should be, so we'll just put _NA_
// check and see if this role was already created and ignore if true
try {
for (Map<String, Object> result : parties) {
String partyId = (String) result.get("partyId");
GenericValue commEventRole = EntityQuery.use(delegator).from("CommunicationEventRole")
.where("communicationEventId", communicationEventId, "partyId", partyId, "roleTypeId", roleTypeId)
.queryOne();
if (commEventRole == null) {
Map<String, Object> input = UtilMisc.toMap("communicationEventId", communicationEventId,
"partyId", partyId, "roleTypeId", roleTypeId, "userLogin", userLogin,
"contactMechId", (String) result.get("contactMechId"),
"statusId", "COM_ROLE_CREATED");
Map<String, Object> resultMap = dispatcher.runSync("createCommunicationEventRole", input);
if (ServiceUtil.isError(resultMap)) {
String errorMessage = ServiceUtil.getErrorMessage(resultMap);
Debug.logError(errorMessage, MODULE);
}
}
}
} catch (GenericServiceException | GenericEntityException e) {
Debug.logError(e, MODULE);
}
}
private static void createCommunicationEventWorkEffs(GenericValue userLogin, LocalDispatcher dispatcher, List<Map<String,
Object>> workEffortInfos, String communicationEventId) {
// create relationship between communication event and work efforts
try {
for (Map<String, Object> result : workEffortInfos) {
String workEffortId = (String) result.get("workEffortId");
Map<String, Object> resultMap = dispatcher.runSync("createCommunicationEventWorkEff",
UtilMisc.toMap("workEffortId", workEffortId, "communicationEventId", communicationEventId, "userLogin", userLogin));
if (ServiceUtil.isError(resultMap)) {
String errorMessage = ServiceUtil.getErrorMessage(resultMap);
Debug.logError(errorMessage, MODULE);
}
}
} catch (GenericServiceException e) {
Debug.logError(e, MODULE);
}
}
/*
* Helper method to retrieve the party information from the first email address of the Address[] specified.
*/
private static Map<String, Object> getParyInfoFromEmailAddress(Address[] addresses, GenericValue userLogin, LocalDispatcher dispatcher)
throws GenericServiceException {
InternetAddress emailAddress = null;
Map<String, Object> map = null;
Map<String, Object> result = null;
if (addresses == null) {
return null;
}
if (addresses.length > 0) {
Address addr = addresses[0];
if (addr instanceof InternetAddress) {
emailAddress = (InternetAddress) addr;
}
}
if (emailAddress != null) {
map = new HashMap<>();
map.put("address", emailAddress.getAddress());
map.put("userLogin", userLogin);
result = dispatcher.runSync("findPartyFromEmailAddress", map);
if (ServiceUtil.isError(result)) {
String errorMessage = ServiceUtil.getErrorMessage(result);
Debug.logError(errorMessage, MODULE);
throw new GenericServiceException(errorMessage);
}
}
return result;
}
/*
* Calls findPartyFromEmailAddress service and returns a List of the results for the array of addresses
*/
private static List<Map<String, Object>> buildListOfPartyInfoFromEmailAddresses(Address[] addresses, GenericValue userLogin,
LocalDispatcher dispatcher) throws GenericServiceException {
InternetAddress emailAddress = null;
Map<String, Object> result = null;
List<Map<String, Object>> tempResults = new LinkedList<>();
if (addresses != null) {
for (Address addr: addresses) {
if (addr instanceof InternetAddress) {
emailAddress = (InternetAddress) addr;
result = dispatcher.runSync("findPartyFromEmailAddress",
UtilMisc.toMap("address", emailAddress.getAddress(), "userLogin", userLogin));
if (ServiceUtil.isError(result)) {
String errorMessage = ServiceUtil.getErrorMessage(result);
Debug.logError(errorMessage, MODULE);
throw new GenericServiceException(errorMessage);
}
if (result.get("partyId") != null) {
tempResults.add(result);
}
}
}
}
return tempResults;
}
/*
* Gets WorkEffort info from e-mail address and returns a List of the results for the array of addresses
*/
private static List<Map<String, Object>> buildListOfWorkEffortInfoFromEmailAddresses(Address[] addresses, GenericValue
userLogin, LocalDispatcher dispatcher) throws GenericServiceException {
InternetAddress emailAddress = null;
Map<String, Object> result = null;
Delegator delegator = dispatcher.getDelegator();
List<Map<String, Object>> tempResults = new LinkedList<>();
String caseInsensitiveEmail = EntityUtilProperties.getPropertyValue("general", "mail.address.caseInsensitive", "N", delegator);
if (addresses != null) {
for (Address addr: addresses) {
if (addr instanceof InternetAddress) {
emailAddress = (InternetAddress) addr;
Map<String, String> inputFields = new HashMap<>();
inputFields.put("infoString", emailAddress.getAddress());
inputFields.put("infoString_ic", caseInsensitiveEmail);
result = dispatcher.runSync("performFind", UtilMisc.<String, Object>toMap("entityName",
"WorkEffortContactMechView", "inputFields", inputFields, "userLogin", userLogin));
if (ServiceUtil.isError(result)) {
String errorMessage = ServiceUtil.getErrorMessage(result);
Debug.logError(errorMessage, MODULE);
throw new GenericServiceException(errorMessage);
}
try (EntityListIterator listIt = (EntityListIterator) result.get("listIt")) {
List<GenericValue> list = listIt.getCompleteList();
List<GenericValue> filteredList = EntityUtil.filterByDate(list);
tempResults.addAll(filteredList);
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
}
}
}
}
return tempResults;
}
/*
* Service to process incoming email and look for a bounce message. If the email is indeed a bounce message
* the CommunicationEvent will be updated with the proper COM_BOUNCED status.
*/
public static Map<String, Object> processBouncedMessage(DispatchContext dctx, Map<String, ? extends Object> context) {
Debug.logInfo("Running process bounced message check...", MODULE);
MimeMessageWrapper wrapper = (MimeMessageWrapper) context.get("messageWrapper");
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
int parts = wrapper.getMainPartCount();
if (parts >= 3) { // it must have all three parts in order to process correctly
// get the second part (delivery report)
String contentType = wrapper.getPartContentType("1"); // index 1 should be the second part
if (contentType != null && "message/delivery-status".equalsIgnoreCase(contentType)) {
Debug.logInfo("Delivery status report part found; processing...", MODULE);
// get the content of the part
String part2Text = wrapper.getPartRawText("1");
if (part2Text == null) {
part2Text = "";
}
if (Debug.verboseOn()) {
Debug.logVerbose("Part 2 Text :\n\n" + part2Text, MODULE);
}
// find the "Action" element and obtain its value (looking for "failed")
Pattern p2 = Pattern.compile("^Action: (.*)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Matcher m2 = p2.matcher(part2Text);
String action = null;
if (m2.find()) {
action = m2.group(1);
}
if (action != null && "failed".equalsIgnoreCase(action)) {
// message bounced -- get the original message
String part3Text = wrapper.getPartRawText("2"); // index 2 should be the third part
if (part3Text == null) {
part3Text = "";
}
if (Debug.verboseOn()) {
Debug.logVerbose("Part 3 Text :\n\n" + part3Text, MODULE);
}
// find the "Message-Id" element and obtain its value (looking for "failed")
Pattern p3 = Pattern.compile("^Message-Id: (.*)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Matcher m3 = p3.matcher(part3Text);
String messageId = null;
if (m3.find()) {
Debug.logInfo("Found message-id : " + m3.group(), MODULE);
messageId = m3.group(1);
}
// find the matching communication event
if (messageId != null) {
List<GenericValue> values;
try {
values = EntityQuery.use(delegator).from("CommunicationEvent").where("messageId", messageId).queryList();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (UtilValidate.isNotEmpty(values)) {
// there should be only one; unique key
GenericValue value = values.get(0);
// update the communication event status
Map<String, Object> updateCtx = new HashMap<>();
updateCtx.put("communicationEventId", value.getString("communicationEventId"));
updateCtx.put("statusId", "COM_BOUNCED");
updateCtx.put("userLogin", context.get("userLogin"));
Map<String, Object> result;
try {
result = dispatcher.runSync("updateCommunicationEvent", updateCtx);
if (ServiceUtil.isError(result)) {
String errorMessage = ServiceUtil.getErrorMessage(result);
Debug.logError(errorMessage, MODULE);
}
} catch (GenericServiceException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
} else {
if (Debug.infoOn()) {
Debug.logInfo("Unable to find CommunicationEvent with the matching messageId : " + messageId, MODULE);
}
// no communication events found for that message ID; possible this is a NEWSLETTER
try {
values = EntityQuery.use(delegator).from("ContactListCommStatus").where("messageId", messageId).queryList();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (UtilValidate.isNotEmpty(values)) {
// there should be only one; unique key
GenericValue value = values.get(0);
Map<String, Object> updateCtx = new HashMap<>();
updateCtx.put("communicationEventId", value.getString("communicationEventId"));
updateCtx.put("contactListId", value.getString("contactListId"));
updateCtx.put("contactMechId", value.getString("contactMechId"));
updateCtx.put("partyId", value.getString("partyId"));
updateCtx.put("statusId", "COM_BOUNCED");
updateCtx.put("userLogin", context.get("userLogin"));
Map<String, Object> result;
try {
result = dispatcher.runSync("updateContactListCommStatus", updateCtx);
} catch (GenericServiceException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
} else {
if (Debug.infoOn()) {
Debug.logInfo("Unable to find ContactListCommStatus with the matching messageId : " + messageId, MODULE);
}
}
}
} else {
Debug.logWarning("No message ID attached to part", MODULE);
}
}
}
}
return ServiceUtil.returnSuccess();
}
public static Map<String, Object> logIncomingMessage(DispatchContext dctx, Map<String, ? extends Object> context) {
MimeMessageWrapper wrapper = (MimeMessageWrapper) context.get("messageWrapper");
Debug.logInfo("Message recevied : " + wrapper.getSubject(), MODULE);
Debug.logInfo("-- Content Type : " + wrapper.getContentType(), MODULE);
Debug.logInfo("-- Number of parts : " + wrapper.getMainPartCount(), MODULE);
Debug.logInfo("-- Number of attachments : " + wrapper.getAttachmentIndexes().size(), MODULE);
Debug.logInfo("-- Message ID : " + wrapper.getMessageId(), MODULE);
Debug.logInfo("### MESSAGE ###\n\n" + wrapper.getMessageBody(), MODULE);
List<String> attachmentIndexes = wrapper.getAttachmentIndexes();
if (!attachmentIndexes.isEmpty()) {
Debug.logInfo("### ATTACHMENTS ###", MODULE);
for (String idx : attachmentIndexes) {
Debug.logInfo("### -- Filename : " + wrapper.getPartFilename(idx), MODULE);
Debug.logInfo("### -- Content Type : " + wrapper.getPartContentType(idx), MODULE);
}
}
return ServiceUtil.returnSuccess();
}
/*
* Event which marks a communication event as read, and returns a 1px image to the browser/mail client
* Is updated because the read status is now stored in the communicationEventRole
* This services is updated but could not be tested. assumed is "read" for partyIdTo on the commevent
*/
public static String markCommunicationAsRead(HttpServletRequest request, HttpServletResponse response) {
String communicationEventId = null;
// pull the communication event from path info, so we can hide the process from the user
String pathInfo = request.getPathInfo();
String[] pathParsed = pathInfo.split("/", 3);
if (pathParsed.length > 2) {
pathInfo = pathParsed[2];
} else {
pathInfo = null;
}
if (pathInfo != null && pathInfo.indexOf('/') > -1) {
pathParsed = pathInfo.split("/");
communicationEventId = pathParsed[0];
}
// update the communication event
if (communicationEventId != null) {
Debug.logInfo("Marking communicationEventId [" + communicationEventId + "] from path info : " + request.getPathInfo()
+ " as read.", MODULE);
Delegator delegator = (Delegator) request.getAttribute("delegator");
GenericValue communicationEvent = null;
try {
communicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId",
communicationEventId).cache().queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
}
LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher");
try {
dispatcher.runAsync("setCommEventRoleToRead", UtilMisc.toMap("communicationEventId", communicationEventId,
"partyId", communicationEvent.getString("partyIdTo")));
} catch (GenericServiceException e) {
Debug.logError(e, MODULE);
}
}
// return the 1px image (spacer.gif)
URL imageUrl;
try {
imageUrl = FlexibleLocation.resolveLocation("component://common-theme/webapp/images/spacer.gif");
try (InputStream imageStream = imageUrl.openStream()) {
UtilHttp.streamContentToBrowser(response, imageStream, 43, "image/gif", null);
}
} catch (IOException e) {
Debug.logError(e, MODULE);
}
// return null to not return any view
return null;
}
}