/******************************************************************************* * 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 org.apache.ofbiz.base.util.GeneralException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; 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.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 { public static final String module = CommunicationEventServices.class.getName(); public 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<Object>(); // 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) || (!(communicationEvent.getRelatedOne("FromContactMech", false).getString("contactMechTypeId").equals("EMAIL_ADDRESS")) || (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<String, Object>(); 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<Map<String,? extends Object>>(); 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); } else { tmpResult = dispatcher.runSync("sendMail", sendMailParams, 360, true); } 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<String, Object>(); 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 (GeneralException eez) { return ServiceUtil.returnError(eez.getMessage()); } catch (IOException 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.size() > 0) { result = ServiceUtil.returnError(errorMessages); } return result; } 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<Object>(); 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 EntityListIterator eli = null; 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<String, Object>(); 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")); eli = EntityQuery.use(delegator).select("partyId", "preferredContactMechId", "fromDate", "infoString") .from("ContactListPartyAndContactMech") .where(EntityCondition.makeCondition(conditionList, EntityOperator.AND)) .cursorScrollInsensitive() .distinct() .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 && contactListCommStatusRecord.getString("statusId").equals("COM_COMPLETE")) { // 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<String, Object>(); 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 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 (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; } else { // 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; } } else { // 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 nonFatalGEE) { Debug.logError(nonFatalGEE, errorInSendEmailToContactListService, module); errorMessages.add(errorInSendEmailToContactListService + ": " + nonFatalGEE.getMessage()); } catch (GenericServiceException nonFatalGSE) { Debug.logError(nonFatalGSE, errorInSendEmailToContactListService, module); errorMessages.add(errorInSendEmailToContactListService + ": " + nonFatalGSE.getMessage()); } } } catch (GenericEntityException fatalGEE) { return ServiceUtil.returnError(fatalGEE.getMessage()); } finally { if (eli != null) { try { eli.close(); } catch (GenericEntityException e) { Debug.logError(e, module); } } } return errorMessages.size() == 0 ? ServiceUtil.returnSuccess() : ServiceUtil.returnError(errorMessages); } public static Map<String, Object> setCommEventComplete(DispatchContext dctx, Map<String, ? extends Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); String communicationEventId = (String) context.get("communicationEventId"); String partyIdFrom = (String) context.get("partyIdFrom"); try { Map<String, Object> result = dispatcher.runSync("updateCommunicationEvent", UtilMisc.<String, Object>toMap("communicationEventId", communicationEventId, "partyIdFrom", partyIdFrom, "statusId", "COM_COMPLETE", "userLogin", userLogin)); if (ServiceUtil.isError(result)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result)); } } catch (GenericServiceException esx) { return ServiceUtil.returnError(esx.getMessage()); } return ServiceUtil.returnSuccess(); } /* * 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"); 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<String, Object>(); 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); } Map<String, Object> createResult; try { createResult = dispatcher.runSync("createCommunicationEvent", commEventMap); } 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<String, Object>(); 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 { dispatcher.runSync("updateCommunicationEvent", commEventMap); } 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 contentType = null; String communicationEventId = null; String contactMechIdFrom = null; String contactMechIdTo = null; Map<String, Object> result = null; try { String contentTypeRaw = wrapper.getContentType(); int idx = contentTypeRaw.indexOf(";"); if (idx == -1) idx = contentTypeRaw.length(); contentType = contentTypeRaw.substring(0, idx); if (contentType == null || contentType.equals("")) contentType = "text/html"; contentType = contentType.toLowerCase(); 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 (!spamHeaderName.equals("N") && 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)); 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--) {} 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<String, Object>(); 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().indexOf("text/plain") > -1) { messageBody = (String) p.getContent(); break; } } } if (messageBody == null) { messageBody = wrapper.getMessageBody(); } commEventMap.put("content", messageBody); commEventMap.put("contentMimeTypeId", messageBodyContentType.toLowerCase()); // 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); 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 e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } catch (Exception 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<String>(); Set<String> emailAddressesTo = new TreeSet<String>(); Set<String> emailAddressesCC = new TreeSet<String>(); Set<String> emailAddressesBCC = new TreeSet<String>(); for (int x = 0 ; x < addressesFrom.length ; x++) { emailAddressesFrom.add(((InternetAddress) addressesFrom[x]).getAddress()); } for (int x = 0 ; x < addressesTo.length ; x++) { emailAddressesTo.add(((InternetAddress) addressesTo[x]).getAddress()); } if (addressesCC != null) { for (int x = 0 ; x < addressesCC.length ; x++) { emailAddressesCC.add(((InternetAddress) addressesCC[x]).getAddress()); } } if (addressesBCC != null) { for (int x = 0 ; x < addressesBCC.length ; x++) { emailAddressesBCC.add(((InternetAddress) addressesBCC[x]).getAddress()); } } String fromString = StringUtil.join(UtilMisc.toList(emailAddressesFrom), ","); String toString = StringUtil.join(UtilMisc.toList(emailAddressesTo), ","); String ccString = StringUtil.join(UtilMisc.toList(emailAddressesCC), ","); String bccString = StringUtil.join(UtilMisc.toList(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<String>(); 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.size() > 0) { 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<String, Object>(); 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().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.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 dispatcher.runSync("createCommContentDataResource", attachmentMap); } } } 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"); dispatcher.runSync("createCommunicationEventRole", input); } } } catch (GenericServiceException e) { Debug.logError(e, module); } catch (Exception 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"); dispatcher.runSync("createCommunicationEventWorkEff", UtilMisc.toMap("workEffortId", workEffortId, "communicationEventId", communicationEventId, "userLogin", userLogin)); } } catch (GenericServiceException e) { Debug.logError(e, module); } catch (Exception 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<String, Object>(); map.put("address", emailAddress.getAddress()); map.put("userLogin", userLogin); result = dispatcher.runSync("findPartyFromEmailAddress", map); } 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<Map<String,Object>>(); if (addresses != null) { for (Address addr: addresses) { if (addr instanceof InternetAddress) { emailAddress = (InternetAddress)addr; if (emailAddress != null) { result = dispatcher.runSync("findPartyFromEmailAddress", UtilMisc.toMap("address", emailAddress.getAddress(), "userLogin", userLogin)); 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<Map<String,Object>>(); String caseInsensitiveEmail = EntityUtilProperties.getPropertyValue("general", "mail.address.caseInsensitive", "N", delegator); if (addresses != null) { for (Address addr: addresses) { if (addr instanceof InternetAddress) { emailAddress = (InternetAddress)addr; if (emailAddress != null) { Map<String, String> inputFields = new HashMap<String, String>(); inputFields.put("infoString", emailAddress.getAddress()); inputFields.put("infoString_ic", caseInsensitiveEmail); result = dispatcher.runSync("performFind", UtilMisc.<String, Object>toMap("entityName", "WorkEffortContactMechView" ,"inputFields", inputFields, "userLogin", userLogin)); EntityListIterator listIt = (EntityListIterator) result.get("listIt"); try { List<GenericValue> list = listIt.getCompleteList(); List<GenericValue> filteredList = EntityUtil.filterByDate(list); tempResults.addAll(filteredList); } catch (GenericEntityException e) { Debug.logError(e, module); } finally { try { listIt.close(); } 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<String, Object>(); 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); } 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<String, Object>(); 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.size() > 0) { 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 != null && 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://images/webapp/images/spacer.gif"); InputStream imageStream = imageUrl.openStream(); UtilHttp.streamContentToBrowser(response, imageStream, 43, "image/gif", null); } catch (MalformedURLException e) { Debug.logError(e, module); } catch (IOException e) { Debug.logError(e, module); } // return null to not return any view return null; } }