增加两个新的APIs增强这个e-mail服务器
级别: 中级
Claude Duguay (claude.duguay@verizon.net)
Chief Architect, Arcessa, Inc.
June 10, 2003
这是讨论用Apache组织提供的James邮件服务器开发应用程序的第二篇文章。这篇文章的内容超出了James基础设施这些基础知识的介绍,它实现了一个实际的应用程序。通过将用户状态定义为available 和 unavailable,程序可以为那些将自己的状态设置为 unavailable 的用户自动答复收到的邮件,而且用户可以对定制自己的答复邮件。
这篇文章是探索 Java Apache Mail Enterprise Server,即James的第二部分。在这个系列的第一篇文章中,我们了解了James的基础设施和能力等基础知识,并且进行了在实验系统上安装James的整个过程。在这篇文章中,我们会在James基础设施上实现一个想法,开发一个支持用户账户的unavailable状态的应用程序。要使用我们的应用程序,用户需要发送指定类型的邮件到一个指定的邮件服务器地址,这个邮件被作为任何收到的邮件的自动答复消息,直到用户发送一个邮件撤销他的不可到达状态。这一机制与邮件客户端程序中常用的方法类似,向发件人发送一封邮件表示用户现在不在,因为去度假或者其它什么原因。但除非用户一直让客户端程序开着,否则这个功能没什么用。 通过使用服务器端解决方案,你可以在度假的时候用任何客户端软件登录服务器,在任何时候改变你的自动答复消息。
应用程序设计
在进行设计之前,我们应该清楚的说明需求。下面的几点将作为我们的例子设计的根据:
用户能够通过发送e-mail到 unavailable@emailserver to 来将他的账户切换到不可到达模式。发送到unavailable@emailserver的消息要存下来以备将来使用。 如果已经存在这个用户的不可到达消息,原来的消息将被新的覆盖,但用户会收到所发生的一切情况的通知。
用户能够通过发送e-mail 到 available@emailserver 来取消所有的不可到达消息。发送到 available@emailserver的消息会被丢弃并移走原来保存的不可到达消息 。用户应该收到账户状态已经变为可以到达的通知。
一个保存了不可到达消息的用户在任何时候收到的e-mail都应该用保存的消息自动回复,以向发件人说明用户当前处于不可到达的状态。触发这个操作的原始邮件应该进行正常的处理。
理解James基础设施
这个系列的第一篇 向你介绍了James的基础设施和它所具有的能力。
我用三段话来描述需求并不是偶然的。每个需求都直接对应着特定情形下的一组操作。每种情形都应该有相应的匹配器进行识别。( 参阅 第一篇 了解匹配器和mailet的详细信息) 例如,发送到特定地址的e-mail能用原有的RecipientIs匹配器进行匹配。但当我研究RecipientIs 的源代码时,我注意到它能处理任意数量的接收者,并且与其中任何一个接收者相匹配都被认为是匹配的。可能在大多数情况下,这样的处理很好,但这对于我们的应用而言是不幸的。我们的应用要求确保一个并且仅有一个特定地址包含在接收者中,所以我们要开发一个简单的匹配器来完成这个任务。我们将要开发的 MatchSingleRecipient 类还将向你展示如何让Matcher API发挥作用。
识别已经保存了准备用于处理的不可到达消息的接收者要稍微复杂一点儿。因此,我们要开发一个 MatchUnavailableUser 匹配器。 为了使匹配操作更有效,我们在测试不可到达目录中保存的文件之前先检查接收者是否是本地用户。另外,e-mail的处理也应该是相当简单的。接下来,我们先开发两个匹配器类,然后进行 mailet的开发。
我们对不可到达目录的使用相当多。实际上,需要对不可到达目录实现几个操作,包括检测消息是否存在、在该目录中保存、读取和删除消息。因为每个功能都需要知道保存消息的目录的地址和相关用户的一些信息,所以我们创建了一个独立的MatchUnavailableUser匹配器类来获取这些信息。我们设计的mailet实现类也用到了它。
我们要实现的匹配器几乎没有相同的地方,但处理我们提出的需求的mailet(每个需求都有一个mailet)都需要相似的配置信息和访问不可到达目录。所以我们用了一个每个mailet都能扩展的基类。除了提供基本的初始化代码,基类还是放置几个子类共用的应用方法的好地方。我们定义的基类是 UnavailableUserBase。
这三个应用功能将由我们的mailet类-- UnavailableMessageSave, UnavailableMessageDrop, 和 UnavailableMessageSend 来处理。前两个mailet功能相似,并且都是对一个接收者地址--可到达的和不可到达的--进行操作,所以它们共用了一些代码,这些共用的代码放在超类中。UnavailableMessageSend mailet是最复杂的一个,主要是因为它要处理多个接收者地址,还要给每个发送消息到不可到达的接收者的发件人发送消息。幸运的是,用Matcher 和 Mailet API 工作比较容易。
编写匹配器
基类 GenericMatcher简化了在James中编写匹配器的工作,大多数实现类都会扩展这个基类。我们的两个匹配器都扩展了这个类。首先,我们要在init() 方法中得到配置信息,然后在match()方法中执行处理动作。从技术上来讲,我们应该实现getMatcherInfo() 方法来报告卖主、版本等信息,但为了让这个例子中的代码更简练,我没有做这些事情。
在James的配置文件config.xml (我们已经在这个系列的第一篇中详细讨论了这个文件)中配置一个匹配器时,我们用一个XML 属性来说明它。匹配器的类名后跟着一个等号和一些文本信息;这些文本信息可以用getCondition() 方法获得。为了利用James提供的邮件地址比较代码,我们用getCondition() 方法返回的值创建了一个实例并把它保存到一个实例变量中。不用说,列表1中的MatchSingleRecipient 需要一个单独有效的本地e-mail地址作为它唯一的参数。
match() 方法得到被处理的e-mail接收者列表 。Mail 对象提供了几个有趣的方法。其中最常用的是getRecipients(),该方法返回一个包含了MailAddress 实例的 Java Collection 对象。为了看看我们是否有一个匹配的结果,首先要通过检查Collection 对象的大小来确信只有一个接收者,然后确保Collection (长度为1)对象包含的地址是我们在配置文件中说明的地址。
要编译这些类,你要保证已经下载了在本系列的第一篇文章中列出的所有组件,你应该在你的 classpath中包含JavaMail (mail.jar) 和 JavaBeans Activation Framework (activation.jar) JAR 文件,还有james.jar文件。如果你找不到 james.jar文件,可以解压James-2.1.2/apps目录下的James.SAR文件。
列表1. MatchSingleRecipient: 识别发送到某一指定地址的e-mail
package com.claudeduguay.mailets;
import java.util.*;
import javax.mail.*;
import org.apache.mailet.*;
public class MatchSingleRecipient
extends GenericMatcher
{
protected MailAddress addressToMatch;
public void init(MatcherConfig config)
throws MessagingException
{
super.init(config);
addressToMatch = new MailAddress(getCondition());
}
public Collection match(Mail mail)
throws MessagingException
{
Collection recipients = mail.getRecipients();
if (recipients.size() == 1 %26amp;%26amp;
recipients.contains(addressToMatch))
{
return recipients;
}
return null;
}
}
列表2中所示的MatchUnavailableUser 类,使用了 UnavailableStore 类,我们一会儿就会介绍到它。为了让它发挥作用,我们需求通过 getCondition() 方法来检索用来存储不可到达消息的目录。
列表2. MatchUnavailableUser: 识别不可到达用户
package com.claudeduguay.mailets;
import java.util.*;
import javax.mail.*;
import org.apache.mailet.*;
public class MatchUnavailableUser
extends GenericMatcher
{
protected UnavailableStore store;
public void init(MatcherConfig config)
throws MessagingException
{
super.init(config);
String folder = getCondition();
store = new UnavailableStore(folder);
}
protected boolean isLocalAddress(MailAddress address)
{
String host = address.getHost();
MailetContext context = getMailetContext();
return context.isLocalServer(host);
}
protected boolean isUserUnavailable(MailAddress address)
{
return store.userMessageExists(address);
}
public Collection match(Mail mail)
throws MessagingException
{
Collection matches = new Vector();
Collection recipients = mail.getRecipients();
Iterator iterator = recipients.iterator();
while (iterator.hasNext())
{
MailAddress address = (MailAddress)iterator.next();
String user = address.getUser();
if (isLocalAddress(address) %26amp;%26amp;
isUserUnavailable(address))
{
matches.add(address);
}
}
return matches;
}
}
为了使 match() 方法容易实现,我又实现了两个应用方法: isLocalAddress(),它调用了 MailetContext的 isLocalServer() 方法来判断接收者是否为本地用户;isUserUnavailable(),这个方法用UnavailableStore的 userMessageExists() 方法来判断用户是否已经说明了一个不可到达消息。
match 方法创建了一个Collection 对象 (一个 Vector) 来收集应该被 mailet 处理的接收者。然后遍历接收者并逐一检查它是否为本地地址、是否已经说明了该用户的不可到达消息。如果这两个条件都为真,match将这个地址添加到接收者列表中,处理结束后返回这个列表。
这就是我们的匹配器的全部内容。让我们快速的看一下列表3中的UnavailableStore代码,MatchUnavailableUser 类和我们实现的每个 mailet 类都用到了它。这种设计思想是将与访问不可到达消息存储相关的所有方法都收集到一个类中。这样,如果必要的话,就可以开发更复杂的实现方法。这个版本有意的将这个类实现的很简单,并且仅仅将所有用户的消息存储到一个单一的目录中。如果你要在服务器上管理几千个用户,这可能是个问题。但对于大多数应用来说,这种处理足够了。
列表3. UnavailableStore: 访问不可到达消息的存储
package com.claudeduguay.mailets;
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.mailet.*;
public class UnavailableStore
{
protected File home;
public UnavailableStore(String folder)
{
home = new File(folder);
if (!home.exists())
{
home.mkdirs();
}
}
public File getUserMessageFile(MailAddress address)
{
String user = address.getUser();
File file = new File(home, user + ".msg");
return file;
}
public boolean userMessageExists(MailAddress address)
{
return getUserMessageFile(address).exists();
}
public void deleteMessage(MailAddress address)
{
getUserMessageFile(address).delete();
}
public void storeMessage(MailAddress address, MimeMessage msg)
throws MessagingException, IOException
{
File file = getUserMessageFile(address);
FileOutputStream out = new FileOutputStream(file);
msg.writeTo(out);
out.close();
}
public MimeMessage getMessage(MailAddress address)
throws MessagingException, IOException
{
File file = getUserMessageFile(address);
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props);
FileInputStream in = new FileInputStream(file);
MimeMessage msg = new MimeMessage(session, in);
in.close();
return msg;
}
}
UnavailableStore 中的关键配置参数是查找文件的目录。我们通过构造方法将这个值传送到类中,并且,如果这个目录不存在就创建它。为了保证程序正确运行,必须确保在config.xml文件中所有的匹配器和mailet的这一配置参数指向同一个目录。
其余的方法主要是用于访问文件。getUserMessageFile() 方法为解析与给定用户相对应的文件名提供了通用的办法。我们用James MailAddress 对象用作这些方法的参数,因为它在我们的匹配器和mailet代码中很好用。文件名的格式为[不可到达目录]/[用户名].msg.
MimeMessage 对象是 JavaMail API中的一部分。James 在它自己的基础设施中借用了这些对象,所以我们能用相同的API。 storeMessage() 和 getMessage() 方法利用了 MimeMessage 在标准的文本格式(大多数邮件服务器使用的格式)中读写消息的能力。虽然使用串行化对象的格式一样容易(并且可能更高效),但消息是可读的文本格式时调试起来更容易。
编写mailets
现在我们有了识别需要处理的e-mail的机制(匹配器),接下来我们还需要通过实现mailet类来将处理元素集成到一起。我们要借用 UnavailableStore 来存储和访问不可到达消息。
在开始实现mailet之前,我们先快速浏览一下实现了通用方法的基类。mailet 的配置参数以XML标签的形式保存在config.xml 文件中。这些标签可以用我们扩展的GenericMailet类中的 getInitParameter() 方法获得。列表4中列出的UnavailableMessageBase 类被声明为abstract的,因为它不直接实现服务方法。
init() 方法保存了一个UnavailableStore 对象的引用,这与MatchUnavailableUser 类中的init() 方法类似。 UnavailableMessageBase 类中的大多数代码都是应用方法。这些方法包括:将一个MailAddress 和一个 Address 对象矩阵相互转化、在一个地址集合中取得第一个MailAddress 、将一个MailAddress打包为一个集合,以及两个创建e-mail消息的方法。
因为消息可能包含任意部分内容,所以最后的 createMessage() 方法用了一个Object和String参数分别说明内容和MIME类型。简单的情况下,用一个字符串说明内容,用一个简化的标记抽象表示 MIME 类型"text/plain" 。我们用更普通的方法发送不可到达消息,因为用户存储的消息的复杂性可能是任意的。
列表4. UnavailableMessageBase: 实现公用方法的基类
package com.claudeduguay.mailets;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.mailet.*;
public abstract class UnavailableMessageBase
extends GenericMailet
{
protected UnavailableStore store;
public void init(MailetConfig config)
throws MessagingException
{
super.init(config);
MailetContext context = config.getMailetContext();
String folder = getInitParameter("folder");
store = new UnavailableStore(folder);
}
protected Address[] toAddressArray(MailAddress address)
throws AddressException
{
InternetAddress[] array = new InternetAddress[1];
array[0] = address.toInternetAddress();
return array;
}
protected MailAddress getFirstAddress(Collection list)
{
Iterator iterator = list.iterator();
return (MailAddress)iterator.next();
}
protected Collection addressAsCollection(MailAddress address)
{
Collection list = new Vector();
list.add(address);
return list;
}
protected MimeMessage createMessage(
MailAddress from, MailAddress to,
String subject, String text)
throws MessagingException
{
return createMessage(from, to, subject, text, "text/plain");
}
protected MimeMessage createMessage(
MailAddress from, MailAddress to,
String subject, Object content, String type)
throws MessagingException
{
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props);
MimeMessage msg = new MimeMessage(session);
msg.addFrom(toAddressArray(from));
msg.addRecipients(Message.RecipientType.TO, toAddressArray(to));
msg.setSubject(subject);
msg.setContent(content, type);
return msg;
}
}
现在我们能够直接在mailets上工作了。要做的第一件事情就是保存发送到不可到达地址的消息。我们能够配置匹配器 MatchSingleRecipient 将与不可到达的接收者匹配的消息转发给 UnavailableMessageSave mailet,如 列表5所示。为了使可配置能力最大化,我已经将说明配置消息的主题和内容字符串变成可能的了。这些值存储在 init() 方法的局部变量中。
service 方法做了所有的工作。因为我们扩展了基类UnavailableMessageBase,所以我们能够访问 UnavailableStore的一个实例。我们的目标是得出谁发送了消息,得到要存储的MimeMessage 并把它保存到正确的目录下,向发件人发送一个确认e-mail。我们可以假设发件人地址是有效的,如果它无效,匹配器是不会允许消息通过的。
注意我们处理异常的方式。log() 方法被用来确保报告出现的问题,但如果你没看到你期望的行为,必须去检查日志文件。另外,注意我们将e-mail的状态设置为GHOST,停止了对接收到的e-mail的进一步的处理。因为我们自己保存了它,所以不再需要e-mail服务器执行额外的处理。
列表5. UnavailableMessageSave: 保存发送到unavailable@emailserver的消息
package com.claudeduguay.mailets;
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.mailet.*;
public class UnavailableMessageSave
extends UnavailableMessageBase
{
protected String subject;
protected String content;
public void init(MailetConfig config)
throws MessagingException
{
super.init(config);
subject = getInitParameter("subject");
content = getInitParameter("content");
}
public void service(Mail mail)
throws MessagingException
{
MailAddress sender = mail.getSender();
Collection recipients = mail.getRecipients();
MailAddress address = getFirstAddress(recipients);
MailetContext context = getMailetContext();
try
{
MimeMessage msg = (MimeMessage)mail.getMessage();
store.storeMessage(sender, msg);
mail.setState(Mail.GHOST);
}
catch (IOException e)
{
log("Unable to store user message", e);
}
try
{
MimeMessage msg = createMessage(address, sender, subject, content);
Collection target = addressAsCollection(sender);
context.sendMail(address, target, msg);
}
catch (MessagingException e)
{
log("Unable to send confirmation message", e);
}
}
}
下一个要看的类是 UnavailableMessageDrop mailet,如列表6所示。这个mailet和UnavailableMessageSave非常相似,但它在发送确认消息之前删除而不是保存消息,所以它的代码少一点。在两种情况下,我们都借用了 UnavailableStore 类来访问不可到达消息的存储。
列表6. UnavailableMessageDrop: 删除不可到达的消息
package com.claudeduguay.mailets;
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.mailet.*;
public class UnavailableMessageDrop
extends UnavailableMessageBase
{
protected String subject;
protected String content;
public void init(MailetConfig config)
throws MessagingException
{
super.init(config);
subject = getInitParameter("subject");
content = getInitParameter("content");
}
public void service(Mail mail)
throws MessagingException
{
MailAddress sender = mail.getSender();
Collection recipients = mail.getRecipients();
MailAddress address = getFirstAddress(recipients);
MailetContext context = getMailetContext();
store.deleteMessage(sender);
mail.setState(Mail.GHOST);
try
{
MimeMessage msg = createMessage(address, sender, subject, content);
Collection target = addressAsCollection(sender);
context.sendMail(address, target, msg);
}
catch (MessagingException e)
{
log("Unable to send confirmation message", e);
}
}
}
列表7中的UnavailableMessageSend mailet是最复杂的,但即使这样你也会看到Mailet API 使这类工作相当简单。init() 在处理给定用户接收到的消息时可以忽略一个地址列表中的地址。为了避免将确认消息发送给用户时产生循环,这是很必要的。在我们的配置中设定了两个地址 --一个用于可到达状态,一个用于不可到达状态 -- 将被忽略的。我已经将这个设定做成可配置的,管理员可以选择使用不同的地址。 地址列表是一个用分号分割的字符串表示的。
我将UnavailableStore 类中的方法 getMessage() 引入到这个类中来记录读取消息时产生的任何问题,并且加了一个 isIgnorable() 方法来检查MailAddress 是否包含在可忽略列表中。这些工作都在service 方法中完成。
列表7. UnavailableMessageSend: 将不可到达的消息发送给发件人
package com.claudeduguay.mailets;
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.mailet.*;
public class UnavailableMessageSend
extends UnavailableMessageBase
{
protected MailAddress[] ignore;
protected MimeMessage getMessage(MailAddress address)
{
try
{
return store.getMessage(address);
}
catch (Exception e)
{
log("Unable to read stored e-mail message: ", e);
}
return null;
}
public void init(MailetConfig config)
throws MessagingException
{
super.init(config);
String skip = getInitParameter("skip");
StringTokenizer tokenizer = new StringTokenizer(skip, ";", false);
int count = tokenizer.countTokens();
ignore = new MailAddress[count];
for (int i = 0; i
{
String address = tokenizer.nextToken().trim();
ignore[i] = new MailAddress(address);
}
}
protected boolean isIgnorable(MailAddress address)
{
for (int i = 0; i
{
if (address.equals(ignore[i]))
{
return true;
}
}
return false;
}
public void service(Mail mail)
throws MessagingException
{
MailAddress sender = mail.getSender();
MailetContext context = getMailetContext();
Collection recipients = mail.getRecipients();
Iterator iterator = recipients.iterator();
while (iterator.hasNext())
{
MailAddress address = (MailAddress)iterator.next();
// If the recipient is unavailable, send the message.
if (!isIgnorable(sender) %26amp;%26amp;
store.userMessageExists(address))
{
Collection target = addressAsCollection(sender);
MimeMessage msg = getMessage(address);
if (msg != null)
{
try
{
msg = createMessage(address, sender, msg.getSubject(),
msg.getContent(), msg.getContentType());
// Send e-mail from unavailable recipient, to sender
context.sendMail(address, target, msg);
}
catch (IOException e)
{
log("Unable to construct new message: ", e);
}
}
}
}
}
}
UnavailableMessageSend 服务方法需要检查所有的接收者来和保存了不可到达消息的用户匹配。我们用了两个方法来测试每个接收者。isIgnorable() 方法用来检查接收者地址是否可以忽略,UnavailableStore 类中的 userMessageExists() 方法检查这个接收者是否是一个存储了不可到达消息的用户。如果这些条件都满足,我们就检索这个接收者的不可到达消息,用原来存储的主题和内容创建一个新消息,然后把它发送给最初的发件人。
构建和部署
构建这个项目中的类相当简单,但部署它们却比较复杂。只要在classpath中包含 JavaMail (mail.jar)、JavaBeans Activation Framework (activation.jar -- JavaMail API需要这个文件)、James (james.jar),我们就可以很容易的构建这些类。要用 James JAR 文件,你必须从 james.sar 文件中将它释放出来,解压james.sar文件后你可以在james/apps 目录下发现这个jar文件。 ( 在资源中你可以找到下载这些包的链接,本系列的第一篇文章有关于安装的详细信息。)
SAR文件 (Server Application Resource)是Phoenix中提出的一个概念。 我们曾经说过,James是运行在 Avalon项目开发的Phoenix 服务器基础设施中的。(查看 资源 了解 Avalon.的详细信息) Phoenix 为 Java 平台的项目提供了一个通用的服务器框架。 SAR 文件是一个带有结构说明的 JAR 文件 (压缩文件)。 我们所需要的 james.jar文件在SAR档案文件的 SAR-INF/lib子目录下。可以用任何合适的应用程序,比如 WinZIP 或者UNIX 下的解压缩应用程序,解压SAR文件后得到它。
很不幸,将mailet添加到James包中的唯一可选的办法就是重新创建SAR文件。其实这是一个笨办法,而且也不应该这么复杂,好在James 开发组已经意识到这样很麻烦并正在打算改善这种状况。然而,现在我们只有解压整个james.sar文件才能用它工作。解压后,将我们的类文件放进SAR-INF/lib 目录(也可以打包成JAR文件后放进这个目录),然后修改 James 的配置文件,以便我们的代码能被正确的识别和使用。接着,我们要重新打包 james.sar 文件。为了避免产生冲突,我们应该将原始的 james.jar 文件放到一个安全的地方,并将我们的文件命名为 james-plus.jar。
为了加入我们自己的 mailet,需要编辑配置文件 config.xml ,它在 SAR-INF 目录下。我们要在配置文件中加两种标签。第一组标签仅仅是告诉James在那个包内查找匹配器类和mailet类。我们可以参考配置文件中对 org.apache 类的定义来定义我们的mailet包和匹配器包的标签。列表8说明了我们添加的标签。
列表8. 匹配器 和 mailet 包的声明
org.apache.james.transport.mailets
com.claudeduguay.mailets
org.apache.james.transport.matchers
com.claudeduguay.mailets
在类的packages定义后,我们能够对应用程序进行配置,让它能够找到匹配器和mailet , 如列表9所示:
列表9. 匹配器 和 mailet 处理的声明
match="MatchSingleRecipient=unavailable@localhost"
class="UnavailableMessageSave"
d:/james/james-2.1.2/unavailable
You have been marked as UNAVAILABLE
Send a message to available@localhost to reset.
match="MatchSingleRecipient=available@localhost"
class="UnavailableMessageDrop"
d:/james/james-2.1.2/unavailable
You have been marked as AVAILABLE
Send a message to unavailable@localhost to send the
message to users when you are unavailable.
match="MatchUnavailableUser=d:/james/james-2.1.2/unavailable"
class="UnavailableMessageSend"
d:/james/james-2.1.2/unavailable
available@localhost;unavailable@localhost
如果你从来没有用过Ant,那么你会觉得列表10中的构建脚本很陌生。Ant是一个构建工具,它也来自 Apache 组织,现在广泛用于自动构建。我们将用这个工具自动解压原始的SAR文件、编译、重构、部署我们的应用程序。如果你不熟悉 Ant, 那你只能自己去了解它了。它不仅功能强大而且完全可移植,是自动构建领域的一个巨大飞跃。通过定制这个构建文件,你可以改变属性标签中说明的目录。 (资源部分有关于Ant的更多信息。)
列表10. Ant build 脚本
Project dir: ${project.dir}
James dir: ${james.dir}
JavaMail dir: ${javamail.dir}
JAF dir: ${jaf.dir}
src="${project.dir}/james.sar"
dest="${project.dir}/${temp.dir}" /
Compiling
Compiled
Packaging
basedir="." includes="com/claudeduguay/**/*.class" /
Packaged
basedir="${project.dir}/${temp.dir}" includes="**/*" /
一旦完成构建工作,我们就可以用james/bin目录下的运行脚本启动James 服务器。如果一切运转正常,你会看到和第一篇中给出的一样的输出,表明James启动并开始运转。
在我们进行测试之前,需要设定 red、 green、 blue 三个用户。如果你还没有设定,可以按照第一篇文章中的指示去做。
模拟用户
一旦我们所有的代码都运行在服务器上之后,我们需要进行一组测试来证明我们的应用程序运转正常。 列表11中所示的JamesApplicationTest 类,集成了第一篇文章中开发的 JamesConfigTest 类,用我们开发的 MailClient 类来发送和读取e-mail。
我们如何测试我们的匹配器和 mailet 呢? 首先,我们清除 red、green,和blue用户的所有消息。然后我们分别从red和green用户发送邮件到blue,接下来让blue用户发送不可到达消息。在blue用户下次检查消息的时候,他应该能看到red和green用户最初发送的消息,还有unavailable用户发送的确认消息。这表明他的邮件被正确的保存下来了,而且他已经处于不可到达状态。blue用户还能继续进行正常的操作,发送和接收邮件。但其他任何用户在这种状态下给blue发送邮件时都会收到blue预存的不可到达消息。
通过给blue发送新的消息我们能够测试出用户处于不可到达状态时会发生什么事情。我们用green用户做这件事情,然后去检查green的收件箱。green用户会收到来自blue的一个不可到达消息,这个消息的内容就是blue发送到unavailable地址存储起来的消息。最后,blue发送一个可以到达消息并且检查是否有available地址发送过来的确认消息。
列表11. JamesApplicationTest: Putting our matchers and mailets through the paces
public class JamesApplicationTest
{
public static void main(String[] args)
throws Exception
{
// CREATE CLIENT INSTANCES
MailClient redClient = new MailClient("red", "localhost");
MailClient greenClient = new MailClient("green", "localhost");
MailClient blueClient = new MailClient("blue", "localhost");
// CLEAR EVERYBODY'S INBOX
redClient.checkInbox(MailClient.CLEAR_MESSAGES);
greenClient.checkInbox(MailClient.CLEAR_MESSAGES);
blueClient.checkInbox(MailClient.CLEAR_MESSAGES);
Thread.sleep(500); // Let the server catch up
// SEND A COUPLE OF MESSAGES TO BLUE (FROM RED AND GREEN)
redClient.sendMessage(
"blue@localhost",
"Testing blue from red",
"This is a test message");
greenClient.sendMessage(
"blue@localhost",
"Testing blue from green",
"This is a test message");
// BLUE SENDS UNAVAILABLE MESSAGE
blueClient.sendMessage(
"unavailable@localhost",
"On Vacation",
"I am on vacation at the moment. " +
"I will answer your mail as soon as I get back.");
Thread.sleep(500); // Let the server catch up
// LIST BLUE MESSAGES (RED, GREEN %26amp; UNAVAILABLE CONFIRMATION)
blueClient.checkInbox(MailClient.SHOW_AND_CLEAR);
// GREEN SENDS A NORMAL MESSAGE TO BLUE
greenClient.sendMessage(
"blue@localhost",
"Testing blue from green",
"This is a test message");
Thread.sleep(500); // Let the server catch up
// GREEN CHECKS MESSAGES (SHOULD SEE BLUE UNAVAILABLE MESSAGES)
greenClient.checkInbox(MailClient.SHOW_AND_CLEAR);
// BLUE SENDS MESSAGES TO BECOME AVAILABLE
blueClient.sendMessage(
"available@localhost",
"Ignored subject",
"Ignored content");
Thread.sleep(500); // Let the server catch up
// BLUE CHECKS MAIL, SHOULD SEE MESSAGE FROM GREEN AND AVAILABLE MSG
blueClient.checkInbox(MailClient.SHOW_AND_CLEAR);
}
}
JamesApplicationTest 的输出应该如下所示:
列表12. JamesApplicationTest 输出
Clear INBOX for red@localhost
Clear INBOX for green@localhost
Clear INBOX for blue@localhost
SENDING message from red@localhost to blue@localhost
SENDING message from green@localhost to blue@localhost
SENDING message from blue@localhost to unavailable@localhost
Show and Clear INBOX for blue@localhost
From: green@localhost
Subject: Testing blue from green
Content: This is a test message
From: red@localhost
Subject: Testing blue from red
Content: This is a test message
SENDING message from green@localhost to blue@localhost
Show and Clear INBOX for green@localhost
From: blue@localhost
Subject: On Vacation
Content: I am on vacation at the moment. I will answer your mail
as soon as I get back.
SENDING message from blue@localhost to available@localhost
Show and Clear INBOX for blue@localhost
From: available@localhost
Subject: You have been marked as AVAILABLE
Content: Send a message to unavailable@localhost to send the
message to users when you are unavailable.
From: green@localhost
Subject: Testing blue from green
Content: This is a test message
From: unavailable@localhost
Subject: You have been marked as UNAVAILABLE
Content: Send a message to available@localhost to reset.
总结
从这篇文章中的代码可以看出,对James进行二次开发是很简单的。目前唯一复杂的处理就是部署的过程,这也是James开发小组现在正在进行改善的地方。既然e-mail是Internet上最广泛的应用,而且很明显它比Web服务更流行,那么思考可以由James基础设施取得的可能性就显的非常有趣了。这必将朝着解决重要问题的方向发展,而且所受到的唯一限制就是你的想像空间。
资源
下载这篇文章中的源代码。
在Apache James主页上下载James包。
到 Avalon 项目主页上学习Avalon
要运行这篇文章中的示例,你需要下载 JavaMail API 和 JavaBeans Activation Framework.
利用教程"Fundamentals of JavaMail API"学习JavaMail (developerWorks, August 2001).
深入了解Apache Ant 构建工具.
, ,