FileDocCategorySizeDatePackage
James.javaAPI DocApache James 2.3.134165Fri Jan 12 12:56:22 GMT 2007org.apache.james

James

public class James extends org.apache.avalon.framework.logger.AbstractLogEnabled implements org.apache.mailet.MailetContext, org.apache.avalon.framework.service.Serviceable, JamesMBean, org.apache.james.services.MailServer, org.apache.avalon.framework.activity.Initializable, org.apache.avalon.framework.context.Contextualizable, org.apache.avalon.framework.configuration.Configurable
Core class for JAMES. Provides three primary services:
1) Instantiates resources, such as user repository, and protocol handlers
2) Handles interactions between components
3) Provides container services for Mailets
version
This is $Revision: 494012 $

Fields Summary
private static final String
SOFTWARE_NAME_VERSION
The software name and version
private org.apache.avalon.framework.service.DefaultServiceManager
compMgr
The component manager used both internally by James and by Mailets.
private org.apache.avalon.framework.context.DefaultContext
context
TODO: Investigate what this is supposed to do. Looks like it was supposed to be the Mailet context.
private org.apache.avalon.framework.configuration.Configuration
conf
The top level configuration object for this server.
private org.apache.avalon.framework.logger.Logger
mailetLogger
The logger used by the Mailet API.
private org.apache.avalon.cornerstone.services.store.Store
store
The mail store containing the inbox repository and the spool.
private org.apache.james.services.UsersStore
usersStore
The store containing the local user repository.
private org.apache.james.services.SpoolRepository
spool
The spool used for processing mail handled by this server.
private String
inboxRootURL
The root URL used to get mailboxes from the repository
private org.apache.james.services.UsersRepository
localusers
The user repository for this mail server. Contains all the users with inboxes on this server.
private Collection
serverNames
The collection of domain/server names for which this instance of James will receive and process mail.
private boolean
ignoreCase
Whether to ignore case when looking up user names on this server
private static long
count
The number of mails generated. Access needs to be synchronized for thread safety and to ensure that all threads see the latest value.
private org.apache.mailet.MailAddress
postmaster
The address of the postmaster for this server
private Map
mailboxes
A map used to store mailboxes and reduce the cost of lookup of individual mailboxes.
private Hashtable
attributes
A hash table of server attributes These are the MailetContext attributes
protected org.apache.avalon.framework.context.Context
myContext
The Avalon context used by the instance
protected org.apache.mailet.Mailet
localDeliveryMailet
Currently used by storeMail to avoid code duplication (we moved store logic to that mailet). TODO We should remove this and its initialization when we remove storeMail method.
Constructors Summary
Methods Summary
public booleanaddUser(java.lang.String userName, java.lang.String password)
Adds a user to this mail server. Currently just adds user to a UsersRepository.

param
userName String representing user name, that is the portion of an email address before the '@'.
param
password String plaintext password
return
boolean true if user added succesfully, else false.
deprecated
we deprecated this in the MailServer interface and this is an implementation this component depends already depends on a UsersRepository: clients could directly use the addUser of the usersRepository.

        boolean success;
        DefaultJamesUser user = new DefaultJamesUser(userName, "SHA");
        user.setPassword(password);
        user.initialize();
        success = localusers.addUser(user);
        return success;
    
public voidbounce(org.apache.mailet.Mail mail, java.lang.String message)
This generates a response to the Return-Path address, or the address of the message's sender if the Return-Path is not available. Note that this is different than a mail-client's reply, which would use the Reply-To or From header. This will send the bounce with the server's postmaster as the sender.

        bounce(mail, message, getPostmaster());
    
