JSIEVE-103 VacationActions should be handled by the mailet sub project
authorbenwa <benwa@minet.net>
Wed, 20 Jan 2016 17:52:03 +0000 (18:52 +0100)
committerbenwa <btellier@linagora.com>
Tue, 1 Mar 2016 09:50:44 +0000 (16:50 +0700)
mailet/pom.xml
mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java
mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java
mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java
mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java
mailet/src/main/java/org/apache/jsieve/mailet/VacationAction.java [new file with mode: 0644]
mailet/src/main/java/org/apache/jsieve/mailet/VacationReply.java [new file with mode: 0644]

index ff80cad..a26032a 100644 (file)
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.9.2</version>
+        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
index 33a94f4..c719009 100644 (file)
 package org.apache.jsieve.mailet;
 
 import java.util.Collection;
+import java.util.Date;
 
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeMessage;
 
 import org.apache.commons.logging.Log;
 import org.apache.mailet.MailAddress;
+import org.joda.time.DateTime;
 
 /**
  * Provides context for action execution.
  */
 public interface ActionContext {
-    
+
+    /**
+     * @return Date the script was activated
+     */
+    DateTime getScriptActivationDate();
+
+    /**
+     * @return Date the script is currently interpreted
+     */
+    DateTime getScriptInterpretationDate();
+
+    /**
+     * @return Recipient receiving the given eMail
+     */
+    MailAddress getRecipient();
+
     /**
      * Gets the log.
      * @return not null
index 0b55bbf..fed4bb4 100644 (file)
@@ -20,6 +20,7 @@
 package org.apache.jsieve.mailet;
 
 import org.apache.jsieve.mail.*;
+import org.apache.jsieve.mail.optional.ActionVacation;
 import org.apache.mailet.Mail;
 
 import javax.mail.MessagingException;
@@ -83,6 +84,7 @@ public class ActionDispatcher {
         actionMap.put(ActionKeep.class, new KeepAction());
         actionMap.put(ActionRedirect.class, new RedirectAction());
         actionMap.put(ActionReject.class, new RejectAction());
+        actionMap.put(ActionVacation.class, new VacationAction());
         return actionMap;
     }
 
index 12f4bc0..64ee54d 100644 (file)
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.jsieve.mailet;
 
+import org.joda.time.DateTime;
+
 import java.io.InputStream;
 
 /**
@@ -54,12 +56,37 @@ import java.io.InputStream;
  * </p>
  */
 public interface ResourceLocator {
-    
+
+    class UserSieveInformation {
+        private DateTime scriptActivationDate;
+        private DateTime scriptInterpretationDate;
+        private InputStream scriptContent;
+
+        public UserSieveInformation(DateTime scriptActivationDate, DateTime scriptInterpretationDate, InputStream scriptContent) {
+            this.scriptActivationDate = scriptActivationDate;
+            this.scriptInterpretationDate = scriptInterpretationDate;
+            this.scriptContent = scriptContent;
+        }
+
+        public DateTime getScriptActivationDate() {
+            return scriptActivationDate;
+        }
+
+        public DateTime getScriptInterpretationDate() {
+            return scriptInterpretationDate;
+        }
+
+        public InputStream getScriptContent() {
+            return scriptContent;
+        }
+    }
+
     /**
      * GET verb locates and loads a resource. 
      * @param uri identifies the Sieve script 
      * @return not null
      * @throws Exception when the resource cannot be located
      */
-    InputStream get(String uri) throws Exception;
+    UserSieveInformation get(String uri) throws Exception;
+
 }
index 89586f5..93389f1 100644 (file)
@@ -40,6 +40,7 @@ import org.apache.jsieve.mail.optional.EnvelopeAccessors;
 import org.apache.mailet.Mail;
 import org.apache.mailet.MailAddress;
 import org.apache.mailet.MailetContext;
+import org.joda.time.DateTime;
 
 import javax.mail.Header;
 import javax.mail.MessagingException;
@@ -81,6 +82,9 @@ public class SieveMailAdapter implements MailAdapter, EnvelopeAccessors, ActionC
     private final ActionDispatcher dispatcher;
     
     private final Poster poster;
+    private final DateTime scriptActivationDate;
+    private final DateTime scriptInterpretationDate;
+    private final MailAddress recipient;
 
     /**
      * Constructor for SieveMailAdapter.
@@ -88,15 +92,30 @@ public class SieveMailAdapter implements MailAdapter, EnvelopeAccessors, ActionC
      * @param aMail
      * @param aMailetContext
      */
-    public SieveMailAdapter(final Mail aMail, final MailetContext aMailetContext, final ActionDispatcher dispatcher, final Poster poster)
+    public SieveMailAdapter(final Mail aMail, final MailetContext aMailetContext, final ActionDispatcher dispatcher, final Poster poster,
+                            DateTime scriptActivationDate, DateTime scriptInterpretationDate, MailAddress recipient)
     {
         this.poster = poster;
         this.dispatcher = dispatcher;
+        this.scriptInterpretationDate = scriptInterpretationDate;
+        this.scriptActivationDate = scriptActivationDate;
+        this.recipient = recipient;
         setMail(aMail);
         setMailetContext(aMailetContext);
     }
-    
-  
+
+    public DateTime getScriptActivationDate() {
+        return scriptActivationDate;
+    }
+
+    public DateTime getScriptInterpretationDate() {
+        return scriptInterpretationDate;
+    }
+
+    public MailAddress getRecipient() {
+        return recipient;
+    }
+
     public void setLog(Log log) {
         this.log = log;
     }
index 0366b3c..5d6d214 100644 (file)
 
 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;
@@ -345,8 +338,8 @@ public class SieveMailboxMailet extends GenericMailet {
     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);
+            final ResourceLocator.UserSieveInformation userSieveInformation = locator.get(getScriptUri(recipient));
+            sieveMessageEvaluate(recipient, aMail, userSieveInformation);
         } catch (Exception ex) {
             // SIEVE is a mail filtering protocol.
             // Rejecting the mail because it cannot be filtered
@@ -359,17 +352,18 @@ public class SieveMailboxMailet extends GenericMailet {
         }
     }
     
