/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.transport.mailets;
import org.apache.james.util.RFC822Date;
import org.apache.mailet.GenericMailet;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.StringTokenizer;
/**
*<P>A mailet providing configurable redirection services<BR>
*This mailet can produce listserver, forward and notify behaviour, with the original
*message intact, attached, appended or left out altogether.<BR>
*This built in functionality is controlled by the configuration as laid out below.</P>
*<P>However it is also designed to be easily subclassed to make authoring redirection
*mailets simple. <BR>
*By extending it and overriding one or more of these methods new behaviour can
*be quickly created without the author having to address any other issue than
*the relevant one:</P>
*<UL>
*<LI>attachError() , should error messages be appended to the message</LI>
*<LI>getAttachementType(), what should be attached to the message</LI>
*<LI>getInLineType(), what should be included in the message</LI>
*<LI>getMessage(), The text of the message itself</LI>
*<LI>getRecipients(), the recipients the mail is sent to</LI>
*<LI>getReplyTo(), where replys to this message will be sent</LI>
*<LI>getSender(), who the mail is from</LI>
*<LI>getSubjectPrefix(), a prefix to be added to the message subject</LI>
*<LI>getTo(), a list of people to whom the mail is *apparently* sent</LI>
*<LI>getPassThrough(), should this mailet GHOST the original message.</LI>
*<LI>isStatic(), should this mailet run the get methods for every mail, or just
*once. </LI>
*</UL>
*<P>The configuration parameters are:</P>
*<TABLE width="75%" border="0" cellspacing="2" cellpadding="2">
*<TR>
*<TD width="20%"><recipients></TD>
*<TD width="80%">A comma delimited list of email addresses for recipients of
*this message, it will use the "to" list if not specified. These
*addresses will only appear in the To: header if no "to" list is
*supplied.</TD>
*</TR>
*<TR>
*<TD width="20%"><to></TD>
*<TD width="80%">A comma delimited list of addresses to appear in the To: header,
*the email will only be delivered to these addresses if they are in the recipients
*list.<BR>
*The recipients list will be used if this is not supplied</TD>
*</TR>
*<TR>
*<TD width="20%"><sender></TD>
*<TD width="80%">A single email address to appear in the From: header <BR>
*It can include constants "sender" and "postmaster"</TD>
*</TR>
*<TR>
*<TD width="20%"><message></TD>
*<TD width="80%">A text message to be the body of the email. Can be omitted.</TD>
*</TR>
*<TR>
*<TD width="20%"><inline></TD>
*<TD width="80%">
*<P>One of the following items:</P>
*<UL>
*<LI>unaltered The original message is the new
* message, for forwarding/aliasing</LI>
*<LI>heads The
* headers of the original message are appended to the message</LI>
*<LI>body The
* body of the original is appended to the new message</LI>
*<LI>all Both
* headers and body are appended</LI>
*<LI>none Neither
* body nor headers are appended</LI>
*</UL>
*</TD>
*</TR>
*<TR>
*<TD width="20%"><attachment></TD>
*<TD width="80%">
*<P>One of the following items:</P>
*<UL>
*<LI>heads The headers of the original
* are attached as text</LI>
*<LI>body The body of the original
* is attached as text</LI>
*<LI>all Both
* headers and body are attached as a single text file</LI>
*<LI>none Nothing is attached</LI>
*<LI>message The original message is attached as type message/rfc822,
* this means that it can, in many cases, be opened, resent, fw'd, replied
* to etc by email client software.</LI>
*</UL>
*</TD>
*</TR>
*<TR>
*<TD width="20%"><passThrough></TD>
*<TD width="80%">TRUE or FALSE, if true the original message continues in the
*mailet processor after this mailet is finished. False causes the original
*to be stopped.</TD>
*</TR>
*<TR>
*<TD width="20%"><attachError></TD>
*<TD width="80%">TRUE or FALSE, if true any error message available to the
*mailet is appended to the message body (except in the case of inline ==
*unaltered)</TD>
*</TR>
*<TR>
*<TD width="20%"><replyto></TD>
*<TD width="80%">A single email address to appear in the Rely-To: header, can
*also be "sender" or "postmaster", this header is not
*set if this is omited.</TD>
*</TR>
*<TR>
*<TD width="20%"><prefix></TD>
*<TD width="80%">An optional subject prefix prepended to the original message
*subject, for example..<BR>
*Undeliverable mail:</TD>
*</TR>
*<TR>
*<TD width="20%"><static></TD>
*<TD width="80%">
*<P>TRUE or FALSE, if this is true it hints to the mailet that none of the
*parameters are set dynamically, and therefore they can be set once in
*the init method.<BR>
*False tells the mailet to call all the "getters" for every mail
*processed.</P>
*<P>This defaults to false.<BR>
*It should be TRUE in all cases, except where one of the getter methods
*has been overriden to provide dynamic values, such as a listserve which
*might override getRecipients() to get a list from a users repository.</P>
*</TD>
*</TR>
*</TABLE>
*
*<P>Example:</P>
*<P> <mailet match="RecipientIs=test@localhost" class="Redirect"><BR>
*<recipients>x@localhost, y@localhost, z@localhost</recipients><BR>
*<to>list@localhost</to><BR>
*<sender>owner@localhost</sender><BR>
*<message>sent on from James</message><BR>
*<inline>unaltered</inline><BR>
*<passThrough>FALSE</passThrough><BR>
*<replyto>postmaster</replyto><BR>
*<prefix>[test mailing]</prefix><BR>
*<static>TRUE</static><BR>
*<passThrough>FALSE</passThrough><BR>
*</mailet><BR>
*</P>
*<P>and:</P>
*<P> <mailet match="All" class="Redirect"><BR>
*<recipients>x@localhost</recipients><BR>
*<sender>postmaster</sender><BR>
*<message>Message marked as spam:<BR>
*</message><BR>
*<inline>heads</inline><BR>
*<attachment>message</attachment><BR>
*<passThrough>FALSE</passThrough><BR>
*<attachError>TRUE</attachError><BR>
*<replyto>postmaster</replyto><BR>
*<prefix>[spam notification]</prefix><BR>
*<static>TRUE</static><BR>
*<passThrough>FALSE</passThrough><BR>
*</mailet></P>
*
* @author Danny Angus <danny@thought.co.uk>
*
*/
public class Redirect extends GenericMailet {
private MailAddress sender;
private MailAddress replyTo;
private String messageText;
private InternetAddress[] apparentlyTo;
private Collection recipients;
private static int UNALTERED = 0;
private static int HEADS = 1;
private static int BODY = 2;
private static int ALL = 3;
private static int NONE = 4;
private static int MESSAGE = 5;
/**
* init will setup static values for sender, recipients, message text, and reply to
* <br> if isStatic() returns true
* it calls getSender(), getReplyTo(), getMessage(), and getRecipients() and getTo()
*
*/
public void init() throws MessagingException {
log("redirect init");
if(isStatic()){
sender = getSender()==null ? getMailetContext().getPostmaster():getSender();
replyTo = getReplyTo()==null ? getMailetContext().getPostmaster():getReplyTo();
messageText = getMessage();
recipients = getRecipients();
apparentlyTo = getTo();
log("static, sender="+sender+", replyTo="+replyTo+", message="+messageText+" ");
}
}
/**
*
* Service does the hard work,and redirects the mail in the form specified
*
*
*/
public void service(Mail mail) throws MessagingException {
if(!isStatic()){
sender = getSender();
replyTo = getReplyTo();
messageText = getMessage();
recipients = getRecipients();
apparentlyTo = getTo();
}
MimeMessage message = mail.getMessage();
MimeMessage reply = new MimeMessage(Session.getDefaultInstance(System.getProperties(), null));
//Create the message
if (getInLineType()!= UNALTERED){
log("alter message inline=:"+getInLineType());
StringWriter sout = new StringWriter();
PrintWriter out = new PrintWriter(sout, true);
Enumeration heads = message.getAllHeaderLines();
String head = "";
while (heads. hasMoreElements() ){
head += heads.nextElement().toString()+"\n";
}
boolean all = false;
if(messageText!=null){
out.println(messageText);
}
switch(getInLineType()){
case 3: //ALL:
all = true;
case 1: //HEADS:
out.println("Message Headers:");
out.println(head);
if(! all){
break;
}
case 2: //BODY:
out.println("Message:");
try{
out.println(message.getContent().toString());
}catch(Exception e){
out.println("body unavailable");
}
break;
default:
case 4: //NONE:
break;
}
MimeMultipart multipart = new MimeMultipart();
//Add message as the first mime body part
MimeBodyPart part = new MimeBodyPart();
part.setText(sout.toString());
part.setDisposition("inline");
multipart.addBodyPart(part);
if(getAttachmentType()!= NONE){
part = new MimeBodyPart();
switch(getAttachmentType()){
case 1: //HEADS:
part.setText(head);
break;
case 2: //BODY:
try{
part.setText(message.getContent().toString());
}catch(Exception e){
part.setText("body unavailable");
}
break;
case 3: //ALL:
part.setText(head +"\n\n"+ message.toString());
break;
case 5: //MESSAGE:
part.setContent(message,"message/rfc822");
break;
}
part.setDisposition("Attachment");
multipart.addBodyPart(part);
}
reply.setContent(multipart);
reply.setHeader("Content-Type", multipart.getContentType());
}else{
log("message resent unaltered:");
reply=message;
}
//Set additional headers
reply.setSubject(getSubjectPrefix()+message.getSubject());
if (reply.getHeader("Date")==null){
reply.setHeader("Date",new RFC822Date().toString());
}
reply.setRecipients(Message.RecipientType.TO, apparentlyTo);
if (replyTo != null){
InternetAddress[] iart = new InternetAddress[1];
iart[0]=replyTo.toInternetAddress();
reply.setReplyTo(iart);
}
if(sender == null){
reply.setHeader("From",message.getHeader("From",","));
}else{
reply.setFrom(sender.toInternetAddress());
}
//Send it off...
getMailetContext().sendMail(sender,recipients,reply);
if(! getPassThrough()){
mail.setState(Mail.GHOST);
}
}
public String getMailetInfo() {
return "Resend Mailet";
}
/**
* must return a Collection of recipient MailAddress'es
*/
public Collection getRecipients(){
Collection newRecipients = new HashSet();
String addressList = getInitParameter("recipients")==null? getInitParameter("to"):getInitParameter("recipients");
StringTokenizer st = new StringTokenizer(addressList, ",", false);
while (st.hasMoreTokens()) {
try{
newRecipients.add(new MailAddress(st.nextToken()));
}catch(Exception e){
log("add recipient failed in getRecipients");
}
}
return newRecipients;
}
/**
* must return either an empty string, or a message to which the redirect can be attached/appended
*/
public String getMessage(){
if(getInitParameter("message")==null){
return "";
}else{
return getInitParameter("message");
}
}
/**
* returns an array of InternetAddress 'es for the To: header
*/
public InternetAddress[] getTo(){
String addressList = getInitParameter("to")==null? getInitParameter("recipients"):getInitParameter("to");
StringTokenizer rec = new StringTokenizer(addressList,",") ;
int tokensn = rec.countTokens();
InternetAddress[] iaarray = new InternetAddress[tokensn];
String tokenx ="";
for( int i=0;i<tokensn;++i){
try{
tokenx=rec.nextToken();
iaarray[i]=new InternetAddress(tokenx);
}catch(Exception e){
log("Internet address exception in getTo()");
}
}
return iaarray;
}
/**
* returns the senders address, as a MailAddress
*/
public MailAddress getSender(){
String sr = getInitParameter("sender");
if(sr != null){
MailAddress rv;
if(sr.compareTo("postmaster")==0){
rv = getMailetContext().getPostmaster();
return rv;
}
if(sr.compareTo("sender")==0){
return null;
}
try{
rv = new MailAddress(sr);
return rv;
}catch(Exception e){
log("Parse error in getSender "+sr);
}
}
return null;
}
/**
* returns one of these values to indicate how to append the original message
*<ul>
* <li>UNALTERED : original message is the new message body</li>
* <li>BODY : original message body is appended to the new message</li>
* <li>HEADS : original message headers are appended to the new message</li>
* <li>ALL : original is appended with all headers</li>
* <li>NONE : original is not appended</li>
*</ul>
*/
public int getInLineType(){
if(getInitParameter("inline")==null){
return BODY;
}else{
return getTypeCode(getInitParameter("inline"));
}
}
/**
* returns one of these values to indicate how to attach the original message
*<ul>
* <li>BODY : original message body is attached as plain text to the new message</li>
* <li>HEADS : original message headers are attached as plain text to the new message</li>
* <li>ALL : original is attached as plain text with all headers</li>
* <li>MESSAGE : original message is attached as type message/rfc822, a complete mail message.</li>
* <li>NONE : original is not attached</li>
*</ul>
*
*/
public int getAttachmentType(){
if(getInitParameter("attachment")==null){
return NONE;
}else{
return getTypeCode(getInitParameter("attachment"));
}
}
/**
* return true to allow thie original message to continue through the processor, false to GHOST it
*/
public boolean getPassThrough(){
if(getInitParameter("passThrough")==null){
return true;
}else{
return new Boolean(getInitParameter("passThrough")).booleanValue();
}
}
/**
* return true to append a description of any error to the main body part
* if getInlineType does not return "UNALTERED"
*/
public boolean attachError(){
if(getInitParameter("attachError")==null){
return false;
}else{
return new Boolean(getInitParameter("attachError")).booleanValue();
}
}
/**
* return the reply to address as a string
*/
public MailAddress getReplyTo(){
String sr = getInitParameter("replyto");
if(sr != null){
MailAddress rv;
if(sr.compareTo("postmaster")==0){
rv = getMailetContext().getPostmaster();
return rv;
}
if(sr.compareTo("sender")==0){
return null;
}
try{
rv = new MailAddress(sr);
return rv;
}catch(Exception e){
log("Parse error in getReplyTo "+sr);
}
}
return null;
}
/**
* return a prefix for the message subject
*/
public String getSubjectPrefix(){
if(getInitParameter("prefix")==null){
return "";
}else{
return getInitParameter("prefix");
}
}
/**
* return true to reduce calls to getTo, getSender, getRecipients, getReplyTo amd getMessage
* where these values don't change (eg hard coded, or got at startup from the mailet config)<br>
* return false where any of these methods generate their results dynamically eg in response to the message being processed,
* or by refrence to a repository of users
*/
public boolean isStatic(){
if(getInitParameter("static")==null){
return false;
}
return new Boolean(getInitParameter("static")).booleanValue();
}
/**
* A private method to convert types from string to int.
*/
private int getTypeCode(String param){
int code;
param = param.toLowerCase() ;
if (param.compareTo("unaltered")==0){return 0;}
if (param.compareTo("heads")==0){return 1;}
if (param.compareTo("body")==0){return 2;}
if (param.compareTo("all")==0){return 3;}
if (param.compareTo("none")==0){return 4;}
if (param.compareTo("message")==0){return 5;}
return 4;
}
}