public voidbounce(org.apache.mailet.Mail mail, java.lang.String message, org.apache.mailet.MailAddress bouncer)
This generates a response to the Return-Path address, or the address of the message's sender if the Return-Path is not available. Note that this is different than a mail-client's reply, which would use the Reply-To or From header. Bounced messages are attached in their entirety (headers and content) and the resulting MIME part type is "message/rfc822". The attachment to the subject of the original message (or "No Subject" if there is no subject in the original message) There are outstanding issues with this implementation revolving around handling of the return-path header. MIME layout of the bounce message: multipart (mixed)/ contentPartRoot (body) = mpContent (alternative)/ part (body) = message part (body) = original

        if (mail.getSender() == null) {
            if (getLogger().isInfoEnabled())
                getLogger().info("Mail to be bounced contains a null (<>) reverse path.  No bounce will be sent.");
            return;
        } else {
            // Bounce message goes to the reverse path, not to the Reply-To address
            if (getLogger().isInfoEnabled())
                getLogger().info("Processing a bounce request for a message with a reverse path of " + mail.getSender().toString());
        }

        MailImpl reply = rawBounce(mail,message);
        //Change the sender...
        reply.getMessage().setFrom(bouncer.toInternetAddress());
        reply.getMessage().saveChanges();
        //Send it off ... with null reverse-path
        reply.setSender(null);
        sendMail(reply);
        ContainerUtil.dispose(reply);
    
public voidconfigure(org.apache.avalon.framework.configuration.Configuration conf)

see
org.apache.avalon.framework.configuration.Configurable#configure(Configuration)

        this.conf = conf;
    
public voidcontextualize(org.apache.avalon.framework.context.Context context)

see
org.apache.avalon.framework.context.Contextualizable#contextualize(Context)

    
           
         
        this.myContext = context;
    
public java.lang.ObjectgetAttribute(java.lang.String key)

        return attributes.get(key);
    
public java.util.IteratorgetAttributeNames()

        Vector names = new Vector();
        for (Enumeration e = attributes.keys(); e.hasMoreElements(); ) {
            names.add(e.nextElement());
        }
        return names.iterator();
    
public java.lang.StringgetId()
Return a new mail id.

return
a new mail id

        long localCount = -1;
        synchronized (James.class) {
            localCount = count++;
        }
        StringBuffer idBuffer =
            new StringBuffer(64)
                    .append("Mail")
                    .append(System.currentTimeMillis())
                    .append("-")
                    .append(localCount);
        return idBuffer.toString();
    
public java.util.CollectiongetMailServers(java.lang.String host)

Get the prioritized list of mail servers for a given host.

TODO: This needs to be made a more specific ordered subtype of Collection.

param
host

        DNSServer dnsServer = null;
        try {
            dnsServer = (DNSServer) compMgr.lookup( DNSServer.ROLE );
        } catch ( final ServiceException cme ) {
            getLogger().error("Fatal configuration error - DNS Servers lost!", cme );
            throw new RuntimeException("Fatal configuration error - DNS Servers lost!");
        }
        return dnsServer.findMXRecords(host);
    
private org.apache.avalon.framework.logger.LoggergetMailetLogger()
Return the logger for the Mailet API

return
the logger for the Mailet API

        if (mailetLogger == null) {
            mailetLogger = getLogger().getChildLogger("Mailet");
        }
        return mailetLogger;
    
public intgetMajorVersion()
Return the major version number for the server

return
the major vesion number for the server

        return 2;
    
public intgetMinorVersion()
Return the minor version number for the server

return
the minor vesion number for the server

        return 3;
    
public org.apache.mailet.MailAddressgetPostmaster()
Returns the address of the postmaster for this server.

return
the MailAddress for the postmaster

        return postmaster;
    
public java.util.IteratorgetSMTPHostAddresses(java.lang.String domainName)
Performs DNS lookups as needed to find servers which should or might support SMTP. Returns an Iterator over HostAddress, a specialized subclass of javax.mail.URLName, which provides location information for servers that are specified as mail handlers for the given hostname. This is done using MX records, and the HostAddress instances are returned sorted by MX priority. If no host is found for domainName, the Iterator returned will be empty and the first call to hasNext() will return false.

see
org.apache.james.DNSServer#getSMTPHostAddresses(String)
since
Mailet API v2.2.0a16-unstable
param
domainName - the domain for which to find mail servers
return
an Iterator over HostAddress instances, sorted by priority

        DNSServer dnsServer = null;
        try {
            dnsServer = (DNSServer) compMgr.lookup( DNSServer.ROLE );
        } catch ( final ServiceException cme ) {
            getLogger().error("Fatal configuration error - DNS Servers lost!", cme );
            throw new RuntimeException("Fatal configuration error - DNS Servers lost!");
        }
        return dnsServer.getSMTPHostAddresses(domainName);
    
