Author: sichen
Date: Fri Nov 24 07:59:43 2006 New Revision: 478902 URL: http://svn.apache.org/viewvc?view=rev&rev=478902 Log: Improved sending of email to contact lists to better handle cases where email addresses are bad or if the email aborts in the middle Modified: incubator/ofbiz/trunk/applications/marketing/entitydef/entitygroup.xml incubator/ofbiz/trunk/applications/marketing/entitydef/entitymodel.xml incubator/ofbiz/trunk/applications/party/config/PartyUiLabels.properties incubator/ofbiz/trunk/applications/party/servicedef/services.xml incubator/ofbiz/trunk/applications/party/src/org/ofbiz/party/communication/CommunicationEventServices.java Modified: incubator/ofbiz/trunk/applications/marketing/entitydef/entitygroup.xml URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/marketing/entitydef/entitygroup.xml?view=diff&rev=478902&r1=478901&r2=478902 ============================================================================== --- incubator/ofbiz/trunk/applications/marketing/entitydef/entitygroup.xml (original) +++ incubator/ofbiz/trunk/applications/marketing/entitydef/entitygroup.xml Fri Nov 24 07:59:43 2006 @@ -34,6 +34,7 @@ <!-- ========================================================= --> <entity-group group="org.ofbiz" entity="ContactList" /> + <entity-group group="org.ofbiz" entity="ContactListCommStatus" /> <entity-group group="org.ofbiz" entity="ContactListParty" /> <entity-group group="org.ofbiz" entity="ContactListPartyAndStatus" /> <entity-group group="org.ofbiz" entity="ContactListPartyStatus" /> Modified: incubator/ofbiz/trunk/applications/marketing/entitydef/entitymodel.xml URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/marketing/entitydef/entitymodel.xml?view=diff&rev=478902&r1=478901&r2=478902 ============================================================================== --- incubator/ofbiz/trunk/applications/marketing/entitydef/entitymodel.xml (original) +++ incubator/ofbiz/trunk/applications/marketing/entitydef/entitymodel.xml Fri Nov 24 07:59:43 2006 @@ -174,6 +174,29 @@ <key-map field-name="ownerPartyId" rel-field-name="partyId"/> </relation> </entity> + <entity entity-name="ContactListCommStatus" + package-name="org.ofbiz.marketing.contact" + title="Contact List Entity"> + <field name="contactListId" type="id-ne"></field> + <field name="communicationEventId" type="id-ne"></field> + <field name="contactMechId" type="id-ne"></field> + <field name="statusId" type="id-ne"></field> + <prim-key field="contactListId"/> + <prim-key field="communicationEventId"/> + <prim-key field="contactMechId"/> + <relation type="one" fk-name="CNCT_LST_CST_CL" rel-entity-name="ContactList"> + <key-map field-name="contactListId"/> + </relation> + <relation type="one" fk-name="CNCT_LST_CST_CE" rel-entity-name="CommunicationEvent"> + <key-map field-name="communicationEventId"/> + </relation> + <relation type="one" fk-name="CNCT_LST_CST_CM" rel-entity-name="ContactMech"> + <key-map field-name="contactMechId"/> + </relation> + <relation type="one" fk-name="CNCT_LST_CST_ST" rel-entity-name="StatusItem"> + <key-map field-name="statusId"/> + </relation> + </entity> <entity entity-name="ContactListParty" package-name="org.ofbiz.marketing.contact" title="Contact List Party Entity"> Modified: incubator/ofbiz/trunk/applications/party/config/PartyUiLabels.properties URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/party/config/PartyUiLabels.properties?view=diff&rev=478902&r1=478901&r2=478902 ============================================================================== --- incubator/ofbiz/trunk/applications/party/config/PartyUiLabels.properties (original) +++ incubator/ofbiz/trunk/applications/party/config/PartyUiLabels.properties Fri Nov 24 07:59:43 2006 @@ -34,6 +34,11 @@ commeventservices.communication_event_must_be_email_for_email=ERROR: Communication event is not an email communication and cannot be emailed for communication event Id commeventservices.communication_event_from_contact_mech_must_be_email=ERROR: Communication event must have a from contact mech that is an email for comm event Id commeventservices.communication_event_to_contact_mech_must_be_email=ERROR: Communication event must have a to contact mech that is an email for comm event Id +commeventservices.errorCallingSendEmailToContactListService=Error calling sendEmailToContactList service +commeventservices.errorInSendEmailToContactListService=Error in sendEmailToContactList service +commeventservices.errorCallingUpdateContactListPartyService=Error calling updateContactListParty service +commeventservices.errorCallingSendMailService=Error calling sendMail service +commeventservices.skippingInvalidEmailAddress=Skipping invalid email address contactmechservices.could_not_create_contact_info_id_generation_failure=ERROR: Could not create contact info (id generation failure). contactmechservices.service_createContactMech_not_be_used_for_POSTAL_ADDRESS=This service (createContactMech) should not be used for POSTAL_ADDRESS type ContactMechs, use the createPostalAddress service. Modified: incubator/ofbiz/trunk/applications/party/servicedef/services.xml URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/party/servicedef/services.xml?view=diff&rev=478902&r1=478901&r2=478902 ============================================================================== --- incubator/ofbiz/trunk/applications/party/servicedef/services.xml (original) +++ incubator/ofbiz/trunk/applications/party/servicedef/services.xml Fri Nov 24 07:59:43 2006 @@ -639,10 +639,20 @@ location="org.ofbiz.party.communication.CommunicationEventServices" invoke="sendCommEventAsEmail" auth="true" transaction-timeout="7200"> <!-- set transaction time out for 2 hours, since this sometimes may run as an async service to send emails to lots of people --> <description>Sends a communication event as a single-part email using sendMail. All parameters come from CommunicationEvent, which must - be of type EMAIL_COMMUNICATION. If there a contactListId, then will send it to every party on that contact list at their - preferredContactMechId. Otherwise, will look for a contactMechIdTo to send the emails</description> + be of type EMAIL_COMMUNICATION. Will look for a contactMechIdTo to send the emails</description> <attribute name="communicationEventId" type="String" mode="IN" optional="false"/> </service> + + <service name="sendEmailToContactList" engine="java" + location="org.ofbiz.party.communication.CommunicationEventServices" invoke="sendEmailToContactList" auth="true" + use-transaction="false" max-retry="3"> <!-- Individual emails will be wrapped in their own transactions --> + <description>Send emails to members of a contact list, wrapping each email in its own transaction and tagging each member + that has been sent, so if the whole effort is aborted, it can start over from the middle. The max-retry is important because if this service is + and some emails cannot sent, it will start again later and try again</description> + <attribute name="contactListId" type="String" mode="IN" optional="false"/> + <attribute name="communicationEventId" type="String" mode="IN" optional="false"/> + </service> + <service name="setCommEventComplete" engine="java" location="org.ofbiz.party.communication.CommunicationEventServices" invoke="setCommEventComplete" auth="true"> <description>Sets the status of a communication event to COM_COMPLETE using the updateCommunicationEvent service</description> Modified: incubator/ofbiz/trunk/applications/party/src/org/ofbiz/party/communication/CommunicationEventServices.java URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/party/src/org/ofbiz/party/communication/CommunicationEventServices.java?view=diff&rev=478902&r1=478901&r2=478902 ============================================================================== --- incubator/ofbiz/trunk/applications/party/src/org/ofbiz/party/communication/CommunicationEventServices.java (original) +++ incubator/ofbiz/trunk/applications/party/src/org/ofbiz/party/communication/CommunicationEventServices.java Fri Nov 24 07:59:43 2006 @@ -20,9 +20,7 @@ import java.util.*; import java.sql.Timestamp; -import org.ofbiz.base.util.UtilDateTime; -import org.ofbiz.base.util.UtilMisc; -import org.ofbiz.base.util.UtilProperties; +import org.ofbiz.base.util.*; import org.ofbiz.entity.GenericDelegator; import org.ofbiz.entity.GenericEntityException; import org.ofbiz.entity.GenericValue; @@ -103,41 +101,107 @@ errorMessages.add(ServiceUtil.getErrorMessage(tmpResult)); } } else { - // there's actually a contact list here, so we want to be sending to the entire contact list - GenericValue contactList = communicationEvent.getRelatedOne("ContactList"); - // set up some variables for single use lists - boolean singleUse = ("Y".equals(contactList.get("singleUse")) ? true : false); - Timestamp now = UtilDateTime.nowTimestamp(); - - // 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 conditionList = UtilMisc.toList( - new EntityExpr("contactListId", EntityOperator.EQUALS, contactList.get("contactListId")), - new EntityExpr("statusId", EntityOperator.EQUALS, "CLPT_ACCEPTED"), - new EntityExpr("preferredContactMechId", EntityOperator.NOT_EQUAL, null), - EntityUtil.getFilterByDateExpr() - ); - EntityConditionList conditions = new EntityConditionList(conditionList, EntityOperator.AND); - List fieldsToSelect = UtilMisc.toList("infoString"); - EntityListIterator sendToEmailsIt = delegator.findListIteratorByCondition("ContactListPartyAndContactMech", conditions, null, fieldsToSelect, null, - new EntityFindOptions(true, EntityFindOptions.TYPE_SCROLL_INSENSITIVE, EntityFindOptions.CONCUR_READ_ONLY, true)); - - // send an email to each contact list member + // Call the sendEmailToContactList service if there's a contactListId present + Map 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 (GenericEntityException eex) { + ServiceUtil.returnError(eex.getMessage()); + } catch (GenericServiceException esx) { + ServiceUtil.returnError(esx.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 sendEmailToContactList(DispatchContext ctx, Map context) { + GenericDelegator delegator = ctx.getDelegator(); + LocalDispatcher dispatcher = ctx.getDispatcher(); + GenericValue userLogin = (GenericValue) context.get("userLogin"); + Locale locale = (Locale) context.get("locale"); + + List errorMessages = new ArrayList(); + String errorCallingUpdateContactListPartyService = UtilProperties.getMessage(resource, "commeventservices.errorCallingUpdateContactListPartyService", locale); + String errorCallingSendMailService = UtilProperties.getMessage(resource, "commeventservices.errorCallingSendMailService", locale); + String errorInSendEmailToContactListService = UtilProperties.getMessage(resource, "commeventservices.errorForEmailAddress", 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 = delegator.findByPrimaryKey("CommunicationEvent", UtilMisc.toMap("communicationEventId", communicationEventId)); + GenericValue contactList = delegator.findByPrimaryKey("ContactList", UtilMisc.toMap("contactListId", contactListId)); + + Map sendMailParams = new HashMap(); + sendMailParams.put("sendFrom", communicationEvent.getRelatedOne("FromContactMech").getString("infoString")); + sendMailParams.put("subject", communicationEvent.getString("subject")); + sendMailParams.put("body", communicationEvent.getString("content")); + 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 conditionList = UtilMisc.toList( + new EntityExpr("contactListId", EntityOperator.EQUALS, contactList.get("contactListId")), + new EntityExpr("statusId", EntityOperator.EQUALS, "CLPT_ACCEPTED"), + new EntityExpr("preferredContactMechId", EntityOperator.NOT_EQUAL, null), + EntityUtil.getFilterByDateExpr() + ); + EntityConditionList conditions = new EntityConditionList(conditionList, EntityOperator.AND); + List fieldsToSelect = UtilMisc.toList("infoString"); + + List sendToEmails = delegator.findByCondition("ContactListPartyAndContactMech", conditions, null, fieldsToSelect, null, + new EntityFindOptions(true, EntityFindOptions.TYPE_SCROLL_INSENSITIVE, EntityFindOptions.CONCUR_READ_ONLY, true)); + + // Send an email to each contact list member + + List orderBy = UtilMisc.toList("-fromDate"); + Iterator sendToEmailsIt = sendToEmails.iterator(); + while (sendToEmailsIt.hasNext()) { - List orderBy = UtilMisc.toList("-fromDate"); - GenericValue contactListPartyAndContactMech = null ; - while ((contactListPartyAndContactMech = (GenericValue) sendToEmailsIt.next()) != null) { + GenericValue contactListPartyAndContactMech = (GenericValue) sendToEmailsIt.next(); + + // 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 (emailAddress == null) continue; - + emailAddress = emailAddress.trim(); + + if (! UtilValidate.isEmail(emailAddress, true)) { + + // 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 clpConditionList = new ArrayList(conditionList); clpConditionList.add(new EntityExpr("infoString", EntityOperator.EQUALS, emailAddress)); EntityConditionList clpConditions = new EntityConditionList(clpConditionList, EntityOperator.AND); - + List emailCLPaCMs = delegator.findByConditionCache("ContactListPartyAndContactMech", clpConditions, null, orderBy); GenericValue lastContactListPartyACM = EntityUtil.getFirst(emailCLPaCMs); if (lastContactListPartyACM == null) continue; @@ -147,38 +211,75 @@ sendMailParams.put("sendTo", emailAddress); sendMailParams.put("partyId", partyId); - // no communicationEventId here - we want to create a communication event for each member of the contact list - - // could be run async as well, but that may spawn a lot of processes if there's a large list and cause problems - Map tmpResult = dispatcher.runSync("sendMail", sendMailParams); - if (ServiceUtil.isError(tmpResult)) { - errorMessages.add(ServiceUtil.getErrorMessage(tmpResult)); - } else if (singleUse) { - // expire the ContactListParty if the list is single use and sendEmail finishes successfully + if (! contactList.getString("contactListTypeId").equals("NEWSLETTER")) { + sendMailParams.put("communicationEventId", communicationEventId); + } + + // Retrieve a record for this contactMechId from ContactListCommStatus + Map contactListCommStatusRecordMap = UtilMisc.toMap("contactListId", contactListId, "communicationEventId", communicationEventId, "contactMechId", lastContactListPartyACM.getString("preferredContactMechId")); + GenericValue contactListCommStatusRecord = delegator.findByPrimaryKey("ContactListCommStatus", contactListCommStatusRecordMap); + 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 newContactListCommStatusRecordMap = new HashMap(contactListCommStatusRecordMap); + newContactListCommStatusRecordMap.put("statusId", "COM_IN_PROGRESS"); + 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; + } + + Map tmpResult = null; + + // Make the attempt to send the email to the address + tmpResult = dispatcher.runSync("sendMail", sendMailParams); + if (tmpResult == null || ServiceUtil.isError(tmpResult)) { + + // 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; + } + + 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", now, "userLogin", userLogin)); + "partyId", partyId, "fromDate", lastContactListPartyACM.get("fromDate"), + "thruDate", UtilDateTime.nowTimestamp(), "userLogin", userLogin)); if (ServiceUtil.isError(tmpResult)) { - errorMessages.add(ServiceUtil.getErrorMessage(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()); } - sendToEmailsIt.close(); } - } catch (GenericEntityException eex) { - ServiceUtil.returnError(eex.getMessage()); - } catch (GenericServiceException esx) { - ServiceUtil.returnError(esx.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); + + } catch (GenericEntityException fatalGEE) { + ServiceUtil.returnError(fatalGEE.getMessage()); } - return result; + + return errorMessages.size() == 0 ? ServiceUtil.returnSuccess() : ServiceUtil.returnError(errorMessages); } - + + public static Map setCommEventComplete(DispatchContext dctx, Map context) { LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); |
Free forum by Nabble | Edit this page |