001: /*
002: * Copyright 2004 Outerthought bvba and Schaubroeck nv
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.outerj.daisy.emailer.serverimpl;
017:
018: import org.apache.avalon.framework.configuration.Configuration;
019: import org.apache.avalon.framework.configuration.ConfigurationException;
020: import org.apache.commons.logging.Log;
021: import org.apache.commons.logging.LogFactory;
022: import org.outerj.daisy.emailer.Emailer;
023: import org.outerj.daisy.repository.spi.ExtensionProvider;
024: import org.outerj.daisy.repository.Repository;
025: import org.outerj.daisy.plugin.PluginRegistry;
026:
027: import javax.mail.Session;
028: import javax.mail.Message;
029: import javax.mail.Transport;
030: import javax.mail.internet.MimeMessage;
031: import javax.mail.internet.InternetAddress;
032: import javax.sql.DataSource;
033: import javax.annotation.PreDestroy;
034: import java.util.Properties;
035: import java.util.Date;
036: import java.sql.*;
037:
038: public class CommonEmailer implements Emailer {
039: private PluginRegistry pluginRegistry;
040: private ExtensionProvider extensionProvider = new MyExtensionProvider();
041: private String smtpHost;
042: private String smtpLocalhost;
043: private String emailCharSet;
044: private String defaultFrom;
045: private int emailThreadInterval;
046: private int maxTryCount;
047: private int retryInterval;
048: private long maxAge;
049: private Session session;
050: private DataSource dataSource;
051: private Thread emailerThread;
052: private boolean debugJavamail;
053: private Log log = LogFactory.getLog(getClass());
054: private static final String EXTENSION_NAME = "Emailer";
055:
056: public CommonEmailer(Configuration configuration,
057: DataSource dataSource, PluginRegistry pluginRegistry)
058: throws Exception {
059: this .dataSource = dataSource;
060: this .pluginRegistry = pluginRegistry;
061: configure(configuration);
062: this .initialize();
063: this .start();
064: }
065:
066: @PreDestroy
067: public void destroy() throws Exception {
068: this .stop();
069: pluginRegistry.removePlugin(ExtensionProvider.class,
070: EXTENSION_NAME, extensionProvider);
071: }
072:
073: private void configure(Configuration configuration)
074: throws ConfigurationException {
075: smtpHost = configuration.getChild("smtpHost").getValue();
076: smtpLocalhost = configuration.getChild("smtpLocalhost")
077: .getValue(null);
078: emailCharSet = configuration.getChild("emailCharSet").getValue(
079: null);
080: defaultFrom = configuration.getChild("fromAddress").getValue();
081: emailThreadInterval = configuration.getChild(
082: "emailThreadInterval").getValueAsInteger();
083: maxTryCount = configuration.getChild("maxTryCount")
084: .getValueAsInteger();
085: retryInterval = configuration.getChild("retryInterval")
086: .getValueAsInteger();
087: debugJavamail = configuration.getChild("javaMailDebug")
088: .getValueAsBoolean(false);
089: maxAge = configuration.getChild("maxAge").getValueAsLong(7)
090: * 24 * 60 * 60 * 1000;
091: }
092:
093: private void initialize() throws Exception {
094: Properties props = new Properties();
095: props.put("mail.smtp.host", smtpHost);
096: if (smtpLocalhost != null)
097: props.put("mail.smtp.localhost", smtpLocalhost);
098: session = Session.getInstance(props);
099: if (debugJavamail)
100: session.setDebug(true);
101:
102: pluginRegistry.addPlugin(ExtensionProvider.class,
103: EXTENSION_NAME, extensionProvider);
104: }
105:
106: private void start() throws Exception {
107: emailerThread = new Thread(new EmailerThread(), "Daisy Emailer");
108: emailerThread.start();
109: }
110:
111: private void stop() throws Exception {
112: log.info("Waiting for emailer thread to end.");
113: emailerThread.interrupt();
114: try {
115: emailerThread.join();
116: } catch (InterruptedException e) {
117: // ignore
118: }
119: }
120:
121: private class MyExtensionProvider implements ExtensionProvider {
122: public Object createExtension(Repository repository) {
123: return new LocalEmailer(repository, CommonEmailer.this );
124: }
125: }
126:
127: private void sendEmail(String from, String to, String subject,
128: String messageText) throws Exception {
129: MimeMessage msg = new MimeMessage(session);
130: msg.setFrom(new InternetAddress(from));
131: msg.setRecipients(Message.RecipientType.TO, InternetAddress
132: .parse(to, true));
133: msg.setSubject(subject);
134: if (emailCharSet != null)
135: msg.setText(messageText, emailCharSet);
136: else
137: msg.setText(messageText);
138: msg.setSentDate(new Date());
139: Transport.send(msg);
140: }
141:
142: public void send(String to, String subject, String messageText) {
143: if (to == null)
144: throw new NullPointerException(
145: "To address should not be null");
146: if (subject == null)
147: throw new NullPointerException("Subject should not be null");
148: if (messageText == null)
149: throw new NullPointerException(
150: "Message text should not be null");
151:
152: Connection conn = null;
153: PreparedStatement stmt = null;
154: try {
155: conn = dataSource.getConnection();
156: stmt = conn
157: .prepareStatement("insert into email_queue(from_address,to_address,subject,message,retry_count,created) values(?,?,?,?,?,?)");
158: stmt.setNull(1, Types.VARCHAR);
159: stmt.setString(2, to);
160: stmt.setString(3, subject);
161: stmt.setString(4, messageText);
162: stmt.setInt(5, 0);
163: stmt.setTimestamp(6, new java.sql.Timestamp(System
164: .currentTimeMillis()));
165: stmt.execute();
166: } catch (SQLException e) {
167: throw new RuntimeException(
168: "Failed to create email db record.", e);
169: } finally {
170: closeStatement(stmt);
171: closeConnection(conn);
172: }
173: }
174:
175: private void closeConnection(Connection conn) {
176: if (conn != null) {
177: try {
178: conn.close();
179: } catch (Exception e) {
180: log.error("Error closing connection.", e);
181: }
182: }
183: }
184:
185: private void closeStatement(PreparedStatement stmt) {
186: if (stmt != null) {
187: try {
188: stmt.close();
189: } catch (Exception e) {
190: log.error("Error closing prepared statement.", e);
191: }
192: }
193: }
194:
195: class EmailerThread implements Runnable {
196: public void run() {
197: try {
198: long lastInvocationTime = System.currentTimeMillis();
199: while (true) {
200: try {
201: lastInvocationTime = System.currentTimeMillis();
202:
203: Connection conn = null;
204: PreparedStatement stmt = null;
205: PreparedStatement stmtUpdate = null;
206: PreparedStatement stmtDelete = null;
207: try {
208: conn = dataSource.getConnection();
209: stmt = conn
210: .prepareStatement("select id,from_address,to_address,subject,message,retry_count,created from email_queue where retry_count < ? and (last_try_time is null or last_try_time < ?) order by created");
211: stmt.setLong(1, maxTryCount);
212: stmt.setTimestamp(2,
213: new java.sql.Timestamp(System
214: .currentTimeMillis()
215: - (retryInterval * 60000)));
216: ResultSet rs = stmt.executeQuery();
217:
218: while (rs.next()) {
219: String from = rs
220: .getString("from_address");
221: if (from == null)
222: from = defaultFrom;
223: String to = rs.getString("to_address");
224: String subject = rs
225: .getString("subject");
226: String message = rs
227: .getString("message");
228: int retryCount = rs
229: .getInt("retry_count");
230: long id = rs.getLong("id");
231: boolean success = false;
232:
233: try {
234: sendEmail(from, to, subject,
235: message);
236: success = true;
237: } catch (Throwable e) {
238: // update DB record
239: if (stmtUpdate == null)
240: stmtUpdate = conn
241: .prepareStatement("update email_queue set retry_count = ?, last_try_time = ?, error = ? where id = ?");
242: stmtUpdate
243: .setInt(1, retryCount + 1);
244: stmtUpdate
245: .setTimestamp(
246: 2,
247: new Timestamp(
248: System
249: .currentTimeMillis()));
250: stmtUpdate.setString(3, e
251: .toString());
252: stmtUpdate.setLong(4, id);
253: stmtUpdate.execute();
254: }
255:
256: if (success) {
257: if (stmtDelete == null)
258: stmtDelete = conn
259: .prepareStatement("delete from email_queue where id = ?");
260: stmtDelete.setLong(1, id);
261: stmtDelete.execute();
262: }
263: }
264:
265: stmt.close();
266:
267: // cleanup expired messages
268: stmt = conn
269: .prepareStatement("delete from email_queue where retry_count >= ? and last_try_time < ?");
270: stmt.setLong(1, maxTryCount);
271: stmt.setTimestamp(2, new Timestamp(System
272: .currentTimeMillis()
273: - maxAge));
274: int messagesDeleted = stmt.executeUpdate();
275: if (messagesDeleted > 0)
276: log
277: .warn("Removed "
278: + messagesDeleted
279: + " expired unsent messages from the email queue.");
280: } catch (SQLException e) {
281: throw new RuntimeException(
282: "Database-related problem in emailer-thread.",
283: e);
284: } finally {
285: closeStatement(stmt);
286: closeStatement(stmtUpdate);
287: closeStatement(stmtDelete);
288: closeConnection(conn);
289: }
290: } catch (Throwable e) {
291: if (e instanceof InterruptedException)
292: return;
293: else
294: log
295: .error(
296: "Error in the emailer thread.",
297: e);
298: }
299:
300: if (Thread.interrupted())
301: return;
302:
303: // sleeping is performed after the try-catch block, so that in case of an exception
304: // we also sleep (the remaining exceptions will probably be problems connecting
305: // to the database, in which case we better wait a bit before trying again)
306: long sleepTime = emailThreadInterval
307: - (System.currentTimeMillis() - lastInvocationTime);
308: if (sleepTime > 0) {
309: Thread.sleep(sleepTime);
310: }
311: }
312: } catch (InterruptedException e) {
313: // ignore
314: } finally {
315: log.info("Emailer thread ended.");
316: }
317: }
318: }
319: }
|