public java.lang.StringgetServerInfo()
Return the type of the server

return
the type of the server

        return "Apache JAMES";
    
public synchronized org.apache.james.services.MailRepositorygetUserInbox(java.lang.String userName)

Retrieve the mail repository for a user

For POP3 server only - at the moment.

param
userName the name of the user whose inbox is to be retrieved
return
the POP3 inbox for the user

        MailRepository userInbox = null;

        userInbox = (MailRepository) mailboxes.get(userName);

        if (userInbox != null) {
            return userInbox;
        } else if (mailboxes.containsKey(userName)) {
            // we have a problem
            getLogger().error("Null mailbox for non-null key");
            throw new RuntimeException("Error in getUserInbox.");
        } else {
            // need mailbox object
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("Retrieving and caching inbox for " + userName );
            }
            StringBuffer destinationBuffer =
                new StringBuffer(192)
                        .append(inboxRootURL)
                        .append(userName)
                        .append("/");
            String destination = destinationBuffer.toString();
            DefaultConfiguration mboxConf
                = new DefaultConfiguration("repository", "generated:AvalonFileRepository.compose()");
            mboxConf.setAttribute("destinationURL", destination);
            mboxConf.setAttribute("type", "MAIL");
            try {
                userInbox = (MailRepository) store.select(mboxConf);
                if (userInbox!=null) {
                    mailboxes.put(userName, userInbox);
                }
            } catch (Exception e) {
                if (getLogger().isErrorEnabled())
                {
                    getLogger().error("Cannot open user Mailbox" + e);
                }
                throw new RuntimeException("Error in getUserInbox." + e);
            }
            return userInbox;
        }
    
public voidinitialize()

