You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2016/09/29 10:51:55 UTC

[05/12] james-project git commit: JAMES-1781 Vacation update should be done using a specific object.

JAMES-1781 Vacation update should be done using a specific object.


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/75b5767e
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/75b5767e
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/75b5767e

Branch: refs/heads/master
Commit: 75b5767e97f9968e8a0445685e86e0c0aa903e95
Parents: b7d41df
Author: Benoit Tellier <bt...@linagora.com>
Authored: Mon Jun 27 17:44:30 2016 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Thu Sep 29 12:48:14 2016 +0200

----------------------------------------------------------------------
 .../apache/james/utils/ExtendedServerProbe.java |   3 +-
 .../apache/james/utils/GuiceServerProbe.java    |   5 +-
 .../james/jmap/api/vacation/VacationPatch.java  | 207 ++++++++++++
 .../jmap/api/vacation/VacationRepository.java   |   2 +-
 .../AbstractVacationRepositoryTest.java         | 283 ++++++++++++++++-
 .../jmap/api/vacation/VacationPatchTest.java    | 311 +++++++++++++++++++
 .../james/jmap/model/VacationResponseTest.java  |   1 +
 7 files changed, 793 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/75b5767e/server/container/guice/guice-common/src/main/java/org/apache/james/utils/ExtendedServerProbe.java
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/utils/ExtendedServerProbe.java b/server/container/guice/guice-common/src/main/java/org/apache/james/utils/ExtendedServerProbe.java
index f1da266..20dbc81 100644
--- a/server/container/guice/guice-common/src/main/java/org/apache/james/utils/ExtendedServerProbe.java
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/utils/ExtendedServerProbe.java
@@ -27,6 +27,7 @@ import javax.mail.Flags;
 import org.apache.james.cli.probe.ServerProbe;
 import org.apache.james.jmap.api.vacation.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
