You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by si...@apache.org on 2006/11/24 16:59:44 UTC

svn commit: r478902 - in /incubator/ofbiz/trunk/applications: marketing/entitydef/ party/config/ party/servicedef/ party/src/org/ofbiz/party/communication/

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");