see
org.apache.avalon.framework.activity.Initializable#initialize()


        getLogger().info("JAMES init...");

        // TODO: This should retrieve a more specific named thread pool from
        // Context that is set up in server.xml
        try {
            store = (Store) compMgr.lookup( Store.ROLE );
        } catch (Exception e) {
            if (getLogger().isWarnEnabled()) {
                getLogger().warn("Can't get Store: " + e);
            }
        }
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Using Store: " + store.toString());
        }
        try {
            spool = (SpoolRepository) compMgr.lookup( SpoolRepository.ROLE );
        } catch (Exception e) {
            if (getLogger().isWarnEnabled()) {
                getLogger().warn("Can't get spoolRepository: " + e);
            }
        }
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Using SpoolRepository: " + spool.toString());
        }
        try {
            usersStore = (UsersStore) compMgr.lookup( UsersStore.ROLE );
        } catch (Exception e) {
            if (getLogger().isWarnEnabled()) {
                getLogger().warn("Can't get Store: " + e);
            }
        }
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Using UsersStore: " + usersStore.toString());
        }

        String hostName = null;
        try {
            hostName = InetAddress.getLocalHost().getHostName();
        } catch  (UnknownHostException ue) {
            hostName = "localhost";
        }

        context = new DefaultContext();
        context.put("HostName", hostName);
        getLogger().info("Local host is: " + hostName);

        // Get the domains and hosts served by this instance
        serverNames = new HashSet();
        Configuration serverConf = conf.getChild("servernames");
        if (serverConf.getAttributeAsBoolean("autodetect") && (!hostName.equals("localhost"))) {
            serverNames.add(hostName.toLowerCase(Locale.US));
        }

        final Configuration[] serverNameConfs =
            conf.getChild( "servernames" ).getChildren( "servername" );
        for ( int i = 0; i < serverNameConfs.length; i++ ) {
            serverNames.add( serverNameConfs[i].getValue().toLowerCase(Locale.US));

            if (serverConf.getAttributeAsBoolean("autodetectIP", true)) {
                try {
                    /* This adds the IP address(es) for each host to support
                     * support <user@address-literal> - RFC 2821, sec 4.1.3.
                     * It might be proper to use the actual IP addresses
                     * available on this server, but we can't do that
                     * without NetworkInterface from JDK 1.4.  Because of
                     * Virtual Hosting considerations, we may need to modify
                     * this to keep hostname and IP associated, rather than
                     * just both in the set.
                     */
                    InetAddress[] addrs = InetAddress.getAllByName(serverNameConfs[i].getValue());
                    for (int j = 0; j < addrs.length ; j++) {
                        serverNames.add(addrs[j].getHostAddress());
                    }
                }
                catch(Exception genericException) {
                    getLogger().error("Cannot get IP address(es) for " + serverNameConfs[i].getValue());
                }
            }
        }
        if (serverNames.isEmpty()) {
            throw new ConfigurationException( "Fatal configuration error: no servernames specified!");
        }

        if (getLogger().isInfoEnabled()) {
            for (Iterator i = serverNames.iterator(); i.hasNext(); ) {
                getLogger().info("Handling mail for: " + i.next());
            }
        }
        
        String defaultDomain = (String) serverNames.iterator().next();
        context.put(Constants.DEFAULT_DOMAIN, defaultDomain);
        attributes.put(Constants.DEFAULT_DOMAIN, defaultDomain);

        // Get postmaster
        String postMasterAddress = conf.getChild("postmaster").getValue("postmaster").toLowerCase(Locale.US);
        // if there is no @domain part, then add the first one from the
        // list of supported domains that isn't localhost.  If that
        // doesn't work, use the hostname, even if it is localhost.
        if (postMasterAddress.indexOf('@") < 0) {
            String domainName = null;    // the domain to use
            // loop through candidate domains until we find one or exhaust the list
            for ( int i = 0; domainName == null && i < serverNameConfs.length ; i++ ) {
                String serverName = serverNameConfs[i].getValue().toLowerCase(Locale.US);
                if (!("localhost".equals(serverName))) {
                    domainName = serverName;    // ok, not localhost, so use it
                }
            }
            // if we found a suitable domain, use it.  Otherwise fallback to the host name.
            postMasterAddress = postMasterAddress + "@" + (domainName != null ? domainName : hostName);
        }
        this.postmaster = new MailAddress( postMasterAddress );
        context.put( Constants.POSTMASTER, postmaster );

        if (!isLocalServer(postmaster.getHost())) {
            StringBuffer warnBuffer
                = new StringBuffer(320)
                        .append("The specified postmaster address ( ")
                        .append(postmaster)
                        .append(" ) is not a local address.  This is not necessarily a problem, but it does mean that emails addressed to the postmaster will be routed to another server.  For some configurations this may cause problems.");
            getLogger().warn(warnBuffer.toString());
        }

        Configuration userNamesConf = conf.getChild("usernames");
        ignoreCase = userNamesConf.getAttributeAsBoolean("ignoreCase", false);
        boolean enableAliases = userNamesConf.getAttributeAsBoolean("enableAliases", false);
        boolean enableForwarding = userNamesConf.getAttributeAsBoolean("enableForwarding", false);
        attributes.put(Constants.DEFAULT_ENABLE_ALIASES,new Boolean(enableAliases));
        attributes.put(Constants.DEFAULT_ENABLE_FORWARDING,new Boolean(enableForwarding));
        attributes.put(Constants.DEFAULT_IGNORE_USERNAME_CASE,new Boolean(ignoreCase));

        //Get localusers
        try {
            localusers = (UsersRepository) compMgr.lookup(UsersRepository.ROLE);
        } catch (Exception e) {
            getLogger().error("Cannot open private UserRepository");
            throw e;
        }
        //}
        compMgr.put( UsersRepository.ROLE, localusers);
        getLogger().info("Local users repository opened");

        Configuration inboxConf = conf.getChild("inboxRepository");
        Configuration inboxRepConf = inboxConf.getChild("repository");
        // we could delete this block. I didn't remove this because I'm not sure
        // wether we need the "check" of the inbox repository here, or not.
        try {
            store.select(inboxRepConf);
        } catch (Exception e) {
            getLogger().error("Cannot open private MailRepository");
            throw e;
        }
        inboxRootURL = inboxRepConf.getAttribute("destinationURL");

        getLogger().info("Private Repository LocalInbox opened");

        // Add this to comp
        compMgr.put( MailServer.ROLE, this);

        // For mailet engine provide MailetContext
        //compMgr.put("org.apache.mailet.MailetContext", this);
        // For AVALON aware mailets and matchers, we put the Component object as
        // an attribute
        attributes.put(Constants.AVALON_COMPONENT_MANAGER, compMgr);

        //Temporary get out to allow complex mailet config files to stop blocking sergei sozonoff's work on bouce processing
        java.io.File configDir = AvalonContextUtilities.getFile(myContext, "file://conf/");
        attributes.put("confDir", configDir.getCanonicalPath());

        // We can safely remove this and the localDeliveryField when we 
        // remove the storeMail method from James and from the MailetContext
        DefaultConfiguration conf = new DefaultConfiguration("mailet", "generated:James.initialize()");
        MailetConfigImpl configImpl = new MailetConfigImpl();
        configImpl.setMailetName("LocalDelivery");
        configImpl.setConfiguration(conf);
        configImpl.setMailetContext(this);
        localDeliveryMailet = new LocalDelivery();
        localDeliveryMailet.init(configImpl);

        System.out.println(SOFTWARE_NAME_VERSION);
        getLogger().info("JAMES ...init end");
    
public booleanisLocalServer(java.lang.String serverName)
Check whether the mail domain in question is to be handled by this server.

param
serverName the name of the server to check
return
whether the server is local

        return serverNames.contains(serverName.toLowerCase(Locale.US));
    
public booleanisLocalUser(java.lang.String name)
Returns whether that account has a local inbox on this server

param
name the name to be checked
return
whether the account has a local inbox

        if (ignoreCase) {
            return localusers.containsCaseInsensitive(name);
        } else {
            return localusers.contains(name);
        }
    
public voidlog(java.lang.String message)
Log a message to the Mailet logger

param
message the message to pass to the Mailet logger

        getMailetLogger().info(message);
    
public voidlog(java.lang.String message, java.lang.Throwable t)
Log a message and a Throwable to the Mailet logger

param
message the message to pass to the Mailet logger
param
t the Throwable to be logged

        getMailetLogger().info(message,t);
    
public static voidmain(java.lang.String[] args)
The main method. Should never be invoked, as James must be called from within an Avalon framework container.

param
args the command line arguments

        System.out.println("ERROR!");
        System.out.println("Cannot execute James as a stand alone application.");
        System.out.println("To run James, you need to have the Avalon framework installed.");
        System.out.println("Please refer to the Readme file to know how to run James.");
    
private org.apache.james.core.MailImplrawBounce(org.apache.mailet.Mail mail, java.lang.String bounceText)
Generates a bounce mail that is a bounce of the original message.

param
bounceText the text to be prepended to the message to describe the bounce condition
return
the bounce mail
throws
MessagingException if the bounce mail could not be created

        //This sends a message to the james component that is a bounce of the sent message
        MimeMessage original = mail.getMessage();
        MimeMessage reply = (MimeMessage) original.reply(false);
        reply.setSubject("Re: " + original.getSubject());
        reply.setSentDate(new Date());
        Collection recipients = new HashSet();
        recipients.add(mail.getSender());
        InternetAddress addr[] = { new InternetAddress(mail.getSender().toString())};
        reply.setRecipients(Message.RecipientType.TO, addr);
        reply.setFrom(new InternetAddress(mail.getRecipients().iterator().next().toString()));
        reply.setText(bounceText);
        reply.setHeader(RFC2822Headers.MESSAGE_ID, "replyTo-" + mail.getName());
        return new MailImpl(
            "replyTo-" + mail.getName(),
            new MailAddress(mail.getRecipients().iterator().next().toString()),
            recipients,
            reply);
    
public voidremoveAttribute(java.lang.String key)

        attributes.remove(key);
    
public voidsendMail(javax.mail.internet.MimeMessage message)
Place a mail on the spool for processing

param
message the message to send
throws
MessagingException if an exception is caught while placing the mail on the spool

        MailAddress sender = new MailAddress((InternetAddress)message.getFrom()[0]);
        Collection recipients = new HashSet();
        Address addresses[] = message.getAllRecipients();
        if (addresses != null) {
            for (int i = 0; i < addresses.length; i++) {
                // Javamail treats the "newsgroups:" header field as a
                // recipient, so we want to filter those out.
                if ( addresses[i] instanceof InternetAddress ) {
                    recipients.add(new MailAddress((InternetAddress)addresses[i]));
                }
            }
        }
        sendMail(sender, recipients, message);
    
public voidsendMail(org.apache.mailet.MailAddress sender, java.util.Collection recipients, javax.mail.internet.MimeMessage message)
Place a mail on the spool for processing

param
sender the sender of the mail
param
recipients the recipients of the mail
param
message the message to send
throws
MessagingException if an exception is caught while placing the mail on the spool

        sendMail(sender, recipients, message, Mail.DEFAULT);
    
public voidsendMail(org.apache.mailet.MailAddress sender, java.util.Collection recipients, javax.mail.internet.MimeMessage message, java.lang.String state)
Place a mail on the spool for processing

param
sender the sender of the mail
param
recipients the recipients of the mail
param
message the message to send
param
state the state of the message
throws
MessagingException if an exception is caught while placing the mail on the spool

        MailImpl mail = new MailImpl(getId(), sender, recipients, message);
        try {
            mail.setState(state);
            sendMail(mail);
        } finally {
            ContainerUtil.dispose(mail);
        }
    
public voidsendMail(org.apache.mailet.MailAddress sender, java.util.Collection recipients, java.io.InputStream msg)
Place a mail on the spool for processing

param
sender the sender of the mail
param
recipients the recipients of the mail
param
msg an InputStream containing the message
throws
MessagingException if an exception is caught while placing the mail on the spool

        // parse headers
        MailHeaders headers = new MailHeaders(msg);

        // if headers do not contains minimum REQUIRED headers fields throw Exception
        if (!headers.isValid()) {
            throw new MessagingException("Some REQURED header field is missing. Invalid Message");
        }
        ByteArrayInputStream headersIn = new ByteArrayInputStream(headers.toByteArray());
        sendMail(new MailImpl(getId(), sender, recipients, new SequenceInputStream(headersIn, msg)));
    
public voidsendMail(org.apache.mailet.Mail mail)
Place a mail on the spool for processing

param
mail the mail to place on the spool
throws
MessagingException if an exception is caught while placing the mail on the spool

        try {
            spool.store(mail);
        } catch (Exception e) {
            getLogger().error("Error storing message: " + e.getMessage(),e);
            try {
                spool.remove(mail);
            } catch (Exception ignored) {
                getLogger().error("Error removing message after an error storing it: " + e.getMessage(),e);
            }
            throw new MessagingException("Exception spooling message: " + e.getMessage(), e);
        }
        if (getLogger().isDebugEnabled()) {
            StringBuffer logBuffer =
                new StringBuffer(64)
                        .append("Mail ")
                        .append(mail.getName())
                        .append(" pushed in spool");
            getLogger().debug(logBuffer.toString());
        }
    
public voidservice(org.apache.avalon.framework.service.ServiceManager comp)

see
org.apache.avalon.framework.service.Serviceable#service(ServiceManager)

        compMgr = new DefaultServiceManager(comp);
        mailboxes = new ReferenceMap();
    
public voidsetAttribute(java.lang.String key, java.lang.Object object)

        attributes.put(key, object);
    
public voidstoreMail(org.apache.mailet.MailAddress sender, org.apache.mailet.MailAddress recipient, javax.mail.internet.MimeMessage msg)
This method has been moved to LocalDelivery (the only client of the method). Now we can safely remove it from the Mailet API and from this implementation of MailetContext. The local field localDeliveryMailet will be removed when we remove the storeMail method.

deprecated
since 2.2.0 look at the LocalDelivery code to find out how to do the local delivery.
see
org.apache.mailet.MailetContext#storeMail(org.apache.mailet.MailAddress, org.apache.mailet.MailAddress, javax.mail.internet.MimeMessage)

        if (recipient == null) {
            throw new IllegalArgumentException("Recipient for mail to be spooled cannot be null.");
        }
        if (msg == null) {
            throw new IllegalArgumentException("Mail message to be spooled cannot be null.");
        } 
        Collection recipients = new HashSet();
        recipients.add(recipient); 
        MailImpl m = new MailImpl(getId(),sender,recipients,msg);
        localDeliveryMailet.service(m);
        ContainerUtil.dispose(m);