+import org.apache.james.jmap.api.vacation.VacationUpdate;
 import org.apache.james.mailbox.exception.BadCredentialsException;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -39,7 +40,7 @@ public interface ExtendedServerProbe extends ServerProbe {
 
     Mailbox getMailbox(String namespace, String user, String name);
 
-    void modifyVacation(AccountId accountId, Vacation vacation);
+    void modifyVacation(AccountId accountId, VacationUpdate vacationUpdate);
 
     Vacation retrieveVacation(AccountId accountId);
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/75b5767e/server/container/guice/guice-common/src/main/java/org/apache/james/utils/GuiceServerProbe.java
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/utils/GuiceServerProbe.java b/server/container/guice/guice-common/src/main/java/org/apache/james/utils/GuiceServerProbe.java
index 8d5b734..fcda3f9 100644
--- a/server/container/guice/guice-common/src/main/java/org/apache/james/utils/GuiceServerProbe.java
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/utils/GuiceServerProbe.java
@@ -36,6 +36,7 @@ import org.apache.james.domainlist.api.DomainList;
 import org.apache.james.jmap.api.vacation.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationRepository;
+import org.apache.james.jmap.api.vacation.VacationUpdate;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
@@ -356,8 +357,8 @@ public class GuiceServerProbe implements ExtendedServerProbe {
     }
 
     @Override
-    public void modifyVacation(AccountId accountId, Vacation vacation) {
-        vacationRepository.modifyVacation(accountId, vacation).join();
+    public void modifyVacation(AccountId accountId, VacationUpdate vacationUpdate) {
+        vacationRepository.modifyVacation(accountId, vacationUpdate).join();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/james-project/blob/75b5767e/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationPatch.java
----------------------------------------------------------------------
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationPatch.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationPatch.java
new file mode 100644
index 0000000..721ab34
--- /dev/null
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationPatch.java
@@ -0,0 +1,207 @@
+/****************************************************************
+ * 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.james.jmap.api.vacation;
+
+import java.time.ZonedDateTime;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.james.util.PatchedValue;
+
+import com.google.common.base.Preconditions;
+
+public class VacationPatch {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static Builder builderFrom(Vacation vacation) {
+        return VacationPatch.builder()
+            .subject(PatchedValue.ofOptional(vacation.getSubject()))
+            .textBody(PatchedValue.ofOptional(vacation.getTextBody()))
+            .htmlBody(PatchedValue.ofOptional(vacation.getHtmlBody()))
+            .fromDate(PatchedValue.ofOptional(vacation.getFromDate()))
+            .toDate(PatchedValue.ofOptional(vacation.getToDate()))
+            .isEnabled(PatchedValue.modifyTo(vacation.isEnabled()));
+    }
+
+    public static class Builder {
+        private PatchedValue<String> subject = PatchedValue.keep();
+        private PatchedValue<String> textBody = PatchedValue.keep();
+        private PatchedValue<String> htmlBody = PatchedValue.keep();
+        private PatchedValue<ZonedDateTime> toDate = PatchedValue.keep();
+        private PatchedValue<ZonedDateTime> fromDate = PatchedValue.keep();
+        private PatchedValue<Boolean> isEnabled = PatchedValue.keep();
+
+        public Builder subject(PatchedValue<String> subject) {
+            Preconditions.checkNotNull(subject);
+            this.subject = subject;
+            return this;
+        }
+
+        public Builder subject(String subject) {
+            this.subject = PatchedValue.modifyTo(subject);
+            return this;
+        }
+
+        public Builder textBody(PatchedValue<String> textBody) {
+            Preconditions.checkNotNull(textBody);
+            this.textBody = textBody;
+            return this;
+        }
+
+        public Builder textBody(String textBody) {
+            this.textBody = PatchedValue.modifyTo(textBody);
+            return this;
+        }
+
+        public Builder htmlBody(PatchedValue<String> htmlBody) {
+            Preconditions.checkNotNull(htmlBody);
+            this.htmlBody = htmlBody;
+            return this;
+        }
+
+        public Builder htmlBody(String htmlBody) {
+            this.htmlBody = PatchedValue.modifyTo(htmlBody);
+            return this;
+        }
+
+        public Builder toDate(PatchedValue<ZonedDateTime> toDate) {
+            Preconditions.checkNotNull(toDate);
+            this.toDate = toDate;
+            return this;
+        }
+
+        public Builder toDate(ZonedDateTime toDate) {
+            this.toDate = PatchedValue.modifyTo(toDate);
+            return this;
+        }
+
+        public Builder fromDate(PatchedValue<ZonedDateTime> fromDate) {
+            Preconditions.checkNotNull(fromDate);
+            this.fromDate = fromDate;
+            return this;
+        }
+
+        public Builder fromDate(ZonedDateTime fromDate) {
+            this.fromDate = PatchedValue.modifyTo(fromDate);
+            return this;
+        }
+
+        public Builder isEnabled(PatchedValue<Boolean> isEnabled) {
+            Preconditions.checkNotNull(isEnabled);
+            this.isEnabled = isEnabled;
+            return this;
+        }
+
+        public Builder isEnabled(Boolean isEnabled) {
+            this.isEnabled = PatchedValue.modifyTo(isEnabled);
+            return this;
+        }
+
+        public VacationPatch build() {
+            return new VacationPatch(subject, textBody, htmlBody, toDate, fromDate, isEnabled);
+        }
+    }
+
+    private final PatchedValue<String> subject;
+    private final PatchedValue<String> textBody;
+    private final PatchedValue<String> htmlBody;
+    private final PatchedValue<ZonedDateTime> toDate;
+    private final PatchedValue<ZonedDateTime> fromDate;
+    private final PatchedValue<Boolean> isEnabled;
+
+    private VacationPatch(PatchedValue<String> subject, PatchedValue<String> textBody, PatchedValue<String> htmlBody,
+                          PatchedValue<ZonedDateTime> toDate, PatchedValue<ZonedDateTime> fromDate, PatchedValue<Boolean> isEnabled) {
+        Preconditions.checkState(!isEnabled.isRemoved());
+        this.subject = subject;
+        this.textBody = textBody;
+        this.htmlBody = htmlBody;
+        this.toDate = toDate;
+        this.fromDate = fromDate;
+        this.isEnabled = isEnabled;
+    }
+
+    public PatchedValue<String> getSubject() {
+        return subject;
+    }
+
+    public PatchedValue<String> getTextBody() {
+        return textBody;
+    }
+
+    public PatchedValue<String> getHtmlBody() {
+        return htmlBody;
+    }
+
+    public PatchedValue<ZonedDateTime> getToDate() {
+        return toDate;
+    }
+
+    public PatchedValue<ZonedDateTime> getFromDate() {
+        return fromDate;
+    }
+
+    public PatchedValue<Boolean> getIsEnabled() {
+        return isEnabled;
+    }
+
+    public Vacation patch(Vacation vacation) {
+        return Vacation.builder()
+            .subject(subject.notKeptOrElse(vacation.getSubject()))
+            .fromDate(fromDate.notKeptOrElse(vacation.getFromDate()))
+            .toDate(toDate.notKeptOrElse(vacation.getToDate()))
+            .textBody(textBody.notKeptOrElse(vacation.getTextBody()))
+            .htmlBody(htmlBody.notKeptOrElse(vacation.getHtmlBody()))
+            .enabled(isEnabled.notKeptOrElse(Optional.of(vacation.isEnabled())).get())
+            .build();
+    }
+
+    public boolean isIdentity() {
+        return subject.isKept()
+            && textBody.isKept()
+            && htmlBody.isKept()
+            && toDate.isKept()
+            && fromDate.isKept()
+            && isEnabled.isKept();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof VacationPatch) {
+            VacationPatch that = (VacationPatch) o;
+
+            return Objects.equals(this.subject, that.subject)
+                && Objects.equals(this.textBody, that.textBody)
+                && Objects.equals(this.htmlBody, that.htmlBody)
+                && Objects.equals(this.toDate, that.toDate)
+                && Objects.equals(this.fromDate, that.fromDate)
+                && Objects.equals(this.isEnabled, that.isEnabled);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(subject, textBody, htmlBody, toDate, fromDate, isEnabled);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/75b5767e/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
----------------------------------------------------------------------
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
index 02ff1c6..8267249 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
@@ -25,7 +25,7 @@ public interface VacationRepository {
 
     Vacation DEFAULT_VACATION = Vacation.builder().enabled(false).build();
 
-    CompletableFuture<Void> modifyVacation(AccountId accountId, Vacation vacation);
+    CompletableFuture<Void> modifyVacation(AccountId accountId, VacationPatch vacationPatch);
 
     CompletableFuture<Vacation> retrieveVacation(AccountId accountId);
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/75b5767e/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AbstractVacationRepositoryTest.java
----------------------------------------------------------------------
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AbstractVacationRepositoryTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AbstractVacationRepositoryTest.java
index ee7ad0c..ef90fb4 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AbstractVacationRepositoryTest.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AbstractVacationRepositoryTest.java
@@ -24,19 +24,20 @@ import static org.assertj.core.api.Assertions.assertThat;
 import java.time.ZonedDateTime;
 import java.util.Optional;
 
+import org.apache.james.util.PatchedValue;
 import org.junit.Before;
 import org.junit.Test;
 
 public abstract class AbstractVacationRepositoryTest {
 
     public static final AccountId ACCOUNT_ID = AccountId.fromString("identifier");
-    public static final ZonedDateTime ZONED_DATE_TIME = ZonedDateTime.parse("2016-04-03T02:01+07:00[Asia/Vientiane]");
-    public static final Vacation VACATION_1 = Vacation.builder()
-        .enabled(true)
-        .textBody("other Message")
-        .build();
-    public static final Vacation VACATION_2 = Vacation.builder()
-        .fromDate(Optional.of(ZONED_DATE_TIME))
+    public static final ZonedDateTime DATE_2014 = ZonedDateTime.parse("2014-04-03T02:01+07:00[Asia/Vientiane]");
+    public static final ZonedDateTime DATE_2015 = ZonedDateTime.parse("2015-04-03T02:01+07:00[Asia/Vientiane]");
+    public static final ZonedDateTime DATE_2016 = ZonedDateTime.parse("2016-04-03T02:01+07:00[Asia/Vientiane]");
+    public static final ZonedDateTime DATE_2017 = ZonedDateTime.parse("2017-04-03T02:01+07:00[Asia/Vientiane]");
+    public static final Vacation VACATION = Vacation.builder()
+        .fromDate(Optional.of(DATE_2015))
+        .toDate(Optional.of(DATE_2016))
         .enabled(true)
         .subject(Optional.of("subject"))
         .textBody("anyMessage")
@@ -59,18 +60,270 @@ public abstract class AbstractVacationRepositoryTest {
     }
 
     @Test
-    public void modifyVacationShouldWork() {
-        vacationRepository.modifyVacation(ACCOUNT_ID, VACATION_1).join();
+    public void modifyVacationShouldUpdateEnabled() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .isEnabled(true)
+            .build();
+
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(Vacation.builder()
+                .enabled(true)
+                .build());
+    }
+
+    @Test
+    public void modifyVacationShouldUpdateFromDate() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .fromDate(DATE_2014)
+            .build();
+
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(Vacation.builder()
+                .fromDate(Optional.of(DATE_2014))
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void modifyVacationShouldUpdateToDate() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .toDate(DATE_2017)
+            .build();
+
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(Vacation.builder()
+                .toDate(Optional.of(DATE_2017))
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void modifyVacationShouldUpdateSubject() {
+        String newSubject = "new subject";
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .subject(newSubject)
+            .build();
+
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(Vacation.builder()
+                .subject(Optional.of(newSubject))
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void modifyVacationShouldUpdateTextBody() {
+        String newTextBody = "new text body";
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .textBody(newTextBody)
+            .build();
+
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(Vacation.builder()
+                .textBody(newTextBody)
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void modifyVacationShouldUpdateHtmlBody() {
+        String newHtmlBody = "new <b>html</b> body";
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .htmlBody(newHtmlBody)
+            .build();
+
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(Vacation.builder()
+                .enabled(false)
+                .htmlBody(newHtmlBody)
+                .build());
+    }
+
+    @Test
+    public void modifyVacationShouldAllowToUpdateAllFieldsAtOnce() {
+        VacationPatch vacationPatch = VacationPatch.builderFrom(VACATION)
+            .build();
 
-        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join()).isEqualTo(VACATION_1);
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(VACATION);
+    }
+
+    @Test
+    public void modifyVacationShouldAllowEmptyUpdates() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .build();
+
+        vacationRepository.modifyVacation(ACCOUNT_ID, vacationPatch).join();
+
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(VacationRepository.DEFAULT_VACATION);
+    }
+
+    @Test
+    public void emptyUpdatesShouldNotChangeExistingVacations() {
+        // Given
+        vacationRepository.modifyVacation(ACCOUNT_ID,
+            VacationPatch.builderFrom(VACATION)
+                .build())
+            .join();
+
+        // When
+        vacationRepository.modifyVacation(ACCOUNT_ID, VacationPatch.builder()
+            .build())
+            .join();
+
+        // Then
+        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join())
+            .isEqualTo(VACATION);
+    }
+
+    @Test
+    public void nullUpdateShouldResetSubject() {
+        // Given
+        vacationRepository.modifyVacation(ACCOUNT_ID,
+            VacationPatch.builderFrom(VACATION)
+                .build())
+            .join();
+
+        // When
+        vacationRepository.modifyVacation(ACCOUNT_ID, VacationPatch.builder()
+            .subject(PatchedValue.remove())
+            .build())
+            .join();
+
+        // Then
+        Vacation vacation = vacationRepository.retrieveVacation(ACCOUNT_ID).join();
+        assertThat(vacation.getSubject()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .textBody(VACATION.getTextBody())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
     }
 
     @Test
-    public void modifyVacationShouldReplacePreviousValue() {
-        vacationRepository.modifyVacation(ACCOUNT_ID, VACATION_1).join();
-        vacationRepository.modifyVacation(ACCOUNT_ID, VACATION_2).join();
+    public void nullUpdateShouldResetText() {
+        // Given
+        vacationRepository.modifyVacation(ACCOUNT_ID,
+            VacationPatch.builderFrom(VACATION)
+                .build())
+            .join();
+
+        // When
+        vacationRepository.modifyVacation(ACCOUNT_ID, VacationPatch.builder()
+            .textBody(PatchedValue.remove())
+            .build())
+            .join();
+
+        // Then
+        Vacation vacation = vacationRepository.retrieveVacation(ACCOUNT_ID).join();
+        assertThat(vacation.getTextBody()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
+    }
+
+    @Test
+    public void nullUpdateShouldResetHtml() {
+        // Given
+        vacationRepository.modifyVacation(ACCOUNT_ID,
+            VacationPatch.builderFrom(VACATION)
+                .build())
+            .join();
+
+        // When
+        vacationRepository.modifyVacation(ACCOUNT_ID, VacationPatch.builder()
+            .htmlBody(PatchedValue.remove())
+            .build())
+            .join();
+
+        // Then
+        Vacation vacation = vacationRepository.retrieveVacation(ACCOUNT_ID).join();
+        assertThat(vacation.getHtmlBody()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .textBody(VACATION.getTextBody())
+                .build());
+    }
+
+    @Test
+    public void nullUpdateShouldResetToDate() {
+        // Given
+        vacationRepository.modifyVacation(ACCOUNT_ID,
+            VacationPatch.builderFrom(VACATION)
+                .build())
+            .join();
+
+        // When
+        vacationRepository.modifyVacation(ACCOUNT_ID, VacationPatch.builder()
+            .toDate(PatchedValue.remove())
+            .build())
+            .join();
+
+        // Then
+        Vacation vacation = vacationRepository.retrieveVacation(ACCOUNT_ID).join();
+        assertThat(vacation.getToDate()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .textBody(VACATION.getTextBody())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
+    }
+
+    @Test
+    public void nullUpdateShouldResetFromDate() {
+        // Given
+        vacationRepository.modifyVacation(ACCOUNT_ID,
+            VacationPatch.builderFrom(VACATION)
+                .build())
+            .join();
+
+        // When
+        vacationRepository.modifyVacation(ACCOUNT_ID, VacationPatch.builder()
+            .fromDate(PatchedValue.remove())
+            .build())
+            .join();
 
-        assertThat(vacationRepository.retrieveVacation(ACCOUNT_ID).join()).isEqualTo(VACATION_2);
+        // Then
+        Vacation vacation = vacationRepository.retrieveVacation(ACCOUNT_ID).join();
+        assertThat(vacation.getFromDate()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .textBody(VACATION.getTextBody())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
     }
 
     @Test(expected = NullPointerException.class)
@@ -80,7 +333,7 @@ public abstract class AbstractVacationRepositoryTest {
 
     @Test(expected = NullPointerException.class)
     public void modifyVacationShouldThrowOnNullAccountId() {
-        vacationRepository.modifyVacation(null, VACATION_1);
+        vacationRepository.modifyVacation(null, VacationPatch.builder().build());
     }
 
     @Test(expected = NullPointerException.class)

http://git-wip-us.apache.org/repos/asf/james-project/blob/75b5767e/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationPatchTest.java
----------------------------------------------------------------------
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationPatchTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationPatchTest.java
new file mode 100644
index 0000000..13db92a
--- /dev/null
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationPatchTest.java
@@ -0,0 +1,311 @@
+/****************************************************************
+ * 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.james.jmap.api.vacation;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.apache.james.util.PatchedValue;
+import org.junit.Test;
+
+public class VacationPatchTest {
+
+    public static final ZonedDateTime DATE_2014 = ZonedDateTime.parse("2014-04-03T02:01+07:00[Asia/Vientiane]");
+    public static final ZonedDateTime DATE_2015 = ZonedDateTime.parse("2015-04-03T02:01+07:00[Asia/Vientiane]");
+    public static final ZonedDateTime DATE_2017 = ZonedDateTime.parse("2017-04-03T02:01+07:00[Asia/Vientiane]");
+    public static final Vacation VACATION = Vacation.builder()
+        .fromDate(Optional.of(DATE_2014))
+        .toDate(Optional.of(DATE_2015))
+        .enabled(true)
+        .subject(Optional.of("subject"))
+        .textBody("anyMessage")
+        .htmlBody("html Message")
+        .build();
+
+    @Test
+    public void fromDateShouldThrowNPEOnNullInput() {
+        assertThatThrownBy(() -> VacationPatch.builder().fromDate((PatchedValue) null)).isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void toDateShouldThrowNPEOnNullInput() {
+        assertThatThrownBy(() -> VacationPatch.builder().toDate((PatchedValue) null)).isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void textBodyShouldThrowNPEOnNullInput() {
+        assertThatThrownBy(() -> VacationPatch.builder().textBody((PatchedValue) null)).isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void htmlBodyShouldThrowNPEOnNullInput() {
+        assertThatThrownBy(() -> VacationPatch.builder().htmlBody((PatchedValue) null)).isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void subjectShouldThrowNPEOnNullInput() {
+        assertThatThrownBy(() -> VacationPatch.builder().subject((PatchedValue) null)).isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void isEnabledShouldThrowNPEOnNullInput() {
+        assertThatThrownBy(() -> VacationPatch.builder().isEnabled((PatchedValue) null)).isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void isIdentityShouldBeTrueWhenUpdateIsEmpty() {
+        assertThat(
+            VacationPatch.builder()
+                .build()
+                .isIdentity())
+            .isTrue();
+    }
+
+    @Test
+    public void isIdentityShouldBeFalseWhenUpdateIsNotEmpty() {
+        assertThat(
+            VacationPatch.builder()
+                .subject(PatchedValue.modifyTo("any subject"))
+                .build()
+                .isIdentity())
+            .isFalse();
+    }
+
+    @Test
+    public void builderShouldWellSetFields() {
+        PatchedValue<String> subject = PatchedValue.modifyTo("subject");
+        PatchedValue<String> htmlBody = PatchedValue.modifyTo("html text");
+        PatchedValue<String> textBody = PatchedValue.modifyTo("simple text");
+        PatchedValue<Boolean> isEnabled = PatchedValue.modifyTo(true);
+
+        VacationPatch update = VacationPatch.builder()
+            .fromDate(PatchedValue.modifyTo(DATE_2014))
+            .toDate(PatchedValue.modifyTo(DATE_2015))
+            .subject(subject)
+            .htmlBody(htmlBody)
+            .textBody(textBody)
+            .isEnabled(isEnabled)
+            .build();
+
+        assertThat(update.getFromDate()).isEqualTo(PatchedValue.modifyTo(DATE_2014));
+        assertThat(update.getToDate()).isEqualTo(PatchedValue.modifyTo(DATE_2015));
+        assertThat(update.getSubject()).isEqualTo(subject);
+        assertThat(update.getHtmlBody()).isEqualTo(htmlBody);
+        assertThat(update.getTextBody()).isEqualTo(textBody);
+        assertThat(update.getIsEnabled()).isEqualTo(isEnabled);
+    }
+
+    @Test
+    public void patchVacationShouldUpdateEnabled() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .isEnabled(PatchedValue.modifyTo(true))
+            .build();
+
+        assertThat(vacationPatch.patch(VacationRepository.DEFAULT_VACATION))
+            .isEqualTo(Vacation.builder()
+                .enabled(true)
+                .build());
+    }
+
+    @Test
+    public void patchVacationShouldUpdateFromDate() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .fromDate(PatchedValue.modifyTo(DATE_2014))
+            .build();
+
+        assertThat(vacationPatch.patch(VacationRepository.DEFAULT_VACATION))
+            .isEqualTo(Vacation.builder()
+                .fromDate(Optional.of(DATE_2014))
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void patchVacationShouldUpdateToDate() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .toDate(PatchedValue.modifyTo(DATE_2017))
+            .build();
+
+        assertThat(vacationPatch.patch(VacationRepository.DEFAULT_VACATION))
+            .isEqualTo(Vacation.builder()
+                .toDate(Optional.of(DATE_2017))
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void patchVacationShouldUpdateSubject() {
+        String newSubject = "new subject";
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .subject(PatchedValue.modifyTo(newSubject))
+            .build();
+
+        assertThat(vacationPatch.patch(VacationRepository.DEFAULT_VACATION))
+            .isEqualTo(Vacation.builder()
+                .subject(Optional.of(newSubject))
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void patchVacationShouldUpdateTextBody() {
+        String newTextBody = "new text body";
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .textBody(PatchedValue.modifyTo(newTextBody))
+            .build();
+
+        assertThat(vacationPatch.patch(VacationRepository.DEFAULT_VACATION))
+            .isEqualTo(Vacation.builder()
+                .textBody(newTextBody)
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    public void patchVacationShouldUpdateHtmlBody() {
+        String newHtmlBody = "new <b>html</b> body";
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .htmlBody(PatchedValue.modifyTo(newHtmlBody))
+            .build();
+
+        assertThat(vacationPatch.patch(VacationRepository.DEFAULT_VACATION))
+            .isEqualTo(Vacation.builder()
+                .enabled(false)
+                .htmlBody(newHtmlBody)
+                .build());
+    }
+
+    @Test
+    public void patchVacationShouldAllowToUpdateAllFieldsAtOnce() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .subject(PatchedValue.ofOptional(VACATION.getSubject()))
+            .textBody(PatchedValue.ofOptional(VACATION.getTextBody()))
+            .htmlBody(PatchedValue.ofOptional(VACATION.getHtmlBody()))
+            .fromDate(PatchedValue.ofOptional(VACATION.getFromDate()))
+            .toDate(PatchedValue.ofOptional(VACATION.getToDate()))
+            .isEnabled(PatchedValue.modifyTo(VACATION.isEnabled()))
+            .build();
+
+        assertThat(vacationPatch.patch(VacationRepository.DEFAULT_VACATION))
+            .isEqualTo(VACATION);
+    }
+
+    @Test
+    public void emptyPatchesShouldNotChangeExistingVacations() {
+        assertThat(VacationPatch.builder()
+            .build()
+            .patch(VACATION))
+            .isEqualTo(VACATION);
+    }
+
+    @Test
+    public void nullUpdateShouldResetSubject() {
+        Vacation vacation = VacationPatch.builderFrom(VACATION)
+            .subject(PatchedValue.remove())
+            .build()
+            .patch(VACATION);
+
+        assertThat(vacation.getSubject()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .textBody(VACATION.getTextBody())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
+    }
+
+    @Test
+    public void nullUpdateShouldResetText() {
+        Vacation vacation = VacationPatch.builderFrom(VACATION)
+            .textBody(PatchedValue.remove())
+            .build()
+            .patch(VACATION);
+
+        assertThat(vacation.getTextBody()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
+    }
+
+    @Test
+    public void nullUpdateShouldResetHtml() {
+        Vacation vacation = VacationPatch.builderFrom(VACATION)
+            .htmlBody(PatchedValue.remove())
+            .build()
+            .patch(VACATION);
+
+        assertThat(vacation.getHtmlBody()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .textBody(VACATION.getTextBody())
+                .build());
+    }
+
+    @Test
+    public void nullUpdateShouldResetToDate() {
+        Vacation vacation = VacationPatch.builderFrom(VACATION)
+            .toDate(PatchedValue.remove())
+            .build()
+            .patch(VACATION);
+
+        assertThat(vacation.getToDate()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .fromDate(VACATION.getFromDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .textBody(VACATION.getTextBody())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
+    }
+
+    @Test
+    public void nullUpdateShouldResetFromDate() {
+        Vacation vacation = VacationPatch.builderFrom(VACATION)
+            .fromDate(PatchedValue.remove())
+            .build()
+            .patch(VACATION);
+
+        assertThat(vacation.getFromDate()).isEmpty();
+        assertThat(vacation)
+            .isEqualTo(Vacation.builder()
+                .toDate(VACATION.getToDate())
+                .enabled(VACATION.isEnabled())
+                .subject(VACATION.getSubject())
+                .textBody(VACATION.getTextBody())
+                .htmlBody(VACATION.getHtmlBody())
+                .build());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/75b5767e/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/VacationResponseTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/VacationResponseTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/VacationResponseTest.java
index 503acdf..779a812 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/VacationResponseTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/VacationResponseTest.java
@@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import java.time.ZonedDateTime;
 import java.util.Optional;
 
+import org.apache.james.jmap.api.vacation.Vacation;
 import org.junit.Test;
 
 public class VacationResponseTest {


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org