001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.transport.mailets;
019:
020: import org.apache.mailet.RFC2822Headers;
021: import org.apache.mailet.GenericMailet;
022: import org.apache.mailet.Mail;
023: import org.apache.mailet.MailAddress;
024: import org.apache.mailet.MailetException;
025:
026: import javax.mail.MessagingException;
027: import javax.mail.internet.MimeMessage;
028: import javax.mail.internet.MimeMultipart;
029: import java.io.IOException;
030: import java.util.Collection;
031: import java.util.Vector;
032:
033: /**
034: * An abstract implementation of a listserv. The underlying implementation must define
035: * various settings, and can vary in their individual configuration. Supports restricting
036: * to members only, allowing attachments or not, sending replies back to the list, and an
037: * optional subject prefix.
038: */
039: public abstract class GenericListserv extends GenericMailet {
040:
041: /**
042: * Returns a Collection of MailAddress objects of members to receive this email
043: */
044: public abstract Collection getMembers() throws MessagingException;
045:
046: /**
047: * Returns whether this list should restrict to senders only
048: */
049: public abstract boolean isMembersOnly() throws MessagingException;
050:
051: /**
052: * Returns whether this listserv allow attachments
053: */
054: public abstract boolean isAttachmentsAllowed()
055: throws MessagingException;
056:
057: /**
058: * Returns whether listserv should add reply-to header
059: */
060: public abstract boolean isReplyToList() throws MessagingException;
061:
062: /**
063: * The email address that this listserv processes on. If returns null, will use the
064: * recipient of the message, which hopefully will be the correct email address assuming
065: * the matcher was properly specified.
066: */
067: public MailAddress getListservAddress() throws MessagingException {
068: return null;
069: }
070:
071: /**
072: * An optional subject prefix.
073: */
074: public abstract String getSubjectPrefix() throws MessagingException;
075:
076: /**
077: * Should the subject prefix be automatically surrounded by [].
078: *
079: * @return whether the subject prefix will be surrounded by []
080: *
081: * @throws MessagingException never, for this implementation
082: */
083: public boolean isPrefixAutoBracketed() throws MessagingException {
084: return true; // preserve old behavior unless subclass overrides.
085: }
086:
087: /**
088: * <p>This takes the subject string and reduces (normailzes) it.
089: * Multiple "Re:" entries are reduced to one, and capitalized. The
090: * prefix is always moved/placed at the beginning of the line, and
091: * extra blanks are reduced, so that the output is always of the
092: * form:</p>
093: * <code>
094: * <prefix> + <one-optional-"Re:"*gt; + <remaining subject>
095: * </code>
096: * <p>I have done extensive testing of this routine with a standalone
097: * driver, and am leaving the commented out debug messages so that
098: * when someone decides to enhance this method, it can be yanked it
099: * from this file, embedded it with a test driver, and the comments
100: * enabled.</p>
101: */
102: static private String normalizeSubject(final String subj,
103: final String prefix) {
104: // JDK IMPLEMENTATION NOTE! When we require JDK 1.4+, all
105: // occurrences of subject.toString.().indexOf(...) can be
106: // replaced by subject.indexOf(...).
107:
108: StringBuffer subject = new StringBuffer(subj);
109: int prefixLength = prefix.length();
110:
111: // System.err.println("In: " + subject);
112:
113: // If the "prefix" is not at the beginning the subject line, remove it
114: int index = subject.toString().indexOf(prefix);
115: if (index != 0) {
116: // System.err.println("(p) index: " + index + ", subject: " + subject);
117: if (index > 0) {
118: subject.delete(index, index + prefixLength);
119: }
120: subject.insert(0, prefix); // insert prefix at the front
121: }
122:
123: // Replace Re: with RE:
124: String match = "Re:";
125: index = subject.toString().indexOf(match, prefixLength);
126:
127: while (index > -1) {
128: // System.err.println("(a) index: " + index + ", subject: " + subject);
129: subject.replace(index, index + match.length(), "RE:");
130: index = subject.toString().indexOf(match, prefixLength);
131: // System.err.println("(b) index: " + index + ", subject: " + subject);
132: }
133:
134: // Reduce them to one at the beginning
135: match = "RE:";
136: int indexRE = subject.toString().indexOf(match, prefixLength)
137: + match.length();
138: index = subject.toString().indexOf(match, indexRE);
139: while (index > 0) {
140: // System.err.println("(c) index: " + index + ", subject: " + subject);
141: subject.delete(index, index + match.length());
142: index = subject.toString().indexOf(match, indexRE);
143: // System.err.println("(d) index: " + index + ", subject: " + subject);
144: }
145:
146: // Reduce blanks
147: match = " ";
148: index = subject.toString().indexOf(match, prefixLength);
149: while (index > -1) {
150: // System.err.println("(e) index: " + index + ", subject: " + subject);
151: subject.replace(index, index + match.length(), " ");
152: index = subject.toString().indexOf(match, prefixLength);
153: // System.err.println("(f) index: " + index + ", subject: " + subject);
154: }
155:
156: // System.err.println("Out: " + subject);
157:
158: return subject.toString();
159: }
160:
161: /**
162: * Processes the message. Assumes it is the only recipient of this forked message.
163: */
164: public final void service(Mail mail) throws MessagingException {
165: try {
166: Collection members = getMembers();
167:
168: //Check for members only flag....
169: if (isMembersOnly() && !members.contains(mail.getSender())) {
170: //Need to bounce the message to say they can't send to this list
171: getMailetContext()
172: .bounce(mail,
173: "Only members of this listserv are allowed to send a message to this address.");
174: mail.setState(Mail.GHOST);
175: return;
176: }
177:
178: //Check for no attachments
179: if (!isAttachmentsAllowed()
180: && mail.getMessage().getContent() instanceof MimeMultipart) {
181: getMailetContext()
182: .bounce(mail,
183: "You cannot send attachments to this listserv.");
184: mail.setState(Mail.GHOST);
185: return;
186: }
187:
188: //Create a copy of this message to send out
189: MimeMessage message = new MimeMessage(mail.getMessage());
190: //We need to remove this header from the copy we're sending around
191: message.removeHeader(RFC2822Headers.RETURN_PATH);
192:
193: //Figure out the listserv address.
194: MailAddress listservAddr = getListservAddress();
195: if (listservAddr == null) {
196: //Use the recipient
197: listservAddr = (MailAddress) mail.getRecipients()
198: .iterator().next();
199: }
200:
201: //Check if the X-been-there header is set to the listserv's name
202: // (the address). If it has, this means it's a message from this
203: // listserv that's getting bounced back, so we need to swallow it
204: if (listservAddr.equals(message.getHeader("X-been-there"))) {
205: mail.setState(Mail.GHOST);
206: return;
207: }
208:
209: //Set the subject if set
210: String prefix = getSubjectPrefix();
211: if (prefix != null) {
212: if (isPrefixAutoBracketed()) {
213: StringBuffer prefixBuffer = new StringBuffer(64)
214: .append("[").append(prefix).append("] ");
215: prefix = prefixBuffer.toString();
216: }
217: String subj = message.getSubject();
218: if (subj == null) {
219: subj = "";
220: }
221: subj = normalizeSubject(subj, prefix);
222: AbstractRedirect.changeSubject(message, subj);
223: }
224:
225: //If replies should go to this list, we need to set the header
226: if (isReplyToList()) {
227: message.setHeader(RFC2822Headers.REPLY_TO, listservAddr
228: .toString());
229: }
230: //We're going to set this special header to avoid bounces
231: // getting sent back out to the list
232: message.setHeader("X-been-there", listservAddr.toString());
233:
234: //Send the message to the list members
235: //We set the postmaster as the sender for now so bounces go to him/her
236: getMailetContext().sendMail(
237: getMailetContext().getPostmaster(), members,
238: message);
239:
240: //Kill the old message
241: mail.setState(Mail.GHOST);
242: } catch (IOException ioe) {
243: throw new MailetException(
244: "Error creating listserv message", ioe);
245: }
246: }
247: }
|