fixed issue 23299: Send email on events
authorAsier Lostalé <asier.lostale@openbravo.com>
Thu, 14 Mar 2013 10:36:14 +0100
changeset 19912 7ee41d144038
parent 19911 5e9e8f9445d8
child 19913 79a1c9976d98
fixed issue 23299: Send email on events

Created infrastructure to send emails based on events
.classpath.template
src/build.xml
src/org/openbravo/email/EmailEventContentGenerator.java
src/org/openbravo/email/EmailEventException.java
src/org/openbravo/email/EmailEventManager.java
--- a/.classpath.template	Wed Mar 06 19:49:24 2013 +0100
+++ b/.classpath.template	Thu Mar 14 10:36:14 2013 +0100
@@ -4,7 +4,7 @@
 	<classpathentry exported="true" kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
 	<classpathentry combineaccessrules="false" exported="true" kind="src" path="/OpenbravoTrl"/>
 	<classpathentry combineaccessrules="false" exported="true" kind="src" path="/OpenbravoWAD"/>
-	<classpathentry including="**/*.ext|**/*.java|**/*.oaw|**/*.properties|**/*.xml|**/*.xpt|**/*.xslt" kind="src" path="src"/>
+	<classpathentry including="**/*.ext|**/*.ftl|**/*.java|**/*.oaw|**/*.properties|**/*.xml|**/*.xpt|**/*.xslt" kind="src" path="src"/>
 	<classpathentry kind="src" path="modules/org.openbravo.client.widgets/src"/>
 	<classpathentry kind="src" path="modules/org.openbravo.client.htmlwidget/src"/>
 	<classpathentry kind="src" path="modules/org.openbravo.base.weld/src"/>
--- a/src/build.xml	Wed Mar 06 19:49:24 2013 +0100
+++ b/src/build.xml	Thu Mar 14 10:36:14 2013 +0100
@@ -13,7 +13,7 @@
  * and  limitations under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2005-2011 Openbravo SLU 
+ * All portions are Copyright (C) 2005-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s): Openbravo S.L.U.
  ************************************************************************
@@ -478,6 +478,9 @@
     <copy todir="${build}">
       <fileset dir="${basedir}" includes="**/*.hbm.xml" />
     </copy>
+    <copy todir="${build}">
+      <fileset dir="${basedir}" includes="**/*.ftl" />
+    </copy>
     <copy todir="${base.design}/design">
       <fileset dir="${basedir}" includes="**/*.xml" />
     </copy>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/email/EmailEventContentGenerator.java	Thu Mar 14 10:36:14 2013 +0100