-    private void sieveMessageEvaluate(MailAddress recipient, Mail aMail, InputStream ins) throws MessagingException, IOException {    
+    private void sieveMessageEvaluate(MailAddress recipient, Mail aMail, ResourceLocator.UserSieveInformation userSieveInformation) throws MessagingException, IOException {
             try {
                 SieveMailAdapter aMailAdapter = new SieveMailAdapter(aMail,
-                        getMailetContext(), actionDispatcher, poster);
+                    getMailetContext(), actionDispatcher, poster, userSieveInformation.getScriptActivationDate(),
+                    userSieveInformation.getScriptInterpretationDate(), recipient);
                 aMailAdapter.setLog(log);
                 // This logging operation is potentially costly
                 if (verbose) {
                     log("Evaluating " + aMailAdapter.toString() + "against \""
                             + getScriptUri(recipient) + "\"");
                 }
-                factory.evaluate(aMailAdapter, factory.parse(ins));
+                factory.evaluate(aMailAdapter, factory.parse(userSieveInformation.getScriptContent()));
             } catch (SieveException ex) {
                 handleFailure(recipient, aMail, ex);
             }
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/VacationAction.java b/mailet/src/main/java/org/apache/jsieve/mailet/VacationAction.java
new file mode 100644 (file)
index 0000000..3a1abba
--- /dev/null
@@ -0,0 +1,100 @@
+/****************************************************************
+ * 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 com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.optional.ActionVacation;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.joda.time.Days;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public class VacationAction implements MailAction {
+
+    @Override
+    public void execute(Action action, Mail mail, ActionContext context) throws MessagingException {
+        ActionVacation actionVacation = (ActionVacation) action;
+        int dayDifference = Days.daysBetween(context.getScriptActivationDate(), context.getScriptInterpretationDate()).getDays();
+        if (isStillInVacation(actionVacation, dayDifference)) {
+            if (isValidForReply(mail, actionVacation, context)) {
+                if (!isMailingList(mail)) {
+                    sendVacationNotification(mail, actionVacation, context);
+                }
+            }
+        }
+    }
+
+    private void sendVacationNotification(Mail mail, ActionVacation actionVacation, ActionContext context) throws MessagingException {
+        VacationReply vacationReply = VacationReply.builder(mail, context)
+            .from(actionVacation.getFrom())
+            .mime(actionVacation.getMime())
+            .reason(actionVacation.getReason())
+            .subject(actionVacation.getSubject())
+            .build();
+        context.post(vacationReply.getSender(), vacationReply.getRecipients(), vacationReply.getMimeMessage());
+    }
+
+    private boolean isStillInVacation(ActionVacation actionVacation, int dayDifference) {
+        return dayDifference >= 0 && dayDifference <= actionVacation.getDuration();
+    }
+
+    private boolean isValidForReply(final Mail mail, ActionVacation actionVacation, final ActionContext context) {
+        Set<MailAddress> currentMailAddresses = ImmutableSet.copyOf(mail.getRecipients());
+        Set<MailAddress> allowedMailAddresses = ImmutableSet.<MailAddress>builder().addAll(
+            Lists.transform(actionVacation.getAddresses(), new Function<String, MailAddress>() {
+                public MailAddress apply(String s) {
+                    return retrieveAddressFromString(s, context);
+                }
+            }))
+            .add(context.getRecipient())
+            .build();
+        return !Sets.intersection(currentMailAddresses, allowedMailAddresses).isEmpty();
+    }
+
+    private MailAddress retrieveAddressFromString(String address, ActionContext context) {
+        try {
+            return new MailAddress(address);
+        } catch (AddressException e) {
+            context.getLog().warn("Mail address " + address + " was not well formatted : " + e.getLocalizedMessage());
+            return null;
+        }
+    }
+
+    private boolean isMailingList(Mail mail) throws MessagingException {
+        Enumeration enumeration = mail.getMessage().getAllHeaderLines();
+        while (enumeration.hasMoreElements()) {
+            String headerName = (String) enumeration.nextElement();
+            if (headerName.startsWith("List-")) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/VacationReply.java b/mailet/src/main/java/org/apache/jsieve/mailet/VacationReply.java
new file mode 100644 (file)
index 0000000..c15f5d5
--- /dev/null
@@ -0,0 +1,169 @@
+/****************************************************************
+ * 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 com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+
+import javax.activation.DataHandler;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.util.ByteArrayDataSource;
+import java.io.IOException;
+import java.util.List;
+
+public class VacationReply {
+
+
+    public static class Builder {
+
+        private final Mail originalMail;
+        private final ActionContext context;
+        private String from;
+        private String reason;
+        private String mime;
+        private String subject;
+
+        public Builder(Mail originalMail, ActionContext context) {
+            this.originalMail = originalMail;
+            this.context = context;
+        }
+
+        public Builder from(String from) {
+            this.from = from;
+            return this;
+        }
+
+        public Builder reason(String reason) {
+            this.reason = reason;
+            return this;
+        }
+
+        public Builder mime(String mime) {
+            this.mime = mime;
+            return this;
+        }
+
+        public Builder subject(String subject) {
+            this.subject = subject;
+            return this;
+        }
+
+        public VacationReply build() throws MessagingException {
+            Preconditions.checkState(eitherReasonOrMime());
+            ActionUtils.detectAndHandleLocalLooping(originalMail, context, "vacation");
+
+            MimeMessage reply = (MimeMessage) originalMail.getMessage().reply(false);
+            reply.setSubject(generateNotificationSubject());
+            reply.setContent(generateNotificationContent());
+
+            return new VacationReply(retrieveOriginalSender(), Lists.newArrayList(originalMail.getSender()), reply);
+        }
+
+        private boolean eitherReasonOrMime() {
+            return (reason == null) ^ (mime == null);
+        }
+
+        private String generateNotificationSubject() {
+            return Optional.fromNullable(subject)
+                .or(context.getRecipient() + " is currently in vacation");
+        }
+
+        private Multipart generateNotificationContent() throws MessagingException {
+            try {
+                if (reason != null) {
+                    return generateNotificationContentFromReasonString();
+                } else {
+                    return generateNotificationContentFromMime();
+                }
+            } catch (IOException e) {
+                throw new MessagingException("Cannot read specified content", e);
+            }
+        }
+
+        private Multipart generateNotificationContentFromMime() throws MessagingException, IOException {
+            return new MimeMultipart(new ByteArrayDataSource(mime, "mixed"));
+        }
+
+        private Multipart generateNotificationContentFromReasonString() throws MessagingException, IOException {
+            Multipart multipart = new MimeMultipart("mixed");
+            MimeBodyPart reasonPart = new MimeBodyPart();
+            reasonPart.setDataHandler(
+                new DataHandler(
+                    new ByteArrayDataSource(
+                        reason,
+                        "text/plain; charset=UTF-8")));
+            reasonPart.setDisposition(MimeBodyPart.INLINE);
+            multipart.addBodyPart(reasonPart);
+            return multipart;
+        }
+
+        private MailAddress retrieveOriginalSender() throws AddressException {
+            return Optional.fromNullable(from).transform(new Function<String, MailAddress>() {
+                public MailAddress apply(String address) {
+                    return retrieveAddressFromString(address, context);
+                }
+            }).or(context.getRecipient());
+        }
+
+        private MailAddress retrieveAddressFromString(String address, ActionContext context) {
+            try {
+                return new MailAddress(address);
+            } catch (AddressException e) {
+                context.getLog().warn("Mail address " + address + " was not well formatted : " + e.getLocalizedMessage());
+                return null;
+            }
+        }
+    }
+
+    public static Builder builder(Mail originalMail, ActionContext context) {
+        return new Builder(originalMail, context);
+    }
+
+    private final MailAddress sender;
+    private final List<MailAddress> recipients;
+    private final MimeMessage mimeMessage;
+
+    private VacationReply(MailAddress sender, List<MailAddress> recipients, MimeMessage mimeMessage) {
+        this.sender = sender;
+        this.recipients = recipients;
+        this.mimeMessage = mimeMessage;
+    }
+
+    public MailAddress getSender() {
+        return sender;
+    }
+
+    public List<MailAddress> getRecipients() {
+        return recipients;
+    }
+
+    public MimeMessage getMimeMessage() {
+        return mimeMessage;
+    }
+}