JSIEVE-103 VacationActions should be handled by the mailet sub project
diff --git a/mailet/pom.xml b/mailet/pom.xml
index ff80cad..a26032a 100644
--- a/mailet/pom.xml
+++ b/mailet/pom.xml
@@ -84,7 +84,11 @@
<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>
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java b/mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java
index 33a94f4..c719009 100644
--- a/mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java
@@ -19,18 +19,35 @@
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
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java b/mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java
index 0b55bbf..fed4bb4 100644
--- a/mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java
@@ -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 @@
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;
}
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java b/mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java
index 12f4bc0..64ee54d 100644
--- a/mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java
@@ -18,6 +18,8 @@
****************************************************************/
package org.apache.jsieve.mailet;
+import org.joda.time.DateTime;
+
import java.io.InputStream;
/**
@@ -54,12 +56,37 @@
* </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;
+
}
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java b/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
index 89586f5..93389f1 100644
--- a/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
@@ -40,6 +40,7 @@
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 @@
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 @@
* @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;
}
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java b/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java
index 0366b3c..5d6d214 100644
--- a/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java
@@ -19,25 +19,18 @@
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 @@
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 @@
}
}
- 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
index 0000000..3a1abba
--- /dev/null
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/VacationAction.java
@@ -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
index 0000000..c15f5d5
--- /dev/null
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/VacationReply.java
@@ -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;
+ }
+}