@@ -0,0 +1,84 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.0  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License.
+ * The Original Code is Openbravo ERP.
+ * The Initial Developer of the Original Code is Openbravo SLU
+ * All portions are Copyright (C) 2013 Openbravo SLU
+ * All Rights Reserved.
+ * Contributor(s):  ______________________________________.
+ *************************************************************************
+ */
+
+package org.openbravo.email;
+
+/**
+ * Classes implementing this interface will be listening to email events. In case the event is valid
+ * for them ({@link EmailEventContentGenerator#isValidEvent(String, Object)} returns
+ * <code>true</code>) they are in charge of generating the content for the email.
+ * 
+ * <code>event</code> in all methods is a <code>String</code> that identifies the event.
+ * 
+ * @author alostale
+ * @see EmailEventManager
+ * 
+ */
+public interface EmailEventContentGenerator {
+
+  /**
+   * Checks if an email should be generated for the <code>event</code>. Same class can be listening
+   * to several events.
+   * 
+   * @param event
+   *          Event to check
+   * @param data
+   *          Data the email will be generated for (this can also determine whether the email must
+   *          be sent)
+   * @return <code>true</code> if the email must be sent, <code>false</code> if not.
+   */
+  public boolean isValidEvent(String event, Object data);
+
+  /**
+   * Returns the email subject for the event and data.
+   */
+  public String getSubject(Object data, String event);
+
+  /**
+   * Returns the email body for the event and data.
+   */
+  public String getBody(Object data, String event);
+
+  /**
+   * Returns the type of content of the email. Tipically <code>"text/html; charset=utf-8"</code> or
+   * <code>"text/plain; charset=utf-8"</code>
+   */
+  public String getContentType();
+
+  /**
+   * When there are several classes listening to the same event, the order the emails are sent is
+   * based on this value.
+   * 
+   * @see EmailEventContentGenerator#preventsOthersExecution()
+   */
+  public int getPriority();
+
+  /**
+   * In case there are several classes listening to the same event, when this method returns
+   * <code>true</code>, other emails with lower priority that might exist, will not be sent.
+   * 
+   */
+  public boolean preventsOthersExecution();
+
+  /**
+   * Asynchronous emails are sent in a separate thread not waiting them to finish to continue the
+   * rest of the execution flow.
+   */
+  public boolean isAsynchronous();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/email/EmailEventException.java	Thu Mar 14 10:36:14 2013 +0100
@@ -0,0 +1,39 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.0  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License.
+ * The Original Code is Openbravo ERP.
+ * The Initial Developer of the Original Code is Openbravo SLU
+ * All portions are Copyright (C) 2013 Openbravo SLU
+ * All Rights Reserved.
+ * Contributor(s):  ______________________________________.
+ *************************************************************************
+ */
+
+package org.openbravo.email;
+
+/**
+ * Exception thrown in case of failure sending email.
+ * 
+ * @author asier
+ * @see EmailEventManager
+ * 
+ */
+public class EmailEventException extends Exception {
+  private static final long serialVersionUID = 1L;
+
+  public EmailEventException(String message) {
+    super(message);
+  }
+
+  public EmailEventException(Exception e) {
+    super(e);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/email/EmailEventManager.java	Thu Mar 14 10:36:14 2013 +0100
@@ -0,0 +1,205 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.0  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License.
+ * The Original Code is Openbravo ERP.
+ * The Initial Developer of the Original Code is Openbravo SLU
+ * All portions are Copyright (C) 2013 Openbravo SLU
+ * All Rights Reserved.
+ * Contributor(s):  ______________________________________.
+ *************************************************************************
+ */
+
+package org.openbravo.email;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Instance;
+import javax.inject.Inject;
+
+import org.apache.log4j.Logger;
+import org.openbravo.dal.core.OBContext;
+import org.openbravo.dal.service.OBCriteria;
+import org.openbravo.dal.service.OBDal;
+import org.openbravo.erpCommon.utility.OBMessageUtils;
+import org.openbravo.erpCommon.utility.poc.EmailManager;
+import org.openbravo.model.common.enterprise.EmailServerConfiguration;
+import org.openbravo.model.common.enterprise.Organization;
+import org.openbravo.utils.FormatUtilities;
+
+/**
+ * This singleton class, is in charge of generating events to send emails.
+ * 
+ * @author asier
+ * @see EmailEventContentGenerator
+ * 
+ */
+@ApplicationScoped
+public class EmailEventManager {
+
+  private static final Logger log = Logger.getLogger(EmailEventManager.class);
+
+  @Inject
+  @Any
+  private Instance<EmailEventContentGenerator> emailGenerators;
+
+  /**
+   * This method is invoked when an event for sending emails is generated. It looks for all
+   * {@link EmailEventContentGenerator} classes listening to this event and generates an email using
+   * them.
+   * 
+   * @param event
+   *          Name of the event to send emails for
+   * @param recipient
+   *          Email address of the email's recipient
+   * @param data
+   *          Object that the EmailEventContentGenerator will receive to generate the email
+   * @return <code>true</code> in case at least one email has been sent
+   * @throws EmailEventException
+   *           is thrown in case of problems sending the email or getting the email server
+   *           configuration
+   * 
+   * @see EmailEventContentGenerator
+   */
+  public boolean sendEmail(String event, final String recipient, Object data)
+      throws EmailEventException {
+    // Retrieves the Email Server configuration
+    final EmailServerConfiguration mailConfig = getEmailConfiguration();
+
+    if (mailConfig == null) {
+      log.warn("Couldn't find email configuarion");
+      throw new EmailEventException(OBMessageUtils.getI18NMessage("EmailConfigurationNotFound",
+          null));
+    }
+
+    try {
+      final String username = mailConfig.getSmtpServerAccount();
+      final String password = FormatUtilities.encryptDecrypt(mailConfig.getSmtpServerPassword(),
+          false);
+      final String connSecurity = mailConfig.getSmtpConnectionSecurity();
+      final int port = mailConfig.getSmtpPort().intValue();
+      final String senderAddress = mailConfig.getSmtpServerSenderAddress();
+      final String host = mailConfig.getSmtpServer();
+      final boolean auth = mailConfig.isSMTPAuthentification();
+
+      boolean sent = false;
+      for (EmailEventContentGenerator gen : getEmailGenerators(event, data)) {
+        sent = true;
+        log.debug("sending email for event " + event + " with generator " + gen);
+
+        if (gen.isAsynchronous()) {
+          final String subject = gen.getSubject(data, event);
+          final String body = gen.getBody(data, event);
+          final String type = gen.getContentType();
+          Thread thread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+              try {
+                EmailManager.sendEmail(host, auth, username, password, connSecurity, port,
+                    senderAddress, recipient, null, null, null, subject, body, type, null, null,
+                    null);
+              } catch (Exception e) {
+                log.error(e.getMessage(), e);
+              }
+            }
+          });
+          thread.start();
+        } else {
+          EmailManager.sendEmail(host, auth, username, password, connSecurity, port, senderAddress,
+              recipient, null, null, null, gen.getSubject(data, event), gen.getBody(data, event),
+              gen.getContentType(), null, null, null);
+        }
+      }
+      if (!sent) {
+        log.warn("No email generator found for event " + event);
+      }
+      return sent;
+    } catch (Exception e) {
+      log.error(e.getMessage(), e);
+      throw new EmailEventException(e);
+    }
+  }
+
+  private List<EmailEventContentGenerator> getEmailGenerators(String event, Object data) {
+    // find valid events
+    List<EmailEventContentGenerator> validGenerators = new ArrayList<EmailEventContentGenerator>();
+    Iterator<EmailEventContentGenerator> i = emailGenerators.iterator();
+    while (i.hasNext()) {
+      EmailEventContentGenerator gen = i.next();
+      if (gen.isValidEvent(event, data)) {
+        validGenerators.add(gen);
+      }
+    }
+
+    // sort them by priority
+    Collections.sort(validGenerators, new Comparator<EmailEventContentGenerator>() {
+      @Override
+      public int compare(EmailEventContentGenerator o1, EmailEventContentGenerator o2) {
+        return o1.getPriority() - o2.getPriority();
+      }
+    });
+
+    // if some of them prevents following execution, stop chain
+    List<EmailEventContentGenerator> generators = new ArrayList<EmailEventContentGenerator>();
+    for (EmailEventContentGenerator gen : validGenerators) {
+      generators.add(gen);
+      if (gen.preventsOthersExecution()) {
+        break;
+      }
+    }
+
+    return generators;
+  }
+
+  /**
+   * First search for the current organization (and use the first returned one), then for
+   * organization '0' (and use the first returned one) and then for any other of the organization
+   * tree where current organization belongs to (and use the first returned one)
+   */
+  private EmailServerConfiguration getEmailConfiguration() {
+    // TODO: this should be centralized and improved, see issue #23198
+
+    Organization currenctOrg = OBContext.getOBContext().getCurrentOrganization();
+    EmailServerConfiguration mailConfig = null;
+
+    OBCriteria<EmailServerConfiguration> mailConfigCriteria = OBDal.getInstance().createCriteria(
+        EmailServerConfiguration.class);
+    mailConfigCriteria.addOrderBy("client.id", false);
+    final List<EmailServerConfiguration> mailConfigList = mailConfigCriteria.list();
+
+    if (mailConfigList.size() == 0) {
+      return null;
+    }
+    for (EmailServerConfiguration currentOrgConfig : mailConfigList) {
+      if (currenctOrg.getId().equals(currentOrgConfig.getOrganization().getId())) {
+        mailConfig = currentOrgConfig;
+        break;
+      }
+    }
+    if (mailConfig == null) {
+      for (EmailServerConfiguration zeroOrgConfig : mailConfigList) {
+        if ("0".equals(zeroOrgConfig.getOrganization().getId())) {
+          mailConfig = zeroOrgConfig;
+          break;
+        }
+      }
+    }
+    if (mailConfig == null) {
+      mailConfig = mailConfigList.get(0);
+    }
+    return mailConfig;
+  }
+}