Skip to content
Permalink
Browse files
JSIEVE-103 Implement vacation command
  • Loading branch information
chibenwa committed Mar 1, 2016
1 parent da0a225 commit dfdb2713d351f20c06f77d7bbbac6f849e427b04
Showing 5 changed files with 560 additions and 0 deletions.
@@ -45,6 +45,11 @@ public class CommandStateManager {
*/
private boolean fieldHasActions = false;

/**
* Did vacation instruction already executed for this script ?
*/
private boolean vacationProcessed = false;

/**
* Constructor for CommandStateManager.
*/
@@ -139,4 +144,20 @@ public void setImplicitKeep(boolean implicitKeep) {
fieldImplicitKeep = implicitKeep;
}

/**
* Returns whether a vacation command was processed.
*
* RFC 5230 section 4.7 : Vacation can only be executed once per script. A script MUST fail
* with an appropriate error if it attempts to execute two or more
* vacation actions.
*
* @return false if no vacation command was executed before in this script, false otherwise
*/
public boolean getVacationProcessed() {
return vacationProcessed;
}

public void setVacationProcessed(boolean vacationProcessed) {
this.vacationProcessed = vacationProcessed;
}
}
@@ -0,0 +1,96 @@
/****************************************************************
* 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.commands.optional;

import org.apache.jsieve.Arguments;
import org.apache.jsieve.Block;
import org.apache.jsieve.SieveContext;
import org.apache.jsieve.commands.AbstractActionCommand;
import org.apache.jsieve.exception.CommandException;
import org.apache.jsieve.exception.SieveException;
import org.apache.jsieve.mail.MailAdapter;
import org.apache.jsieve.mail.optional.ActionVacation;
import org.apache.jsieve.utils.ArgumentParser;

/**
* See https://tools.ietf.org/html/rfc5230
*/
public class Vacation extends AbstractActionCommand {

public static final String DAYS = ":days";
public static final String DAYS_EXCEPTION_MESSAGE = "Expecting a number argument setting the number of days after tag " + DAYS;
public static final String SUBJECT = ":subject";
public static final String SUBJECT_EXCEPTION_MESSAGE = "Expecting a string argument setting the subject after tag " + SUBJECT;
public static final String FROM = ":from";
public static final String FROM_EXCEPTION_MESSAGE = "Expecting a string argument setting the from field of sent mails after tag " + FROM;
public static final String ADDRESSES = ":addresses";
public static final String ADDRESSES_EXCEPTION_MESSAGE = "Expecting a string list argument setting the additional addresses this script is authorized to respond to after tag " + ADDRESSES;
public static final String MIME = ":mime";
public static final String MIME_EXCEPTION_MESSAGE = "Expecting a string argument setting a mime message instead of the reason string after tag " + MIME;
public static final String HANDLE = ":handle";
public static final String HANDLE_EXCEPTION_MESSAGE = "Expecting a string argument setting handle string after tag " + HANDLE;

@Override
protected Object executeBasic(MailAdapter mail, Arguments arguments, Block block, SieveContext context) throws SieveException {
mail.addAction(retrieveAction(arguments));
return null;
}

@Override
protected void validateState(SieveContext context) throws CommandException {
// RFC-5230 Section 4.7 : The vacation action is incompatible with the Sieve reject and refuse actions
super.validateState(context);
// RFC-5230 Section 4.7 : Vacation can only be executed once per script
if (context.getCommandStateManager().getVacationProcessed())
throw context.getCoordinate()
.commandException("The \"vacation\" command is not allowed to be executed after other vacation commands");
}

@Override
protected void updateState(SieveContext context) {
context.getCommandStateManager().setHasActions(true);
context.getCommandStateManager().setInProlog(false);
// RFC-5230 Section 4.7 : Vacation does not affect Sieve's implicit keep action.
context.getCommandStateManager().setVacationProcessed(true);
context.getCommandStateManager().setImplicitKeep(context.getCommandStateManager().isImplicitKeep());
}

@Override
protected void validateArguments(Arguments arguments, SieveContext context) throws SieveException {
retrieveAction(arguments);
}

private ActionVacation retrieveAction(Arguments arguments) throws SieveException {
ArgumentParser argumentParser = new ArgumentParser(arguments.getArgumentList());
argumentParser.throwOnUnvalidSeenSingleTag();
argumentParser.throwOnUnvalidSeenTagWithValue(FROM, SUBJECT, HANDLE, MIME, DAYS, ADDRESSES);

return ActionVacation.builder()
.addresses(argumentParser.getStringListForTag(ADDRESSES, ADDRESSES_EXCEPTION_MESSAGE))
.duration(argumentParser.getNumericValueForTag(DAYS, DAYS_EXCEPTION_MESSAGE))
.handle(argumentParser.getStringValueForTag(HANDLE, HANDLE_EXCEPTION_MESSAGE))
.mime(argumentParser.getStringValueForTag(MIME, MIME_EXCEPTION_MESSAGE))
.subject(argumentParser.getStringValueForTag(SUBJECT, SUBJECT_EXCEPTION_MESSAGE))
.from(argumentParser.getStringValueForTag(FROM, FROM_EXCEPTION_MESSAGE))
.reason(argumentParser.getRemainingStringValue("Expecting a single String value as a reason"))
.build();
}

}
@@ -0,0 +1,206 @@
/****************************************************************
* 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.mail.optional;

import org.apache.jsieve.exception.SyntaxException;
import org.apache.jsieve.mail.Action;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Class ActionVacation encapsulates the information required to reject a mail.
* See RFC 5230, Section 4.1.
*/
public class ActionVacation implements Action {

public static class ActionVacationBuilder {

private static final int SITE_DEFINED_DEFAULT_VACATION_DURATION = 7;
private static final int MINIMUM_VACATION_DURATION = 1;

private String subject;
private String from;
private List<String> addresses = new ArrayList<String>();
private String handle;
private String reason;
private String mime;
private Integer duration;

public ActionVacationBuilder() {
duration = SITE_DEFINED_DEFAULT_VACATION_DURATION;
}

public ActionVacationBuilder subject(String subject) {
this.subject = subject;
return this;
}

public ActionVacationBuilder from(String from) {
this.from = from;
return this;
}

public ActionVacationBuilder addresses(List<String> addresses) {
this.addresses = addresses;
return this;
}

public ActionVacationBuilder handle(String handle) {
this.handle = handle;
return this;
}

public ActionVacationBuilder reason(String reason) {
this.reason = reason;
return this;
}

public ActionVacationBuilder mime(String mime) {
this.mime = mime;
return this;
}

public ActionVacationBuilder duration(Integer duration) {
this.duration = duration;
return this;
}

public ActionVacation build() throws SyntaxException {
if (!eitherReasonOrMime()) {
throw new SyntaxException("vacation need you to set you either the reason string or a MIME message after tag :mime");
}
return new ActionVacation(subject, from, addresses, reason, computeDuration(duration), handle, mime);
}

private int computeDuration(Integer duration) {
if (duration == null) {
return SITE_DEFINED_DEFAULT_VACATION_DURATION;
}
if (duration < MINIMUM_VACATION_DURATION) {
return MINIMUM_VACATION_DURATION;
} else {
return duration;
}
}

private boolean eitherReasonOrMime() {
return (reason == null) ^ (mime == null);
}
}

public static ActionVacationBuilder builder() {
return new ActionVacationBuilder();
}

private final String subject;
private final String from;
private final List<String> addresses;
private final String handle;
private final String reason;
private final String mime;
private final int duration;

private ActionVacation(String subject, String from, List<String> addresses, String reason, int duration, String handle, String mime) {
this.subject = subject;
this.from = from;
this.addresses = Collections.unmodifiableList(addresses);
this.reason = reason;
this.duration = duration;
this.handle = handle;
this.mime = mime;
}

public String getSubject() {
return subject;
}

public String getFrom() {
return from;
}

public List<String> getAddresses() {
return addresses;
}

public String getHandle() {
return handle;
}

public String getReason() {
return reason;
}

public int getDuration() {
return duration;
}

public String getMime() {
return mime;
}

public String toString() {
return "Action: " + getClass().getName();
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}

ActionVacation that = (ActionVacation) o;

return duration == that.duration
&& equalsNullProtected(this.subject, subject)
&& equalsNullProtected(this.from, from)
&& equalsNullProtected(this.handle, handle)
&& equalsNullProtected(this.reason, reason)
&& equalsNullProtected(this.mime, mime);
}

private boolean equalsNullProtected(Object object1, Object object2) {
if (object1 == null) {
return object2 == null;
}
return object1.equals(object2);
}

@Override
public int hashCode() {
int result = hashCodeNullProtected(subject);
result = 31 * result + hashCodeNullProtected(from);
result = 31 * result + hashCodeNullProtected(handle);
result = 31 * result + hashCodeNullProtected(reason);
result = 31 * result + hashCodeNullProtected(mime);
result = 31 * result + hashCodeNullProtected(subject);
result = 31 * result + duration;
return result;
}

public int hashCodeNullProtected(Object object) {
if (object == null) {
return 0;
}
return object.hashCode();
}

}

0 comments on commit dfdb271

Please sign in to comment.