Java tutorial
/**************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * * or more contributor license agreements. See the NOTICE file * * distributed with this work for additional information * * regarding copyright ownership. The ASF licenses this file * * to you under the Apache License, Version 2.0 (the * * "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, * * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * * KIND, either express or implied. See the License for the * * specific language governing permissions and limitations * * under the License. * ****************************************************************/ package org.apache.jsieve.mailet; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.Scanner; import java.util.Vector; import javax.activation.DataHandler; import javax.mail.Header; import javax.mail.MessagingException; import javax.mail.internet.InternetHeaders; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.util.ByteArrayDataSource; import org.apache.commons.logging.Log; import org.apache.jsieve.ConfigurationManager; import org.apache.jsieve.SieveConfigurationException; import org.apache.jsieve.SieveFactory; import org.apache.jsieve.exception.SieveException; import org.apache.jsieve.parser.generated.ParseException; import org.apache.jsieve.parser.generated.TokenMgrError; import org.apache.mailet.Mail; import org.apache.mailet.MailAddress; import org.apache.mailet.MailetConfig; import org.apache.mailet.MailetException; import org.apache.mailet.base.GenericMailet; import org.apache.mailet.base.RFC2822Headers; /** * <p>Executes a <a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>Sieve</a> * script against incoming mail. The script applied is based on the recipient.</p> * <h4>Init Parameters</h4> * <table> * <thead><tr><th>Name</th><th>Required</th><th>Values</th><th>Role</th></thead> * <tr><td>verbose</td><td>No - defaults to false</td><td>true (ignoring case) to enable, otherwise disable</td> * <td> * Enables verbose logging. * </td></tr> * </table> */ public class SieveMailboxMailet extends GenericMailet { /** * The delivery header */ private String deliveryHeader; /** * resetReturnPath */ private boolean resetReturnPath; /** Experimental */ private Poster poster; /** Experimental */ private ResourceLocator locator; /** Indicates whether this mailet should log verbosely */ private boolean verbose = false; private boolean consume = true; /** Indicates whether this mailet should log minimal information */ private boolean quiet = true; private SieveFactory factory; private ActionDispatcher actionDispatcher; private Log log; /** * For SDI */ public SieveMailboxMailet() { } /** * CDI * @param poster not null */ public SieveMailboxMailet(Poster poster, ResourceLocator locator) { this(); this.poster = poster; this.locator = locator; } public ResourceLocator getLocator() { return locator; } /** * For SDI * @param locator not null */ public void setLocator(ResourceLocator locator) { this.locator = locator; } public Poster getPoster() { return poster; } /** * For SDI * @param poster not null */ public void setPoster(Poster poster) { this.poster = poster; } /** * Is this mailet GHOSTing all mail it processes? * @return true when mailet consumes all mail, false otherwise */ public boolean isConsume() { return consume; } /** * Sets whether this mailet should GHOST all mail. * @param consume true when the mailet should consume all mail, * false otherwise */ public void setConsume(boolean consume) { this.consume = consume; } /** * Is this mailet logging verbosely? * This property is set by init parameters. * @return true if logging should be verbose, false otherwise */ public boolean isVerbose() { return verbose; } /** * Sets whether logging should be verbose for this mailet. * This property is set by init parameters. * This setting overrides {@link #isQuiet()}. * @param verbose true when logging should be verbose, * false otherwise */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Is the logging for this mailet set to minimal? * @return true */ public boolean isQuiet() { return quiet; } /** * Sets the logging for this mailet to minimal. * This is overriden by {@link #setVerbose(boolean)}. * @param quiet true for minimal logging, false otherwise */ public void setQuiet(boolean quiet) { this.quiet = quiet; } /** * Is informational logging turned on? * @return true when minimal logging is off, * false when logging is minimal */ public boolean isInfoLoggingOn() { return verbose || !quiet; } @Override public void init(MailetConfig config) throws MessagingException { super.init(config); try { final ConfigurationManager configurationManager = new ConfigurationManager(); final int logLevel; if (verbose) { logLevel = CommonsLoggingAdapter.TRACE; } else if (quiet) { logLevel = CommonsLoggingAdapter.FATAL; } else { logLevel = CommonsLoggingAdapter.WARN; } log = new CommonsLoggingAdapter(this, logLevel); configurationManager.setLog(log); factory = configurationManager.build(); } catch (SieveConfigurationException e) { throw new MessagingException("Failed to load standard Sieve configuration.", e); } } /** * Delivers a mail to a local mailbox. * * @param mail * the mail being processed * * @throws MessagingException * if an error occurs while storing the mail */ @SuppressWarnings("unchecked") @Override public void service(Mail mail) throws MessagingException { Collection<MailAddress> recipients = mail.getRecipients(); Collection<MailAddress> errors = new Vector<MailAddress>(); MimeMessage message = null; if (deliveryHeader != null || resetReturnPath) { message = mail.getMessage(); } if (resetReturnPath) { // Set Return-Path and remove all other Return-Path headers from the // message // This only works because there is a placeholder inserted by // MimeMessageWrapper message.setHeader(RFC2822Headers.RETURN_PATH, (mail.getSender() == null ? "<>" : "<" + mail.getSender() + ">")); } Enumeration headers; InternetHeaders deliveredTo = new InternetHeaders(); if (deliveryHeader != null) { // Copy any Delivered-To headers from the message headers = message.getMatchingHeaders(new String[] { deliveryHeader }); while (headers.hasMoreElements()) { Header header = (Header) headers.nextElement(); deliveredTo.addHeader(header.getName(), header.getValue()); } } for (Iterator<MailAddress> i = recipients.iterator(); i.hasNext();) { MailAddress recipient = i.next(); try { if (deliveryHeader != null) { // Add qmail's de facto standard Delivered-To header message.addHeader(deliveryHeader, recipient.toString()); } storeMail(mail.getSender(), recipient, mail); if (deliveryHeader != null) { if (i.hasNext()) { // Remove headers but leave all placeholders message.removeHeader(deliveryHeader); headers = deliveredTo.getAllHeaders(); // And restore any original Delivered-To headers while (headers.hasMoreElements()) { Header header = (Header) headers.nextElement(); message.addHeader(header.getName(), header.getValue()); } } } } catch (Exception ex) { log("Error while storing mail.", ex); errors.add(recipient); } } if (!errors.isEmpty()) { // If there were errors, we redirect the email to the ERROR // processor. // In order for this server to meet the requirements of the SMTP // specification, mails on the ERROR processor must be returned to // the sender. Note that this email doesn't include any details // regarding the details of the failure(s). // In the future we may wish to address this. getMailetContext().sendMail(mail.getSender(), errors, mail.getMessage(), Mail.ERROR); } if (consume) { // Consume this message mail.setState(Mail.GHOST); } } /** * Return a string describing this mailet. * * @return a string describing this mailet */ @Override public String getMailetInfo() { return "Sieve Mailbox Mailet"; } /** * * @param sender * @param recipient * @param mail * @throws MessagingException */ public void storeMail(MailAddress sender, MailAddress recipient, Mail mail) throws MessagingException { if (recipient == null) { throw new IllegalArgumentException("Recipient for mail to be spooled cannot be null."); } if (mail.getMessage() == null) { throw new IllegalArgumentException("Mail message to be spooled cannot be null."); } sieveMessage(recipient, mail); } protected void sieveMessage(MailAddress recipient, Mail aMail) throws MessagingException { String username = getUsername(recipient); try { final InputStream ins = locator.get(getScriptUri(recipient)); sieveMessageEvaluate(recipient, aMail, ins); } catch (Exception ex) { // SIEVE is a mail filtering protocol. // Rejecting the mail because it cannot be filtered // seems very unfriendly. // So just log and store in INBOX if (isInfoLoggingOn()) { log("Cannot evaluate Sieve script. Storing mail in user INBOX.", ex); } storeMessageInbox(username, aMail.getMessage()); } } private void sieveMessageEvaluate(MailAddress recipient, Mail aMail, InputStream ins) throws MessagingException, IOException { try { SieveMailAdapter aMailAdapter = new SieveMailAdapter(aMail, getMailetContext(), actionDispatcher, poster); aMailAdapter.setLog(log); // This logging operation is potentially costly if (verbose) { log("Evaluating " + aMailAdapter.toString() + "against \"" + getScriptUri(recipient) + "\""); } factory.evaluate(aMailAdapter, factory.parse(ins)); } catch (SieveException ex) { handleFailure(recipient, aMail, ex); } catch (ParseException ex) { handleFailure(recipient, aMail, ex); } catch (TokenMgrError ex) { handleFailure(recipient, aMail, new SieveException(ex)); } } protected void storeMessageInbox(String username, MimeMessage message) throws MessagingException { String url = "mailbox://" + username + "/"; poster.post(url, message); } /** * @see org.apache.mailet.base.GenericMailet#init() */ @Override public void init() throws MessagingException { super.init(); if (poster == null || locator == null) { throw new MailetException("Not initialised. Please ensure that the mailet container supports either" + " setter or constructor injection"); } this.deliveryHeader = getInitParameter("addDeliveryHeader"); this.resetReturnPath = getInitParameter("resetReturnPath", true); this.consume = getInitParameter("consume", true); this.verbose = getInitParameter("verbose", false); this.quiet = getInitParameter("quiet", false); actionDispatcher = new ActionDispatcher(); } /** * Return the username to use for sieve processing for the given MailAddress * * @param m * @return username */ protected String getUsername(MailAddress m) { return m.getLocalPart() + "@localhost"; } /** * Return the URI for the sieve script * * @param m * @return */ protected String getScriptUri(MailAddress m) { return "//" + getUsername(m) + "/sieve"; } /** * Deliver the original mail as an attachment with the main part being an error report. * * @param recipient * @param aMail * @param ex * @throws MessagingException * @throws IOException */ protected void handleFailure(MailAddress recipient, Mail aMail, Exception ex) throws MessagingException, IOException { String user = getUsername(recipient); MimeMessage originalMessage = aMail.getMessage(); MimeMessage message = new MimeMessage(originalMessage); MimeMultipart multipart = new MimeMultipart(); MimeBodyPart noticePart = new MimeBodyPart(); noticePart.setText(new StringBuilder().append( "An error was encountered while processing this mail with the active sieve script for user \"") .append(user).append("\". The error encountered was:\r\n").append(ex.getLocalizedMessage()) .append("\r\n").toString()); multipart.addBodyPart(noticePart); MimeBodyPart originalPart = new MimeBodyPart(); originalPart.setContent(originalMessage, "message/rfc822"); if ((originalMessage.getSubject() != null) && (!originalMessage.getSubject().trim().isEmpty())) { originalPart.setFileName(originalMessage.getSubject().trim()); } else { originalPart.setFileName("No Subject"); } originalPart.setDisposition(MimeBodyPart.INLINE); multipart.addBodyPart(originalPart); message.setContent(multipart); message.setSubject("[SIEVE ERROR] " + originalMessage.getSubject()); message.setHeader("X-Priority", "1"); message.saveChanges(); storeMessageInbox(user, message); } }