001: package dalma.endpoints.email;
002:
003: import dalma.EndPoint;
004: import dalma.ReplyIterator;
005: import dalma.TimeUnit;
006: import dalma.spi.port.MultiplexedEndPoint;
007:
008: import javax.mail.Address;
009: import javax.mail.Message;
010: import javax.mail.MessagingException;
011: import javax.mail.Session;
012: import javax.mail.internet.AddressException;
013: import javax.mail.internet.InternetAddress;
014: import javax.mail.internet.MimeMessage;
015: import java.util.Date;
016: import java.util.UUID;
017: import java.util.logging.Level;
018:
019: /**
020: * {@link EndPoint} connected to an e-mail address.
021: *
022: * @author Kohsuke Kawaguchi
023: */
024: public class EmailEndPoint extends
025: MultiplexedEndPoint<UUID, MimeMessage> {
026:
027: /**
028: * The address that receives replies.
029: */
030: private final InternetAddress address;
031:
032: private final Listener listener;
033:
034: private NewMailHandler newMailHandler;
035:
036: /**
037: * The JavaMail configuration.
038: */
039: private final Session session;
040:
041: private final SenderThread sender;
042:
043: /**
044: * Creates a new e-mail end point.
045: *
046: * <p>
047: * This uses {@code Session.getInstance(System.getProperties())} as the JavaMail session,
048: * so effectively it configures JavaMail from the system properties.
049: *
050: * @param name
051: * The unique name assigned by the application that identifies this endpoint.
052: * @param address
053: * The e-mail address of this endpoint.
054: * @param listener
055: * The object that fetches incoming e-mails.
056: */
057: public EmailEndPoint(String name, InternetAddress address,
058: Listener listener) {
059: this (name, address, listener, Session.getInstance(System
060: .getProperties()));
061: }
062:
063: /**
064: * Creates a new e-mail end point.
065: *
066: * <p>
067: * This version takes the address as string so that it can be invoked from Spring.
068: * It's just a short-cut for:
069: * <pre>
070: * this(name,new InternetAddress(address),listener,Session.getInstance(System.getProperties()))
071: * </pre>
072: * @see #EmailEndPoint(String, InternetAddress, Listener)
073: */
074: public EmailEndPoint(String name, String address, Listener listener)
075: throws AddressException {
076: this (name, new InternetAddress(address), listener, Session
077: .getInstance(System.getProperties()));
078: }
079:
080: /**
081: * Creates a new e-mail end point.
082: *
083: * @param name
084: * The unique name assigned by the application that identifies this endpoint.
085: * @param address
086: * The e-mail address of this endpoint.
087: * @param listener
088: * The object that fetches incoming e-mails.
089: * @param session
090: * The JavaMail configuration.
091: */
092: public EmailEndPoint(String name, InternetAddress address,
093: Listener listener, Session session) {
094: super (name);
095: this .address = address;
096: this .listener = listener;
097: this .session = session;
098: this .sender = new SenderThread(session);
099: if (address == null || listener == null || session == null)
100: throw new IllegalArgumentException();
101: listener.setEndPoint(this );
102: }
103:
104: protected void start() {
105: listener.start();
106: sender.start();
107: }
108:
109: protected void stop() {
110: listener.stop();
111: sender.shutDown();
112: }
113:
114: /**
115: * Gets the listener set by {@link #setNewMailHandler(NewMailHandler)}.
116: */
117: public NewMailHandler getNewMailHandler() {
118: return newMailHandler;
119: }
120:
121: /**
122: * Sets the handler that receives uncorrelated incoming e-mails.
123: *
124: * @see NewMailHandler
125: */
126: public void setNewMailHandler(NewMailHandler newMailHandler) {
127: this .newMailHandler = newMailHandler;
128: }
129:
130: /**
131: * Gets the JavaMail session that this endpoint uses to configure
132: * JavaMail.
133: *
134: * @return
135: * always non-null.
136: */
137: public Session getSession() {
138: return session;
139: }
140:
141: /**
142: * Gets the e-mail address that this endpoint is connected to.
143: */
144: public InternetAddress getAddress() {
145: return address;
146: }
147:
148: protected UUID getKey(MimeMessage msg) {
149: try {
150: UUID id = getIdHeader(msg, "References");
151: if (id != null)
152: return id;
153:
154: return getIdHeader(msg, "In-reply-to");
155: } catch (MessagingException e) {
156: throw new EmailException(e);
157: }
158: }
159:
160: protected void onNewMessage(MimeMessage msg) {
161: NewMailHandler h = newMailHandler;
162: if (h != null) {
163: try {
164: h.onNewMail(new MimeMessageEx(msg));
165: } catch (Exception e) {
166: logger.log(Level.WARNING, "Unhandled exception", e);
167: }
168: }
169: }
170:
171: private static UUID getIdHeader(Message msg, String name)
172: throws MessagingException {
173: String[] h = msg.getHeader(name);
174: if (h == null || h.length == 0)
175: return null;
176:
177: String val = h[0].trim();
178: if (val.length() < 2)
179: return null;
180:
181: // find the last token, if there are more than one
182: int idx = val.lastIndexOf(' ');
183: if (idx > 0)
184: val = val.substring(idx + 1);
185:
186: if (!val.startsWith("<"))
187: return null;
188: val = val.substring(1);
189: if (!val.endsWith("@localhost>"))
190: return null;
191: val = val.substring(0, val.length() - "@localhost>".length());
192:
193: try {
194: return UUID.fromString(val);
195: } catch (IllegalArgumentException e) {
196: return null; // not a UUID
197: }
198: }
199:
200: protected void handleMessage(MimeMessage msg) {
201: try {
202: super .handleMessage(new MimeMessageEx(msg));
203: } catch (MessagingException e) {
204: throw new EmailException(e);
205: }
206: }
207:
208: /**
209: * Sends a message and return immediately.
210: *
211: * Use this method when no further reply is expected.
212: */
213: public UUID send(MimeMessage msg) {
214: try {
215: msg.setReplyTo(new Address[] { address });
216: if (msg.getFrom() == null || msg.getFrom().length == 0) {
217: msg.setFrom(address);
218: }
219:
220: // this creates a cryptographically strong GUID,
221: // meaning someone who knows any number of GUIDs can't
222: // predict another one (to steal the session)
223: UUID uuid = UUID.randomUUID();
224: msg.setHeader("Message-ID", '<' + uuid.toString()
225: + "@localhost>");
226:
227: sender.queue(msg);
228:
229: return uuid;
230: } catch (MessagingException e) {
231: throw new EmailException(e);
232: }
233: }
234:
235: /**
236: * Sends an e-mail out and waits for a reply to come back.
237: *
238: * <p>
239: * This method blocks forever until a reply is received.
240: *
241: * @param outgoing
242: * The message to be sent. Must not be null.
243: * @return
244: * a message that represents the received reply.
245: * always a non-null valid message.
246: */
247: public MimeMessage waitForReply(MimeMessage outgoing) {
248: return super .waitForReply(outgoing);
249: }
250:
251: /**
252: * Sends an e-mail out and waits for multiple replies.
253: *
254: * <p>
255: * Upon a successful completion, this method returns an {@link ReplyIterator}
256: * that receives replies to the e-mail that was just sent, until the specified
257: * expiration date is reached.
258: *
259: * @param outgoing
260: * The message to be sent. Must not be null.
261: * @param expirationDate
262: * null to indicate that the iterator shall never be expired.
263: * Otherwise, the iterator will stop accepting reply messages
264: * once the specified date is reached.
265: * @return
266: * always non-null.
267: * @see ReplyIterator
268: */
269: public ReplyIterator<MimeMessage> waitForMultipleReplies(
270: MimeMessage outgoing, Date expirationDate) {
271: return super .waitForMultipleReplies(outgoing, expirationDate);
272: }
273:
274: /**
275: * Sends an e-mail out and waits for multiple replies.
276: *
277: * <p>
278: * The timeout and unit parameters together specifies the time period
279: * in which the returned iterator waits for replies. For example,
280: * if you set "1 week", the returned iterator will catch all replies received
281: * within 1 week from now. See {@link #waitForMultipleReplies(MimeMessage, long, TimeUnit)} for
282: * details.
283: */
284: public ReplyIterator<MimeMessage> waitForMultipleReplies(
285: MimeMessage outgoing, long timeout, TimeUnit unit) {
286: return waitForMultipleReplies(outgoing, unit.fromNow(timeout));
287: }
288:
289: /**
290: * Sends an e-mail out and waits for multiple replies.
291: *
292: * <p>
293: * This overloaded version returns a {@link ReplyIterator} that never expires.
294: * See {@link #waitForMultipleReplies(MimeMessage, long, TimeUnit)} for details.
295: */
296: public ReplyIterator<MimeMessage> waitForMultipleReplies(
297: MimeMessage outgoing) {
298: return waitForMultipleReplies(outgoing, null);
299: }
300:
301: /**
302: * Sends an e-mail out and waits for a reply to come back,
303: * at most the time specfied.
304: *
305: * @param outgoing
306: * The message to be sent. Must not be null.
307: * @return
308: * a message that represents the received reply.
309: * if a response was not received within the specified timeout period,
310: * null is returned.
311: */
312: public MimeMessage waitForReply(MimeMessage outgoing, long timeout,
313: TimeUnit unit) {
314: return super .waitForReply(outgoing, unit.fromNow(timeout));
315: }
316:
317: /**
318: * Sends an e-mail out and waits for a reply to come back,
319: * until the specified date.
320: *
321: * @param outgoing
322: * The message to be sent. Must not be null.
323: * @param timeoutDate
324: * the time when this method returns even if no reply is received by then.
325: * @return
326: * a message that represents the received reply.
327: * if a response was not received within the specified timeout period,
328: * null is returned.
329: */
330: public MimeMessage waitForReply(MimeMessage outgoing,
331: Date timeoutDate) {
332: return super.waitForReply(outgoing, timeoutDate);
333: }
334: }
|